From 0b04fda1e805a042fa5cf9943facf4c4dda8c6f8 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 12 Dec 2024 14:49:44 +0100 Subject: [PATCH 001/241] chore: update some fields in package.json (#1) --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0c0e979..22fd23c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", - "repository": "git+https://github.com/codesandbox/codesandbox-applications.git", + "repository": "git+https://github.com/codesandbox/codesandbox-sdk.git", "bin": { "csb": "dist/bin/codesandbox.js" }, @@ -93,11 +93,10 @@ "prettier": "^2.2.1", "semver": "^6.3.0", "tslib": "^2.1.0", + "typescript": "^5.7.2", "util": "0.12.5", "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" }, - "dependencies": { - "typescript": "^5.7.2" - } + "dependencies": {} } From a943df2abda26be00fe81077472116305f67279b Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 19 Dec 2024 16:45:32 -0500 Subject: [PATCH 002/241] chore: include package-lock.json --- .gitignore | 4 - package-lock.json | 3891 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3891 insertions(+), 4 deletions(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index dded572..4203c65 100644 --- a/.gitignore +++ b/.gitignore @@ -99,8 +99,4 @@ typings/ !.vscode/launch.json !.vscode/extensions.json -# Lock files -package-lock.json -yarn.lock - # End of https://www.gitignore.io/api/node,visualstudiocode,macos diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f103998 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3891 @@ +{ + "name": "@codesandbox/sdk", + "version": "0.0.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@codesandbox/sdk", + "version": "0.0.6", + "license": "MIT", + "bin": { + "csb": "dist/bin/codesandbox.js" + }, + "devDependencies": { + "@codesandbox/pitcher-client": "0.360.2", + "@codesandbox/pitcher-common": "0.360.2", + "@codesandbox/pitcher-protocol": "0.360.4", + "@hey-api/client-fetch": "^0.5.1", + "@types/blessed": "^0.1.25", + "@types/yargs": "^17.0.33", + "blessed": "^0.1.81", + "blessed-contrib": "^4.11.0", + "buffer-browserify": "^0.2.5", + "crypto-browserify": "^3.12.1", + "esbuild": "^0.24.0", + "ignore": "^6.0.2", + "ora": "5.4.1", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "prettier": "^2.2.1", + "semver": "^6.3.0", + "tslib": "^2.1.0", + "typescript": "^5.7.2", + "util": "0.12.5", + "why-is-node-running": "^2.3.0", + "yargs": "^17.7.2" + } + }, + "node_modules/@absinthe/socket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", + "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.2.0", + "@jumpn/utils-array": "0.3.4", + "@jumpn/utils-composite": "0.7.0", + "@jumpn/utils-graphql": "0.6.0", + "core-js": "2.6.0", + "zen-observable": "0.8.11" + }, + "peerDependencies": { + "phoenix": "^1.4.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", + "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.12.0" + } + }, + "node_modules/@codesandbox/api": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-0.1.0.tgz", + "integrity": "sha512-MCGIwWDjpMNlz1csJMnKAMplvmByNYlXC2C0R5P2RL1oZ2j6X4KJJEG06iz//izwBJzDgZJGiis1dq9siuJ8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@absinthe/socket": "^0.2.1", + "@codesandbox/create-gql-api": "0.1.0", + "class-states": "1.0.15", + "humps": "^2.0.1", + "node-fetch": "^2.6.1", + "phoenix": "^1.6.6", + "preact": "^10.22.0", + "universal-cookie": "^4.0.4" + } + }, + "node_modules/@codesandbox/api/node_modules/class-states": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.15.tgz", + "integrity": "sha512-ReR0LKl1C3tK+Wwe2zlAtXEjLavuR3+m+oFFNELdJmywEoh00iDYCWLxWJof83YSpqIBK/iegy0blUuktJ7+6A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@codesandbox/create-gql-api": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-0.1.0.tgz", + "integrity": "sha512-opCFfhdrgeiM2ffGpfMdjdx5mB2kjY79sBuD82775OUvAUnmTH2csa/CxscCV/JY4F/Qd3e+omQlP4CiMjMB5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "get-graphql-schema": "^2.1.2", + "graphql": "^16.8.1" + }, + "bin": { + "create-gql-api": "bin/index.js" + } + }, + "node_modules/@codesandbox/nodebox": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.9.tgz", + "integrity": "sha512-h/SCSA5iasmEUlxWWQf2ecv1WgGoI5KLV5iZoN9YA1Hhz4DIacE3vwwD24Vn+1qczny4TEr8zwMQYZ1K8TbvzQ==", + "dev": true, + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/pitcher-client": { + "version": "0.360.2", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-0.360.2.tgz", + "integrity": "sha512-rESLa3P2ohDbGMXQ5Jx0iUIhQkIuSDKocXDsIn2gOiCTQloxqXmX/d6rbk4MlQcjvcc4eBbrw1kIPxfyNFk/PQ==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@codesandbox/api": "0.1.0", + "@codesandbox/nodebox": "^0.1.8", + "@codesandbox/pitcher-common": "0.360.2", + "@codesandbox/pitcher-protocol": "0.360.3", + "@codesandbox/sandpack-client": "^2.18.0", + "@types/ws": "^7.4.7", + "class-states": "1.0.16", + "debug": "^4.3.4", + "isomorphic-ws": "^5.0.0", + "js-untar": "^2.0.0", + "jszip": "^3.10.1", + "minimatch": "9.0.3", + "semver": "^7.3.5", + "vscode-jsonrpc": "^8.2.0" + } + }, + "node_modules/@codesandbox/pitcher-client/node_modules/@codesandbox/pitcher-protocol": { + "version": "0.360.3", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.3.tgz", + "integrity": "sha512-E1ksPxx1/RTKZDPHGLw4vh4mfPFphwHJMIOtQwXIUBsasMNLuSqh6E+Tr8f1DfxKDv81+HJ6y8PxiXn9JcpgyA==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@codesandbox/pitcher-common": "*", + "@msgpack/msgpack": "^2.7.1" + } + }, + "node_modules/@codesandbox/pitcher-client/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@codesandbox/pitcher-common": { + "version": "0.360.2", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-common/-/pitcher-common-0.360.2.tgz", + "integrity": "sha512-FWy4YgDh0LFZRo9N8j7mTSoKyIftWJVs/9LR5rwzAta0lFUAV2X8hsGMzt0u16Ng5Wpbi92svMZQy6WygC93gg==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@types/micromatch": "^4.0.2", + "@types/retry": "^0.12.2", + "cross-fetch": "^4.0.0", + "lru_map": "^0.4.1", + "micromatch": "^4.0.4", + "oo-ascii-tree": "^1.34.0", + "p-queue": "^6.6.2", + "retry": "^0.13.1", + "strip-json-comments": "^3.1.1", + "tiny-invariant": "^1.2.0", + "ts-mixer": "^6.0.0", + "type-fest": "^2.11.1", + "uuid": "^11.0.2", + "vscode-diff": "^2.0.1" + } + }, + "node_modules/@codesandbox/pitcher-protocol": { + "version": "0.360.4", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.4.tgz", + "integrity": "sha512-oPxA2/F/ywyR73elJwdFOsw3D+rOId2UTNAXnRrTGjh66Ujyx/IFGVqfTlibGaQUg2HENkRoiRsvRJa5qphITA==", + "dev": true, + "license": "GPL-3.0", + "dependencies": { + "@codesandbox/pitcher-common": "0.360.2", + "@msgpack/msgpack": "^2.7.1" + } + }, + "node_modules/@codesandbox/sandpack-client": { + "version": "2.19.8", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", + "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@codesandbox/nodebox": "0.1.8", + "buffer": "^6.0.3", + "dequal": "^2.0.2", + "mime-db": "^1.52.0", + "outvariant": "1.4.0", + "static-browser-server": "1.0.3" + } + }, + "node_modules/@codesandbox/sandpack-client/node_modules/@codesandbox/nodebox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", + "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", + "dev": true, + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/sandpack-client/node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hey-api/client-fetch": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.5.2.tgz", + "integrity": "sha512-ix8MuuLTME5BWMQ8iCALqWflHtDyuoEwX0b77TQL+VgEXL89cYtjoHxE2bm2DYQQm8qDJXvTS6ju/D6wB6l6Hg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@jumpn/utils-array": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", + "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "flow-static-land": "0.2.7" + } + }, + "node_modules/@jumpn/utils-composite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", + "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jumpn/utils-array": "0.3.4", + "babel-polyfill": "6.26.0", + "babel-runtime": "6.26.0", + "fast-deep-equal": "1.0.0", + "flow-static-land": "0.2.8" + } + }, + "node_modules/@jumpn/utils-composite/node_modules/flow-static-land": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", + "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jumpn/utils-graphql": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", + "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.2.0", + "core-js": "2.6.0", + "graphql": "14.0.2" + } + }, + "node_modules/@jumpn/utils-graphql/node_modules/graphql": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", + "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iterall": "^1.2.2" + }, + "engines": { + "node": ">= 6.x" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/blessed": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.25.tgz", + "integrity": "sha512-kQsjBgtsbJLmG6CJA+Z6Nujj+tq1fcSE3UIowbDvzQI4wWmoTV7djUDhSo5lDjgwpIN0oRvks0SA5mMdKE5eFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/braces": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.4.tgz", + "integrity": "sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/micromatch": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", + "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/braces": "*" + } + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", + "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-term": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ansi-term/-/ansi-term-0.0.2.tgz", + "integrity": "sha512-jLnGE+n8uAjksTJxiWZf/kcUmXq+cRWSl550B9NmQ8YiqaTM+lILcSe5dHdp8QkJPhaOghDjnMKwyYSMjosgAA==", + "dev": true, + "license": "ISC", + "dependencies": { + "x256": ">=0.0.1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "node_modules/babel-polyfill/node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "dev": true, + "license": "MIT", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/blessed-contrib": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/blessed-contrib/-/blessed-contrib-4.11.0.tgz", + "integrity": "sha512-P00Xji3xPp53+FdU9f74WpvnOAn/SS0CKLy4vLAf5Ps7FGDOTY711ruJPZb3/7dpFuP+4i7f4a/ZTZdLlKG9WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-term": ">=0.0.2", + "chalk": "^1.1.0", + "drawille-canvas-blessed-contrib": ">=0.1.3", + "lodash": "~>=4.17.21", + "map-canvas": ">=0.1.5", + "marked": "^4.0.12", + "marked-terminal": "^5.1.1", + "memory-streams": "^0.1.0", + "memorystream": "^0.3.1", + "picture-tuber": "^1.0.1", + "sparkline": "^0.1.1", + "strip-ansi": "^3.0.0", + "term-canvas": "0.0.5", + "x256": ">=0.0.1" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bresenham": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bresenham/-/bresenham-0.0.3.tgz", + "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-browserify": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/buffer-browserify/-/buffer-browserify-0.2.5.tgz", + "integrity": "sha512-3ko6TTBwXb15w2OgZuyAzLJwUFClBMvcKcmhF+iQ79G71K8Fc3RqKzroCN0a0DbZw2GM3q9lNoqfYYCTq6w7QA==", + "deprecated": "Package not maintained. Recent browserify uses https://github.com/feross/buffer", + "dev": true, + "license": "MIT/X11", + "dependencies": { + "base64-js": "0.0.8" + } + }, + "node_modules/buffer-browserify/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "dev": true, + "license": "MIT/X11" + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/class-states": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.16.tgz", + "integrity": "sha512-DhQHga7pUo3LRfZawNIw+4yHBx9AncBAjXv4YXjwOLf06ZnxCR/hi2hPqMhZr//NhwhLlq9EmmWXM0o7BZgnNw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", + "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drawille-blessed-contrib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/drawille-blessed-contrib/-/drawille-blessed-contrib-1.0.0.tgz", + "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/drawille-canvas-blessed-contrib": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/drawille-canvas-blessed-contrib/-/drawille-canvas-blessed-contrib-0.1.3.tgz", + "integrity": "sha512-bdDvVJOxlrEoPLifGDPaxIzFh3cD7QH05ePoQ4fwnqfi08ZSxzEhOUpI5Z0/SQMlWgcCQOEtuw0zrwezacXglw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-term": ">=0.0.2", + "bresenham": "0.0.3", + "drawille-blessed-contrib": ">=0.0.1", + "gl-matrix": "^2.1.0", + "x256": ">=0.0.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/event-stream": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-0.9.8.tgz", + "integrity": "sha512-o5h0Mp1bkoR6B0i7pTCAzRy+VzdsRWH997KQD4Psb0EOPoKEIiaRx/EsOdUl7p1Ktjw7aIWvweI/OY1R9XrlUg==", + "dev": true, + "dependencies": { + "optimist": "0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/event-stream/node_modules/optimist": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz", + "integrity": "sha512-Wy7E3cQDpqsTIFyW7m22hSevyTLxw850ahYv7FWsw4G6MIKVTZ8NSA95KBrQ95a4SMsMr1UGUUnwEFKhVaSzIg==", + "dev": true, + "license": "MIT/X11", + "dependencies": { + "wordwrap": ">=0.0.1 <0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flow-static-land": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", + "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", + "dev": true, + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-graphql-schema": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", + "integrity": "sha512-1z5Hw91VrE3GrpCZE6lE8Dy+jz4kXWesLS7rCSjwOxf5BOcIedAZeTUJRIeIzmmR+PA9CKOkPTYFRJbdgUtrxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "graphql": "^14.0.2", + "minimist": "^1.2.0", + "node-fetch": "^2.2.0" + }, + "bin": { + "get-graphql-schema": "dist/index.js" + } + }, + "node_modules/get-graphql-schema/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-graphql-schema/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-graphql-schema/node_modules/graphql": { + "version": "14.7.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", + "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "iterall": "^1.2.2" + }, + "engines": { + "node": ">= 6.x" + } + }, + "node_modules/get-graphql-schema/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/get-graphql-schema/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gl-matrix": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", + "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/here": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/here/-/here-0.0.2.tgz", + "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/humps": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", + "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-untar": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", + "integrity": "sha512-7CsDLrYQMbLxDt2zl9uKaPZSdmJMvGGQ7wo9hoB3J+z/VcO2w63bXFgHVnjF1+S9wD3zAu8FBVj7EYWjTQ3Z7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru_map": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", + "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/map-canvas": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", + "integrity": "sha512-f7M3sOuL9+up0NCOZbb1rQpWDLZwR/ftCiNbyscjl9LUUEwrRaoumH4sz6swgs58lF21DQ0hsYOCw5C6Zz7hbg==", + "dev": true, + "license": "ISC", + "dependencies": { + "drawille-canvas-blessed-contrib": ">=0.0.1", + "xml2js": "^0.4.5" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/marked-terminal": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", + "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^6.2.0", + "cardinal": "^2.1.1", + "chalk": "^5.2.0", + "cli-table3": "^0.6.3", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.3.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/memory-streams": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.3.tgz", + "integrity": "sha512-qVQ/CjkMyMInPaaRMrwWNDvf6boRZXaT/DbQeMYcCWuXPEBf1v8qChOc9OlEVQp2uOvRXa1Qu30fLmKhY6NipA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.2" + } + }, + "node_modules/memory-streams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/memory-streams/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/memory-streams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oo-ascii-tree": { + "version": "1.105.0", + "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.105.0.tgz", + "integrity": "sha512-fz4QixX/ImVEMbABqCJxxSwvJGfw9vfq2121RMq/qtCv7BiarY4ZPpheHheOTBvEnhqy81dyMpxiXAY8U3rPjA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 14.17.0" + } + }, + "node_modules/optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==", + "dev": true, + "license": "MIT/X11", + "dependencies": { + "wordwrap": "~0.0.2" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/phoenix": { + "version": "1.7.18", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.18.tgz", + "integrity": "sha512-Qo+V9+knfEd+R1pzCe+XJlj3GPSxWz4PNwzFl7GgssuTVYPoh/he3mbPQJ+NEDdqulxAbBtWCNYGPB3WplS5Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/picture-tuber": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/picture-tuber/-/picture-tuber-1.0.2.tgz", + "integrity": "sha512-49/xq+wzbwDeI32aPvwQJldM8pr7dKDRuR76IjztrkmiCkAQDaWFJzkmfVqCHmt/iFoPFhHmI9L0oKhthrTOQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "charm": "~0.1.0", + "event-stream": "~0.9.8", + "optimist": "~0.3.4", + "png-js": "~0.1.0", + "x256": "~0.0.1" + }, + "bin": { + "picture-tube": "bin/tube.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/png-js": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", + "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==", + "dev": true + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/preact": { + "version": "10.25.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.2.tgz", + "integrity": "sha512-GEts1EH3oMnqdOIeXhlbBSddZ9nrINd070WBOiPO2ous1orrKGUM4SMDbwyjSWD1iMS2dBvaDjAa5qUhz3TXqw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sparkline": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", + "integrity": "sha512-t//aVOiWt9fi/e22ea1vXVWBDX+gp18y+Ch9sKqmHl828bRfvP2VtfTJVEcgWFBQHd0yDPNQRiHdqzCvbcYSDA==", + "dev": true, + "dependencies": { + "here": "0.0.2", + "nopt": "~2.1.2" + }, + "bin": { + "sparkline": "bin/sparkline" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/static-browser-server": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", + "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@open-draft/deferred-promise": "^2.1.0", + "dotenv": "^16.0.3", + "mime-db": "^1.52.0", + "outvariant": "^1.3.0" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/term-canvas": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", + "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==", + "dev": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vscode-diff": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-diff/-/vscode-diff-2.1.1.tgz", + "integrity": "sha512-S2BwZbrQCk4N6FqgYQQPlQ44OZKZNcS2VwhMj4xU8QvixXN9GeNQnN7/7XHFbwrs6h5BiLADDcERTrKvfWeG9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/x256": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/x256/-/x256-0.0.2.tgz", + "integrity": "sha512-ZsIH+sheoF8YG9YG+QKEEIdtqpHRA9FYuD7MqhfyB1kayXU43RUNBFSxBEnF8ywSUxdg+8no4+bPr5qLbyxKgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zen-observable": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", + "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", + "dev": true, + "license": "MIT" + } + } +} From 9a737ea3f6b3a26d6dd7953f7742494bae9d68a7 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 19 Dec 2024 16:45:49 -0500 Subject: [PATCH 003/241] fix: include sandbox id in build command --- src/bin/commands/build.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 70ae8ac..048f0a6 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -111,9 +111,9 @@ export const buildCommand: yargs.CommandModule< } if (argv.fromSandbox) { - spinner.succeed("Sandbox reused"); + spinner.succeed(`Sandbox reused: ${sandboxId}`); } else { - spinner.succeed("Sandbox created"); + spinner.succeed(`Sandbox created: ${sandboxId}`); } if (argv.cluster) { @@ -275,6 +275,9 @@ export const buildCommand: yargs.CommandModule< ); spinner.succeed("All ports are open"); + } else { + spinner.succeed("No ports to open, waiting 5 seconds for tasks to run..."); + await new Promise((resolve) => setTimeout(resolve, 5000)); } spinner.start("Creating memory snapshot..."); From 9b1774c5d55ef802c069481a10e317e96d618f70 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 20 Dec 2024 18:55:59 +0100 Subject: [PATCH 004/241] perf: start a vm while forking --- openapi.json | 90 +++++++++++++-- src/client/sdk.gen.ts | 238 +++++++++++----------------------------- src/client/types.gen.ts | 61 ++++++++-- src/sandbox-client.ts | 31 ++++-- 4 files changed, 212 insertions(+), 208 deletions(-) diff --git a/openapi.json b/openapi.json index cefe302..b431bfd 100644 --- a/openapi.json +++ b/openapi.json @@ -200,6 +200,38 @@ "description": "Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox.", "type": "integer" }, + "start_options": { + "description": "Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation.", + "properties": { + "hibernation_timeout_seconds": { + "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", + "example": 300, + "maximum": 86400, + "minimum": 1, + "type": "integer" + }, + "ipcountry": { + "description": "This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster).", + "example": "NL", + "pattern": "^[A-Z]{2}$", + "type": "string" + }, + "tier": { + "description": "Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace.\n\nYou can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error.\n\nNot all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs.\n", + "enum": [ + "Pico", + "Nano", + "Micro", + "Small", + "Medium", + "Large", + "XLarge" + ], + "example": "Micro" + } + }, + "type": "object" + }, "tags": { "default": [], "description": "Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox.", @@ -213,7 +245,8 @@ "type": "string" } }, - "title": "SandboxForkRequest" + "title": "SandboxForkRequest", + "type": "object" }, "SandboxForkResponse": { "allOf": [ @@ -242,6 +275,37 @@ "properties": { "alias": { "type": "string" }, "id": { "type": "string" }, + "start_response": { + "description": "VM start response. Only present when start_options were provided in the request.", + "nullable": true, + "properties": { + "bootup_type": { "type": "string" }, + "cluster": { "type": "string" }, + "id": { "type": "string" }, + "latest_pitcher_version": { "type": "string" }, + "pitcher_manager_version": { "type": "string" }, + "pitcher_token": { "type": "string" }, + "pitcher_url": { "type": "string" }, + "pitcher_version": { "type": "string" }, + "reconnect_token": { "type": "string" }, + "user_workspace_path": { "type": "string" }, + "workspace_path": { "type": "string" } + }, + "required": [ + "bootup_type", + "cluster", + "id", + "latest_pitcher_version", + "pitcher_manager_version", + "pitcher_token", + "pitcher_url", + "pitcher_version", + "reconnect_token", + "user_workspace_path", + "workspace_path" + ], + "type": "object" + }, "title": { "nullable": true, "type": "string" } }, "required": ["alias", "id", "title"], @@ -605,6 +669,7 @@ "ipcountry": { "description": "This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster).", "example": "NL", + "pattern": "^[A-Z]{2}$", "type": "string" }, "tier": { @@ -618,11 +683,11 @@ "Large", "XLarge" ], - "example": "Micro", - "type": "string" + "example": "Micro" } }, - "title": "VMStartRequest" + "title": "VMStartRequest", + "type": "object" }, "VMStartResponse": { "allOf": [ @@ -695,12 +760,12 @@ "Large", "XLarge" ], - "example": "Micro", - "type": "string" + "example": "Micro" } }, "required": ["tier"], - "title": "VMUpdateSpecsRequest" + "title": "VMUpdateSpecsRequest", + "type": "object" }, "VMUpdateSpecsResponse": { "allOf": [ @@ -748,7 +813,8 @@ } }, "required": ["name"], - "title": "WorkspaceCreateRequest" + "title": "WorkspaceCreateRequest", + "type": "object" }, "WorkspaceCreateResponse": { "allOf": [ @@ -930,8 +996,8 @@ "name": "order_by", "required": false, "schema": { - "enum": ["inserted_at", "updated_at"], - "type": "string" + "default": "updated_at", + "enum": ["inserted_at", "updated_at"] } }, { @@ -940,7 +1006,7 @@ "in": "query", "name": "direction", "required": false, - "schema": { "enum": ["asc", "desc"], "type": "string" } + "schema": { "default": "desc", "enum": ["asc", "desc"] } }, { "description": "Maximum number of sandboxes to return in a single response", @@ -1236,6 +1302,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], "tags": [] } diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index b2452cb..6bd98a4 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -1,64 +1,18 @@ // This file is auto-generated by @hey-api/openapi-ts -import { - createClient, - createConfig, - type OptionsLegacyParser, -} from "@hey-api/client-fetch"; -import type { - MetaInfoError, - MetaInfoResponse, - WorkspaceCreateData, - WorkspaceCreateError, - WorkspaceCreateResponse2, - TokenCreateData, - TokenCreateError, - TokenCreateResponse2, - TokenUpdateData, - TokenUpdateError, - TokenUpdateResponse2, - SandboxListData, - SandboxListError, - SandboxListResponse2, - SandboxCreateData, - SandboxCreateError, - SandboxCreateResponse2, - SandboxGetData, - SandboxGetError, - SandboxGetResponse2, - SandboxForkData, - SandboxForkError, - SandboxForkResponse2, - VmHibernateData, - VmHibernateError, - VmHibernateResponse, - VmShutdownData, - VmShutdownError, - VmShutdownResponse, - VmStartData, - VmStartError, - VmStartResponse, - VmUpdateSpecsData, - VmUpdateSpecsError, - VmUpdateSpecsResponse, -} from "./types.gen"; +import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch'; +import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse } from './types.gen'; export const client = createClient(createConfig()); /** * Metadata about the API */ -export const metaInfo = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).get< - MetaInfoResponse, - MetaInfoError, - ThrowOnError - >({ - ...options, - url: "/meta/info", - }); +export const metaInfo = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).get({ + ...options, + url: '/meta/info' + }); }; /** @@ -66,17 +20,11 @@ export const metaInfo = ( * Create a new, empty, workspace in the current organization * */ -export const workspaceCreate = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - WorkspaceCreateResponse2, - WorkspaceCreateError, - ThrowOnError - >({ - ...options, - url: "/org/workspace", - }); +export const workspaceCreate = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/org/workspace' + }); }; /** @@ -84,17 +32,11 @@ export const workspaceCreate = ( * Create a new API token for a workspace that is part of the current organization. * */ -export const tokenCreate = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - TokenCreateResponse2, - TokenCreateError, - ThrowOnError - >({ - ...options, - url: "/org/workspace/{team_id}/tokens", - }); +export const tokenCreate = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/org/workspace/{team_id}/tokens' + }); }; /** @@ -102,17 +44,11 @@ export const tokenCreate = ( * Update an API token for a workspace that is part of the current organization. * */ -export const tokenUpdate = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).patch< - TokenUpdateResponse2, - TokenUpdateError, - ThrowOnError - >({ - ...options, - url: "/org/workspace/{team_id}/tokens/{token_id}", - }); +export const tokenUpdate = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).patch({ + ...options, + url: '/org/workspace/{team_id}/tokens/{token_id}' + }); }; /** @@ -121,17 +57,11 @@ export const tokenUpdate = ( * Results are limited to a maximum of 50 sandboxes per request. * */ -export const sandboxList = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).get< - SandboxListResponse2, - SandboxListError, - ThrowOnError - >({ - ...options, - url: "/sandbox", - }); +export const sandboxList = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).get({ + ...options, + url: '/sandbox' + }); }; /** @@ -139,17 +69,11 @@ export const sandboxList = ( * Create a new sandbox in the current workspace with file contents * */ -export const sandboxCreate = ( - options?: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - SandboxCreateResponse2, - SandboxCreateError, - ThrowOnError - >({ - ...options, - url: "/sandbox", - }); +export const sandboxCreate = (options?: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/sandbox' + }); }; /** @@ -157,17 +81,11 @@ export const sandboxCreate = ( * Retrieve a sandbox by its ID * */ -export const sandboxGet = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).get< - SandboxGetResponse2, - SandboxGetError, - ThrowOnError - >({ - ...options, - url: "/sandbox/{id}", - }); +export const sandboxGet = (options: OptionsLegacyParser) => { + return (options?.client ?? client).get({ + ...options, + url: '/sandbox/{id}' + }); }; /** @@ -175,17 +93,11 @@ export const sandboxGet = ( * Fork an existing sandbox to the current workspace * */ -export const sandboxFork = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - SandboxForkResponse2, - SandboxForkError, - ThrowOnError - >({ - ...options, - url: "/sandbox/{id}/fork", - }); +export const sandboxFork = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/sandbox/{id}/fork' + }); }; /** @@ -199,17 +111,11 @@ export const sandboxFork = ( * minimal latency. * */ -export const vmHibernate = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - VmHibernateResponse, - VmHibernateError, - ThrowOnError - >({ - ...options, - url: "/vm/{id}/hibernate", - }); +export const vmHibernate = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/vm/{id}/hibernate' + }); }; /** @@ -222,17 +128,11 @@ export const vmHibernate = ( * Shutdown VMs require additional time to start up. * */ -export const vmShutdown = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - VmShutdownResponse, - VmShutdownError, - ThrowOnError - >({ - ...options, - url: "/vm/{id}/shutdown", - }); +export const vmShutdown = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/vm/{id}/shutdown' + }); }; /** @@ -247,17 +147,11 @@ export const vmShutdown = ( * This endpoint is subject to special rate limits related to concurrent VM usage. * */ -export const vmStart = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - VmStartResponse, - VmStartError, - ThrowOnError - >({ - ...options, - url: "/vm/{id}/start", - }); +export const vmStart = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/vm/{id}/start' + }); }; /** @@ -268,15 +162,9 @@ export const vmStart = ( * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs = ( - options: OptionsLegacyParser -) => { - return (options?.client ?? client).post< - VmUpdateSpecsResponse, - VmUpdateSpecsError, - ThrowOnError - >({ - ...options, - url: "/vm/{id}/update_specs", - }); -}; +export const vmUpdateSpecs = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/vm/{id}/update_specs' + }); +}; \ No newline at end of file diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 8ba0b40..66d3f61 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -132,6 +132,31 @@ export type SandboxForkRequest = { * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. */ privacy?: number; + /** + * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. + */ + start_options?: { + /** + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * + */ + hibernation_timeout_seconds?: number; + /** + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + */ + ipcountry?: string; + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; + }; /** * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. */ @@ -142,6 +167,16 @@ export type SandboxForkRequest = { title?: string; }; +/** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ +export type tier = 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; + export type SandboxForkResponse = { errors?: Array<((string | { [key: string]: unknown; @@ -151,6 +186,22 @@ export type SandboxForkResponse = { data?: { alias: string; id: string; + /** + * VM start response. Only present when start_options were provided in the request. + */ + start_response?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + } | null; title: (string) | null; }; }; @@ -318,16 +369,6 @@ export type VMStartRequest = { tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; -/** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ -export type tier = 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; - export type VMStartResponse = { errors?: Array<((string | { [key: string]: unknown; diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index ce4749f..5ed4fed 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -148,6 +148,15 @@ export class VMTier { } } +function startOptionsFromOpts(opts: StartSandboxOpts | undefined) { + if (!opts) return undefined; + return { + ipcountry: opts.ipcountry, + tier: opts.vmTier?.name, + hibernation_timeout_seconds: opts.hibernationTimeoutSeconds, + }; +} + export interface StartSandboxOpts { /** * Country, served as a hint on where you want the sandbox to be scheduled. For example, if "NL" is given @@ -218,11 +227,7 @@ export class SandboxClient { ): Promise { const startResult = await vmStart({ client: this.apiClient, - body: { - ipcountry: opts?.ipcountry, - tier: opts?.vmTier?.name, - hibernation_timeout_seconds: opts?.hibernationTimeoutSeconds, - }, + body: startOptionsFromOpts(opts), path: { id, }, @@ -261,6 +266,7 @@ export class SandboxClient { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + console.log(opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts)) const result = await sandboxFork({ client: this.apiClient, body: { @@ -269,6 +275,8 @@ export class SandboxClient { description: opts?.description, tags: tagsWithSdk, path, + start_options: + opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts || {}), }, path: { id: typeof templateId === "string" ? templateId : templateId.id, @@ -276,14 +284,15 @@ export class SandboxClient { }); const sandbox = handleResponse(result, "Failed to create sandbox"); + console.log(sandbox); - if (opts?.autoConnect === false) { - return this.start(sandbox.id, opts); - } + return this.connectToSandbox(sandbox.id, () => { + if (sandbox.start_response) { + return Promise.resolve(sandbox.start_response); + } - return this.connectToSandbox(sandbox.id, () => - this.start(sandbox.id, opts) - ); + return this.start(sandbox.id, opts); + }); } /** From af3a0233f1ac8dfae0a0d7cab6b206b4fe1dea5c Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 20 Dec 2024 18:59:39 +0100 Subject: [PATCH 005/241] feat: add support for together-ai api token --- .prettierrc | 1 + src/index.ts | 16 +++++++++++++--- src/sandbox-client.ts | 5 +++-- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/src/index.ts b/src/index.ts index 4e5970f..c343bfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,6 +36,14 @@ function ensure(value: T | undefined, message: string): T { return value; } +function getBaseUrl(token: string) { + if (token.startsWith("csb_")) { + return "https://api.codesandbox.io"; + } + + return "https://api.together.ai/csb/sdk"; +} + export class CodeSandbox { private baseUrl: string; private apiToken: string; @@ -44,13 +52,15 @@ export class CodeSandbox { public readonly sandbox: SandboxClient; constructor(apiToken?: string, private readonly opts: ClientOpts = {}) { - this.baseUrl = opts.baseUrl ?? "https://api.codesandbox.io"; this.apiToken = apiToken || ensure( - typeof process !== "undefined" ? process.env?.CSB_API_KEY : undefined, - "CSB_API_KEY is not set" + typeof process !== "undefined" + ? process.env?.CSB_API_KEY || process.env?.TOGETHER_API_KEY + : undefined, + "CSB_API_KEY or TOGETHER_API_KEY is not set" ); + this.baseUrl = opts.baseUrl ?? getBaseUrl(this.apiToken); this.apiClient = createClient( createConfig({ diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 5ed4fed..8ae4730 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -266,7 +266,6 @@ export class SandboxClient { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - console.log(opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts)) const result = await sandboxFork({ client: this.apiClient, body: { @@ -276,7 +275,9 @@ export class SandboxClient { tags: tagsWithSdk, path, start_options: - opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts || {}), + opts?.autoConnect === false + ? undefined + : startOptionsFromOpts(opts || {}), }, path: { id: typeof templateId === "string" ? templateId : templateId.id, From e95e428e7b68b92fce0bad3056a0a0246f7268bb Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 20 Dec 2024 19:06:49 +0100 Subject: [PATCH 006/241] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22fd23c..74b622f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.0.6", + "version": "0.0.7", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 5a7ca8d340f162b447eecaf2d48e1a763d62aa49 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 20 Dec 2024 14:15:21 -0500 Subject: [PATCH 007/241] chore: update readme (#9) * fix(cli): always create new sandboxes * chore: remove console.log * update version * chore: update readme timings Feels good! --- README.md | 5 ++--- package.json | 2 +- src/bin/commands/build.ts | 35 ++++++----------------------------- src/sandbox-client.ts | 1 - 4 files changed, 9 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 78b7d2b..b8981e1 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ CodeSandbox SDK enables you to programmatically spin up development environments Under the hood, the SDK uses the microVM infrastructure of CodeSandbox to spin up sandboxes. It supports: -- Starting fresh VMs within 4 seconds - Snapshotting/restoring VMs (checkpointing) at any point in time - - With snapshot restore times of less than 2 seconds -- Cloning VMs within 3 seconds + - With snapshot restore times within 1 second +- Cloning VMs & Snapshots within 2 seconds - Source control (git, GitHub, CodeSandbox SCM) - Running any Dockerfile diff --git a/package.json b/package.json index 74b622f..6bb8680 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.0.7", + "version": "0.0.9", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 048f0a6..4cc2b00 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -15,7 +15,6 @@ import { hashDirectory } from "../utils/hash"; export type BuildCommandArgs = { path: string; - force?: boolean; ipCountry?: string; fromSandbox?: string; skipFiles?: boolean; @@ -31,12 +30,6 @@ export const buildCommand: yargs.CommandModule< "Build an efficient memory snapshot from a directory. This snapshot can be used to create sandboxes quickly.", builder: (yargs: yargs.Argv) => yargs - .option("force", { - alias: "f", - describe: - "Force the creation of a new sandbox, normally a sandbox is reused if it already exists for the given folder (if the files are the same)", - type: "boolean", - }) .option("ip-country", { describe: "Cluster closest to this country to create the snapshot in, this ensures that sandboxes created of this snapshot will be created in the same cluster", @@ -101,7 +94,7 @@ export const buildCommand: yargs.CommandModule< alreadyExists: true, sandboxId: argv.fromSandbox, } - : await getOrCreateSandbox(apiClient, tag, argv.force); + : await createSandbox(apiClient, tag); if (alreadyExists && !argv.fromSandbox) { spinner.succeed("Sandbox snapshot has been created before:"); @@ -276,7 +269,9 @@ export const buildCommand: yargs.CommandModule< spinner.succeed("All ports are open"); } else { - spinner.succeed("No ports to open, waiting 5 seconds for tasks to run..."); + spinner.succeed( + "No ports to open, waiting 5 seconds for tasks to run..." + ); await new Promise((resolve) => setTimeout(resolve, 5000)); } @@ -297,31 +292,13 @@ export const buildCommand: yargs.CommandModule< }, }; -async function getOrCreateSandbox( +async function createSandbox( apiClient: Client, - shaTag: string, - force = false + shaTag: string ): Promise<{ alreadyExists: boolean; sandboxId: string; }> { - if (!force) { - const res = handleResponse( - await sandboxList({ - query: { - page_size: 1, - tags: shaTag, - }, - client: apiClient, - }), - "Failed to get sandboxes from API" - ); - - if (res.sandboxes.length > 0) { - return { alreadyExists: true, sandboxId: res.sandboxes[0].id }; - } - } - const sandbox = handleResponse( await sandboxCreate({ client: apiClient, diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 8ae4730..63f8960 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -285,7 +285,6 @@ export class SandboxClient { }); const sandbox = handleResponse(result, "Failed to create sandbox"); - console.log(sandbox); return this.connectToSandbox(sandbox.id, () => { if (sandbox.start_response) { From 393d53c68bc4ea33983302fc2056727126124fc8 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Sat, 21 Dec 2024 14:23:56 -0500 Subject: [PATCH 008/241] fix: don't keep VMs alive with polling (#10) --- package.json | 2 +- src/sandbox.ts | 104 +++++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 6bb8680..8805dfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.0.9", + "version": "0.0.10", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sandbox.ts b/src/sandbox.ts index 9603af5..6fb2de3 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -98,12 +98,13 @@ export class SandboxWithoutClient extends Disposable { constructor(protected pitcherClient: IPitcherClient) { super(); - const metricsDisposable = { - dispose: - this.pitcherClient.clients.system.startMetricsPollingAtInterval(5000), - }; + // TODO: Bring this back once metrics polling does not reset inactivity + // const metricsDisposable = { + // dispose: + // this.pitcherClient.clients.system.startMetricsPollingAtInterval(5000), + // }; - this.addDisposable(metricsDisposable); + // this.addDisposable(metricsDisposable); this.addDisposable(this.pitcherClient); } @@ -123,52 +124,53 @@ export class SandboxWithoutClient extends Disposable { return `https://codesandbox.io/p/devbox/${this.id}`; } - /** - * Get the current system metrics. This return type may change in the future. - */ - public async getMetrics(): Promise { - await this.pitcherClient.clients.system.update(); - - const barrier = new Barrier<_protocol.system.SystemMetricsStatus>(); - const initialMetrics = this.pitcherClient.clients.system.getMetrics(); - if (!initialMetrics) { - const disposable = this.pitcherClient.clients.system.onMetricsUpdated( - (metrics) => { - if (metrics) { - barrier.open(metrics); - } - } - ); - disposable.dispose(); - } else { - barrier.open(initialMetrics); - } - - const barrierResult = await barrier.wait(); - if (barrierResult.status === "disposed") { - throw new Error("Metrics not available"); - } - - const metrics = barrierResult.value; - - return { - cpu: { - cores: metrics.cpu.cores, - used: metrics.cpu.used / 100, - configured: metrics.cpu.configured, - }, - memory: { - usedKiB: metrics.memory.used * 1024 * 1024, - totalKiB: metrics.memory.total * 1024 * 1024, - configuredKiB: metrics.memory.total * 1024 * 1024, - }, - storage: { - usedKB: metrics.storage.used * 1000 * 1000, - totalKB: metrics.storage.total * 1000 * 1000, - configuredKB: metrics.storage.configured * 1000 * 1000, - }, - }; - } + // TODO: Bring this back once metrics polling does not reset inactivity + // /** + // * Get the current system metrics. This return type may change in the future. + // */ + // public async getMetrics(): Promise { + // await this.pitcherClient.clients.system.update(); + + // const barrier = new Barrier<_protocol.system.SystemMetricsStatus>(); + // const initialMetrics = this.pitcherClient.clients.system.getMetrics(); + // if (!initialMetrics) { + // const disposable = this.pitcherClient.clients.system.onMetricsUpdated( + // (metrics) => { + // if (metrics) { + // barrier.open(metrics); + // } + // } + // ); + // disposable.dispose(); + // } else { + // barrier.open(initialMetrics); + // } + + // const barrierResult = await barrier.wait(); + // if (barrierResult.status === "disposed") { + // throw new Error("Metrics not available"); + // } + + // const metrics = barrierResult.value; + + // return { + // cpu: { + // cores: metrics.cpu.cores, + // used: metrics.cpu.used / 100, + // configured: metrics.cpu.configured, + // }, + // memory: { + // usedKiB: metrics.memory.used * 1024 * 1024, + // totalKiB: metrics.memory.total * 1024 * 1024, + // configuredKiB: metrics.memory.total * 1024 * 1024, + // }, + // storage: { + // usedKB: metrics.storage.used * 1000 * 1000, + // totalKB: metrics.storage.total * 1000 * 1000, + // configuredKB: metrics.storage.configured * 1000 * 1000, + // }, + // }; + // } /** * Disconnect from the sandbox, this does not hibernate the sandbox (but it will From 6995957220a3cc0da61f84d472a4e64d9bda0ebc Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 7 Jan 2025 13:41:51 +0100 Subject: [PATCH 009/241] fix: remove require banner for esm builds (#15) --- esbuild.js | 12 ------------ package-lock.json | 4 ++-- package.json | 5 ++--- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/esbuild.js b/esbuild.js index 3e3a8e0..8f6b307 100644 --- a/esbuild.js +++ b/esbuild.js @@ -59,12 +59,6 @@ Promise.all([ platform: "node", outdir: "dist/esm", plugins: [browserifyPlugin], - banner: { - js: ` -import {createRequire} from 'module' -const require = createRequire(import.meta.url) - `.trim(), - }, }), // Edge: @@ -86,12 +80,6 @@ const require = createRequire(import.meta.url) format: "esm", platform: "browser", plugins: [browserifyPlugin], - banner: { - js: ` -import {createRequire} from 'module' -const require = createRequire(import.meta.url) - `.trim(), - }, }), // Bin builds: diff --git a/package-lock.json b/package-lock.json index f103998..4c74bcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.0.6", + "version": "0.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.0.6", + "version": "0.0.11", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 8805dfa..0838571 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.0.10", + "version": "0.0.11", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -97,6 +97,5 @@ "util": "0.12.5", "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" - }, - "dependencies": {} + } } From f72caaaea99ab9701ed4900105d771913a2b6e5d Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 00:54:04 +0100 Subject: [PATCH 010/241] chore: add release please (#17) --- .github/workflows/release-please.yml | 40 ++++++++++++++++++++++++++++ release-please-config.json | 13 +++++++++ 2 files changed, 53 insertions(+) create mode 100644 .github/workflows/release-please.yml create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..5a80f55 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,40 @@ +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + release-type: node + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + registry-url: "https://registry.npmjs.org" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm build + + - name: Publish to NPM + if: ${{ steps.release.outputs.release_created }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --no-git-checks diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..122ce4d --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "node", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "draft": false, + "prerelease": false + } + } +} From 1be1154ece7920ed2cfbb49d2be887e0b8810974 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 00:57:46 +0100 Subject: [PATCH 011/241] chore: add more release please configuration (#19) * chore: add release please * chore: fix pnpm mentions * chore: only do minor releases --- .github/workflows/release-please.yml | 1 + package.json | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 5a80f55..229e4cf 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -18,6 +18,7 @@ jobs: with: release-type: node token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + bump-minor-pre-major: true - uses: actions/checkout@v4 diff --git a/package.json b/package.json index 0838571..987d01f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ }, "types": "./dist/esm/index.d.ts", "scripts": { - "build": "pnpm clean && pnpm build:esbuild && pnpm build:cjs:types && pnpm build:esm:types && chmod +x dist/bin/codesandbox.js", + "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.js", "build:cjs": "tsc -p ./tsconfig.build-cjs.json", "build:esm": "tsc -p ./tsconfig.build-esm.json", "build:esbuild": "node esbuild.js", @@ -61,9 +61,8 @@ "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", "postbuild": "rimraf {lib,es}/**/__tests__ {lib,es}/**/*.{spec,test}.{js,d.ts,js.map}", - "preversion": "pnpm test", "postversion": "git push && git push --tags", - "prepublish": "pnpm build" + "prepublish": "npm run build" }, "keywords": [ "typescript", From 8ae6182757fbbeaeeda6c7b1000180261f3e99d8 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 00:59:20 +0100 Subject: [PATCH 012/241] chore: use npm install (#20) --- .github/workflows/release-please.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 229e4cf..e3f1e76 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -29,7 +29,7 @@ jobs: cache: "npm" - name: Install dependencies - run: npm ci + run: npm install - name: Build run: npm build From 20c9628e8e61b942510ebeda250fcc4a8114fe91 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:01:03 +0100 Subject: [PATCH 013/241] chore: install rimraf (#22) * chore: use npm install * chore: install rimraf --- package-lock.json | 473 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 474 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4c74bcd..52c24f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", + "rimraf": "^6.0.1", "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", @@ -664,6 +665,109 @@ "url": "https://github.com/sponsors/hey-api" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@jumpn/utils-array": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", @@ -1526,6 +1630,21 @@ "node-fetch": "^2.6.12" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-browserify": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", @@ -1691,6 +1810,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -1908,6 +2034,36 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2040,6 +2196,46 @@ "dev": true, "license": "MIT" }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2349,6 +2545,13 @@ "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/isomorphic-ws": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", @@ -2366,6 +2569,22 @@ "dev": true, "license": "MIT" }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/js-untar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", @@ -2493,6 +2712,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/map-canvas": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", @@ -2714,6 +2943,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2968,6 +3207,13 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -3000,6 +3246,33 @@ "dev": true, "license": "MIT" }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -3225,6 +3498,26 @@ "node": ">= 4" } }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -3313,6 +3606,29 @@ "sha.js": "bin.js" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3402,6 +3718,45 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3438,6 +3793,30 @@ "node": ">=0.10.0" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3660,6 +4039,22 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-typed-array": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", @@ -3725,6 +4120,84 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", diff --git a/package.json b/package.json index 987d01f..2ba2541 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", + "rimraf": "^6.0.1", "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", From d9f16b89de9b788960b261e00496f52690ba031d Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:14:39 +0100 Subject: [PATCH 014/241] chore: force 0.1.0 release (#23) --- .github/workflows/release-please.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index e3f1e76..4efd913 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -18,7 +18,9 @@ jobs: with: release-type: node token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + release-as: 0.1.0 # Temp. Remove after initial release-please PR has been merged bump-minor-pre-major: true + bump-patch-for-minor-pre-major: true - uses: actions/checkout@v4 From 7210b80176ab91b7505ae2f480437df6645b8b45 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:17:44 +0100 Subject: [PATCH 015/241] chore: fix npm build command (#24) --- .github/workflows/release-please.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 4efd913..cf18882 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -34,7 +34,7 @@ jobs: run: npm install - name: Build - run: npm build + run: npm run build - name: Publish to NPM if: ${{ steps.release.outputs.release_created }} From f458b7517577a31c5e5f81b68ca3ad5d78763c70 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:19:34 +0100 Subject: [PATCH 016/241] chore: release as 0.1.0 in config (#26) --- release-please-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/release-please-config.json b/release-please-config.json index 122ce4d..46cd473 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -6,6 +6,7 @@ "release-type": "node", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, + "release-as": "0.1.0", "draft": false, "prerelease": false } From 352d0492f78f508089d77750b7d5b4f773da797e Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:21:42 +0100 Subject: [PATCH 017/241] chore: force release as 0.1.0 (#28) Release-As: 0.1.0 --- .github/workflows/release-please.yml | 1 - release-please-config.json | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cf18882..7a46637 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -18,7 +18,6 @@ jobs: with: release-type: node token: ${{ secrets.RELEASE_PLEASE_TOKEN }} - release-as: 0.1.0 # Temp. Remove after initial release-please PR has been merged bump-minor-pre-major: true bump-patch-for-minor-pre-major: true diff --git a/release-please-config.json b/release-please-config.json index 46cd473..122ce4d 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -6,7 +6,6 @@ "release-type": "node", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, - "release-as": "0.1.0", "draft": false, "prerelease": false } From 9368a6a70db58fd5bceda67d4ab6ea8618b981e9 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:22:33 +0100 Subject: [PATCH 018/241] chore(main): release 0.1.0 (#29) --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..03e00d9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## 0.1.0 (2025-01-09) + + +### Features + +* add support for together-ai api token ([af3a023](https://github.com/codesandbox/codesandbox-sdk/commit/af3a0233f1ac8dfae0a0d7cab6b206b4fe1dea5c)) + + +### Bug Fixes + +* don't keep VMs alive with polling ([#10](https://github.com/codesandbox/codesandbox-sdk/issues/10)) ([393d53c](https://github.com/codesandbox/codesandbox-sdk/commit/393d53c68bc4ea33983302fc2056727126124fc8)) +* include sandbox id in build command ([9a737ea](https://github.com/codesandbox/codesandbox-sdk/commit/9a737ea3f6b3a26d6dd7953f7742494bae9d68a7)) +* remove require banner for esm builds ([#15](https://github.com/codesandbox/codesandbox-sdk/issues/15)) ([6995957](https://github.com/codesandbox/codesandbox-sdk/commit/6995957220a3cc0da61f84d472a4e64d9bda0ebc)) + + +### Performance Improvements + +* start a vm while forking ([9b1774c](https://github.com/codesandbox/codesandbox-sdk/commit/9b1774c5d55ef802c069481a10e317e96d618f70)) + + +### Miscellaneous Chores + +* force release as 0.1.0 ([#28](https://github.com/codesandbox/codesandbox-sdk/issues/28)) ([352d049](https://github.com/codesandbox/codesandbox-sdk/commit/352d0492f78f508089d77750b7d5b4f773da797e)) diff --git a/package-lock.json b/package-lock.json index 52c24f7..dcf8b3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.0.11", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.0.11", + "version": "0.1.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 2ba2541..fcba75d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.0.11", + "version": "0.1.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 6cd070ed1c0c8fc81eba21fcd990bf145489cf91 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 9 Jan 2025 01:25:26 +0100 Subject: [PATCH 019/241] perf(build): precreate files inside sandbox (#16) --- package-lock.json | 719 +++++++------------------------------- package.json | 3 +- src/bin/commands/build.ts | 53 ++- 3 files changed, 166 insertions(+), 609 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcf8b3e..a143ec4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "crypto-browserify": "^3.12.1", "esbuild": "^0.24.0", "ignore": "^6.0.2", - "ora": "5.4.1", + "isbinaryfile": "^5.0.4", + "ora": "7.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -247,74 +248,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", @@ -332,329 +265,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@hey-api/client-fetch": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.5.2.tgz", @@ -1079,42 +689,17 @@ "license": "MIT" }, "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", "dev": true, "license": "MIT", "dependencies": { - "buffer": "^5.5.0", + "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bl/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -1442,16 +1027,19 @@ "license": "ISC" }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-spinners": { @@ -1521,16 +1109,6 @@ "node": ">=8" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1690,19 +1268,6 @@ } } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2490,13 +2055,16 @@ } }, "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-number": { @@ -2526,13 +2094,13 @@ } }, "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2545,6 +2113,19 @@ "dev": true, "license": "MIT" }, + "node_modules/isbinaryfile": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", + "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2623,88 +2204,35 @@ "license": "MIT" }, "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lru_map": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", @@ -3041,116 +2569,94 @@ } }, "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true, "license": "MIT" }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/ora/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/os-browserify": { @@ -3475,9 +2981,9 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "license": "MIT", "dependencies": { @@ -3485,7 +2991,10 @@ "signal-exit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -3679,6 +3188,22 @@ "outvariant": "^1.3.0" } }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strict-event-emitter": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", @@ -4011,16 +3536,6 @@ "node": ">=14.0.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index fcba75d..dd901d9 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,8 @@ "crypto-browserify": "^3.12.1", "esbuild": "^0.24.0", "ignore": "^6.0.2", - "ora": "5.4.1", + "isbinaryfile": "^5.0.4", + "ora": "7.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 4cc2b00..71fb9e5 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,5 +1,6 @@ import { promises as fs } from "fs"; import path from "path"; +import { isBinaryFile } from "isbinaryfile"; import { DisposableStore } from "@codesandbox/pitcher-common"; import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; @@ -89,12 +90,13 @@ export const buildCommand: yargs.CommandModule< const tag = `sha:${shortHash}-${cluster || ""}`; spinner.start(`Creating or updating sandbox...`); - const { alreadyExists, sandboxId } = argv.fromSandbox + const { alreadyExists, sandboxId, filesIncluded } = argv.fromSandbox ? { alreadyExists: true, + filesIncluded: false, sandboxId: argv.fromSandbox, } - : await createSandbox(apiClient, tag); + : await createSandbox(apiClient, tag, filePaths, argv.path); if (alreadyExists && !argv.fromSandbox) { spinner.succeed("Sandbox snapshot has been created before:"); @@ -120,7 +122,7 @@ export const buildCommand: yargs.CommandModule< }); spinner.succeed("Sandbox opened"); - if (!argv.skipFiles) { + if (!argv.skipFiles && !filesIncluded) { spinner.start("Writing files to sandbox..."); let i = 0; for (const filePath of filePaths) { @@ -294,16 +296,22 @@ export const buildCommand: yargs.CommandModule< async function createSandbox( apiClient: Client, - shaTag: string + shaTag: string, + filePaths: string[], + rootPath: string ): Promise<{ alreadyExists: boolean; sandboxId: string; + filesIncluded: boolean; }> { + // Include the files in the sandbox if there are no binary files and there are 30 or less files + const files = await getFiles(filePaths, rootPath); + const sandbox = handleResponse( await sandboxCreate({ client: apiClient, body: { - files: {}, + files, privacy: 1, tags: ["sdk", shaTag], path: "/SDK-Templates", @@ -314,5 +322,38 @@ async function createSandbox( "Failed to create sandbox" ); - return { alreadyExists: false, sandboxId: sandbox.id }; + return { + alreadyExists: false, + sandboxId: sandbox.id, + filesIncluded: Object.keys(files).length > 0, + }; +} + +async function getFiles( + filePaths: string[], + rootPath: string +): Promise> { + if (filePaths.length > 30) { + return {}; + } + + let hasBinaryFile = false; + const files: Record = {}; + await Promise.all( + filePaths.map(async (filePath) => { + const content = await fs.readFile(path.join(rootPath, filePath)); + + if (await isBinaryFile(content)) { + hasBinaryFile = true; + } + + files[filePath] = { code: content.toString() }; + }) + ); + + if (hasBinaryFile) { + return {}; + } + + return files; } From efd20510512cfeacc776d47cb0cf5d5563ba2914 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 10 Jan 2025 16:50:36 +0100 Subject: [PATCH 020/241] feat: add support for updating hibernation timeout (#31) --- openapi.json | 137 +++++++++++++++++++++++++++++++++++++++- src/client/sdk.gen.ts | 36 ++++++++++- src/client/types.gen.ts | 62 +++++++++++++++++- src/sandbox-client.ts | 23 +++++++ src/sandbox.ts | 9 +++ 5 files changed, 260 insertions(+), 7 deletions(-) diff --git a/openapi.json b/openapi.json index b431bfd..b8c6e7e 100644 --- a/openapi.json +++ b/openapi.json @@ -747,6 +747,57 @@ ], "title": "VMStartResponse" }, + "VMUpdateHibernationTimeoutRequest": { + "properties": { + "hibernation_timeout_seconds": { + "description": "The new hibernation timeout in seconds.\n\nMust be greater than 0 and less than or equal to 86400 (24 hours).\n", + "example": 300, + "maximum": 86400, + "minimum": 1, + "type": "integer" + } + }, + "required": ["hibernation_timeout_seconds"], + "title": "VMUpdateHibernationTimeoutRequest", + "type": "object" + }, + "VMUpdateHibernationTimeoutResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": [ + { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + } + ], + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "hibernation_timeout_seconds": { "type": "integer" }, + "id": { "type": "string" } + }, + "required": ["id", "hibernation_timeout_seconds"], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMUpdateHibernationTimeoutResponse" + }, "VMUpdateSpecsRequest": { "properties": { "tier": { @@ -1181,6 +1232,49 @@ "tags": ["vm"] } }, + "/vm/{id}/hibernation_timeout": { + "put": { + "callbacks": {}, + "description": "Updates the hibernation timeout of a running VM.\n\nThis endpoint can only be used on VMs that belong to your team's workspace.\nThe new timeout must be greater than 0 and less than or equal to 86400 seconds (24 hours).\n", + "operationId": "vm/update_hibernation_timeout", + "parameters": [ + { + "description": "Sandbox ID", + "example": "new", + "in": "path", + "name": "id", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMUpdateHibernationTimeoutRequest" + } + } + }, + "description": "VM Update Hibernation Timeout Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMUpdateHibernationTimeoutResponse" + } + } + }, + "description": "VM Update Hibernation Timeout Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Update VM Hibernation Timeout", + "tags": ["vm"] + } + }, "/vm/{id}/shutdown": { "post": { "callbacks": {}, @@ -1220,6 +1314,47 @@ "tags": ["vm"] } }, + "/vm/{id}/specs": { + "put": { + "callbacks": {}, + "description": "Updates the specifications (CPU, memory, storage) of a running VM.\n\nThis endpoint can only be used on VMs that belong to your team's workspace.\nThe new tier must not exceed your team's maximum allowed tier.\n", + "operationId": "vm/update_specs", + "parameters": [ + { + "description": "Sandbox ID", + "example": "new", + "in": "path", + "name": "id", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } + } + }, + "description": "VM Update Specs Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMUpdateSpecsResponse" + } + } + }, + "description": "VM Update Specs Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Update VM Specs", + "tags": ["vm"] + } + }, "/vm/{id}/start": { "post": { "callbacks": {}, @@ -1263,7 +1398,7 @@ "post": { "callbacks": {}, "description": "Updates the specifications (CPU, memory, storage) of a running VM.\n\nThis endpoint can only be used on VMs that belong to your team's workspace.\nThe new tier must not exceed your team's maximum allowed tier.\n", - "operationId": "vm/update_specs", + "operationId": "vm/update_specs (2)", "parameters": [ { "description": "Sandbox ID", diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index 6bd98a4..db5daed 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch'; -import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse } from './types.gen'; +import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutError, VmUpdateHibernationTimeoutResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecs2Data, VmUpdateSpecs2Error, VmUpdateSpecs2Response } from './types.gen'; export const client = createClient(createConfig()); @@ -118,6 +118,21 @@ export const vmHibernate = (options: Optio }); }; +/** + * Update VM Hibernation Timeout + * Updates the hibernation timeout of a running VM. + * + * This endpoint can only be used on VMs that belong to your team's workspace. + * The new timeout must be greater than 0 and less than or equal to 86400 seconds (24 hours). + * + */ +export const vmUpdateHibernationTimeout = (options: OptionsLegacyParser) => { + return (options?.client ?? client).put({ + ...options, + url: '/vm/{id}/hibernation_timeout' + }); +}; + /** * Shutdown a VM * Stops a running VM, ending all currently running processes @@ -135,6 +150,21 @@ export const vmShutdown = (options: Option }); }; +/** + * Update VM Specs + * Updates the specifications (CPU, memory, storage) of a running VM. + * + * This endpoint can only be used on VMs that belong to your team's workspace. + * The new tier must not exceed your team's maximum allowed tier. + * + */ +export const vmUpdateSpecs = (options: OptionsLegacyParser) => { + return (options?.client ?? client).put({ + ...options, + url: '/vm/{id}/specs' + }); +}; + /** * Start a VM * Start a virtual machine for the sandbox (devbox) with the given ID @@ -162,8 +192,8 @@ export const vmStart = (options: OptionsLe * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmUpdateSpecs2 = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ ...options, url: '/vm/{id}/update_specs' }); diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 66d3f61..915521e 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -390,6 +390,28 @@ export type VMStartResponse = { }; }; +export type VMUpdateHibernationTimeoutRequest = { + /** + * The new hibernation timeout in seconds. + * + * Must be greater than 0 and less than or equal to 86400 (24 hours). + * + */ + hibernation_timeout_seconds: number; +}; + +export type VMUpdateHibernationTimeoutResponse = { + errors?: Array<((string | { + [key: string]: unknown; +}))>; + success?: boolean; +} & { + data?: { + hibernation_timeout_seconds: number; + id: string; + }; +}; + export type VMUpdateSpecsRequest = { /** * Determines which specs to update the VM with. @@ -555,6 +577,23 @@ export type VmHibernateResponse = (VMHibernateResponse); export type VmHibernateError = unknown; +export type VmUpdateHibernationTimeoutData = { + /** + * VM Update Hibernation Timeout Request + */ + body?: VMUpdateHibernationTimeoutRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; +}; + +export type VmUpdateHibernationTimeoutResponse = (VMUpdateHibernationTimeoutResponse); + +export type VmUpdateHibernationTimeoutError = unknown; + export type VmShutdownData = { /** * VM Shutdown Request @@ -572,6 +611,23 @@ export type VmShutdownResponse = (VMShutdownResponse); export type VmShutdownError = unknown; +export type VmUpdateSpecsData = { + /** + * VM Update Specs Request + */ + body?: VMUpdateSpecsRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; +}; + +export type VmUpdateSpecsResponse = (VMUpdateSpecsResponse); + +export type VmUpdateSpecsError = unknown; + export type VmStartData = { /** * VM Start Request @@ -589,7 +645,7 @@ export type VmStartResponse = (VMStartResponse); export type VmStartError = unknown; -export type VmUpdateSpecsData = { +export type VmUpdateSpecs2Data = { /** * VM Update Specs Request */ @@ -602,6 +658,6 @@ export type VmUpdateSpecsData = { }; }; -export type VmUpdateSpecsResponse = (VMUpdateSpecsResponse); +export type VmUpdateSpecs2Response = (VMUpdateSpecsResponse); -export type VmUpdateSpecsError = unknown; \ No newline at end of file +export type VmUpdateSpecs2Error = unknown; \ No newline at end of file diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 63f8960..c6fb14f 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -7,6 +7,7 @@ import { vmHibernate, vmShutdown, vmStart, + vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./client"; import { Sandbox } from "./sandbox"; @@ -371,6 +372,28 @@ export class SandboxClient { handleResponse(response, `Failed to update sandbox tier ${id}`); } + /** + * Updates the hibernation timeout of a sandbox. + * + * @param id The ID of the sandbox to update + * @param timeoutSeconds The new hibernation timeout in seconds + */ + async updateHibernationTimeout( + id: string, + timeoutSeconds: number + ): Promise { + const response = await vmUpdateHibernationTimeout({ + client: this.apiClient, + path: { id }, + body: { hibernation_timeout_seconds: timeoutSeconds }, + }); + + handleResponse( + response, + `Failed to update hibernation timeout for sandbox ${id}` + ); + } + private async connectToSandbox( id: string, startVm: () => Promise< diff --git a/src/sandbox.ts b/src/sandbox.ts index 6fb2de3..136b1e0 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -245,4 +245,13 @@ export class Sandbox extends SandboxWithoutClient { public async updateTier(tier: VMTier): Promise { await this.sandboxClient.updateTier(this.id, tier); } + + /** + * Updates the hibernation timeout of a sandbox. + * + * @param timeoutSeconds The new hibernation timeout in seconds + */ + public async updateHibernationTimeout(timeoutSeconds: number): Promise { + await this.sandboxClient.updateHibernationTimeout(this.id, timeoutSeconds); + } } From c5f262d988f75727644c8e9b6785986920fd4cb1 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Mon, 13 Jan 2025 14:00:06 +0100 Subject: [PATCH 021/241] chore(main): release 0.2.0 (#30) --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e00d9..3849839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.1.0...v0.2.0) (2025-01-10) + + +### Features + +* add support for updating hibernation timeout ([#31](https://github.com/codesandbox/codesandbox-sdk/issues/31)) ([efd2051](https://github.com/codesandbox/codesandbox-sdk/commit/efd20510512cfeacc776d47cb0cf5d5563ba2914)) + + +### Performance Improvements + +* **build:** precreate files inside sandbox ([#16](https://github.com/codesandbox/codesandbox-sdk/issues/16)) ([6cd070e](https://github.com/codesandbox/codesandbox-sdk/commit/6cd070ed1c0c8fc81eba21fcd990bf145489cf91)) + ## 0.1.0 (2025-01-09) diff --git a/package-lock.json b/package-lock.json index a143ec4..dfc9ec1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index dd901d9..9889bef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.1.0", + "version": "0.2.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 677139624f95431ec97e12ec985c3545bfe43678 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 14 Jan 2025 19:05:43 +0100 Subject: [PATCH 022/241] feat: add support for listing sandboxes (#33) --- src/index.ts | 11 +++++++- src/sandbox-client.ts | 61 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c343bfe..90d9bb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,18 @@ import { SandboxStartData, CreateSandboxOpts, VMTier, + SandboxListOpts, + SandboxInfo, } from "./sandbox-client"; -export { SandboxClient, SandboxStartData, CreateSandboxOpts, VMTier }; +export { + SandboxClient, + SandboxStartData, + CreateSandboxOpts, + VMTier, + SandboxListOpts, + SandboxInfo, +}; export * from "./sandbox"; export interface ClientOpts { diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index c6fb14f..d825d5c 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -4,6 +4,7 @@ import type { Client } from "@hey-api/client-fetch"; import type { VmStartResponse, tier } from "./client"; import { sandboxFork, + sandboxList, vmHibernate, vmShutdown, vmStart, @@ -16,6 +17,24 @@ import { handleResponse } from "./utils/handle-response"; export type SandboxPrivacy = "public" | "unlisted" | "private"; export type SandboxStartData = Required["data"]; +export type SandboxInfo = { + id: string; + createdAt: Date; + updatedAt: Date; + title?: string; + description?: string; + privacy: SandboxPrivacy; + tags: string[]; +}; + +export type SandboxListOpts = { + tags?: string[]; + page?: number; + pageSize?: number; + orderBy?: "inserted_at" | "updated_at"; + direction?: "asc" | "desc"; +}; + export const DEFAULT_SUBSCRIPTIONS = { client: { status: true, @@ -352,6 +371,35 @@ export class SandboxClient { handleResponse(response, `Failed to hibernate sandbox ${id}`); } + /** + * List sandboxes from the current workspace with optional filters. + * Results are limited to a maximum of 50 sandboxes per request. + */ + async list(opts: SandboxListOpts = {}): Promise { + const response = await sandboxList({ + client: this.apiClient, + query: { + tags: opts.tags?.join(","), + page: opts.page, + page_size: opts.pageSize, + order_by: opts.orderBy, + direction: opts.direction, + }, + }); + + const info = handleResponse(response, "Failed to list sandboxes"); + + return info.sandboxes.map((sandbox) => ({ + id: sandbox.id, + createdAt: new Date(sandbox.created_at), + updatedAt: new Date(sandbox.updated_at), + title: sandbox.title ?? undefined, + description: sandbox.description ?? undefined, + privacy: privacyFromNumber(sandbox.privacy), + tags: sandbox.tags, + })); + } + /** * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory @@ -467,3 +515,16 @@ function privacyToNumber(privacy: SandboxPrivacy): number { return 2; } } + +function privacyFromNumber(privacy: number): SandboxPrivacy { + switch (privacy) { + case 0: + return "public"; + case 1: + return "unlisted"; + case 2: + return "private"; + } + + throw new Error(`Invalid privacy number: ${privacy}`); +} From 0e5f742df0a0fe11a9a41adfb48ee4114489e9c6 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 14 Jan 2025 19:48:49 +0100 Subject: [PATCH 023/241] chore(main): release 0.3.0 (#34) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3849839..e2a864d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.3.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.2.0...v0.3.0) (2025-01-14) + + +### Features + +* add support for listing sandboxes ([#33](https://github.com/codesandbox/codesandbox-sdk/issues/33)) ([6771396](https://github.com/codesandbox/codesandbox-sdk/commit/677139624f95431ec97e12ec985c3545bfe43678)) + ## [0.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.1.0...v0.2.0) (2025-01-10) diff --git a/package-lock.json b/package-lock.json index dfc9ec1..ba8292f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 9889bef..c91b5a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.2.0", + "version": "0.3.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 491d13db77992df5e3ab3fefb5b9de7e8edbd1c9 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 22 Jan 2025 19:18:47 +0100 Subject: [PATCH 024/241] feat: support for creating sessions (#32) * feat: add support for creating sessions * support autoConnect and createReadOnly --- openapi.json | 128 ++++++++++++++++++++++++++++++++++++++++ src/browser.ts | 81 +++++++++++++++++-------- src/client/sdk.gen.ts | 18 +++++- src/client/types.gen.ts | 69 ++++++++++++++++++++++ src/sandbox-client.ts | 68 ++++++++++++++++++++- src/sandbox.ts | 10 +++- src/sessions.ts | 80 +++++++++++++++++++++++++ 7 files changed, 425 insertions(+), 29 deletions(-) create mode 100644 src/sessions.ts diff --git a/openapi.json b/openapi.json index b8c6e7e..6028b1b 100644 --- a/openapi.json +++ b/openapi.json @@ -599,6 +599,91 @@ ], "title": "TokenUpdateResponse" }, + "VMCreateSessionRequest": { + "properties": { + "permission": { + "description": "Permission level for the session", + "enum": ["read", "write"], + "example": "write", + "type": "string" + }, + "session_id": { + "description": "Unique identifier for the session", + "example": "my-session-1", + "type": "string" + } + }, + "required": ["session_id", "permission"], + "title": "VMCreateSessionRequest", + "type": "object" + }, + "VMCreateSessionResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": [ + { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + } + ], + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "capabilities": { + "description": "List of capabilities granted to this session", + "items": { "type": "string" }, + "type": "array" + }, + "permissions": { + "description": "Detailed permissions for this session", + "type": "object" + }, + "pitcher_token": { + "description": "Token to authenticate with Pitcher", + "type": "string" + }, + "pitcher_url": { + "description": "WebSocket URL to connect to Pitcher", + "type": "string" + }, + "user_workspace_path": { + "description": "Path to the user's workspace in the VM", + "type": "string" + }, + "username": { + "description": "The Linux username created for this session", + "type": "string" + } + }, + "required": [ + "username", + "pitcher_token", + "user_workspace_path", + "capabilities", + "permissions", + "pitcher_url" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMCreateSessionResponse" + }, "VMHibernateRequest": { "properties": {}, "title": "VMHibernateRequest" }, "VMHibernateResponse": { "allOf": [ @@ -1275,6 +1360,49 @@ "tags": ["vm"] } }, + "/vm/{id}/sessions": { + "post": { + "callbacks": {}, + "description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container\nwhile their API token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the username is based on the session ID.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n", + "operationId": "vm/create_session", + "parameters": [ + { + "description": "Sandbox ID", + "example": "new", + "in": "path", + "name": "id", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMCreateSessionRequest" + } + } + }, + "description": "VM Create Session Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMCreateSessionResponse" + } + } + }, + "description": "VM Create Session Response" + } + }, + "security": [{ "authorization": ["sandbox:read", "vm:manage"] }], + "summary": "Create a new session on a VM", + "tags": ["vm"] + } + }, "/vm/{id}/shutdown": { "post": { "callbacks": {}, diff --git a/src/browser.ts b/src/browser.ts index 5b8540e..bea9f8d 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,10 +1,20 @@ -import { initPitcherClient } from "@codesandbox/pitcher-client"; +import { + initPitcherClient, + PitcherManagerResponse, +} from "@codesandbox/pitcher-client"; -import { SandboxWithoutClient } from "./sandbox"; +import { SandboxSession } from "./sandbox"; import { DEFAULT_SUBSCRIPTIONS, type SandboxStartData } from "./sandbox-client"; +import { SessionConnectInfo } from "./sessions"; export { SandboxStartData }; +function isStartData( + data: SandboxStartData | SessionConnectInfo +): data is SandboxStartData { + return "bootup_type" in data; +} + /** * With this function you can connect to a sandbox from the browser. * @@ -41,8 +51,47 @@ export { SandboxStartData }; * ``` */ export async function connectToSandbox( - startInfo: SandboxStartData, -): Promise { + startInfo: SandboxStartData | SessionConnectInfo +): Promise { + const useStartData = isStartData(startInfo); + + let requestPitcherInstance: () => Promise; + if (useStartData) { + requestPitcherInstance = async () => { + const data = startInfo; + + return { + bootupType: data.bootup_type as "RUNNING" | "CLEAN" | "RESUME" | "FORK", + pitcherURL: data.pitcher_url, + workspacePath: data.workspace_path, + userWorkspacePath: data.user_workspace_path, + pitcherManagerVersion: data.pitcher_manager_version, + pitcherVersion: data.pitcher_version, + latestPitcherVersion: data.latest_pitcher_version, + pitcherToken: data.pitcher_token, + cluster: data.cluster, + }; + }; + } else { + requestPitcherInstance = async () => { + const data = startInfo; + + return { + bootupType: "RESUME", + cluster: "session", + id: data.id, + latestPitcherVersion: "1.0.0-session", + pitcherManagerVersion: "1.0.0-session", + pitcherToken: data.pitcher_token, + pitcherURL: data.pitcher_url, + pitcherVersion: "1.0.0-session", + reconnectToken: "", + userWorkspacePath: data.user_workspace_path, + workspacePath: data.user_workspace_path, + }; + }; + } + const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -50,29 +99,11 @@ export async function connectToSandbox( onFocusChange() { return () => {}; }, - requestPitcherInstance: async () => { - const data = startInfo; - - return { - bootupType: data.bootup_type as - | "RUNNING" - | "CLEAN" - | "RESUME" - | "FORK", - pitcherURL: data.pitcher_url, - workspacePath: data.workspace_path, - userWorkspacePath: data.user_workspace_path, - pitcherManagerVersion: data.pitcher_manager_version, - pitcherVersion: data.pitcher_version, - latestPitcherVersion: data.latest_pitcher_version, - pitcherToken: data.pitcher_token, - cluster: data.cluster, - }; - }, + requestPitcherInstance, subscriptions: DEFAULT_SUBSCRIPTIONS, }, - () => {}, + () => {} ); - return new SandboxWithoutClient(pitcherClient); + return new SandboxSession(pitcherClient); } diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index db5daed..c786fb6 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch'; -import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutError, VmUpdateHibernationTimeoutResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecs2Data, VmUpdateSpecs2Error, VmUpdateSpecs2Response } from './types.gen'; +import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutError, VmUpdateHibernationTimeoutResponse, VmCreateSessionData, VmCreateSessionError, VmCreateSessionResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecs2Data, VmUpdateSpecs2Error, VmUpdateSpecs2Response } from './types.gen'; export const client = createClient(createConfig()); @@ -133,6 +133,22 @@ export const vmUpdateHibernationTimeout = }); }; +/** + * Create a new session on a VM + * Creates a new session on a running VM. A session represents an isolated Linux user, with their own container + * while their API token has specific permissions (currently, read or write). + * The session is identified by a unique session ID, and the username is based on the session ID. + * + * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. + * + */ +export const vmCreateSession = (options: OptionsLegacyParser) => { + return (options?.client ?? client).post({ + ...options, + url: '/vm/{id}/sessions' + }); +}; + /** * Shutdown a VM * Stops a running VM, ending all currently running processes diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 915521e..64f3f96 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -316,6 +316,58 @@ export type TokenUpdateResponse = { }; }; +export type VMCreateSessionRequest = { + /** + * Permission level for the session + */ + permission: 'read' | 'write'; + /** + * Unique identifier for the session + */ + session_id: string; +}; + +/** + * Permission level for the session + */ +export type permission = 'read' | 'write'; + +export type VMCreateSessionResponse = { + errors?: Array<((string | { + [key: string]: unknown; +}))>; + success?: boolean; +} & { + data?: { + /** + * List of capabilities granted to this session + */ + capabilities: Array<(string)>; + /** + * Detailed permissions for this session + */ + permissions: { + [key: string]: unknown; + }; + /** + * Token to authenticate with Pitcher + */ + pitcher_token: string; + /** + * WebSocket URL to connect to Pitcher + */ + pitcher_url: string; + /** + * Path to the user's workspace in the VM + */ + user_workspace_path: string; + /** + * The Linux username created for this session + */ + username: string; + }; +}; + export type VMHibernateRequest = { [key: string]: unknown; }; @@ -594,6 +646,23 @@ export type VmUpdateHibernationTimeoutResponse = (VMUpdateHibernationTimeoutResp export type VmUpdateHibernationTimeoutError = unknown; +export type VmCreateSessionData = { + /** + * VM Create Session Request + */ + body?: VMCreateSessionRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; +}; + +export type VmCreateSessionResponse = (VMCreateSessionResponse); + +export type VmCreateSessionError = unknown; + export type VmShutdownData = { /** * VM Shutdown Request diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index d825d5c..5ec5578 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -4,6 +4,7 @@ import type { Client } from "@hey-api/client-fetch"; import type { VmStartResponse, tier } from "./client"; import { sandboxFork, + vmCreateSession, sandboxList, vmHibernate, vmShutdown, @@ -11,8 +12,9 @@ import { vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./client"; -import { Sandbox } from "./sandbox"; +import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; +import { SessionCreateOptions, SessionConnectInfo } from "./sessions"; export type SandboxPrivacy = "public" | "unlisted" | "private"; export type SandboxStartData = Required["data"]; @@ -503,6 +505,70 @@ export class SandboxClient { return new Sandbox(this, pitcherClient); } + + public async createSession( + sandboxId: string, + sessionId: string, + options: SessionCreateOptions & { autoConnect: false } + ): Promise; + public async createSession( + sandboxId: string, + sessionId: string, + options?: SessionCreateOptions & { autoConnect?: true } + ): Promise; + public async createSession( + sandboxId: string, + sessionId: string, + options?: SessionCreateOptions + ): Promise; + public async createSession( + sandboxId: string, + sessionId: string, + options: SessionCreateOptions = {} + ): Promise { + const response = await vmCreateSession({ + client: this.apiClient, + body: { + session_id: sessionId, + permission: options.permission ?? "write", + }, + path: { + id: sandboxId, + }, + }); + + const handledResponse = handleResponse( + response, + `Failed to create session ${sessionId}` + ); + + if (options.autoConnect === false) { + return { + id: sandboxId, + pitcher_token: handledResponse.pitcher_token, + pitcher_url: handledResponse.pitcher_url, + user_workspace_path: handledResponse.user_workspace_path, + }; + } + + const connectedSandbox = await this.connectToSandbox(sandboxId, () => + Promise.resolve({ + bootup_type: "RESUME", + cluster: "session", + id: sandboxId, + latest_pitcher_version: "1.0.0-session", + pitcher_manager_version: "1.0.0-session", + pitcher_token: handledResponse.pitcher_token, + pitcher_url: handledResponse.pitcher_url, + pitcher_version: "1.0.0-session", + reconnect_token: "", + user_workspace_path: handledResponse.user_workspace_path, + workspace_path: handledResponse.user_workspace_path, + }) + ); + + return connectedSandbox; + } } function privacyToNumber(privacy: SandboxPrivacy): number { diff --git a/src/sandbox.ts b/src/sandbox.ts index 136b1e0..8a45b1b 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -12,6 +12,8 @@ import { Shells } from "./shells"; import { Tasks } from "./tasks"; import type { SandboxClient, VMTier } from "."; +import { Sessions } from "./sessions"; +export { SessionConnectInfo } from "./sessions"; export { FSStatResult, @@ -55,7 +57,7 @@ export interface SystemMetricsStatus { }; } -export class SandboxWithoutClient extends Disposable { +export class SandboxSession extends Disposable { /** * Namespace for all filesystem operations on this sandbox. */ @@ -182,7 +184,11 @@ export class SandboxWithoutClient extends Disposable { } } -export class Sandbox extends SandboxWithoutClient { +export class Sandbox extends SandboxSession { + public readonly sessions = this.addDisposable( + new Sessions(this.id, this.sandboxClient) + ); + constructor( private sandboxClient: SandboxClient, pitcherClient: IPitcherClient diff --git a/src/sessions.ts b/src/sessions.ts new file mode 100644 index 0000000..f42932d --- /dev/null +++ b/src/sessions.ts @@ -0,0 +1,80 @@ +import { Disposable } from "./utils/disposable"; +import { SandboxClient } from "./sandbox-client"; +import { SandboxSession } from "."; + +export interface SessionCreateOptions { + permission?: "read" | "write"; + autoConnect?: boolean; +} + +export type SessionConnectInfo = { + id: string; + pitcher_token: string; + pitcher_url: string; + user_workspace_path: string; +}; + +export class Sessions extends Disposable { + constructor( + private readonly id: string, + private readonly apiClient: SandboxClient + ) { + super(); + } + + /** + * Create a new session inside the VM. This is a new Linux user (inside the VM) with its + * own home directory and permissions. + * + * @param sessionId The id of the session, this will also be used for the username + * @param options Optional settings including permissions + * + * @returns if `autoConnect` is true, returns a `SandboxSession` object (which can be used to connect), otherwise returns + * a connected session. + */ + async create( + sessionId: string, + options: SessionCreateOptions & { autoConnect: false } + ): Promise<{ + pitcher_token: string; + pitcher_url: string; + user_workspace_path: string; + }>; + async create( + sessionId: string, + options?: SessionCreateOptions & { autoConnect?: true } + ): Promise; + async create( + sessionId: string, + options: SessionCreateOptions = {} + ): Promise< + | SandboxSession + | { + pitcher_token: string; + pitcher_url: string; + user_workspace_path: string; + } + > { + const defaultOptions: SessionCreateOptions = { + permission: "write", + autoConnect: true, + }; + + const mergedOptions = { + ...defaultOptions, + ...options, + }; + + return this.apiClient.createSession(this.id, sessionId, mergedOptions); + } + + /** + * Creates or reuses a session inside the VM with read-only permissions. Because read-only sessions + * cannot affect each-other, we use the same session id for all read-only sessions ("anonymous"). + * + * @returns The new session + */ + async createReadOnly(): Promise { + return this.create("anonymous", { permission: "read" }); + } +} From 4b277574e2b37089c794da578560ec4371cec747 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 22 Jan 2025 19:21:18 +0100 Subject: [PATCH 025/241] chore(main): release 0.4.0 (#35) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a864d..75d52b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.4.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.3.0...v0.4.0) (2025-01-22) + + +### Features + +* support for creating sessions ([#32](https://github.com/codesandbox/codesandbox-sdk/issues/32)) ([491d13d](https://github.com/codesandbox/codesandbox-sdk/commit/491d13db77992df5e3ab3fefb5b9de7e8edbd1c9)) + ## [0.3.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.2.0...v0.3.0) (2025-01-14) diff --git a/package-lock.json b/package-lock.json index ba8292f..683fddf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index c91b5a2..4925ecc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.3.0", + "version": "0.4.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 1e9e19c7df09ea15025873e8557cbed031d2e9d0 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 18:43:46 +0100 Subject: [PATCH 026/241] feat: list sandboxes by running status (#37) * feat: allow listing sandboxes by running status * feat: add cli command * feat: enable piping * better logging * add limit support to list --- openapi.json | 35 +- package-lock.json | 530 ++++++++++++++++++++++++++ package.json | 9 +- src/bin/commands/sandbox/hibernate.ts | 98 ++++- src/bin/commands/sandbox/index.ts | 128 +++++++ src/bin/commands/sandbox/list.ts | 156 ++++++++ src/bin/commands/sandbox/shutdown.ts | 98 ++++- src/bin/main.ts | 31 +- src/client/sdk.gen.ts | 6 +- src/client/types.gen.ts | 18 +- src/index.ts | 3 +- src/sandbox-client.ts | 71 ++-- 12 files changed, 1098 insertions(+), 85 deletions(-) create mode 100644 src/bin/commands/sandbox/index.ts create mode 100644 src/bin/commands/sandbox/list.ts diff --git a/openapi.json b/openapi.json index 6028b1b..6461013 100644 --- a/openapi.json +++ b/openapi.json @@ -392,6 +392,19 @@ "properties": { "data": { "properties": { + "pagination": { + "properties": { + "current_page": { "type": "integer" }, + "next_page": { + "description": "The number of the next page, if any. If `null`, the current page is the last page of records.", + "nullable": true, + "type": "integer" + }, + "total_records": { "type": "integer" } + }, + "required": ["total_records", "current_page", "next_page"], + "type": "object" + }, "sandboxes": { "items": { "properties": { @@ -426,7 +439,7 @@ "type": "array" } }, - "required": ["sandboxes"], + "required": ["sandboxes", "pagination"], "type": "object" } }, @@ -610,6 +623,7 @@ "session_id": { "description": "Unique identifier for the session", "example": "my-session-1", + "pattern": "^[a-zA-Z0-9_-]+$", "type": "string" } }, @@ -643,16 +657,16 @@ "data": { "properties": { "capabilities": { - "description": "List of capabilities granted to this session", + "description": "List of capabilities that Pitcher has", "items": { "type": "string" }, "type": "array" }, "permissions": { - "description": "Detailed permissions for this session", + "description": "The permissions of the current session", "type": "object" }, "pitcher_token": { - "description": "Token to authenticate with Pitcher", + "description": "Token to authenticate with Pitcher (the agent running inside the VM)", "type": "string" }, "pitcher_url": { @@ -1162,6 +1176,13 @@ "name": "page", "required": false, "schema": { "default": 1, "minimum": 1, "type": "integer" } + }, + { + "description": "If true, only returns VMs for which a heartbeat was received in the last 30 seconds.", + "in": "query", + "name": "status", + "required": false, + "schema": { "enum": ["running"] } } ], "responses": { @@ -1363,7 +1384,7 @@ "/vm/{id}/sessions": { "post": { "callbacks": {}, - "description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container\nwhile their API token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the username is based on the session ID.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n", + "description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container.\nA session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the Linux username is based on the session ID.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n", "operationId": "vm/create_session", "parameters": [ { @@ -1398,7 +1419,7 @@ "description": "VM Create Session Response" } }, - "security": [{ "authorization": ["sandbox:read", "vm:manage"] }], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Create a new session on a VM", "tags": ["vm"] } @@ -1565,6 +1586,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], "tags": [] } diff --git a/package-lock.json b/package-lock.json index 683fddf..b7b8bda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,13 @@ "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.5.1", + "@hey-api/openapi-ts": "0.60.1", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "buffer-browserify": "^0.2.5", + "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", "esbuild": "^0.24.0", "ignore": "^6.0.2", @@ -56,6 +58,24 @@ "phoenix": "^1.4.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.3.tgz", + "integrity": "sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@babel/runtime": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", @@ -275,6 +295,31 @@ "url": "https://github.com/sponsors/hey-api" } }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.60.1", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.60.1.tgz", + "integrity": "sha512-W3fW6pjQ3rwRsmvp9+hDEStyedTWgNjd6PfdVOIegtPBKj3o4ThK3J14kXeqdH2tJXrudfLpXHRp43+ahi4QSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.7.3", + "c12": "2.0.1", + "commander": "12.1.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -378,6 +423,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@jumpn/utils-array": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", @@ -477,6 +529,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/micromatch": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", @@ -538,6 +597,19 @@ "dev": true, "license": "ISC" }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-escapes": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", @@ -588,6 +660,13 @@ "dev": true, "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -934,6 +1013,35 @@ "node": ">=0.2.0" } }, + "node_modules/c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -1005,6 +1113,32 @@ "dev": true, "license": "MIT/X11" }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/cipher-base": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", @@ -1019,6 +1153,16 @@ "node": ">= 0.10" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/class-states": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.16.tgz", @@ -1126,6 +1270,33 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -1286,6 +1457,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1307,6 +1485,13 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -1629,6 +1814,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1754,6 +1965,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/giget": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.4.tgz", + "integrity": "sha512-Wv+daGyispVoA31TrWAVR+aAdP7roubTPEM/8JzRnqXhLbdJH0T9eQyXVFF8fjk3WKTsctII6QcyxILYgNp2DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.5.1", + "ohash": "^1.1.4", + "pathe": "^2.0.2", + "tar": "^6.2.1" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/giget/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/gl-matrix": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", @@ -1824,6 +2062,35 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2166,6 +2433,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-untar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", @@ -2173,6 +2450,19 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -2481,6 +2771,66 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2488,6 +2838,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -2519,6 +2876,13 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", + "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nopt": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", @@ -2532,6 +2896,41 @@ "nopt": "bin/nopt.js" } }, + "node_modules/nypm": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.5.2.tgz", + "integrity": "sha512-AHzvnyUJYSrrphPhRWWZNcoZfArGNp3Vrc4pm/ZurO74tYNTgAPrEyBQEKy+qioqmWlPXwvMZCG2wOaHlPG0Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.2", + "pkg-types": "^1.3.1", + "tinyexec": "^0.3.2", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/nypm/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true, + "license": "MIT" + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -2779,6 +3178,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -2796,6 +3202,13 @@ "node": ">=0.12" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/phoenix": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.18.tgz", @@ -2837,6 +3250,25 @@ "node": ">=0.4.0" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/png-js": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", @@ -2930,6 +3362,17 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -2953,6 +3396,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", @@ -3152,6 +3609,16 @@ "dev": true, "license": "ISC" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparkline": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", @@ -3392,6 +3859,34 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/term-canvas": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", @@ -3405,6 +3900,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3466,6 +3968,27 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3839,6 +4362,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 4925ecc..2cc37d2 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "build:esbuild": "node esbuild.js", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", - "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && npx @hey-api/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", - "build-openapi:staging": "rimraf src/client && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && npx -y @hey-api/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", + "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", + "build-openapi:staging": "rimraf src/client && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", "clean": "rimraf ./dist", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", @@ -78,11 +78,13 @@ "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.5.1", + "@hey-api/openapi-ts": "0.60.1", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "buffer-browserify": "^0.2.5", + "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", "esbuild": "^0.24.0", "ignore": "^6.0.2", @@ -98,5 +100,6 @@ "util": "0.12.5", "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" - } + }, + "dependencies": {} } diff --git a/src/bin/commands/sandbox/hibernate.ts b/src/bin/commands/sandbox/hibernate.ts index 735930d..d30dcd8 100644 --- a/src/bin/commands/sandbox/hibernate.ts +++ b/src/bin/commands/sandbox/hibernate.ts @@ -1,14 +1,96 @@ import ora from "ora"; - import { CodeSandbox } from "../../../"; -export async function hibernateSandbox(sandboxId: string) { - const sdk = new CodeSandbox(); +type CommandResult = { + success: boolean; + message: string; +}; + +async function hibernateSingleSandbox( + id: string, + spinner: ReturnType +): Promise { + try { + await new CodeSandbox().sandbox.hibernate(id); + const message = `✔ Sandbox ${id} hibernated successfully`; + // eslint-disable-next-line no-console + console.log(message); + return { success: true, message }; + } catch (error) { + const message = `✖ Failed to hibernate sandbox ${id}`; + // eslint-disable-next-line no-console + console.log(message); + return { success: false, message }; + } +} + +export async function hibernateSandbox(id?: string) { + if (id) { + const spinner = ora("Hibernating sandbox...").start(); + const result = await hibernateSingleSandbox(id, spinner); + spinner.stop(); + if (!result.success) { + process.exit(1); + } + return; + } + + // No ID provided, try to read from stdin + process.stdin.resume(); + process.stdin.setEncoding("utf-8"); + + let data = ""; + + try { + for await (const chunk of process.stdin) { + data += chunk; + } + + const ids = data + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0); + + if (ids.length === 0) { + // eslint-disable-next-line no-console + console.log("No sandbox IDs provided"); + process.exit(1); + } + + // eslint-disable-next-line no-console + console.log(`⠋ Hibernating ${ids.length} sandboxes...`); + + let successCount = 0; + let failCount = 0; + const results: CommandResult[] = []; - const spinner = ora("Hibernating sandbox...").start(); - await sdk.sandbox.hibernate(sandboxId); - spinner.succeed("Sandbox hibernated successfully"); + for (const sandboxId of ids) { + try { + const result = await hibernateSingleSandbox(sandboxId, null as any); + results.push(result); + if (result.success) { + successCount++; + } else { + failCount++; + } + } catch (error) { + failCount++; + } + } - // eslint-disable-next-line no-console - console.log(sandboxId); + // Final summary + if (failCount === 0) { + // eslint-disable-next-line no-console + console.log(`\n✔ Successfully hibernated all ${successCount} sandboxes`); + } else { + // eslint-disable-next-line no-console + console.log( + `\n⚠ Hibernation completed: ${successCount} succeeded, ${failCount} failed` + ); + } + } catch (error) { + // eslint-disable-next-line no-console + console.log("Failed to hibernate sandboxes"); + throw error; + } } diff --git a/src/bin/commands/sandbox/index.ts b/src/bin/commands/sandbox/index.ts new file mode 100644 index 0000000..9d66245 --- /dev/null +++ b/src/bin/commands/sandbox/index.ts @@ -0,0 +1,128 @@ +import type { CommandModule } from "yargs"; +import { forkSandbox } from "./fork"; +import { hibernateSandbox } from "./hibernate"; +import { DEFAULT_LIMIT, listSandboxes } from "./list"; +import { shutdownSandbox } from "./shutdown"; + +export const sandboxCommand: CommandModule = { + command: "sandbox", + describe: "Manage sandboxes", + builder: (yargs) => { + return yargs + .command({ + command: "list", + describe: "List sandboxes", + builder: (yargs) => { + return yargs + .option("output", { + alias: "o", + describe: + "Output format (comma-separated list of fields: id,title,privacy,tags,createdAt,updatedAt)", + type: "string", + }) + .option("headers", { + describe: "Show headers", + type: "boolean", + default: true, + }) + .option("tags", { + alias: "t", + describe: "Filter by tags (comma-separated)", + type: "string", + }) + .option("status", { + alias: "s", + describe: "Filter by status", + choices: ["running"], + type: "string", + }) + .option("page", { + alias: "p", + describe: "Page number", + type: "number", + }) + .option("page-size", { + describe: "Number of items per page", + type: "number", + }) + .option("order-by", { + describe: "Order results by field", + choices: ["inserted_at", "updated_at"], + type: "string", + }) + .option("direction", { + describe: "Sort direction", + choices: ["asc", "desc"], + type: "string", + }) + .option("limit", { + alias: "l", + describe: `Maximum number of sandboxes to list (default: ${DEFAULT_LIMIT})`, + type: "number", + default: DEFAULT_LIMIT, + }); + }, + handler: async (argv) => { + await listSandboxes( + argv.output as string | undefined, + { + tags: argv.tags?.split(","), + status: argv.status as "running" | undefined, + page: argv.page as number | undefined, + pageSize: argv["page-size"] as number | undefined, + orderBy: argv["order-by"] as + | "inserted_at" + | "updated_at" + | undefined, + direction: argv.direction as "asc" | "desc" | undefined, + }, + argv["headers"] as boolean, + argv.limit as number | undefined + ); + }, + }) + .command({ + command: "fork ", + describe: "Fork a sandbox", + builder: (yargs) => { + return yargs.positional("id", { + describe: "ID of the sandbox to fork", + type: "string", + }); + }, + handler: async (argv) => { + await forkSandbox(argv.id as string); + }, + }) + .command({ + command: "hibernate [id]", + describe: + "Hibernate sandbox(es). If no ID is provided, reads sandbox IDs from stdin", + builder: (yargs) => { + return yargs.positional("id", { + describe: "ID of the sandbox to hibernate", + type: "string", + }); + }, + handler: async (argv) => { + await hibernateSandbox(argv.id); + }, + }) + .command({ + command: "shutdown [id]", + describe: + "Shutdown sandbox(es). If no ID is provided, reads sandbox IDs from stdin", + builder: (yargs) => { + return yargs.positional("id", { + describe: "ID of the sandbox to shutdown", + type: "string", + }); + }, + handler: async (argv) => { + await shutdownSandbox(argv.id); + }, + }) + .demandCommand(1, "Please specify a sandbox command"); + }, + handler: () => {}, +}; diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts new file mode 100644 index 0000000..80ccd47 --- /dev/null +++ b/src/bin/commands/sandbox/list.ts @@ -0,0 +1,156 @@ +import ora from "ora"; +import Table from "cli-table3"; +import { CodeSandbox } from "../../../"; +import type { SandboxListOpts, SandboxInfo } from "../../../sandbox-client"; + +export const DEFAULT_LIMIT = 100; + +type OutputFormat = { + field: string; + header: string; + width?: number; +}; + +const TABLE_FORMAT: OutputFormat[] = [ + { field: "id", header: "NAME", width: 24 }, + { field: "title", header: "TITLE", width: 40 }, + { field: "privacy", header: "PRIVACY", width: 10 }, + { field: "tags", header: "TAGS", width: 20 }, + { field: "updatedAt", header: "AGE" }, +]; + +function formatAge(date: Date): string { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + return `${days}d`; + } + if (hours > 0) { + return `${hours}h`; + } + if (minutes > 0) { + return `${minutes}m`; + } + return `${seconds}s`; +} + +export async function listSandboxes( + outputFields?: string, + listOpts: SandboxListOpts = {}, + showHeaders = true, + limit = DEFAULT_LIMIT +) { + const sdk = new CodeSandbox(); + const spinner = ora("Fetching sandboxes...").start(); + + try { + let allSandboxes: SandboxInfo[] = []; + let currentPage = listOpts.page || 1; + const pageSize = listOpts.pageSize || 20; + + // Keep fetching until we hit the limit or run out of sandboxes + while (true) { + const { sandboxes, pagination } = await sdk.sandbox.list({ + ...listOpts, + page: currentPage, + pageSize, + }); + + allSandboxes = [...allSandboxes, ...sandboxes]; + + // Stop if we've hit the limit + if (allSandboxes.length >= limit) { + allSandboxes = allSandboxes.slice(0, limit); + break; + } + + // Stop if there are no more pages + if (!pagination.nextPage) { + break; + } + + currentPage = pagination.nextPage; + spinner.text = `Fetching sandboxes... (${allSandboxes.length}/${limit})`; + } + + spinner.stop(); + + if (outputFields) { + // Custom output format - just print the requested fields + const fields = outputFields.split(",").map((f) => f.trim()); + + if (showHeaders) { + // eslint-disable-next-line no-console + console.log(fields.join("\t")); + } + + allSandboxes.forEach((sandbox) => { + const values = fields.map((field) => { + const value = sandbox[field as keyof typeof sandbox]; + if (value instanceof Date) { + return value.toISOString(); + } + if (Array.isArray(value)) { + return value.join(","); + } + return value?.toString() || ""; + }); + // eslint-disable-next-line no-console + console.log(values.join("\t")); + }); + return; + } + + // Table output format + const table = new Table({ + head: showHeaders ? TABLE_FORMAT.map((f) => f.header) : [], + colWidths: TABLE_FORMAT.map((f) => f.width ?? null), + style: { + head: ["bold"], + border: [], + }, + chars: { + top: "", + "top-mid": "", + "top-left": "", + "top-right": "", + bottom: "", + "bottom-mid": "", + "bottom-left": "", + "bottom-right": "", + left: "", + "left-mid": "", + right: "", + "right-mid": "", + mid: "", + "mid-mid": "", + middle: " ", + }, + }); + + allSandboxes.forEach((sandbox) => { + const row = TABLE_FORMAT.map((format) => { + const value = sandbox[format.field as keyof typeof sandbox]; + if (format.field === "updatedAt" && value instanceof Date) { + return formatAge(value); + } + if (Array.isArray(value)) { + return value.join(","); + } + return value?.toString() || ""; + }); + table.push(row); + }); + + // eslint-disable-next-line no-console + console.log(table.toString()); + } catch (error) { + spinner.fail("Failed to fetch sandboxes"); + throw error; + } +} diff --git a/src/bin/commands/sandbox/shutdown.ts b/src/bin/commands/sandbox/shutdown.ts index dc9fca0..09d3388 100644 --- a/src/bin/commands/sandbox/shutdown.ts +++ b/src/bin/commands/sandbox/shutdown.ts @@ -1,14 +1,96 @@ import ora from "ora"; - import { CodeSandbox } from "../../../"; -export async function shutdownSandbox(sandboxId: string) { - const sdk = new CodeSandbox(); +type CommandResult = { + success: boolean; + message: string; +}; + +async function shutdownSingleSandbox( + id: string, + spinner: ReturnType +): Promise { + try { + await new CodeSandbox().sandbox.shutdown(id); + const message = `✔ Sandbox ${id} shutdown successfully`; + // eslint-disable-next-line no-console + console.log(message); + return { success: true, message }; + } catch (error) { + const message = `✖ Failed to shutdown sandbox ${id}`; + // eslint-disable-next-line no-console + console.log(message); + return { success: false, message }; + } +} + +export async function shutdownSandbox(id?: string) { + if (id) { + const spinner = ora("Shutting down sandbox...").start(); + const result = await shutdownSingleSandbox(id, spinner); + spinner.stop(); + if (!result.success) { + process.exit(1); + } + return; + } + + // No ID provided, try to read from stdin + process.stdin.resume(); + process.stdin.setEncoding("utf-8"); + + let data = ""; + + try { + for await (const chunk of process.stdin) { + data += chunk; + } + + const ids = data + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0); + + if (ids.length === 0) { + // eslint-disable-next-line no-console + console.log("No sandbox IDs provided"); + process.exit(1); + } + + // eslint-disable-next-line no-console + console.log(`⠋ Shutting down ${ids.length} sandboxes...`); + + let successCount = 0; + let failCount = 0; + const results: CommandResult[] = []; - const spinner = ora("Shutting down sandbox...").start(); - await sdk.sandbox.shutdown(sandboxId); - spinner.succeed("Sandbox shutdown successfully"); + for (const sandboxId of ids) { + try { + const result = await shutdownSingleSandbox(sandboxId, null as any); + results.push(result); + if (result.success) { + successCount++; + } else { + failCount++; + } + } catch (error) { + failCount++; + } + } - // eslint-disable-next-line no-console - console.log(sandboxId); + // Final summary + if (failCount === 0) { + // eslint-disable-next-line no-console + console.log(`\n✔ Successfully shutdown all ${successCount} sandboxes`); + } else { + // eslint-disable-next-line no-console + console.log( + `\n⚠ Shutdown completed: ${successCount} succeeded, ${failCount} failed` + ); + } + } catch (error) { + // eslint-disable-next-line no-console + console.log("Failed to shutdown sandboxes"); + throw error; + } } diff --git a/src/bin/main.ts b/src/bin/main.ts index e9126cb..dd6fe34 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -2,9 +2,7 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { buildCommand } from "./commands/build"; -import { forkSandbox } from "./commands/sandbox/fork"; -import { hibernateSandbox } from "./commands/sandbox/hibernate"; -import { shutdownSandbox } from "./commands/sandbox/shutdown"; +import { sandboxCommand } from "./commands/sandbox"; yargs(hideBin(process.argv)) .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") @@ -13,30 +11,5 @@ yargs(hideBin(process.argv)) .strict() .recommendCommands() .command(buildCommand) - .command( - "sandbox ", - "Manage sandboxes", - (yargs) => { - return yargs - .positional("action", { - describe: "Action to perform on the sandbox", - choices: ["hibernate", "fork", "shutdown"], - type: "string", - }) - .positional("id", { - describe: "ID of the sandbox", - type: "string", - demandOption: true, - }); - }, - async (argv) => { - if (argv.action === "hibernate") { - await hibernateSandbox(argv.id); - } else if (argv.action === "fork") { - await forkSandbox(argv.id); - } else if (argv.action === "shutdown") { - await shutdownSandbox(argv.id); - } - } - ) + .command(sandboxCommand) .parse(); diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index c786fb6..a2abfb8 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -135,9 +135,9 @@ export const vmUpdateHibernationTimeout = /** * Create a new session on a VM - * Creates a new session on a running VM. A session represents an isolated Linux user, with their own container - * while their API token has specific permissions (currently, read or write). - * The session is identified by a unique session ID, and the username is based on the session ID. + * Creates a new session on a running VM. A session represents an isolated Linux user, with their own container. + * A session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write). + * The session is identified by a unique session ID, and the Linux username is based on the session ID. * * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. * diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 64f3f96..e6e699c 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -231,6 +231,14 @@ export type SandboxListResponse = { success?: boolean; } & { data?: { + pagination: { + current_page: number; + /** + * The number of the next page, if any. If `null`, the current page is the last page of records. + */ + next_page: (number) | null; + total_records: number; + }; sandboxes: Array<{ created_at: string; description?: (string) | null; @@ -340,17 +348,17 @@ export type VMCreateSessionResponse = { } & { data?: { /** - * List of capabilities granted to this session + * List of capabilities that Pitcher has */ capabilities: Array<(string)>; /** - * Detailed permissions for this session + * The permissions of the current session */ permissions: { [key: string]: unknown; }; /** - * Token to authenticate with Pitcher + * Token to authenticate with Pitcher (the agent running inside the VM) */ pitcher_token: string; /** @@ -560,6 +568,10 @@ export type SandboxListData = { * Maximum number of sandboxes to return in a single response */ page_size?: number; + /** + * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. + */ + status?: 'running'; /** * Comma-separated list of tags to filter by */ diff --git a/src/index.ts b/src/index.ts index 90d9bb0..3b1ea21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,7 +69,8 @@ export class CodeSandbox { : undefined, "CSB_API_KEY or TOGETHER_API_KEY is not set" ); - this.baseUrl = opts.baseUrl ?? getBaseUrl(this.apiToken); + this.baseUrl = + process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(this.apiToken); this.apiClient = createClient( createConfig({ diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 5ec5578..c3aeb2f 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -35,6 +35,7 @@ export type SandboxListOpts = { pageSize?: number; orderBy?: "inserted_at" | "updated_at"; direction?: "asc" | "desc"; + status?: "running"; }; export const DEFAULT_SUBSCRIPTIONS = { @@ -375,31 +376,55 @@ export class SandboxClient { /** * List sandboxes from the current workspace with optional filters. - * Results are limited to a maximum of 50 sandboxes per request. + * By default, returns up to 100 sandboxes. */ - async list(opts: SandboxListOpts = {}): Promise { - const response = await sandboxList({ - client: this.apiClient, - query: { - tags: opts.tags?.join(","), - page: opts.page, - page_size: opts.pageSize, - order_by: opts.orderBy, - direction: opts.direction, - }, - }); + async list( + opts: Omit & { limit?: number } = {} + ): Promise<{ + sandboxes: SandboxInfo[]; + }> { + const limit = opts.limit ?? 100; + const pageSize = 50; // API's maximum page size + let allSandboxes: SandboxInfo[] = []; + let currentPage = 1; + + while (allSandboxes.length < limit) { + const response = await sandboxList({ + client: this.apiClient, + query: { + tags: opts.tags?.join(","), + page: currentPage, + page_size: pageSize, + order_by: opts.orderBy, + direction: opts.direction, + status: opts.status, + }, + }); + + const info = handleResponse(response, "Failed to list sandboxes"); + + const sandboxes = info.sandboxes.map((sandbox) => ({ + id: sandbox.id, + createdAt: new Date(sandbox.created_at), + updatedAt: new Date(sandbox.updated_at), + title: sandbox.title ?? undefined, + description: sandbox.description ?? undefined, + privacy: privacyFromNumber(sandbox.privacy), + tags: sandbox.tags, + })); + + allSandboxes = [...allSandboxes, ...sandboxes]; + + // Stop if we've hit the limit or there are no more pages + if (!info.pagination.next_page || allSandboxes.length >= limit) { + allSandboxes = allSandboxes.slice(0, limit); + break; + } + + currentPage = info.pagination.next_page; + } - const info = handleResponse(response, "Failed to list sandboxes"); - - return info.sandboxes.map((sandbox) => ({ - id: sandbox.id, - createdAt: new Date(sandbox.created_at), - updatedAt: new Date(sandbox.updated_at), - title: sandbox.title ?? undefined, - description: sandbox.description ?? undefined, - privacy: privacyFromNumber(sandbox.privacy), - tags: sandbox.tags, - })); + return { sandboxes: allSandboxes }; } /** From f086bb88be2cb92848c8b8eae411408028978791 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 19:09:44 +0100 Subject: [PATCH 027/241] feat: add more advanced pagination options (#39) * feat: add more advanced pagination options * feat: pagination in list api --- src/bin/commands/sandbox/index.ts | 13 +++-- src/bin/commands/sandbox/list.ts | 73 ++++++++++++++++++++-------- src/index.ts | 2 + src/sandbox-client.ts | 79 ++++++++++++++++++++++++++----- 4 files changed, 131 insertions(+), 36 deletions(-) diff --git a/src/bin/commands/sandbox/index.ts b/src/bin/commands/sandbox/index.ts index 9d66245..c762f69 100644 --- a/src/bin/commands/sandbox/index.ts +++ b/src/bin/commands/sandbox/index.ts @@ -1,9 +1,11 @@ import type { CommandModule } from "yargs"; import { forkSandbox } from "./fork"; import { hibernateSandbox } from "./hibernate"; -import { DEFAULT_LIMIT, listSandboxes } from "./list"; +import { listSandboxes } from "./list"; import { shutdownSandbox } from "./shutdown"; +const DEFAULT_LIMIT = 100; + export const sandboxCommand: CommandModule = { command: "sandbox", describe: "Manage sandboxes", @@ -68,13 +70,18 @@ export const sandboxCommand: CommandModule = { { tags: argv.tags?.split(","), status: argv.status as "running" | undefined, - page: argv.page as number | undefined, - pageSize: argv["page-size"] as number | undefined, orderBy: argv["order-by"] as | "inserted_at" | "updated_at" | undefined, direction: argv.direction as "asc" | "desc" | undefined, + pagination: + argv.page || argv["page-size"] + ? { + page: argv.page, + pageSize: argv["page-size"], + } + : undefined, }, argv["headers"] as boolean, argv.limit as number | undefined diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 80ccd47..8aaf1fd 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -1,9 +1,11 @@ import ora from "ora"; import Table from "cli-table3"; import { CodeSandbox } from "../../../"; -import type { SandboxListOpts, SandboxInfo } from "../../../sandbox-client"; - -export const DEFAULT_LIMIT = 100; +import type { + SandboxListOpts, + SandboxInfo, + PaginationOpts, +} from "../../../sandbox-client"; type OutputFormat = { field: string; @@ -41,41 +43,59 @@ function formatAge(date: Date): string { export async function listSandboxes( outputFields?: string, - listOpts: SandboxListOpts = {}, + listOpts: SandboxListOpts & { pagination?: PaginationOpts } = {}, showHeaders = true, - limit = DEFAULT_LIMIT + limit?: number ) { const sdk = new CodeSandbox(); const spinner = ora("Fetching sandboxes...").start(); try { let allSandboxes: SandboxInfo[] = []; - let currentPage = listOpts.page || 1; - const pageSize = listOpts.pageSize || 20; + let totalCount = 0; + let currentPage = 1; + const pageSize = 50; // API's maximum page size - // Keep fetching until we hit the limit or run out of sandboxes while (true) { - const { sandboxes, pagination } = await sdk.sandbox.list({ + const { + sandboxes, + totalCount: total, + pagination, + } = await sdk.sandbox.list({ ...listOpts, - page: currentPage, - pageSize, + limit: undefined, // Force pagination so we can show progress + pagination: { + page: currentPage, + pageSize, + }, }); - allSandboxes = [...allSandboxes, ...sandboxes]; - - // Stop if we've hit the limit - if (allSandboxes.length >= limit) { - allSandboxes = allSandboxes.slice(0, limit); + if (sandboxes.length === 0) { break; } - // Stop if there are no more pages - if (!pagination.nextPage) { + totalCount = total; + const newSandboxes = sandboxes.filter( + (sandbox) => + !allSandboxes.some((existing) => existing.id === sandbox.id) + ); + allSandboxes = [...allSandboxes, ...newSandboxes]; + + spinner.text = `Fetching sandboxes... (${allSandboxes.length}${ + limit ? `/${Math.min(limit, totalCount)}` : `/${totalCount}` + })`; + + // Stop if we've reached the total count + if (allSandboxes.length >= totalCount) { break; } - currentPage = pagination.nextPage; - spinner.text = `Fetching sandboxes... (${allSandboxes.length}/${limit})`; + currentPage++; + } + + // Apply limit after fetching all sandboxes + if (limit) { + allSandboxes = allSandboxes.slice(0, limit); } spinner.stop(); @@ -103,6 +123,12 @@ export async function listSandboxes( // eslint-disable-next-line no-console console.log(values.join("\t")); }); + + // eslint-disable-next-line no-console + console.log( + `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` + ); + return; } @@ -149,6 +175,13 @@ export async function listSandboxes( // eslint-disable-next-line no-console console.log(table.toString()); + + if (limit && totalCount > allSandboxes.length) { + // eslint-disable-next-line no-console + console.log( + `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` + ); + } } catch (error) { spinner.fail("Failed to fetch sandboxes"); throw error; diff --git a/src/index.ts b/src/index.ts index 3b1ea21..5889515 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { VMTier, SandboxListOpts, SandboxInfo, + PaginationOpts, } from "./sandbox-client"; export { @@ -17,6 +18,7 @@ export { VMTier, SandboxListOpts, SandboxInfo, + PaginationOpts, }; export * from "./sandbox"; diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index c3aeb2f..d5fd83d 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -31,13 +31,27 @@ export type SandboxInfo = { export type SandboxListOpts = { tags?: string[]; - page?: number; - pageSize?: number; orderBy?: "inserted_at" | "updated_at"; direction?: "asc" | "desc"; status?: "running"; }; +export interface SandboxListResponse { + sandboxes: SandboxInfo[]; + hasMore: boolean; + totalCount: number; + pagination: { + currentPage: number; + nextPage: number | null; + pageSize: number; + }; +} + +export type PaginationOpts = { + page?: number; + pageSize?: number; +}; + export const DEFAULT_SUBSCRIPTIONS = { client: { status: true, @@ -376,19 +390,47 @@ export class SandboxClient { /** * List sandboxes from the current workspace with optional filters. - * By default, returns up to 100 sandboxes. + * + * This method supports two modes of operation: + * 1. Simple limit-based fetching (default): + * ```ts + * // Get up to 100 sandboxes (default) + * const { sandboxes, totalCount } = await client.list(); + * + * // Get up to 200 sandboxes + * const { sandboxes, totalCount } = await client.list({ limit: 200 }); + * ``` + * + * 2. Manual pagination: + * ```ts + * // Get first page + * const { sandboxes, pagination } = await client.list({ + * pagination: { page: 1, pageSize: 50 } + * }); + * // pagination = { currentPage: 1, nextPage: 2, pageSize: 50 } + * + * // Get next page if available + * if (pagination.nextPage) { + * const { sandboxes, pagination: nextPagination } = await client.list({ + * pagination: { page: pagination.nextPage, pageSize: 50 } + * }); + * } + * ``` */ async list( - opts: Omit & { limit?: number } = {} - ): Promise<{ - sandboxes: SandboxInfo[]; - }> { + opts: SandboxListOpts & { + limit?: number; + pagination?: PaginationOpts; + } = {} + ): Promise { const limit = opts.limit ?? 100; - const pageSize = 50; // API's maximum page size let allSandboxes: SandboxInfo[] = []; - let currentPage = 1; + let currentPage = opts.pagination?.page ?? 1; + let pageSize = opts.pagination?.pageSize ?? 50; + let totalCount = 0; + let nextPage: number | null = null; - while (allSandboxes.length < limit) { + while (true) { const response = await sandboxList({ client: this.apiClient, query: { @@ -402,6 +444,8 @@ export class SandboxClient { }); const info = handleResponse(response, "Failed to list sandboxes"); + totalCount = info.pagination.total_records; + nextPage = info.pagination.next_page; const sandboxes = info.sandboxes.map((sandbox) => ({ id: sandbox.id, @@ -416,15 +460,24 @@ export class SandboxClient { allSandboxes = [...allSandboxes, ...sandboxes]; // Stop if we've hit the limit or there are no more pages - if (!info.pagination.next_page || allSandboxes.length >= limit) { + if (!nextPage || allSandboxes.length >= limit) { allSandboxes = allSandboxes.slice(0, limit); break; } - currentPage = info.pagination.next_page; + currentPage = nextPage; } - return { sandboxes: allSandboxes }; + return { + sandboxes: allSandboxes, + hasMore: totalCount > allSandboxes.length, + totalCount, + pagination: { + currentPage, + nextPage: allSandboxes.length >= limit ? nextPage : null, + pageSize, + }, + }; } /** From 116c513831452050b6a1e2943d25e76f513719f1 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 19:28:33 +0100 Subject: [PATCH 028/241] tweak: list api (#40) --- src/bin/commands/sandbox/list.ts | 7 +++---- src/sandbox-client.ts | 13 ++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 8aaf1fd..81f1219 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -45,7 +45,7 @@ export async function listSandboxes( outputFields?: string, listOpts: SandboxListOpts & { pagination?: PaginationOpts } = {}, showHeaders = true, - limit?: number + limit = 100 ) { const sdk = new CodeSandbox(); const spinner = ora("Fetching sandboxes...").start(); @@ -63,7 +63,6 @@ export async function listSandboxes( pagination, } = await sdk.sandbox.list({ ...listOpts, - limit: undefined, // Force pagination so we can show progress pagination: { page: currentPage, pageSize, @@ -86,11 +85,11 @@ export async function listSandboxes( })`; // Stop if we've reached the total count - if (allSandboxes.length >= totalCount) { + if (allSandboxes.length >= limit || pagination.nextPage == null) { break; } - currentPage++; + currentPage = pagination.nextPage; } // Apply limit after fetching all sandboxes diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index d5fd83d..f2a9a28 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -394,7 +394,7 @@ export class SandboxClient { * This method supports two modes of operation: * 1. Simple limit-based fetching (default): * ```ts - * // Get up to 100 sandboxes (default) + * // Get up to 50 sandboxes (default) * const { sandboxes, totalCount } = await client.list(); * * // Get up to 200 sandboxes @@ -423,10 +423,10 @@ export class SandboxClient { pagination?: PaginationOpts; } = {} ): Promise { - const limit = opts.limit ?? 100; + const limit = opts.limit ?? 50; let allSandboxes: SandboxInfo[] = []; let currentPage = opts.pagination?.page ?? 1; - let pageSize = opts.pagination?.pageSize ?? 50; + let pageSize = opts.pagination?.pageSize ?? limit; let totalCount = 0; let nextPage: number | null = null; @@ -457,11 +457,14 @@ export class SandboxClient { tags: sandbox.tags, })); - allSandboxes = [...allSandboxes, ...sandboxes]; + const newSandboxes = sandboxes.filter( + (sandbox) => + !allSandboxes.some((existing) => existing.id === sandbox.id) + ); + allSandboxes = [...allSandboxes, ...newSandboxes]; // Stop if we've hit the limit or there are no more pages if (!nextPage || allSandboxes.length >= limit) { - allSandboxes = allSandboxes.slice(0, limit); break; } From 85d77a95470a2f4cf7a19e91c8702dc5d159f808 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 19:29:37 +0100 Subject: [PATCH 029/241] tweak: rename header (#41) --- src/bin/commands/sandbox/list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 81f1219..d6d9371 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -14,7 +14,7 @@ type OutputFormat = { }; const TABLE_FORMAT: OutputFormat[] = [ - { field: "id", header: "NAME", width: 24 }, + { field: "id", header: "ID", width: 24 }, { field: "title", header: "TITLE", width: 40 }, { field: "privacy", header: "PRIVACY", width: 10 }, { field: "tags", header: "TAGS", width: 20 }, From 7f4b063f89da27f7b0366fe1ec0a72176f219dca Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 19:33:22 +0100 Subject: [PATCH 030/241] tweak: use console error for limit log (#42) --- src/bin/commands/sandbox/list.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index d6d9371..042d537 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -124,7 +124,7 @@ export async function listSandboxes( }); // eslint-disable-next-line no-console - console.log( + console.error( `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` ); @@ -177,7 +177,7 @@ export async function listSandboxes( if (limit && totalCount > allSandboxes.length) { // eslint-disable-next-line no-console - console.log( + console.error( `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` ); } From 60cf5bb582c427fc87754ad51f4f5aa5ae72e13e Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 28 Jan 2025 19:37:16 +0100 Subject: [PATCH 031/241] chore(main): release 0.5.0 (#38) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d52b8..559a6f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.5.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.4.0...v0.5.0) (2025-01-28) + + +### Features + +* add more advanced pagination options ([#39](https://github.com/codesandbox/codesandbox-sdk/issues/39)) ([f086bb8](https://github.com/codesandbox/codesandbox-sdk/commit/f086bb88be2cb92848c8b8eae411408028978791)) +* list sandboxes by running status ([#37](https://github.com/codesandbox/codesandbox-sdk/issues/37)) ([1e9e19c](https://github.com/codesandbox/codesandbox-sdk/commit/1e9e19c7df09ea15025873e8557cbed031d2e9d0)) + ## [0.4.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.3.0...v0.4.0) (2025-01-22) diff --git a/package-lock.json b/package-lock.json index b7b8bda..70fcff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 2cc37d2..a6e52ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.4.0", + "version": "0.5.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 67c8a2ce246c5a49555fc76730a54fb398802e2c Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 30 Jan 2025 12:11:19 +0100 Subject: [PATCH 032/241] update pitcher-client to fix pong detection issue --- package-lock.json | 78 +++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 26 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70fcff3..2ec0628 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "csb": "dist/bin/codesandbox.js" }, "devDependencies": { - "@codesandbox/pitcher-client": "0.360.2", + "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.5.1", @@ -87,17 +87,16 @@ } }, "node_modules/@codesandbox/api": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-0.1.0.tgz", - "integrity": "sha512-MCGIwWDjpMNlz1csJMnKAMplvmByNYlXC2C0R5P2RL1oZ2j6X4KJJEG06iz//izwBJzDgZJGiis1dq9siuJ8Rg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-1.1.3.tgz", + "integrity": "sha512-GHrxd/tL4CHy/+42Qx0RC65EHUH6jjzuRm+JcBgAlRcMCJPvSTANpoNgI6fQKofvEJIVc4ghqR7X87j6emEZyg==", "dev": true, "license": "MIT", "dependencies": { "@absinthe/socket": "^0.2.1", - "@codesandbox/create-gql-api": "0.1.0", + "@codesandbox/create-gql-api": "^1.0.1", "class-states": "1.0.15", "humps": "^2.0.1", - "node-fetch": "^2.6.1", "phoenix": "^1.6.6", "preact": "^10.22.0", "universal-cookie": "^4.0.4" @@ -111,9 +110,9 @@ "license": "ISC" }, "node_modules/@codesandbox/create-gql-api": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-0.1.0.tgz", - "integrity": "sha512-opCFfhdrgeiM2ffGpfMdjdx5mB2kjY79sBuD82775OUvAUnmTH2csa/CxscCV/JY4F/Qd3e+omQlP4CiMjMB5Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-1.0.1.tgz", + "integrity": "sha512-k+C8OS2wYdakIoYEtHjW+pvwb7uq+emHQX5W1tqJQ9gDEYYy2dHItQNulY/8H0ymudhW3b6zcKTVNTMl9QgGWQ==", "dev": true, "license": "ISC", "dependencies": { @@ -136,16 +135,16 @@ } }, "node_modules/@codesandbox/pitcher-client": { - "version": "0.360.2", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-0.360.2.tgz", - "integrity": "sha512-rESLa3P2ohDbGMXQ5Jx0iUIhQkIuSDKocXDsIn2gOiCTQloxqXmX/d6rbk4MlQcjvcc4eBbrw1kIPxfyNFk/PQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-1.1.5.tgz", + "integrity": "sha512-YRDF7X1i2+P888N+P9DQkwatUDNEAPcaAHjojwvt8dYoshO8IL9iKeN6sdHp+dDi/NLnVzz1cDO6mcAvlXAC0Q==", "dev": true, "license": "GPL-3.0", "dependencies": { - "@codesandbox/api": "0.1.0", + "@codesandbox/api": "^1.1.3", "@codesandbox/nodebox": "^0.1.8", - "@codesandbox/pitcher-common": "0.360.2", - "@codesandbox/pitcher-protocol": "0.360.3", + "@codesandbox/pitcher-common": "^0.360.2", + "@codesandbox/pitcher-protocol": "^0.360.4", "@codesandbox/sandpack-client": "^2.18.0", "@types/ws": "^7.4.7", "class-states": "1.0.16", @@ -153,22 +152,11 @@ "isomorphic-ws": "^5.0.0", "js-untar": "^2.0.0", "jszip": "^3.10.1", - "minimatch": "9.0.3", + "minimatch": "^10.0.1", "semver": "^7.3.5", "vscode-jsonrpc": "^8.2.0" } }, - "node_modules/@codesandbox/pitcher-client/node_modules/@codesandbox/pitcher-protocol": { - "version": "0.360.3", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.3.tgz", - "integrity": "sha512-E1ksPxx1/RTKZDPHGLw4vh4mfPFphwHJMIOtQwXIUBsasMNLuSqh6E+Tr8f1DfxKDv81+HJ6y8PxiXn9JcpgyA==", - "dev": true, - "license": "GPL-3.0", - "dependencies": { - "@codesandbox/pitcher-common": "*", - "@msgpack/msgpack": "^2.7.1" - } - }, "node_modules/@codesandbox/pitcher-client/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -2023,22 +2011,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2053,9 +2025,9 @@ } }, "node_modules/graphql": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", - "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", "dev": true, "license": "MIT", "engines": { @@ -2736,16 +2708,16 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3286,9 +3258,9 @@ } }, "node_modules/preact": { - "version": "10.25.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.2.tgz", - "integrity": "sha512-GEts1EH3oMnqdOIeXhlbBSddZ9nrINd070WBOiPO2ous1orrKGUM4SMDbwyjSWD1iMS2dBvaDjAa5qUhz3TXqw==", + "version": "10.25.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", + "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index a6e52ff..45a10f7 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "README.md" ], "devDependencies": { - "@codesandbox/pitcher-client": "0.360.2", + "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.5.1", From c8e6f46299f51f9f8ac3acc5934d34a16e7db051 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 30 Jan 2025 13:14:31 +0100 Subject: [PATCH 033/241] fix: uploads of big files (#44) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8981e1..954cb88 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ To get started, install the SDK: npm install @codesandbox/sdk ``` -Then, create an API token by going to https://codesandbox.io/t/api, and clicking on the "Create API Token" button. You can then use this token to authenticate with the SDK: +Create an API token by going to https://codesandbox.io/t/api, and clicking on the "Create API Token" button. You can then use this token to authenticate with the SDK: ```javascript import { CodeSandbox } from "@codesandbox/sdk"; From 8c56c58d46fd8d54a90dd6966862a5d516096343 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 30 Jan 2025 13:15:25 +0100 Subject: [PATCH 034/241] chore(main): release 0.5.1 (#45) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 559a6f2..d6c8992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.5.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.0...v0.5.1) (2025-01-30) + + +### Bug Fixes + +* uploads of big files ([#44](https://github.com/codesandbox/codesandbox-sdk/issues/44)) ([c8e6f46](https://github.com/codesandbox/codesandbox-sdk/commit/c8e6f46299f51f9f8ac3acc5934d34a16e7db051)) + ## [0.5.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.4.0...v0.5.0) (2025-01-28) diff --git a/package-lock.json b/package-lock.json index 2ec0628..7740e01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.5.0", + "version": "0.5.1", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 45a10f7..5dbf2d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.5.0", + "version": "0.5.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 46918131508bcf56bde7b0d3f2afb07ae710fec5 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 30 Jan 2025 16:04:40 +0100 Subject: [PATCH 035/241] fix: esm node build (#36) * fix: esm node build * chore: import esbuild * chore: fix build * update pitcher * make edge default * fix --- .codesandbox/ci.json | 1 + esbuild.js => esbuild.cjs | 7 + openapi.json | 2 +- package-lock.json | 129 +++++--------- package.json | 7 +- src/client/client.gen.ts | 5 + src/client/index.ts | 4 +- src/client/sdk.gen.ts | 242 +++++++++++++++++++------ src/client/types.gen.ts | 366 ++++++++++++++++++++++---------------- src/index.ts | 1 + src/sandbox-client.ts | 6 +- 11 files changed, 479 insertions(+), 291 deletions(-) create mode 100644 .codesandbox/ci.json rename esbuild.js => esbuild.cjs (90%) create mode 100644 src/client/client.gen.ts diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.codesandbox/ci.json @@ -0,0 +1 @@ +{} diff --git a/esbuild.js b/esbuild.cjs similarity index 90% rename from esbuild.js rename to esbuild.cjs index 8f6b307..68e1bf9 100644 --- a/esbuild.js +++ b/esbuild.cjs @@ -57,6 +57,13 @@ Promise.all([ bundle: true, format: "esm", platform: "node", + banner: { + js: ` +import { fileURLToPath } from 'url'; +import { createRequire as topLevelCreateRequire } from 'module'; +const require = topLevelCreateRequire(import.meta.url); + `.trim(), + }, outdir: "dist/esm", plugins: [browserifyPlugin], }), diff --git a/openapi.json b/openapi.json index 6461013..5243ece 100644 --- a/openapi.json +++ b/openapi.json @@ -1586,6 +1586,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], "tags": [] } diff --git a/package-lock.json b/package-lock.json index 7740e01..f4108de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", - "@hey-api/client-fetch": "^0.5.1", - "@hey-api/openapi-ts": "0.60.1", + "@hey-api/client-fetch": "^0.7.3", + "@hey-api/openapi-ts": "^0.63.2", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", @@ -45,7 +45,6 @@ "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", "@jumpn/utils-array": "0.3.4", @@ -58,30 +57,11 @@ "phoenix": "^1.4.0" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.7.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.3.tgz", - "integrity": "sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, "node_modules/@babel/runtime": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", "dev": true, - "license": "MIT", "dependencies": { "regenerator-runtime": "^0.12.0" } @@ -91,7 +71,6 @@ "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-1.1.3.tgz", "integrity": "sha512-GHrxd/tL4CHy/+42Qx0RC65EHUH6jjzuRm+JcBgAlRcMCJPvSTANpoNgI6fQKofvEJIVc4ghqR7X87j6emEZyg==", "dev": true, - "license": "MIT", "dependencies": { "@absinthe/socket": "^0.2.1", "@codesandbox/create-gql-api": "^1.0.1", @@ -106,15 +85,13 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.15.tgz", "integrity": "sha512-ReR0LKl1C3tK+Wwe2zlAtXEjLavuR3+m+oFFNELdJmywEoh00iDYCWLxWJof83YSpqIBK/iegy0blUuktJ7+6A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@codesandbox/create-gql-api": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-1.0.1.tgz", "integrity": "sha512-k+C8OS2wYdakIoYEtHjW+pvwb7uq+emHQX5W1tqJQ9gDEYYy2dHItQNulY/8H0ymudhW3b6zcKTVNTMl9QgGWQ==", "dev": true, - "license": "ISC", "dependencies": { "get-graphql-schema": "^2.1.2", "graphql": "^16.8.1" @@ -274,38 +251,56 @@ } }, "node_modules/@hey-api/client-fetch": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.5.2.tgz", - "integrity": "sha512-ix8MuuLTME5BWMQ8iCALqWflHtDyuoEwX0b77TQL+VgEXL89cYtjoHxE2bm2DYQQm8qDJXvTS6ju/D6wB6l6Hg==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.7.3.tgz", + "integrity": "sha512-nysIXMag9nr5ENy+47G0AYsegdT7vT6S4KLfY7NVgM6HsyZ0DrhCZvz5nP70M16x9i860SrnXhjpcuHx0g5sDQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.1.tgz", + "integrity": "sha512-dBt0A7op9kf4BcK++x6HBYDmvCvnJUZEGe5QytghPFHnMXPyKwDKomwL/v5e9ERk6E0e1GzL/e/y6pWUso9zrQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, "funding": { "url": "https://github.com/sponsors/hey-api" } }, "node_modules/@hey-api/openapi-ts": { - "version": "0.60.1", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.60.1.tgz", - "integrity": "sha512-W3fW6pjQ3rwRsmvp9+hDEStyedTWgNjd6PfdVOIegtPBKj3o4ThK3J14kXeqdH2tJXrudfLpXHRp43+ahi4QSg==", + "version": "0.63.2", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.63.2.tgz", + "integrity": "sha512-HC5fR3+07P1AvDYrcZv0kbnhWogvMFot848PfpS3Gbncm47mDdO/uhNGIr2RF9CaRIvJNSJvAf1jL3XAQZ18RA==", "dev": true, "license": "MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "11.7.3", + "@hey-api/json-schema-ref-parser": "1.0.1", "c12": "2.0.1", - "commander": "12.1.0", + "commander": "13.0.0", "handlebars": "4.7.8" }, "bin": { "openapi-ts": "bin/index.cjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=22.11.0" }, "funding": { "url": "https://github.com/sponsors/hey-api" }, "peerDependencies": { - "typescript": "^5.x" + "typescript": "^5.5.3" } }, "node_modules/@isaacs/cliui": { @@ -423,7 +418,6 @@ "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", "dev": true, - "license": "MIT", "dependencies": { "babel-polyfill": "6.26.0", "babel-runtime": "6.26.0", @@ -435,7 +429,6 @@ "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", "dev": true, - "license": "MIT", "dependencies": { "@jumpn/utils-array": "0.3.4", "babel-polyfill": "6.26.0", @@ -448,15 +441,13 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@jumpn/utils-graphql": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", "core-js": "2.6.0", @@ -468,7 +459,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", "dev": true, - "license": "MIT", "dependencies": { "iterall": "^1.2.2" }, @@ -514,8 +504,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -695,7 +684,6 @@ "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", "dev": true, - "license": "MIT", "dependencies": { "babel-runtime": "^6.26.0", "core-js": "^2.5.0", @@ -706,15 +694,13 @@ "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", "dev": true, - "license": "MIT", "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -724,8 +710,7 @@ "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", @@ -1246,7 +1231,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -1255,13 +1239,12 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", + "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", "dev": true, "license": "MIT", "engines": { @@ -1290,7 +1273,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1300,8 +1282,7 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/core-util-is": { "version": "1.0.3", @@ -1739,8 +1720,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fill-range": { "version": "7.1.1", @@ -1759,8 +1739,7 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/for-each": { "version": "0.3.3", @@ -1853,7 +1832,6 @@ "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", "integrity": "sha512-1z5Hw91VrE3GrpCZE6lE8Dy+jz4kXWesLS7rCSjwOxf5BOcIedAZeTUJRIeIzmmR+PA9CKOkPTYFRJbdgUtrxA==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^2.4.1", "graphql": "^14.0.2", @@ -1869,7 +1847,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -1882,7 +1859,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1897,7 +1873,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", "dev": true, - "license": "MIT", "dependencies": { "iterall": "^1.2.2" }, @@ -1910,7 +1885,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -1920,7 +1894,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -2029,7 +2002,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -2189,8 +2161,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/ieee754": { "version": "1.2.1", @@ -2386,8 +2357,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jackspeak": { "version": "4.0.2", @@ -3185,8 +3155,7 @@ "version": "1.7.18", "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.18.tgz", "integrity": "sha512-Qo+V9+knfEd+R1pzCe+XJlj3GPSxWz4PNwzFl7GgssuTVYPoh/he3mbPQJ+NEDdqulxAbBtWCNYGPB3WplS5Mg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -3262,7 +3231,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", "dev": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -3396,8 +3364,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/require-directory": { "version": "2.1.1", @@ -3973,7 +3940,6 @@ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", "dev": true, - "license": "MIT", "dependencies": { "@types/cookie": "^0.3.3", "cookie": "^0.4.0" @@ -4374,8 +4340,7 @@ "version": "0.8.11", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", - "dev": true, - "license": "MIT" + "dev": true } } } diff --git a/package.json b/package.json index 5dbf2d3..a2305f3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "author": "CodeSandbox", "license": "MIT", "repository": "git+https://github.com/codesandbox/codesandbox-sdk.git", + "type": "module", "bin": { "csb": "dist/bin/codesandbox.js" }, @@ -52,7 +53,7 @@ "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.js", "build:cjs": "tsc -p ./tsconfig.build-cjs.json", "build:esm": "tsc -p ./tsconfig.build-esm.json", - "build:esbuild": "node esbuild.js", + "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", @@ -77,8 +78,8 @@ "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", - "@hey-api/client-fetch": "^0.5.1", - "@hey-api/openapi-ts": "0.60.1", + "@hey-api/client-fetch": "^0.7.3", + "@hey-api/openapi-ts": "^0.63.2", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", diff --git a/src/client/client.gen.ts b/src/client/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/client/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/client/index.ts b/src/client/index.ts index 81abc82..e64537d 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './sdk.gen'; -export * from './types.gen'; \ No newline at end of file +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index a2abfb8..58d7a67 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -1,17 +1,25 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch'; -import type { MetaInfoError, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateError, WorkspaceCreateResponse2, TokenCreateData, TokenCreateError, TokenCreateResponse2, TokenUpdateData, TokenUpdateError, TokenUpdateResponse2, SandboxListData, SandboxListError, SandboxListResponse2, SandboxCreateData, SandboxCreateError, SandboxCreateResponse2, SandboxGetData, SandboxGetError, SandboxGetResponse2, SandboxForkData, SandboxForkError, SandboxForkResponse2, VmHibernateData, VmHibernateError, VmHibernateResponse, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutError, VmUpdateHibernationTimeoutResponse, VmCreateSessionData, VmCreateSessionError, VmCreateSessionResponse, VmShutdownData, VmShutdownError, VmShutdownResponse, VmUpdateSpecsData, VmUpdateSpecsError, VmUpdateSpecsResponse, VmStartData, VmStartError, VmStartResponse, VmUpdateSpecs2Data, VmUpdateSpecs2Error, VmUpdateSpecs2Response } from './types.gen'; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export const client = createClient(createConfig()); +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; /** * Metadata about the API */ -export const metaInfo = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).get({ - ...options, - url: '/meta/info' +export const metaInfo = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + url: '/meta/info', + ...options }); }; @@ -20,10 +28,20 @@ export const metaInfo = (options?: Options * Create a new, empty, workspace in the current organization * */ -export const workspaceCreate = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const workspaceCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace', ...options, - url: '/org/workspace' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -32,10 +50,20 @@ export const workspaceCreate = (options?: * Create a new API token for a workspace that is part of the current organization. * */ -export const tokenCreate = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const tokenCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace/{team_id}/tokens', ...options, - url: '/org/workspace/{team_id}/tokens' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -44,10 +72,20 @@ export const tokenCreate = (options?: Opti * Update an API token for a workspace that is part of the current organization. * */ -export const tokenUpdate = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).patch({ +export const tokenUpdate = (options?: Options) => { + return (options?.client ?? _heyApiClient).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace/{team_id}/tokens/{token_id}', ...options, - url: '/org/workspace/{team_id}/tokens/{token_id}' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -57,10 +95,16 @@ export const tokenUpdate = (options?: Opti * Results are limited to a maximum of 50 sandboxes per request. * */ -export const sandboxList = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).get({ - ...options, - url: '/sandbox' +export const sandboxList = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox', + ...options }); }; @@ -69,10 +113,20 @@ export const sandboxList = (options?: Opti * Create a new sandbox in the current workspace with file contents * */ -export const sandboxCreate = (options?: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const sandboxCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox', ...options, - url: '/sandbox' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -81,10 +135,16 @@ export const sandboxCreate = (options?: Op * Retrieve a sandbox by its ID * */ -export const sandboxGet = (options: OptionsLegacyParser) => { - return (options?.client ?? client).get({ - ...options, - url: '/sandbox/{id}' +export const sandboxGet = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}', + ...options }); }; @@ -93,10 +153,20 @@ export const sandboxGet = (options: Option * Fork an existing sandbox to the current workspace * */ -export const sandboxFork = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const sandboxFork = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/fork', ...options, - url: '/sandbox/{id}/fork' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -111,10 +181,20 @@ export const sandboxFork = (options: Optio * minimal latency. * */ -export const vmHibernate = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmHibernate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/hibernate', ...options, - url: '/vm/{id}/hibernate' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -126,10 +206,20 @@ export const vmHibernate = (options: Optio * The new timeout must be greater than 0 and less than or equal to 86400 seconds (24 hours). * */ -export const vmUpdateHibernationTimeout = (options: OptionsLegacyParser) => { - return (options?.client ?? client).put({ +export const vmUpdateHibernationTimeout = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/hibernation_timeout', ...options, - url: '/vm/{id}/hibernation_timeout' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -142,10 +232,20 @@ export const vmUpdateHibernationTimeout = * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. * */ -export const vmCreateSession = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmCreateSession = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/sessions', ...options, - url: '/vm/{id}/sessions' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -159,10 +259,20 @@ export const vmCreateSession = (options: O * Shutdown VMs require additional time to start up. * */ -export const vmShutdown = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmShutdown = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/shutdown', ...options, - url: '/vm/{id}/shutdown' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -174,10 +284,20 @@ export const vmShutdown = (options: Option * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs = (options: OptionsLegacyParser) => { - return (options?.client ?? client).put({ +export const vmUpdateSpecs = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/specs', ...options, - url: '/vm/{id}/specs' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -193,10 +313,20 @@ export const vmUpdateSpecs = (options: Opt * This endpoint is subject to special rate limits related to concurrent VM usage. * */ -export const vmStart = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmStart = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/start', ...options, - url: '/vm/{id}/start' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; @@ -208,9 +338,19 @@ export const vmStart = (options: OptionsLe * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs2 = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ +export const vmUpdateSpecs2 = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/update_specs', ...options, - url: '/vm/{id}/update_specs' + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } }); }; \ No newline at end of file diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index e6e699c..2d288ce 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -export type Error = string | { +export type _Error = string | { [key: string]: unknown; }; @@ -16,16 +16,14 @@ export type MetaInformation = { * Meta information about the current authentication context */ auth?: { - scopes: Array<(string)>; - team: (string) | null; + scopes: Array; + team: string | null; version: string; }; }; export type Response = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; }; @@ -41,7 +39,7 @@ export type SandboxCreateRequest = { /** * Array of strings with external resources to load. */ - external_resources?: Array<(string)>; + external_resources?: Array; /** * Map of `path => file` where each file is a map. */ @@ -69,7 +67,7 @@ export type SandboxCreateRequest = { * Map of dependencies and their version specifications. */ npm_dependencies?: { - [key: string]: (string); + [key: string]: string; }; /** * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. @@ -86,7 +84,7 @@ export type SandboxCreateRequest = { /** * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. */ - tags?: Array<(string)>; + tags?: Array; /** * Name of the template from which the sandbox is derived (for example, `"static"`). */ @@ -97,21 +95,14 @@ export type SandboxCreateRequest = { title?: string; }; -/** - * Runtime to use for the sandbox. Defaults to `"browser"`. - */ -export type runtime = 'browser' | 'vm'; - export type SandboxCreateResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { alias: string; id: string; - title: (string) | null; + title: string | null; }; }; @@ -160,27 +151,15 @@ export type SandboxForkRequest = { /** * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. */ - tags?: Array<(string)>; + tags?: Array; /** * Sandbox title. Maximum 255 characters. Defaults to title of original sandbox with (forked). */ title?: string; }; -/** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ -export type tier = 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; - export type SandboxForkResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { @@ -202,32 +181,28 @@ export type SandboxForkResponse = { user_workspace_path: string; workspace_path: string; } | null; - title: (string) | null; + title: string | null; }; }; export type SandboxGetResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { created_at: string; - description?: (string) | null; + description?: string | null; id: string; is_frozen: boolean; privacy: number; - tags: Array<(string)>; - title?: (string) | null; + tags: Array; + title?: string | null; updated_at: string; }; }; export type SandboxListResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { @@ -236,17 +211,17 @@ export type SandboxListResponse = { /** * The number of the next page, if any. If `null`, the current page is the last page of records. */ - next_page: (number) | null; + next_page: number | null; total_records: number; }; sandboxes: Array<{ created_at: string; - description?: (string) | null; + description?: string | null; id: string; is_frozen: boolean; privacy: number; - tags: Array<(string)>; - title?: (string) | null; + tags: Array; + title?: string | null; updated_at: string; }>; }; @@ -256,7 +231,7 @@ export type TokenCreateRequest = { /** * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. */ - default_version?: (string) | null; + default_version?: string | null; /** * Description of this token, for instance where it will be used. */ @@ -264,23 +239,21 @@ export type TokenCreateRequest = { /** * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. */ - expires_at?: (string) | null; + expires_at?: string | null; /** * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. */ - scopes?: Array<('sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage')>; + scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; export type TokenCreateResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { - description: (string) | null; - expires_at: (string) | null; - scopes: Array<(string)>; + description: string | null; + expires_at: string | null; + scopes: Array; team_id: string; token: string; token_id: string; @@ -294,37 +267,35 @@ export type TokenUpdateRequest = { /** * API Version to use, formatted as YYYY-MM-DD */ - default_version?: (string) | null; + default_version?: string | null; /** * Description of this token, for instance where it will be used. */ - description?: (string) | null; + description?: string | null; /** * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. */ - expires_at?: (string) | null; + expires_at?: string | null; /** * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. */ - scopes?: Array<('sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage')>; + scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; export type TokenUpdateResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { - description: (string) | null; - expires_at?: (string) | null; - scopes: Array<(string)>; + description: string | null; + expires_at?: string | null; + scopes: Array; team_id: string; token_id: string; }; }; -export type VMCreateSessionRequest = { +export type VmCreateSessionRequest = { /** * Permission level for the session */ @@ -335,22 +306,15 @@ export type VMCreateSessionRequest = { session_id: string; }; -/** - * Permission level for the session - */ -export type permission = 'read' | 'write'; - -export type VMCreateSessionResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmCreateSessionResponse = { + errors?: Array; success?: boolean; } & { data?: { /** * List of capabilities that Pitcher has */ - capabilities: Array<(string)>; + capabilities: Array; /** * The permissions of the current session */ @@ -376,14 +340,12 @@ export type VMCreateSessionResponse = { }; }; -export type VMHibernateRequest = { +export type VmHibernateRequest = { [key: string]: unknown; }; -export type VMHibernateResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmHibernateResponse = { + errors?: Array; success?: boolean; } & { data?: { @@ -391,14 +353,12 @@ export type VMHibernateResponse = { }; }; -export type VMShutdownRequest = { +export type VmShutdownRequest = { [key: string]: unknown; }; -export type VMShutdownResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmShutdownResponse = { + errors?: Array; success?: boolean; } & { data?: { @@ -406,7 +366,7 @@ export type VMShutdownResponse = { }; }; -export type VMStartRequest = { +export type VmStartRequest = { /** * The time in seconds after which the VM will hibernate due to inactivity. * Must be a positive integer between 1 and 86400 (24 hours). @@ -429,10 +389,8 @@ export type VMStartRequest = { tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; -export type VMStartResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmStartResponse = { + errors?: Array; success?: boolean; } & { data?: { @@ -450,7 +408,7 @@ export type VMStartResponse = { }; }; -export type VMUpdateHibernationTimeoutRequest = { +export type VmUpdateHibernationTimeoutRequest = { /** * The new hibernation timeout in seconds. * @@ -460,10 +418,8 @@ export type VMUpdateHibernationTimeoutRequest = { hibernation_timeout_seconds: number; }; -export type VMUpdateHibernationTimeoutResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmUpdateHibernationTimeoutResponse = { + errors?: Array; success?: boolean; } & { data?: { @@ -472,7 +428,7 @@ export type VMUpdateHibernationTimeoutResponse = { }; }; -export type VMUpdateSpecsRequest = { +export type VmUpdateSpecsRequest = { /** * Determines which specs to update the VM with. * @@ -482,10 +438,8 @@ export type VMUpdateSpecsRequest = { tier: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; -export type VMUpdateSpecsResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; +export type VmUpdateSpecsResponse = { + errors?: Array; success?: boolean; } & { data?: { @@ -502,9 +456,7 @@ export type WorkspaceCreateRequest = { }; export type WorkspaceCreateResponse = { - errors?: Array<((string | { - [key: string]: unknown; -}))>; + errors?: Array; success?: boolean; } & { data?: { @@ -513,99 +465,159 @@ export type WorkspaceCreateResponse = { }; }; -export type MetaInfoResponse = (MetaInformation); +export type MetaInfoData = { + body?: never; + path?: never; + query?: never; + url: '/meta/info'; +}; + +export type MetaInfoResponses = { + /** + * Meta Info Response + */ + 200: MetaInformation; +}; -export type MetaInfoError = unknown; +export type MetaInfoResponse = MetaInfoResponses[keyof MetaInfoResponses]; export type WorkspaceCreateData = { /** * Workspace Create Request */ body?: WorkspaceCreateRequest; + path?: never; + query?: never; + url: '/org/workspace'; }; -export type WorkspaceCreateResponse2 = (WorkspaceCreateResponse); +export type WorkspaceCreateResponses = { + /** + * Workspace Create Response + */ + 201: WorkspaceCreateResponse; +}; -export type WorkspaceCreateError = unknown; +export type WorkspaceCreateResponse2 = WorkspaceCreateResponses[keyof WorkspaceCreateResponses]; export type TokenCreateData = { /** * Token Create Request */ body?: TokenCreateRequest; + path?: never; + query?: never; + url: '/org/workspace/{team_id}/tokens'; }; -export type TokenCreateResponse2 = (TokenCreateResponse); +export type TokenCreateResponses = { + /** + * Token Create Response + */ + 201: TokenCreateResponse; +}; -export type TokenCreateError = unknown; +export type TokenCreateResponse2 = TokenCreateResponses[keyof TokenCreateResponses]; export type TokenUpdateData = { /** * Token Update Request */ body?: TokenUpdateRequest; + path?: never; + query?: never; + url: '/org/workspace/{team_id}/tokens/{token_id}'; }; -export type TokenUpdateResponse2 = (TokenUpdateResponse); +export type TokenUpdateResponses = { + /** + * Token Update Response + */ + 201: TokenUpdateResponse; +}; -export type TokenUpdateError = unknown; +export type TokenUpdateResponse2 = TokenUpdateResponses[keyof TokenUpdateResponses]; export type SandboxListData = { + body?: never; + path?: never; query?: { /** - * Order direction + * Comma-separated list of tags to filter by */ - direction?: 'asc' | 'desc'; + tags?: string; /** * Field to order results by */ order_by?: 'inserted_at' | 'updated_at'; /** - * Page number to return + * Order direction */ - page?: number; + direction?: 'asc' | 'desc'; /** * Maximum number of sandboxes to return in a single response */ page_size?: number; /** - * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. + * Page number to return */ - status?: 'running'; + page?: number; /** - * Comma-separated list of tags to filter by + * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. */ - tags?: string; + status?: 'running'; }; + url: '/sandbox'; }; -export type SandboxListResponse2 = (SandboxListResponse); +export type SandboxListResponses = { + /** + * Sandbox List Response + */ + 200: SandboxListResponse; +}; -export type SandboxListError = unknown; +export type SandboxListResponse2 = SandboxListResponses[keyof SandboxListResponses]; export type SandboxCreateData = { /** * Sandbox Create Request */ body?: SandboxCreateRequest; + path?: never; + query?: never; + url: '/sandbox'; }; -export type SandboxCreateResponse2 = (SandboxCreateResponse); +export type SandboxCreateResponses = { + /** + * Sandbox Create Response + */ + 201: SandboxCreateResponse; +}; -export type SandboxCreateError = unknown; +export type SandboxCreateResponse2 = SandboxCreateResponses[keyof SandboxCreateResponses]; export type SandboxGetData = { + body?: never; path: { /** * Short ID of the sandbox to retrieve */ id: string; }; + query?: never; + url: '/sandbox/{id}'; }; -export type SandboxGetResponse2 = (SandboxGetResponse); +export type SandboxGetResponses = { + /** + * Sandbox Get Response + */ + 200: SandboxGetResponse; +}; -export type SandboxGetError = unknown; +export type SandboxGetResponse2 = SandboxGetResponses[keyof SandboxGetResponses]; export type SandboxForkData = { /** @@ -618,127 +630,183 @@ export type SandboxForkData = { */ id: string; }; + query?: never; + url: '/sandbox/{id}/fork'; }; -export type SandboxForkResponse2 = (SandboxForkResponse); +export type SandboxForkResponses = { + /** + * Sandbox Fork Response + */ + 201: SandboxForkResponse; +}; -export type SandboxForkError = unknown; +export type SandboxForkResponse2 = SandboxForkResponses[keyof SandboxForkResponses]; export type VmHibernateData = { /** * VM Hibernate Request */ - body?: VMHibernateRequest; + body?: VmHibernateRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/hibernate'; }; -export type VmHibernateResponse = (VMHibernateResponse); +export type VmHibernateResponses = { + /** + * VM Hibernate Response + */ + 200: VmHibernateResponse; +}; -export type VmHibernateError = unknown; +export type VmHibernateResponse2 = VmHibernateResponses[keyof VmHibernateResponses]; export type VmUpdateHibernationTimeoutData = { /** * VM Update Hibernation Timeout Request */ - body?: VMUpdateHibernationTimeoutRequest; + body?: VmUpdateHibernationTimeoutRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/hibernation_timeout'; }; -export type VmUpdateHibernationTimeoutResponse = (VMUpdateHibernationTimeoutResponse); +export type VmUpdateHibernationTimeoutResponses = { + /** + * VM Update Hibernation Timeout Response + */ + 200: VmUpdateHibernationTimeoutResponse; +}; -export type VmUpdateHibernationTimeoutError = unknown; +export type VmUpdateHibernationTimeoutResponse2 = VmUpdateHibernationTimeoutResponses[keyof VmUpdateHibernationTimeoutResponses]; export type VmCreateSessionData = { /** * VM Create Session Request */ - body?: VMCreateSessionRequest; + body?: VmCreateSessionRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/sessions'; }; -export type VmCreateSessionResponse = (VMCreateSessionResponse); +export type VmCreateSessionResponses = { + /** + * VM Create Session Response + */ + 200: VmCreateSessionResponse; +}; -export type VmCreateSessionError = unknown; +export type VmCreateSessionResponse2 = VmCreateSessionResponses[keyof VmCreateSessionResponses]; export type VmShutdownData = { /** * VM Shutdown Request */ - body?: VMShutdownRequest; + body?: VmShutdownRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/shutdown'; }; -export type VmShutdownResponse = (VMShutdownResponse); +export type VmShutdownResponses = { + /** + * VM Shutdown Response + */ + 200: VmShutdownResponse; +}; -export type VmShutdownError = unknown; +export type VmShutdownResponse2 = VmShutdownResponses[keyof VmShutdownResponses]; export type VmUpdateSpecsData = { /** * VM Update Specs Request */ - body?: VMUpdateSpecsRequest; + body?: VmUpdateSpecsRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/specs'; }; -export type VmUpdateSpecsResponse = (VMUpdateSpecsResponse); +export type VmUpdateSpecsResponses = { + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; +}; -export type VmUpdateSpecsError = unknown; +export type VmUpdateSpecsResponse2 = VmUpdateSpecsResponses[keyof VmUpdateSpecsResponses]; export type VmStartData = { /** * VM Start Request */ - body?: VMStartRequest; + body?: VmStartRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/start'; }; -export type VmStartResponse = (VMStartResponse); +export type VmStartResponses = { + /** + * VM Start Response + */ + 200: VmStartResponse; +}; -export type VmStartError = unknown; +export type VmStartResponse2 = VmStartResponses[keyof VmStartResponses]; export type VmUpdateSpecs2Data = { /** * VM Update Specs Request */ - body?: VMUpdateSpecsRequest; + body?: VmUpdateSpecsRequest; path: { /** * Sandbox ID */ id: string; }; + query?: never; + url: '/vm/{id}/update_specs'; }; -export type VmUpdateSpecs2Response = (VMUpdateSpecsResponse); +export type VmUpdateSpecs2Responses = { + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; +}; -export type VmUpdateSpecs2Error = unknown; \ No newline at end of file +export type VmUpdateSpecs2Response = VmUpdateSpecs2Responses[keyof VmUpdateSpecs2Responses]; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5889515..bd012aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -81,6 +81,7 @@ export class CodeSandbox { Authorization: `Bearer ${this.apiToken}`, ...(opts.headers ?? {}), }, + fetch: opts.fetch ?? fetch, }) ); diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index f2a9a28..097a903 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -1,7 +1,7 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; import type { Client } from "@hey-api/client-fetch"; -import type { VmStartResponse, tier } from "./client"; +import type { VmStartResponse, VmUpdateSpecsRequest } from "./client"; import { sandboxFork, vmCreateSession, @@ -155,13 +155,13 @@ export class VMTier { public static readonly XLarge = new VMTier("XLarge", 64, 128, 50); private constructor( - public readonly name: tier, + public readonly name: VmUpdateSpecsRequest["tier"], public readonly cpuCores: number, public readonly memoryGiB: number, public readonly diskGB: number ) {} - public static fromName(name: tier): VMTier { + public static fromName(name: VmUpdateSpecsRequest["tier"]): VMTier { return VMTier[name]; } From eee911771e7493ee20fa101e4d715cad6990371e Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 30 Jan 2025 16:05:25 +0100 Subject: [PATCH 036/241] chore(main): release 0.5.2 (#46) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c8992..7f71fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.5.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.1...v0.5.2) (2025-01-30) + + +### Bug Fixes + +* esm node build ([#36](https://github.com/codesandbox/codesandbox-sdk/issues/36)) ([4691813](https://github.com/codesandbox/codesandbox-sdk/commit/46918131508bcf56bde7b0d3f2afb07ae710fec5)) + ## [0.5.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.0...v0.5.1) (2025-01-30) diff --git a/package-lock.json b/package-lock.json index f4108de..87904db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.5.1", + "version": "0.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.5.1", + "version": "0.5.2", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index a2305f3..8a6f454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.5.1", + "version": "0.5.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 7fc76a5648f1f2665dfda7c39d1432ca2ae2f768 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 30 Jan 2025 20:27:58 +0100 Subject: [PATCH 037/241] fix: use cjs for binary (#47) --- esbuild.cjs | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index 68e1bf9..396810f 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -92,7 +92,7 @@ const require = topLevelCreateRequire(import.meta.url); // Bin builds: esbuild.build({ entryPoints: ["src/bin/main.ts"], - outfile: "dist/bin/codesandbox.js", + outfile: "dist/bin/codesandbox.cjs", bundle: true, format: "cjs", platform: "node", diff --git a/package.json b/package.json index 8a6f454..818407d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "repository": "git+https://github.com/codesandbox/codesandbox-sdk.git", "type": "module", "bin": { - "csb": "dist/bin/codesandbox.js" + "csb": "dist/bin/codesandbox.cjs" }, "module": "./dist/esm/index.js", "main": "./dist/esm/index.js", @@ -50,7 +50,7 @@ }, "types": "./dist/esm/index.d.ts", "scripts": { - "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.js", + "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.cjs", "build:cjs": "tsc -p ./tsconfig.build-cjs.json", "build:esm": "tsc -p ./tsconfig.build-esm.json", "build:esbuild": "node esbuild.cjs", From 92ea845cf91f7cc056593527464f142e66aaa8ee Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 30 Jan 2025 20:28:26 +0100 Subject: [PATCH 038/241] chore(main): release 0.5.3 (#48) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f71fa0..542237f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.5.3](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.2...v0.5.3) (2025-01-30) + + +### Bug Fixes + +* use cjs for binary ([#47](https://github.com/codesandbox/codesandbox-sdk/issues/47)) ([7fc76a5](https://github.com/codesandbox/codesandbox-sdk/commit/7fc76a5648f1f2665dfda7c39d1432ca2ae2f768)) + ## [0.5.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.1...v0.5.2) (2025-01-30) diff --git a/package-lock.json b/package-lock.json index 87904db..057a4d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.5.2", + "version": "0.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.5.2", + "version": "0.5.3", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 818407d..a4ff2f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.5.2", + "version": "0.5.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 64abff88deec9bf2ccac0a857be48b7de1ee6205 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 6 Feb 2025 11:55:37 +0100 Subject: [PATCH 039/241] fix(snapshot): don't include ignorefiles when building snapshots (#50) --- src/bin/utils/hash.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/bin/utils/hash.ts b/src/bin/utils/hash.ts index 2d297a2..8dc82e1 100644 --- a/src/bin/utils/hash.ts +++ b/src/bin/utils/hash.ts @@ -13,25 +13,20 @@ interface HashResult { const MAX_FILES = 50_000; export async function hashDirectory(dirPath: string): Promise { - // Initialize ignore rules from .gitignore and .dockerignore + // Initialize ignore rules from .gitignore, .dockerignore and .csbignore const ig = ignore(); - const gitignorePath = join(dirPath, ".gitignore"); - const dockerignorePath = join(dirPath, ".dockerignore"); - const csbIgnorePath = join(dirPath, ".csbignore"); + const ignoreFiles = [".gitignore", ".dockerignore", ".csbignore"]; + ignoreFiles.forEach((file) => { + const fullPath = join(dirPath, file); + if (existsSync(fullPath)) { + ig.add(readFileSync(fullPath, "utf8")); + ig.add(file); + } + }); // Always ignore .git folder ig.add(".git/**"); - if (existsSync(gitignorePath)) { - ig.add(readFileSync(gitignorePath, "utf8")); - } - if (existsSync(dockerignorePath)) { - ig.add(readFileSync(dockerignorePath, "utf8")); - } - if (existsSync(csbIgnorePath)) { - ig.add(readFileSync(csbIgnorePath, "utf8")); - } - const relevantFiles: string[] = []; const fileHashes: string[] = []; From 7bf1e35c79b347e1b85dc557236c601aa0f455a9 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 6 Feb 2025 12:05:07 +0100 Subject: [PATCH 040/241] feat: support env and cwd in shell api (#52) --- src/shells.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/shells.ts b/src/shells.ts index f6473c2..4983621 100644 --- a/src/shells.ts +++ b/src/shells.ts @@ -18,6 +18,8 @@ export type ShellCreateOpts = { export type ShellRunOpts = { ptySize?: ShellSize; shellName?: string; + env?: Record; + cwd?: string; }; export type ShellOpenOpts = { ptySize?: ShellSize; @@ -123,8 +125,9 @@ export class Shells extends Disposable { command, opts?.ptySize ?? DEFAULT_SHELL_SIZE, undefined, - undefined, - opts?.shellName + opts?.env, + opts?.shellName, + opts?.cwd ); return shell; @@ -287,7 +290,8 @@ function runCommandAsUser( shellSize: ShellSize = DEFAULT_SHELL_SIZE, runPreCommand?: () => Promise, env?: Record, - shellName?: string + shellName?: string, + cwd?: string ): RunningCommand { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); @@ -300,10 +304,15 @@ function runCommandAsUser( await runPreCommand(); } - const commandWithEnv = `env ${Object.entries(env ?? {}) + // TODO: use a new shell API that natively supports cwd & env + let commandWithEnv = `env ${Object.entries(env ?? {}) .map(([key, value]) => `${key}=${value}`) .join(" ")} ${command}`; + if (cwd) { + commandWithEnv = `cd ${cwd} && ${commandWithEnv}`; + } + shell = await pitcher.clients.shell.create( pitcher.workspacePath, shellSize, From 7ad63d73926a13c7c1d4c23568901f7fd418c899 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 6 Feb 2025 12:13:28 +0100 Subject: [PATCH 041/241] feat: allow sandbox to keep active while connected (#53) --- src/sandbox.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sandbox.ts b/src/sandbox.ts index 8a45b1b..e72645e 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -260,4 +260,28 @@ export class Sandbox extends SandboxSession { public async updateHibernationTimeout(timeoutSeconds: number): Promise { await this.sandboxClient.updateHibernationTimeout(this.id, timeoutSeconds); } + + private keepAliveInterval: NodeJS.Timeout | null = null; + /** + * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. + */ + public keepActiveWhileConnected(enabled: boolean) { + if (enabled && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(() => { + this.pitcherClient.clients.system.update(); + }, 1000 * 30); + + this.onWillDispose(() => { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + }); + } else { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + } + } } From b7d19ae8bd093eb97d0e97334fbe99c2953006f3 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 6 Feb 2025 16:40:35 +0100 Subject: [PATCH 042/241] chore(main): release 0.6.0 (#51) --- CHANGELOG.md | 13 +++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 542237f..d867db4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.6.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.3...v0.6.0) (2025-02-06) + + +### Features + +* allow sandbox to keep active while connected ([#53](https://github.com/codesandbox/codesandbox-sdk/issues/53)) ([7ad63d7](https://github.com/codesandbox/codesandbox-sdk/commit/7ad63d73926a13c7c1d4c23568901f7fd418c899)) +* support env and cwd in shell api ([#52](https://github.com/codesandbox/codesandbox-sdk/issues/52)) ([7bf1e35](https://github.com/codesandbox/codesandbox-sdk/commit/7bf1e35c79b347e1b85dc557236c601aa0f455a9)) + + +### Bug Fixes + +* **snapshot:** don't include ignorefiles when building snapshots ([#50](https://github.com/codesandbox/codesandbox-sdk/issues/50)) ([64abff8](https://github.com/codesandbox/codesandbox-sdk/commit/64abff88deec9bf2ccac0a857be48b7de1ee6205)) + ## [0.5.3](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.2...v0.5.3) (2025-01-30) diff --git a/package-lock.json b/package-lock.json index 057a4d8..3a6e595 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.5.3", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.5.3", + "version": "0.6.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index a4ff2f0..5060d08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.5.3", + "version": "0.6.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From d8e4c6f54ac91aef526af7ac8207f62f084b159d Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Sat, 8 Feb 2025 01:46:15 +0100 Subject: [PATCH 043/241] perf: do hibernation and shutdown in parallel (#54) --- src/bin/commands/sandbox/hibernate.ts | 33 +++++++++++++++++---------- src/bin/commands/sandbox/shutdown.ts | 33 +++++++++++++++++---------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/bin/commands/sandbox/hibernate.ts b/src/bin/commands/sandbox/hibernate.ts index d30dcd8..316807c 100644 --- a/src/bin/commands/sandbox/hibernate.ts +++ b/src/bin/commands/sandbox/hibernate.ts @@ -64,19 +64,28 @@ export async function hibernateSandbox(id?: string) { let failCount = 0; const results: CommandResult[] = []; - for (const sandboxId of ids) { - try { - const result = await hibernateSingleSandbox(sandboxId, null as any); - results.push(result); - if (result.success) { - successCount++; - } else { + // Run all hibernations in parallel + const hibernatePromises = ids.map((sandboxId) => + hibernateSingleSandbox(sandboxId, null as any) + .then((result) => { + results.push(result); + if (result.success) { + successCount++; + } else { + failCount++; + } + return result; + }) + .catch(() => { failCount++; - } - } catch (error) { - failCount++; - } - } + return { + success: false, + message: `Failed to hibernate sandbox ${sandboxId}`, + }; + }) + ); + + await Promise.all(hibernatePromises); // Final summary if (failCount === 0) { diff --git a/src/bin/commands/sandbox/shutdown.ts b/src/bin/commands/sandbox/shutdown.ts index 09d3388..fb36189 100644 --- a/src/bin/commands/sandbox/shutdown.ts +++ b/src/bin/commands/sandbox/shutdown.ts @@ -64,19 +64,28 @@ export async function shutdownSandbox(id?: string) { let failCount = 0; const results: CommandResult[] = []; - for (const sandboxId of ids) { - try { - const result = await shutdownSingleSandbox(sandboxId, null as any); - results.push(result); - if (result.success) { - successCount++; - } else { + // Run all shutdowns in parallel + const shutdownPromises = ids.map((sandboxId) => + shutdownSingleSandbox(sandboxId, null as any) + .then((result) => { + results.push(result); + if (result.success) { + successCount++; + } else { + failCount++; + } + return result; + }) + .catch(() => { failCount++; - } - } catch (error) { - failCount++; - } - } + return { + success: false, + message: `Failed to shutdown sandbox ${sandboxId}`, + }; + }) + ); + + await Promise.all(shutdownPromises); // Final summary if (failCount === 0) { From 23d146987485e5d32cbe2d0b4e1e3523eabec903 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Sat, 8 Feb 2025 01:46:46 +0100 Subject: [PATCH 044/241] chore(main): release 0.6.1 (#55) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d867db4..561748b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.6.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.0...v0.6.1) (2025-02-08) + + +### Performance Improvements + +* do hibernation and shutdown in parallel ([#54](https://github.com/codesandbox/codesandbox-sdk/issues/54)) ([d8e4c6f](https://github.com/codesandbox/codesandbox-sdk/commit/d8e4c6f54ac91aef526af7ac8207f62f084b159d)) + ## [0.6.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.3...v0.6.0) (2025-02-06) diff --git a/package-lock.json b/package-lock.json index 3a6e595..a4926c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.6.0", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.6.0", + "version": "0.6.1", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.js" diff --git a/package.json b/package.json index 5060d08..5556885 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.6.0", + "version": "0.6.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 528022e1e6beabd7e91ea755e544bffec02f265e Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 10 Feb 2025 13:43:55 -0500 Subject: [PATCH 045/241] fix: CommonJS build requires .cjs extension for ESM package (#56) --- esbuild.cjs | 11 +++++++---- package-lock.json | 2 +- package.json | 11 ++++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index 396810f..ea75e46 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -24,7 +24,8 @@ Promise.all([ entryPoints: ["src/browser.ts"], bundle: true, format: "cjs", - outdir: "dist/cjs", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/browser.cjs", platform: "browser", plugins: [browserifyPlugin], }), @@ -47,7 +48,8 @@ Promise.all([ bundle: true, format: "cjs", platform: "node", - outdir: "dist/cjs", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/index.cjs", plugins: [browserifyPlugin], }), @@ -64,7 +66,7 @@ import { createRequire as topLevelCreateRequire } from 'module'; const require = topLevelCreateRequire(import.meta.url); `.trim(), }, - outdir: "dist/esm", + outfile: "dist/esm/index.js", plugins: [browserifyPlugin], }), @@ -72,7 +74,8 @@ const require = topLevelCreateRequire(import.meta.url); // CommonJS build esbuild.build({ entryPoints: ["src/index.ts"], - outfile: "dist/cjs/index.edge.js", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/index.edge.cjs", bundle: true, format: "cjs", platform: "browser", diff --git a/package-lock.json b/package-lock.json index a4926c9..092861f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.6.1", "license": "MIT", "bin": { - "csb": "dist/bin/codesandbox.js" + "csb": "dist/bin/codesandbox.cjs" }, "devDependencies": { "@codesandbox/pitcher-client": "1.1.5", diff --git a/package.json b/package.json index 5556885..1771bf0 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "types": "./dist/esm/index.d.ts", "edge-light": { "import": "./dist/esm/index.edge.js", - "require": "./dist/cjs/index.edge.js", - "default": "./dist/cjs/index.edge.js" + "require": "./dist/cjs/index.edge.cjs", + "default": "./dist/cjs/index.edge.cjs" }, "worker": { "import": "./dist/esm/index.edge.js", @@ -39,13 +39,14 @@ "default": "./dist/cjs/index.edge.js" }, "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js", - "default": "./dist/cjs/index.js" + "require": "./dist/cjs/index.cjs", + "default": "./dist/cjs/index.cjs" }, "./browser": { "types": "./dist/esm/browser.d.ts", "import": "./dist/esm/browser.js", - "require": "./dist/cjs/browser.js" + "require": "./dist/cjs/browser.cjs", + "default": "./dist/cjs/browser.cjs" } }, "types": "./dist/esm/index.d.ts", From be0138afe02828139ca39001b21ea1f1a11c6fbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:05:54 +0100 Subject: [PATCH 046/241] chore(deps-dev): bump esbuild from 0.24.0 to 0.25.0 (#58) Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.0 to 0.25.0. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 471 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 441 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 092861f..0372253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "buffer-browserify": "^0.2.5", "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", - "esbuild": "^0.24.0", + "esbuild": "^0.25.0", "ignore": "^6.0.2", "isbinaryfile": "^5.0.4", "ora": "7.0.1", @@ -233,10 +233,78 @@ "dev": true, "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], @@ -250,6 +318,346 @@ "node": ">=18" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@hey-api/client-fetch": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.7.3.tgz", @@ -1600,9 +2008,9 @@ } }, "node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1613,30 +2021,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/escalade": { diff --git a/package.json b/package.json index 1771bf0..b7af2cc 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "buffer-browserify": "^0.2.5", "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", - "esbuild": "^0.24.0", + "esbuild": "^0.25.0", "ignore": "^6.0.2", "isbinaryfile": "^5.0.4", "ora": "7.0.1", From 20a2d4852ab7a441a93e7c6bce7cb681e66ac8f2 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Mon, 10 Feb 2025 20:06:40 +0100 Subject: [PATCH 047/241] chore(main): release 0.6.2 (#57) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561748b..09669cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.6.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.1...v0.6.2) (2025-02-10) + + +### Bug Fixes + +* CommonJS build requires .cjs extension for ESM package ([#56](https://github.com/codesandbox/codesandbox-sdk/issues/56)) ([528022e](https://github.com/codesandbox/codesandbox-sdk/commit/528022e1e6beabd7e91ea755e544bffec02f265e)) + ## [0.6.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.0...v0.6.1) (2025-02-08) diff --git a/package-lock.json b/package-lock.json index 0372253..218a981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.6.1", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.6.1", + "version": "0.6.2", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index b7af2cc..d5f1a03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.6.1", + "version": "0.6.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 00e8c201b4dcd55f9ce15fc3bd7db09b7c88a103 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Sat, 15 Feb 2025 02:59:26 +0100 Subject: [PATCH 048/241] feat: add support for passing name and path to cli (#60) --- openapi.json | 370 ++++++++++++++++++-------------------- src/bin/commands/build.ts | 102 ++++++++--- src/client/sdk.gen.ts | 8 +- src/client/types.gen.ts | 107 +++++++---- src/sandbox-client.ts | 10 ++ 5 files changed, 337 insertions(+), 260 deletions(-) diff --git a/openapi.json b/openapi.json index 5243ece..b0be77b 100644 --- a/openapi.json +++ b/openapi.json @@ -38,15 +38,13 @@ "Response": { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -54,6 +52,28 @@ "title": "Response", "type": "object" }, + "Sandbox": { + "properties": { + "created_at": { "format": "date-time", "type": "string" }, + "description": { "nullable": true, "type": "string" }, + "id": { "type": "string" }, + "is_frozen": { "type": "boolean" }, + "privacy": { "type": "integer" }, + "tags": { "items": { "type": "string" }, "type": "array" }, + "title": { "nullable": true, "type": "string" }, + "updated_at": { "format": "date-time", "type": "string" } + }, + "required": [ + "id", + "privacy", + "is_frozen", + "created_at", + "updated_at", + "tags" + ], + "title": "Sandbox", + "type": "object" + }, "SandboxCreateRequest": { "properties": { "description": { @@ -144,15 +164,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -175,7 +193,8 @@ "type": "object" } ], - "title": "SandboxCreateResponse" + "title": "SandboxCreateResponse", + "type": "object" }, "SandboxForkRequest": { "properties": { @@ -253,15 +272,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -315,22 +332,21 @@ "type": "object" } ], - "title": "SandboxForkResponse" + "title": "SandboxForkResponse", + "type": "object" }, "SandboxGetResponse": { "allOf": [ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -340,47 +356,26 @@ }, { "properties": { - "data": { - "properties": { - "created_at": { "format": "date-time", "type": "string" }, - "description": { "nullable": true, "type": "string" }, - "id": { "type": "string" }, - "is_frozen": { "type": "boolean" }, - "privacy": { "type": "integer" }, - "tags": { "items": { "type": "string" }, "type": "array" }, - "title": { "nullable": true, "type": "string" }, - "updated_at": { "format": "date-time", "type": "string" } - }, - "required": [ - "id", - "privacy", - "is_frozen", - "created_at", - "updated_at", - "tags" - ], - "type": "object" - } + "data": { "$ref": "#/components/schemas/Sandbox" } }, "type": "object" } ], - "title": "SandboxGetResponse" + "title": "SandboxGetResponse", + "type": "object" }, "SandboxListResponse": { "allOf": [ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -406,36 +401,7 @@ "type": "object" }, "sandboxes": { - "items": { - "properties": { - "created_at": { - "format": "date-time", - "type": "string" - }, - "description": { "nullable": true, "type": "string" }, - "id": { "type": "string" }, - "is_frozen": { "type": "boolean" }, - "privacy": { "type": "integer" }, - "tags": { - "items": { "type": "string" }, - "type": "array" - }, - "title": { "nullable": true, "type": "string" }, - "updated_at": { - "format": "date-time", - "type": "string" - } - }, - "required": [ - "id", - "privacy", - "is_frozen", - "created_at", - "updated_at", - "tags" - ], - "type": "object" - }, + "items": { "$ref": "#/components/schemas/Sandbox" }, "type": "array" } }, @@ -446,7 +412,8 @@ "type": "object" } ], - "title": "SandboxListResponse" + "title": "SandboxListResponse", + "type": "object" }, "TokenCreateRequest": { "properties": { @@ -488,15 +455,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -529,7 +494,8 @@ "type": "object" } ], - "title": "TokenCreateResponse" + "title": "TokenCreateResponse", + "type": "object" }, "TokenUpdateRequest": { "description": "Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value.", @@ -573,15 +539,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -610,7 +574,8 @@ "type": "object" } ], - "title": "TokenUpdateResponse" + "title": "TokenUpdateResponse", + "type": "object" }, "VMCreateSessionRequest": { "properties": { @@ -636,15 +601,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -696,7 +659,8 @@ "type": "object" } ], - "title": "VMCreateSessionResponse" + "title": "VMCreateSessionResponse", + "type": "object" }, "VMHibernateRequest": { "properties": {}, "title": "VMHibernateRequest" }, "VMHibernateResponse": { @@ -704,15 +668,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -725,7 +687,8 @@ "type": "object" } ], - "title": "VMHibernateResponse" + "title": "VMHibernateResponse", + "type": "object" }, "VMShutdownRequest": { "properties": {}, "title": "VMShutdownRequest" }, "VMShutdownResponse": { @@ -733,15 +696,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -754,7 +715,8 @@ "type": "object" } ], - "title": "VMShutdownResponse" + "title": "VMShutdownResponse", + "type": "object" }, "VMStartRequest": { "properties": { @@ -793,15 +755,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -844,7 +804,8 @@ "type": "object" } ], - "title": "VMStartResponse" + "title": "VMStartResponse", + "type": "object" }, "VMUpdateHibernationTimeoutRequest": { "properties": { @@ -865,15 +826,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -895,7 +854,8 @@ "type": "object" } ], - "title": "VMUpdateHibernationTimeoutResponse" + "title": "VMUpdateHibernationTimeoutResponse", + "type": "object" }, "VMUpdateSpecsRequest": { "properties": { @@ -922,15 +882,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -952,7 +910,8 @@ "type": "object" } ], - "title": "VMUpdateSpecsResponse" + "title": "VMUpdateSpecsResponse", + "type": "object" }, "WorkspaceCreateRequest": { "properties": { @@ -971,15 +930,13 @@ { "properties": { "errors": { - "items": [ - { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - } - ], + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, "type": "array" }, "success": { "type": "boolean" } @@ -1001,7 +958,8 @@ "type": "object" } ], - "title": "WorkspaceCreateResponse" + "title": "WorkspaceCreateResponse", + "type": "object" } }, "securitySchemes": { @@ -1070,7 +1028,16 @@ "callbacks": {}, "description": "Create a new API token for a workspace that is part of the current organization.\n", "operationId": "token/create", - "parameters": [], + "parameters": [ + { + "description": "ID of the workspace to create the token in", + "example": "ws_123abcdef", + "in": "path", + "name": "team_id", + "required": true, + "schema": { "type": "string" } + } + ], "requestBody": { "content": { "application/json": { @@ -1100,7 +1067,24 @@ "callbacks": {}, "description": "Update an API token for a workspace that is part of the current organization.\n", "operationId": "token/update", - "parameters": [], + "parameters": [ + { + "description": "ID of the workspace the token belongs to", + "example": "ws_123abcdef", + "in": "path", + "name": "team_id", + "required": true, + "schema": { "type": "string" } + }, + { + "description": "ID of token to update", + "example": "tok_123abcdef", + "in": "path", + "name": "token_id", + "required": true, + "schema": { "type": "string" } + } + ], "requestBody": { "content": { "application/json": { @@ -1586,6 +1570,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], "tags": [] } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 71fb9e5..e019b73 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -7,26 +7,29 @@ import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import type { SetupProgress } from "../../"; +import { SetupProgress, VMTier } from "../../"; import { CodeSandbox, PortInfo } from "../../"; -import { sandboxList, sandboxCreate } from "../../client"; +import { sandboxCreate, sandboxFork, VmUpdateSpecsRequest } from "../../client"; import { handleResponse } from "../../utils/handle-response"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; export type BuildCommandArgs = { - path: string; + directory: string; + name?: string; + path?: string; ipCountry?: string; fromSandbox?: string; skipFiles?: boolean; cluster?: string; + vmTier?: VmUpdateSpecsRequest["tier"]; }; export const buildCommand: yargs.CommandModule< Record, BuildCommandArgs > = { - command: "build ", + command: "build ", describe: "Build an efficient memory snapshot from a directory. This snapshot can be used to create sandboxes quickly.", builder: (yargs: yargs.Argv) => @@ -48,11 +51,27 @@ export const buildCommand: yargs.CommandModule< describe: "Cluster to create the sandbox in", type: "string", }) - .positional("path", { - describe: "Path to the project", + .option("name", { + describe: "Name for the resulting sandbox that will serve as snapshot", + type: "string", + }) + .option("path", { + describe: + "Which folder (in the dashboard) the sandbox will be created in", + default: "SDK-Templates", + type: "string", + }) + .option("vm-tier", { + describe: "Base specs to use for the template sandbox", + type: "string", + choices: VMTier.All.map((t) => t.name), + }) + .positional("directory", { + describe: "Path to the project that we'll create a snapshot from", type: "string", demandOption: "Path to the project is required", }), + handler: async (argv) => { const API_KEY = getApiKey(); const apiClient: Client = createClient( @@ -84,29 +103,26 @@ export const buildCommand: yargs.CommandModule< }; const { sdk, cluster } = getSdk(argv.cluster); - const { hash, files: filePaths } = await hashDirectory(argv.path); + const { hash, files: filePaths } = await hashDirectory(argv.directory); spinner.succeed(`Indexed ${filePaths.length} files`); const shortHash = hash.slice(0, 6); const tag = `sha:${shortHash}-${cluster || ""}`; spinner.start(`Creating or updating sandbox...`); - const { alreadyExists, sandboxId, filesIncluded } = argv.fromSandbox - ? { - alreadyExists: true, - filesIncluded: false, - sandboxId: argv.fromSandbox, - } - : await createSandbox(apiClient, tag, filePaths, argv.path); - - if (alreadyExists && !argv.fromSandbox) { - spinner.succeed("Sandbox snapshot has been created before:"); - // eslint-disable-next-line no-console - console.log(sandboxId); - return; - } + const { sandboxId, filesIncluded } = await createSandbox( + apiClient, + tag, + filePaths, + argv.directory, + argv.fromSandbox, + argv.path, + argv.name + ); if (argv.fromSandbox) { - spinner.succeed(`Sandbox reused: ${sandboxId}`); + spinner.succeed( + `Created sandbox from template (${argv.fromSandbox}): ${sandboxId}` + ); } else { spinner.succeed(`Sandbox created: ${sandboxId}`); } @@ -119,6 +135,7 @@ export const buildCommand: yargs.CommandModule< const sandbox = await sdk.sandbox.open(sandboxId, { ipcountry: argv.ipCountry, + vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); spinner.succeed("Sandbox opened"); @@ -128,7 +145,7 @@ export const buildCommand: yargs.CommandModule< for (const filePath of filePaths) { i++; spinner.start(`Writing file ${i} of ${filePaths.length}...`); - const fullPath = path.join(argv.path, filePath); + const fullPath = path.join(argv.directory, filePath); const content = await fs.readFile(fullPath); const dirname = path.dirname(filePath); await sandbox.fs.mkdir(dirname, true); @@ -298,23 +315,55 @@ async function createSandbox( apiClient: Client, shaTag: string, filePaths: string[], - rootPath: string + rootPath: string, + fromSandbox?: string, + collectionPath?: string, + name?: string ): Promise<{ - alreadyExists: boolean; sandboxId: string; filesIncluded: boolean; }> { // Include the files in the sandbox if there are no binary files and there are 30 or less files const files = await getFiles(filePaths, rootPath); + const sanitizedCollectionPath = collectionPath + ? collectionPath.startsWith("/") + ? collectionPath + : `/${collectionPath}` + : "/SDK-Templates"; + + if (fromSandbox) { + const sandbox = handleResponse( + await sandboxFork({ + client: apiClient, + path: { + id: fromSandbox, + }, + body: { + title: name, + privacy: 1, + tags: ["sdk", shaTag], + path: sanitizedCollectionPath, + }, + }), + "Failed to fork sandbox" + ); + + return { + sandboxId: sandbox.id, + filesIncluded: false, + }; + } + const sandbox = handleResponse( await sandboxCreate({ client: apiClient, body: { + title: name, files, privacy: 1, tags: ["sdk", shaTag], - path: "/SDK-Templates", + path: sanitizedCollectionPath, runtime: "vm", is_frozen: true, }, @@ -323,7 +372,6 @@ async function createSandbox( ); return { - alreadyExists: false, sandboxId: sandbox.id, filesIncluded: Object.keys(files).length > 0, }; diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index 58d7a67..72a2b4d 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -50,8 +50,8 @@ export const workspaceCreate = (options?: * Create a new API token for a workspace that is part of the current organization. * */ -export const tokenCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ +export const tokenCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ security: [ { scheme: 'bearer', @@ -72,8 +72,8 @@ export const tokenCreate = (options?: Opti * Update an API token for a workspace that is part of the current organization. * */ -export const tokenUpdate = (options?: Options) => { - return (options?.client ?? _heyApiClient).patch({ +export const tokenUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ security: [ { scheme: 'bearer', diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 2d288ce..1d63ab2 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -23,10 +23,23 @@ export type MetaInformation = { }; export type Response = { - errors?: Array; + errors?: Array; success?: boolean; }; +export type Sandbox = { + created_at: string; + description?: string | null; + id: string; + is_frozen: boolean; + privacy: number; + tags: Array; + title?: string | null; + updated_at: string; +}; + export type SandboxCreateRequest = { /** * Optional text description of the sandbox. Defaults to no description. @@ -96,7 +109,9 @@ export type SandboxCreateRequest = { }; export type SandboxCreateResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -159,7 +174,9 @@ export type SandboxForkRequest = { }; export type SandboxForkResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -186,23 +203,18 @@ export type SandboxForkResponse = { }; export type SandboxGetResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { - data?: { - created_at: string; - description?: string | null; - id: string; - is_frozen: boolean; - privacy: number; - tags: Array; - title?: string | null; - updated_at: string; - }; + data?: Sandbox; }; export type SandboxListResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -214,16 +226,7 @@ export type SandboxListResponse = { next_page: number | null; total_records: number; }; - sandboxes: Array<{ - created_at: string; - description?: string | null; - id: string; - is_frozen: boolean; - privacy: number; - tags: Array; - title?: string | null; - updated_at: string; - }>; + sandboxes: Array; }; }; @@ -247,7 +250,9 @@ export type TokenCreateRequest = { }; export type TokenCreateResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -283,7 +288,9 @@ export type TokenUpdateRequest = { }; export type TokenUpdateResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -307,7 +314,9 @@ export type VmCreateSessionRequest = { }; export type VmCreateSessionResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -345,7 +354,9 @@ export type VmHibernateRequest = { }; export type VmHibernateResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -358,7 +369,9 @@ export type VmShutdownRequest = { }; export type VmShutdownResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -390,7 +403,9 @@ export type VmStartRequest = { }; export type VmStartResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -419,7 +434,9 @@ export type VmUpdateHibernationTimeoutRequest = { }; export type VmUpdateHibernationTimeoutResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -439,7 +456,9 @@ export type VmUpdateSpecsRequest = { }; export type VmUpdateSpecsResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -456,7 +475,9 @@ export type WorkspaceCreateRequest = { }; export type WorkspaceCreateResponse = { - errors?: Array; + errors?: Array; success?: boolean; } & { data?: { @@ -505,7 +526,12 @@ export type TokenCreateData = { * Token Create Request */ body?: TokenCreateRequest; - path?: never; + path: { + /** + * ID of the workspace to create the token in + */ + team_id: string; + }; query?: never; url: '/org/workspace/{team_id}/tokens'; }; @@ -524,7 +550,16 @@ export type TokenUpdateData = { * Token Update Request */ body?: TokenUpdateRequest; - path?: never; + path: { + /** + * ID of the workspace the token belongs to + */ + team_id: string; + /** + * ID of token to update + */ + token_id: string; + }; query?: never; url: '/org/workspace/{team_id}/tokens/{token_id}'; }; diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 097a903..33d447b 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -154,6 +154,16 @@ export class VMTier { /** 64 CPU, 128GiB RAM */ public static readonly XLarge = new VMTier("XLarge", 64, 128, 50); + public static readonly All = [ + VMTier.Pico, + VMTier.Nano, + VMTier.Micro, + VMTier.Small, + VMTier.Medium, + VMTier.Large, + VMTier.XLarge, + ]; + private constructor( public readonly name: VmUpdateSpecsRequest["tier"], public readonly cpuCores: number, From eed86b15b236418196582a1357515390e468870c Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Sat, 15 Feb 2025 03:00:01 +0100 Subject: [PATCH 049/241] chore(main): release 0.7.0 (#61) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09669cc..f8abe63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.7.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.2...v0.7.0) (2025-02-15) + + +### Features + +* add support for passing name and path to cli ([#60](https://github.com/codesandbox/codesandbox-sdk/issues/60)) ([00e8c20](https://github.com/codesandbox/codesandbox-sdk/commit/00e8c201b4dcd55f9ce15fc3bd7db09b7c88a103)) + ## [0.6.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.1...v0.6.2) (2025-02-10) diff --git a/package-lock.json b/package-lock.json index 218a981..6e7efa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.6.2", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.6.2", + "version": "0.7.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index d5f1a03..d3d5481 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.6.2", + "version": "0.7.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 993e50981f907f3a2ccf7421a2fd8e4aba96e9cf Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 18 Feb 2025 20:39:24 +0100 Subject: [PATCH 050/241] feat: support for private previews and preview tokens (#63) --- openapi.json | 2075 +++++++++++++++----- src/bin/commands/sandbox/index.ts | 117 +- src/bin/commands/sandbox/preview-tokens.ts | 168 ++ src/browser.ts | 4 +- src/client/sdk.gen.ts | 82 +- src/client/types.gen.ts | 649 +++--- src/ports.ts | 34 + src/preview-tokens.ts | 86 + src/sandbox-client.ts | 160 ++ src/sandbox.ts | 21 + 10 files changed, 2614 insertions(+), 782 deletions(-) create mode 100644 src/bin/commands/sandbox/preview-tokens.ts create mode 100644 src/preview-tokens.ts diff --git a/openapi.json b/openapi.json index b0be77b..dc15321 100644 --- a/openapi.json +++ b/openapi.json @@ -2,178 +2,298 @@ "components": { "responses": {}, "schemas": { - "Error": { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - }, - "MetaInformation": { - "properties": { - "api": { - "description": "Meta information about the CodeSandbox API", - "properties": { - "latest_version": { "type": "string" }, - "name": { "type": "string" } - }, - "required": ["name", "latest_version"], - "type": "object" - }, - "auth": { - "description": "Meta information about the current authentication context", - "properties": { - "scopes": { "items": { "type": "string" }, "type": "array" }, - "team": { "format": "uuid", "nullable": true, "type": "string" }, - "version": { "type": "string" } - }, - "required": ["scopes", "team", "version"], - "type": "object" - } - }, - "required": ["api"], - "title": "MetaInformation", - "type": "object" - }, "Response": { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" }, - "Sandbox": { + "PreviewTokenListResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "sandbox_id": { + "type": "string" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/PreviewToken" + }, + "type": "array" + } + }, + "required": [ + "tokens", + "sandbox_id" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "PreviewTokenListResponse", + "type": "object" + }, + "VMUpdateHibernationTimeoutRequest": { "properties": { - "created_at": { "format": "date-time", "type": "string" }, - "description": { "nullable": true, "type": "string" }, - "id": { "type": "string" }, - "is_frozen": { "type": "boolean" }, - "privacy": { "type": "integer" }, - "tags": { "items": { "type": "string" }, "type": "array" }, - "title": { "nullable": true, "type": "string" }, - "updated_at": { "format": "date-time", "type": "string" } + "hibernation_timeout_seconds": { + "description": "The new hibernation timeout in seconds.\n\nMust be greater than 0 and less than or equal to 86400 (24 hours).\n", + "example": 300, + "maximum": 86400, + "minimum": 1, + "type": "integer" + } }, "required": [ - "id", - "privacy", - "is_frozen", - "created_at", - "updated_at", - "tags" + "hibernation_timeout_seconds" ], - "title": "Sandbox", + "title": "VMUpdateHibernationTimeoutRequest", "type": "object" }, - "SandboxCreateRequest": { + "PreviewToken": { "properties": { - "description": { - "description": "Optional text description of the sandbox. Defaults to no description.", + "expires_at": { + "nullable": true, "type": "string" }, - "entry": { - "description": "Filename of the entrypoint of the sandbox.", + "last_used_at": { + "nullable": true, "type": "string" }, - "external_resources": { - "description": "Array of strings with external resources to load.", - "items": { "type": "string" }, - "type": "array" + "token_id": { + "type": "string" }, - "files": { - "additionalProperties": { - "properties": { - "binary_content": { - "description": "If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`.", - "type": "string" - }, - "code": { - "description": "If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`.", - "type": "string" + "token_prefix": { + "type": "string" + } + }, + "required": [ + "expires_at", + "last_used_at", + "token_id", + "token_prefix" + ], + "title": "PreviewToken", + "type": "object" + }, + "VMShutdownResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" }, - "is_binary": { - "default": false, - "description": "Whether the file contains binary contents.", - "type": "boolean" - } + "type": "array" }, - "type": "object" + "success": { + "type": "boolean" + } }, - "description": "Map of `path => file` where each file is a map.", + "title": "Response", "type": "object" }, - "is_frozen": { - "default": false, - "description": "Whether changes to the sandbox are disallowed. Defaults to `false`.", - "type": "boolean" + { + "properties": { + "data": { + "properties": {}, + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMShutdownResponse", + "type": "object" + }, + "PreviewTokenRevokeAllResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" }, - "npm_dependencies": { - "additionalProperties": { "type": "string" }, - "description": "Map of dependencies and their version specifications.", + { + "properties": { + "data": { + "properties": {}, + "type": "object" + } + }, "type": "object" + } + ], + "title": "PreviewTokenRevokeAllResponse", + "type": "object" + }, + "Sandbox": { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" }, - "path": { - "default": "/", - "description": "Path to the collection where the new sandbox should be stored. Defaults to \"/\". If no collection exists at the given path, it will be created.", + "description": { + "nullable": true, "type": "string" }, + "id": { + "type": "string" + }, + "is_frozen": { + "type": "boolean" + }, "privacy": { - "default": 0, - "description": "0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public.", - "maximum": 2, - "minimum": 0, "type": "integer" }, - "runtime": { - "default": "browser", - "description": "Runtime to use for the sandbox. Defaults to `\"browser\"`.", - "enum": ["browser", "vm"], - "type": "string" - }, "tags": { - "default": [], - "description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "type": "array" }, - "template": { - "description": "Name of the template from which the sandbox is derived (for example, `\"static\"`).", + "title": { + "nullable": true, "type": "string" }, - "title": { - "default": "", - "description": "Sandbox title. Maximum 255 characters. Defaults to no title.", - "maxLength": 255, + "updated_at": { + "format": "date-time", "type": "string" } }, - "required": ["files"], - "title": "SandboxCreateRequest" + "required": [ + "id", + "privacy", + "is_frozen", + "created_at", + "updated_at", + "tags" + ], + "title": "Sandbox", + "type": "object" }, - "SandboxCreateResponse": { + "Error": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "VMHibernateRequest": { + "properties": {}, + "title": "VMHibernateRequest" + }, + "PreviewTokenCreateRequest": { + "properties": { + "expires_at": { + "description": "UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry.", + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "title": "PreviewTokenCreateRequest", + "type": "object" + }, + "SandboxGetResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -181,19 +301,13 @@ { "properties": { "data": { - "properties": { - "alias": { "type": "string" }, - "id": { "type": "string" }, - "title": { "nullable": true, "type": "string" } - }, - "required": ["alias", "id", "title"], - "type": "object" + "$ref": "#/components/schemas/Sandbox" } }, "type": "object" } ], - "title": "SandboxCreateResponse", + "title": "SandboxGetResponse", "type": "object" }, "SandboxForkRequest": { @@ -254,7 +368,9 @@ "tags": { "default": [], "description": "Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox.", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "type": "array" }, "title": { @@ -267,21 +383,28 @@ "title": "SandboxForkRequest", "type": "object" }, - "SandboxForkResponse": { + "SandboxListResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -290,95 +413,195 @@ "properties": { "data": { "properties": { - "alias": { "type": "string" }, - "id": { "type": "string" }, - "start_response": { - "description": "VM start response. Only present when start_options were provided in the request.", - "nullable": true, + "pagination": { "properties": { - "bootup_type": { "type": "string" }, - "cluster": { "type": "string" }, - "id": { "type": "string" }, - "latest_pitcher_version": { "type": "string" }, - "pitcher_manager_version": { "type": "string" }, - "pitcher_token": { "type": "string" }, - "pitcher_url": { "type": "string" }, - "pitcher_version": { "type": "string" }, - "reconnect_token": { "type": "string" }, - "user_workspace_path": { "type": "string" }, - "workspace_path": { "type": "string" } + "current_page": { + "type": "integer" + }, + "next_page": { + "description": "The number of the next page, if any. If `null`, the current page is the last page of records.", + "nullable": true, + "type": "integer" + }, + "total_records": { + "type": "integer" + } }, "required": [ - "bootup_type", - "cluster", - "id", - "latest_pitcher_version", - "pitcher_manager_version", - "pitcher_token", - "pitcher_url", - "pitcher_version", - "reconnect_token", - "user_workspace_path", - "workspace_path" + "total_records", + "current_page", + "next_page" ], "type": "object" }, - "title": { "nullable": true, "type": "string" } + "sandboxes": { + "items": { + "$ref": "#/components/schemas/Sandbox" + }, + "type": "array" + } }, - "required": ["alias", "id", "title"], + "required": [ + "sandboxes", + "pagination" + ], "type": "object" } }, "type": "object" } ], - "title": "SandboxForkResponse", + "title": "SandboxListResponse", "type": "object" }, - "SandboxGetResponse": { + "MetaInformation": { + "properties": { + "api": { + "description": "Meta information about the CodeSandbox API", + "properties": { + "latest_version": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "latest_version" + ], + "type": "object" + }, + "auth": { + "description": "Meta information about the current authentication context", + "properties": { + "scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "team": { + "format": "uuid", + "nullable": true, + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "scopes", + "team", + "version" + ], + "type": "object" + } + }, + "required": [ + "api" + ], + "title": "MetaInformation", + "type": "object" + }, + "TokenUpdateRequest": { + "description": "Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value.", + "properties": { + "default_version": { + "description": "API Version to use, formatted as YYYY-MM-DD", + "format": "date", + "nullable": true, + "type": "string" + }, + "description": { + "description": "Description of this token, for instance where it will be used.", + "maxLength": 255, + "nullable": true, + "type": "string" + }, + "expires_at": { + "description": "Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire.", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "scopes": { + "description": "Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list.", + "items": { + "enum": [ + "sandbox_create", + "sandbox_edit_code", + "sandbox_read", + "vm_manage" + ] + }, + "type": "array" + } + }, + "title": "TokenUpdateRequest", + "type": "object" + }, + "VMHibernateResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" }, { "properties": { - "data": { "$ref": "#/components/schemas/Sandbox" } + "data": { + "properties": {}, + "type": "object" + } }, "type": "object" } ], - "title": "SandboxGetResponse", + "title": "VMHibernateResponse", "type": "object" }, - "SandboxListResponse": { + "PreviewTokenUpdateResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -387,84 +610,48 @@ "properties": { "data": { "properties": { - "pagination": { - "properties": { - "current_page": { "type": "integer" }, - "next_page": { - "description": "The number of the next page, if any. If `null`, the current page is the last page of records.", - "nullable": true, - "type": "integer" - }, - "total_records": { "type": "integer" } - }, - "required": ["total_records", "current_page", "next_page"], - "type": "object" + "sandbox_id": { + "type": "string" }, - "sandboxes": { - "items": { "$ref": "#/components/schemas/Sandbox" }, - "type": "array" + "token": { + "$ref": "#/components/schemas/PreviewToken" } }, - "required": ["sandboxes", "pagination"], + "required": [ + "sandbox_id", + "token" + ], "type": "object" } }, "type": "object" } ], - "title": "SandboxListResponse", - "type": "object" - }, - "TokenCreateRequest": { - "properties": { - "default_version": { - "description": "API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation.", - "format": "date", - "nullable": true, - "type": "string" - }, - "description": { - "description": "Description of this token, for instance where it will be used.", - "maxLength": 255, - "type": "string" - }, - "expires_at": { - "description": "UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry.", - "format": "date-time", - "nullable": true, - "type": "string" - }, - "scopes": { - "description": "Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list.", - "items": { - "enum": [ - "sandbox_create", - "sandbox_edit_code", - "sandbox_read", - "vm_manage" - ] - }, - "type": "array" - } - }, - "title": "TokenCreateRequest", + "title": "PreviewTokenUpdateResponse", "type": "object" }, - "TokenCreateResponse": { + "SandboxCreateResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -473,20 +660,21 @@ "properties": { "data": { "properties": { - "description": { "nullable": true, "type": "string" }, - "expires_at": { "nullable": true, "type": "string" }, - "scopes": { "items": { "type": "string" }, "type": "array" }, - "team_id": { "type": "string" }, - "token": { "type": "string" }, - "token_id": { "type": "string" } + "alias": { + "type": "string" + }, + "id": { + "type": "string" + }, + "title": { + "nullable": true, + "type": "string" + } }, "required": [ - "description", - "expires_at", - "scopes", - "team_id", - "token", - "token_id" + "alias", + "id", + "title" ], "type": "object" } @@ -494,14 +682,467 @@ "type": "object" } ], - "title": "TokenCreateResponse", + "title": "SandboxCreateResponse", "type": "object" }, - "TokenUpdateRequest": { - "description": "Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value.", + "VMStartRequest": { + "properties": { + "hibernation_timeout_seconds": { + "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", + "example": 300, + "maximum": 86400, + "minimum": 1, + "type": "integer" + }, + "ipcountry": { + "description": "This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster).", + "example": "NL", + "pattern": "^[A-Z]{2}$", + "type": "string" + }, + "tier": { + "description": "Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace.\n\nYou can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error.\n\nNot all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs.\n", + "enum": [ + "Pico", + "Nano", + "Micro", + "Small", + "Medium", + "Large", + "XLarge" + ], + "example": "Micro" + } + }, + "title": "VMStartRequest", + "type": "object" + }, + "PreviewTokenUpdateRequest": { + "properties": { + "expires_at": { + "description": "UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry.", + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "title": "PreviewTokenUpdateRequest", + "type": "object" + }, + "VMShutdownRequest": { + "properties": {}, + "title": "VMShutdownRequest" + }, + "VMUpdateSpecsRequest": { + "properties": { + "tier": { + "description": "Determines which specs to update the VM with.\n\nNot all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs.\n", + "enum": [ + "Pico", + "Nano", + "Micro", + "Small", + "Medium", + "Large", + "XLarge" + ], + "example": "Micro" + } + }, + "required": [ + "tier" + ], + "title": "VMUpdateSpecsRequest", + "type": "object" + }, + "WorkspaceCreateRequest": { + "properties": { + "name": { + "description": "Name for the new workspace. Maximum length 64 characters.", + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "WorkspaceCreateRequest", + "type": "object" + }, + "VMStartResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "bootup_type": { + "type": "string" + }, + "cluster": { + "type": "string" + }, + "id": { + "type": "string" + }, + "latest_pitcher_version": { + "type": "string" + }, + "pitcher_manager_version": { + "type": "string" + }, + "pitcher_token": { + "type": "string" + }, + "pitcher_url": { + "type": "string" + }, + "pitcher_version": { + "type": "string" + }, + "reconnect_token": { + "type": "string" + }, + "user_workspace_path": { + "type": "string" + }, + "workspace_path": { + "type": "string" + } + }, + "required": [ + "bootup_type", + "cluster", + "id", + "latest_pitcher_version", + "pitcher_manager_version", + "pitcher_token", + "pitcher_url", + "pitcher_version", + "reconnect_token", + "user_workspace_path", + "workspace_path" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMStartResponse", + "type": "object" + }, + "VMUpdateSpecsResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "id": { + "type": "string" + }, + "tier": { + "type": "string" + } + }, + "required": [ + "id", + "tier" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMUpdateSpecsResponse", + "type": "object" + }, + "SandboxCreateRequest": { + "properties": { + "description": { + "description": "Optional text description of the sandbox. Defaults to no description.", + "type": "string" + }, + "entry": { + "description": "Filename of the entrypoint of the sandbox.", + "type": "string" + }, + "external_resources": { + "description": "Array of strings with external resources to load.", + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "additionalProperties": { + "properties": { + "binary_content": { + "description": "If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`.", + "type": "string" + }, + "code": { + "description": "If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`.", + "type": "string" + }, + "is_binary": { + "default": false, + "description": "Whether the file contains binary contents.", + "type": "boolean" + } + }, + "type": "object" + }, + "description": "Map of `path => file` where each file is a map.", + "type": "object" + }, + "is_frozen": { + "default": false, + "description": "Whether changes to the sandbox are disallowed. Defaults to `false`.", + "type": "boolean" + }, + "npm_dependencies": { + "additionalProperties": { + "type": "string" + }, + "description": "Map of dependencies and their version specifications.", + "type": "object" + }, + "path": { + "default": "/", + "description": "Path to the collection where the new sandbox should be stored. Defaults to \"/\". If no collection exists at the given path, it will be created.", + "type": "string" + }, + "privacy": { + "default": 0, + "description": "0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public.", + "maximum": 2, + "minimum": 0, + "type": "integer" + }, + "runtime": { + "default": "browser", + "description": "Runtime to use for the sandbox. Defaults to `\"browser\"`.", + "enum": [ + "browser", + "vm" + ], + "type": "string" + }, + "tags": { + "default": [], + "description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.", + "items": { + "type": "string" + }, + "type": "array" + }, + "template": { + "description": "Name of the template from which the sandbox is derived (for example, `\"static\"`).", + "type": "string" + }, + "title": { + "default": "", + "description": "Sandbox title. Maximum 255 characters. Defaults to no title.", + "maxLength": 255, + "type": "string" + } + }, + "required": [ + "files" + ], + "title": "SandboxCreateRequest" + }, + "TokenUpdateResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "description": { + "nullable": true, + "type": "string" + }, + "expires_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "team_id": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "required": [ + "description", + "scopes", + "team_id", + "token_id" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "TokenUpdateResponse", + "type": "object" + }, + "TokenCreateResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { + "type": "boolean" + } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "description": { + "nullable": true, + "type": "string" + }, + "expires_at": { + "nullable": true, + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "team_id": { + "type": "string" + }, + "token": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "required": [ + "description", + "expires_at", + "scopes", + "team_id", + "token", + "token_id" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "TokenCreateResponse", + "type": "object" + }, + "TokenCreateRequest": { "properties": { "default_version": { - "description": "API Version to use, formatted as YYYY-MM-DD", + "description": "API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation.", "format": "date", "nullable": true, "type": "string" @@ -509,11 +1150,10 @@ "description": { "description": "Description of this token, for instance where it will be used.", "maxLength": 255, - "nullable": true, "type": "string" }, "expires_at": { - "description": "Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire.", + "description": "UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry.", "format": "date-time", "nullable": true, "type": "string" @@ -531,57 +1171,17 @@ "type": "array" } }, - "title": "TokenUpdateRequest", - "type": "object" - }, - "TokenUpdateResponse": { - "allOf": [ - { - "properties": { - "errors": { - "items": { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - }, - "type": "array" - }, - "success": { "type": "boolean" } - }, - "title": "Response", - "type": "object" - }, - { - "properties": { - "data": { - "properties": { - "description": { "nullable": true, "type": "string" }, - "expires_at": { - "format": "date-time", - "nullable": true, - "type": "string" - }, - "scopes": { "items": { "type": "string" }, "type": "array" }, - "team_id": { "type": "string" }, - "token_id": { "type": "string" } - }, - "required": ["description", "scopes", "team_id", "token_id"], - "type": "object" - } - }, - "type": "object" - } - ], - "title": "TokenUpdateResponse", + "title": "TokenCreateRequest", "type": "object" }, "VMCreateSessionRequest": { "properties": { "permission": { "description": "Permission level for the session", - "enum": ["read", "write"], + "enum": [ + "read", + "write" + ], "example": "write", "type": "string" }, @@ -592,7 +1192,10 @@ "type": "string" } }, - "required": ["session_id", "permission"], + "required": [ + "session_id", + "permission" + ], "title": "VMCreateSessionRequest", "type": "object" }, @@ -603,14 +1206,21 @@ "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -621,7 +1231,9 @@ "properties": { "capabilities": { "description": "List of capabilities that Pitcher has", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "type": "array" }, "permissions": { @@ -662,109 +1274,28 @@ "title": "VMCreateSessionResponse", "type": "object" }, - "VMHibernateRequest": { "properties": {}, "title": "VMHibernateRequest" }, - "VMHibernateResponse": { - "allOf": [ - { - "properties": { - "errors": { - "items": { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - }, - "type": "array" - }, - "success": { "type": "boolean" } - }, - "title": "Response", - "type": "object" - }, - { - "properties": { "data": { "properties": {}, "type": "object" } }, - "type": "object" - } - ], - "title": "VMHibernateResponse", - "type": "object" - }, - "VMShutdownRequest": { "properties": {}, "title": "VMShutdownRequest" }, - "VMShutdownResponse": { - "allOf": [ - { - "properties": { - "errors": { - "items": { - "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } - ], - "title": "Error" - }, - "type": "array" - }, - "success": { "type": "boolean" } - }, - "title": "Response", - "type": "object" - }, - { - "properties": { "data": { "properties": {}, "type": "object" } }, - "type": "object" - } - ], - "title": "VMShutdownResponse", - "type": "object" - }, - "VMStartRequest": { - "properties": { - "hibernation_timeout_seconds": { - "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", - "example": 300, - "maximum": 86400, - "minimum": 1, - "type": "integer" - }, - "ipcountry": { - "description": "This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster).", - "example": "NL", - "pattern": "^[A-Z]{2}$", - "type": "string" - }, - "tier": { - "description": "Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace.\n\nYou can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error.\n\nNot all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs.\n", - "enum": [ - "Pico", - "Nano", - "Micro", - "Small", - "Medium", - "Large", - "XLarge" - ], - "example": "Micro" - } - }, - "title": "VMStartRequest", - "type": "object" - }, - "VMStartResponse": { + "WorkspaceCreateResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -773,30 +1304,16 @@ "properties": { "data": { "properties": { - "bootup_type": { "type": "string" }, - "cluster": { "type": "string" }, - "id": { "type": "string" }, - "latest_pitcher_version": { "type": "string" }, - "pitcher_manager_version": { "type": "string" }, - "pitcher_token": { "type": "string" }, - "pitcher_url": { "type": "string" }, - "pitcher_version": { "type": "string" }, - "reconnect_token": { "type": "string" }, - "user_workspace_path": { "type": "string" }, - "workspace_path": { "type": "string" } + "id": { + "type": "string" + }, + "name": { + "type": "string" + } }, "required": [ - "bootup_type", - "cluster", "id", - "latest_pitcher_version", - "pitcher_manager_version", - "pitcher_token", - "pitcher_url", - "pitcher_version", - "reconnect_token", - "user_workspace_path", - "workspace_path" + "name" ], "type": "object" } @@ -804,21 +1321,7 @@ "type": "object" } ], - "title": "VMStartResponse", - "type": "object" - }, - "VMUpdateHibernationTimeoutRequest": { - "properties": { - "hibernation_timeout_seconds": { - "description": "The new hibernation timeout in seconds.\n\nMust be greater than 0 and less than or equal to 86400 (24 hours).\n", - "example": 300, - "maximum": 86400, - "minimum": 1, - "type": "integer" - } - }, - "required": ["hibernation_timeout_seconds"], - "title": "VMUpdateHibernationTimeoutRequest", + "title": "WorkspaceCreateResponse", "type": "object" }, "VMUpdateHibernationTimeoutResponse": { @@ -828,14 +1331,21 @@ "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -844,10 +1354,17 @@ "properties": { "data": { "properties": { - "hibernation_timeout_seconds": { "type": "integer" }, - "id": { "type": "string" } + "hibernation_timeout_seconds": { + "type": "integer" + }, + "id": { + "type": "string" + } }, - "required": ["id", "hibernation_timeout_seconds"], + "required": [ + "id", + "hibernation_timeout_seconds" + ], "type": "object" } }, @@ -857,41 +1374,28 @@ "title": "VMUpdateHibernationTimeoutResponse", "type": "object" }, - "VMUpdateSpecsRequest": { - "properties": { - "tier": { - "description": "Determines which specs to update the VM with.\n\nNot all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs.\n", - "enum": [ - "Pico", - "Nano", - "Micro", - "Small", - "Medium", - "Large", - "XLarge" - ], - "example": "Micro" - } - }, - "required": ["tier"], - "title": "VMUpdateSpecsRequest", - "type": "object" - }, - "VMUpdateSpecsResponse": { + "SandboxForkResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -900,46 +1404,106 @@ "properties": { "data": { "properties": { - "id": { "type": "string" }, - "tier": { "type": "string" } + "alias": { + "type": "string" + }, + "id": { + "type": "string" + }, + "start_response": { + "description": "VM start response. Only present when start_options were provided in the request.", + "nullable": true, + "properties": { + "bootup_type": { + "type": "string" + }, + "cluster": { + "type": "string" + }, + "id": { + "type": "string" + }, + "latest_pitcher_version": { + "type": "string" + }, + "pitcher_manager_version": { + "type": "string" + }, + "pitcher_token": { + "type": "string" + }, + "pitcher_url": { + "type": "string" + }, + "pitcher_version": { + "type": "string" + }, + "reconnect_token": { + "type": "string" + }, + "user_workspace_path": { + "type": "string" + }, + "workspace_path": { + "type": "string" + } + }, + "required": [ + "bootup_type", + "cluster", + "id", + "latest_pitcher_version", + "pitcher_manager_version", + "pitcher_token", + "pitcher_url", + "pitcher_version", + "reconnect_token", + "user_workspace_path", + "workspace_path" + ], + "type": "object" + }, + "title": { + "nullable": true, + "type": "string" + } }, - "required": ["id", "tier"], + "required": [ + "alias", + "id", + "title" + ], "type": "object" } }, "type": "object" } ], - "title": "VMUpdateSpecsResponse", - "type": "object" - }, - "WorkspaceCreateRequest": { - "properties": { - "name": { - "description": "Name for the new workspace. Maximum length 64 characters.", - "maxLength": 64, - "type": "string" - } - }, - "required": ["name"], - "title": "WorkspaceCreateRequest", + "title": "SandboxForkResponse", "type": "object" }, - "WorkspaceCreateResponse": { + "PreviewTokenCreateResponse": { "allOf": [ { "properties": { "errors": { "items": { "oneOf": [ - { "type": "string" }, - { "additionalProperties": true, "type": "object" } + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + } ], "title": "Error" }, "type": "array" }, - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } }, "title": "Response", "type": "object" @@ -948,25 +1512,77 @@ "properties": { "data": { "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } + "sandbox_id": { + "type": "string" + }, + "token": { + "allOf": [ + { + "properties": { + "expires_at": { + "nullable": true, + "type": "string" + }, + "last_used_at": { + "nullable": true, + "type": "string" + }, + "token_id": { + "type": "string" + }, + "token_prefix": { + "type": "string" + } + }, + "required": [ + "expires_at", + "last_used_at", + "token_id", + "token_prefix" + ], + "title": "PreviewToken", + "type": "object" + }, + { + "properties": { + "token": { + "type": "string" + } + }, + "required": [ + "token" + ], + "type": "object" + } + ], + "type": "object" + } }, - "required": ["id", "name"], + "required": [ + "sandbox_id", + "token" + ], "type": "object" } }, "type": "object" } ], - "title": "WorkspaceCreateResponse", + "title": "PreviewTokenCreateResponse", "type": "object" } }, "securitySchemes": { - "authorization": { "scheme": "bearer", "type": "http" } + "authorization": { + "scheme": "bearer", + "type": "http" + } } }, - "info": { "title": "CodeSandbox API", "version": "2023-07-01" }, + "info": { + "title": "CodeSandbox API", + "version": "2023-07-01" + }, "openapi": "3.0.0", "paths": { "/meta/info": { @@ -978,15 +1594,21 @@ "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/MetaInformation" } + "schema": { + "$ref": "#/components/schemas/MetaInformation" + } } }, "description": "Meta Info Response" } }, - "security": [{}], + "security": [ + {} + ], "summary": "Metadata about the API", - "tags": ["meta"] + "tags": [ + "meta" + ] } }, "/org/workspace": { @@ -1018,7 +1640,13 @@ "description": "Workspace Create Response" } }, - "security": [{ "authorization": ["workspace:create"] }], + "security": [ + { + "authorization": [ + "workspace:create" + ] + } + ], "summary": "Create a Workspace", "tags": [] } @@ -1035,13 +1663,17 @@ "in": "path", "name": "team_id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/TokenCreateRequest" } + "schema": { + "$ref": "#/components/schemas/TokenCreateRequest" + } } }, "description": "Token Create Request", @@ -1051,13 +1683,21 @@ "201": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/TokenCreateResponse" } + "schema": { + "$ref": "#/components/schemas/TokenCreateResponse" + } } }, "description": "Token Create Response" } }, - "security": [{ "authorization": ["token:manage"] }], + "security": [ + { + "authorization": [ + "token:manage" + ] + } + ], "summary": "Create an API Token", "tags": [] } @@ -1074,7 +1714,9 @@ "in": "path", "name": "team_id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } }, { "description": "ID of token to update", @@ -1082,13 +1724,17 @@ "in": "path", "name": "token_id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/TokenUpdateRequest" } + "schema": { + "$ref": "#/components/schemas/TokenUpdateRequest" + } } }, "description": "Token Update Request", @@ -1098,13 +1744,21 @@ "201": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/TokenUpdateResponse" } + "schema": { + "$ref": "#/components/schemas/TokenUpdateResponse" + } } }, "description": "Token Update Response" } }, - "security": [{ "authorization": ["token:manage"] }], + "security": [ + { + "authorization": [ + "token:manage" + ] + } + ], "summary": "Update an API Token", "tags": [] } @@ -1121,7 +1775,9 @@ "in": "query", "name": "tags", "required": false, - "schema": { "type": "string" } + "schema": { + "type": "string" + } }, { "description": "Field to order results by", @@ -1131,7 +1787,10 @@ "required": false, "schema": { "default": "updated_at", - "enum": ["inserted_at", "updated_at"] + "enum": [ + "inserted_at", + "updated_at" + ] } }, { @@ -1140,7 +1799,13 @@ "in": "query", "name": "direction", "required": false, - "schema": { "default": "desc", "enum": ["asc", "desc"] } + "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ] + } }, { "description": "Maximum number of sandboxes to return in a single response", @@ -1159,29 +1824,47 @@ "in": "query", "name": "page", "required": false, - "schema": { "default": 1, "minimum": 1, "type": "integer" } + "schema": { + "default": 1, + "minimum": 1, + "type": "integer" + } }, { "description": "If true, only returns VMs for which a heartbeat was received in the last 30 seconds.", "in": "query", "name": "status", "required": false, - "schema": { "enum": ["running"] } + "schema": { + "enum": [ + "running" + ] + } } ], "responses": { "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SandboxListResponse" } + "schema": { + "$ref": "#/components/schemas/SandboxListResponse" + } } }, "description": "Sandbox List Response" } }, - "security": [{ "authorization": ["sandbox:read"] }], + "security": [ + { + "authorization": [ + "sandbox:read" + ] + } + ], "summary": "List Sandboxes", - "tags": ["sandbox"] + "tags": [ + "sandbox" + ] }, "post": { "callbacks": {}, @@ -1191,7 +1874,9 @@ "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SandboxCreateRequest" } + "schema": { + "$ref": "#/components/schemas/SandboxCreateRequest" + } } }, "description": "Sandbox Create Request", @@ -1209,9 +1894,17 @@ "description": "Sandbox Create Response" } }, - "security": [{ "authorization": ["sandbox:create"] }], + "security": [ + { + "authorization": [ + "sandbox:create" + ] + } + ], "summary": "Create a Sandbox", - "tags": ["sandbox"] + "tags": [ + "sandbox" + ] } }, "/sandbox/{id}": { @@ -1226,22 +1919,34 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "responses": { "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SandboxGetResponse" } + "schema": { + "$ref": "#/components/schemas/SandboxGetResponse" + } } }, "description": "Sandbox Get Response" } }, - "security": [{ "authorization": ["sandbox:read"] }], + "security": [ + { + "authorization": [ + "sandbox:read" + ] + } + ], "summary": "Get a Sandbox", - "tags": ["sandbox"] + "tags": [ + "sandbox" + ] } }, "/sandbox/{id}/fork": { @@ -1256,13 +1961,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SandboxForkRequest" } + "schema": { + "$ref": "#/components/schemas/SandboxForkRequest" + } } }, "description": "Sandbox Fork Request", @@ -1272,15 +1981,213 @@ "201": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SandboxForkResponse" } + "schema": { + "$ref": "#/components/schemas/SandboxForkResponse" + } } }, "description": "Sandbox Fork Response" } }, - "security": [{ "authorization": ["sandbox:create"] }], + "security": [ + { + "authorization": [ + "sandbox:create" + ] + } + ], "summary": "Fork a Sandbox", - "tags": ["sandbox"] + "tags": [ + "sandbox" + ] + } + }, + "/sandbox/{id}/tokens": { + "delete": { + "callbacks": {}, + "description": "Immediately expires all active preview tokens associated with this sandbox\n", + "operationId": "preview_token/revoke_all", + "parameters": [ + { + "description": "Shortid of the sandbox to revoke tokens for", + "example": "1ab3c", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenRevokeAllResponse" + } + } + }, + "description": "RevokeALlPreviewTokensResponse" + } + }, + "security": [ + { + "authorization": [ + "preview_token:manage" + ] + } + ], + "summary": "Revoke preview tokens", + "tags": [] + }, + "get": { + "callbacks": {}, + "description": "List information about the preview tokens associated with the current sandbox\n", + "operationId": "preview_token/list", + "parameters": [ + { + "description": "Shortid of the sandbox to list the tokens for", + "example": "1ab3c", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenListResponse" + } + } + }, + "description": "Token List Response" + } + }, + "security": [ + { + "authorization": [ + "preview_token:manage" + ] + } + ], + "summary": "List Preview Tokens", + "tags": [] + }, + "post": { + "callbacks": {}, + "description": "Create a new Preview token that allow access to a private sandbox\n", + "operationId": "preview_token/create", + "parameters": [ + { + "description": "Shortid of the sandbox to create the token for", + "example": "1ab3c", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenCreateRequest" + } + } + }, + "description": "Token Create Request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenCreateResponse" + } + } + }, + "description": "Token Create Response" + } + }, + "security": [ + { + "authorization": [ + "preview_token:manage" + ] + } + ], + "summary": "Create a Preview Token", + "tags": [] + } + }, + "/sandbox/{id}/tokens/{token_id}": { + "patch": { + "callbacks": {}, + "description": "Update a Preview token that allow access to a private sandbox\n", + "operationId": "preview_token/update", + "parameters": [ + { + "description": "Shortid of the sandbox to create the token for", + "example": "1ab3c", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "ID of the token to update. Does not accept the token itself.", + "example": "prv_abcd12345", + "in": "path", + "name": "token_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenUpdateRequest" + } + } + }, + "description": "Token Update Request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewTokenUpdateResponse" + } + } + }, + "description": "Token Update Response" + } + }, + "security": [ + { + "authorization": [ + "preview_token:manage" + ] + } + ], + "summary": "Update a Preview Token", + "tags": [] } }, "/vm/{id}/hibernate": { @@ -1295,13 +2202,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMHibernateRequest" } + "schema": { + "$ref": "#/components/schemas/VMHibernateRequest" + } } }, "description": "VM Hibernate Request", @@ -1311,15 +2222,25 @@ "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMHibernateResponse" } + "schema": { + "$ref": "#/components/schemas/VMHibernateResponse" + } } }, "description": "VM Hibernate Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Hibernate a VM", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/hibernation_timeout": { @@ -1334,7 +2255,9 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { @@ -1360,9 +2283,17 @@ "description": "VM Update Hibernation Timeout Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Update VM Hibernation Timeout", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/sessions": { @@ -1377,7 +2308,9 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { @@ -1403,9 +2336,17 @@ "description": "VM Create Session Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Create a new session on a VM", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/shutdown": { @@ -1420,13 +2361,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMShutdownRequest" } + "schema": { + "$ref": "#/components/schemas/VMShutdownRequest" + } } }, "description": "VM Shutdown Request", @@ -1436,15 +2381,25 @@ "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMShutdownResponse" } + "schema": { + "$ref": "#/components/schemas/VMShutdownResponse" + } } }, "description": "VM Shutdown Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Shutdown a VM", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/specs": { @@ -1459,13 +2414,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } + "schema": { + "$ref": "#/components/schemas/VMUpdateSpecsRequest" + } } }, "description": "VM Update Specs Request", @@ -1483,9 +2442,17 @@ "description": "VM Update Specs Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Update VM Specs", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/start": { @@ -1500,13 +2467,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMStartRequest" } + "schema": { + "$ref": "#/components/schemas/VMStartRequest" + } } }, "description": "VM Start Request", @@ -1516,15 +2487,26 @@ "200": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMStartResponse" } + "schema": { + "$ref": "#/components/schemas/VMStartResponse" + } } }, "description": "VM Start Response" } }, - "security": [{ "authorization": ["sandbox:read", "vm:manage"] }], + "security": [ + { + "authorization": [ + "sandbox:read", + "vm:manage" + ] + } + ], "summary": "Start a VM", - "tags": ["vm"] + "tags": [ + "vm" + ] } }, "/vm/{id}/update_specs": { @@ -1539,13 +2521,17 @@ "in": "path", "name": "id", "required": true, - "schema": { "type": "string" } + "schema": { + "type": "string" + } } ], "requestBody": { "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } + "schema": { + "$ref": "#/components/schemas/VMUpdateSpecsRequest" + } } }, "description": "VM Update Specs Request", @@ -1563,13 +2549,26 @@ "description": "VM Update Specs Response" } }, - "security": [{ "authorization": ["vm:manage"] }], + "security": [ + { + "authorization": [ + "vm:manage" + ] + } + ], "summary": "Update VM Specs", - "tags": ["vm"] + "tags": [ + "vm" + ] } } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], + "servers": [ + { + "url": "https://api.codesandbox.stream", + "variables": {} + } + ], "tags": [] -} +} \ No newline at end of file diff --git a/src/bin/commands/sandbox/index.ts b/src/bin/commands/sandbox/index.ts index c762f69..0cc559d 100644 --- a/src/bin/commands/sandbox/index.ts +++ b/src/bin/commands/sandbox/index.ts @@ -3,6 +3,13 @@ import { forkSandbox } from "./fork"; import { hibernateSandbox } from "./hibernate"; import { listSandboxes } from "./list"; import { shutdownSandbox } from "./shutdown"; +import { + listPreviewTokens, + createPreviewToken, + revokeAllPreviewTokens, + revokePreviewToken, + updatePreviewToken, +} from "./preview-tokens"; const DEFAULT_LIMIT = 100; @@ -95,10 +102,11 @@ export const sandboxCommand: CommandModule = { return yargs.positional("id", { describe: "ID of the sandbox to fork", type: "string", + demandOption: true, }); }, handler: async (argv) => { - await forkSandbox(argv.id as string); + await forkSandbox(argv.id); }, }) .command({ @@ -129,6 +137,113 @@ export const sandboxCommand: CommandModule = { await shutdownSandbox(argv.id); }, }) + .command({ + command: "preview-tokens", + describe: "Manage preview tokens", + builder: (yargs) => { + return yargs + .command({ + command: "list ", + describe: "List preview tokens", + builder: (yargs) => { + return yargs.positional("id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }); + }, + handler: async (argv) => { + await listPreviewTokens(argv.id); + }, + }) + .command({ + command: "create ", + describe: "Create a preview token", + builder: (yargs) => { + return yargs + .positional("id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .option("expires-at", { + alias: "e", + describe: + "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to create a token that never expires.", + type: "string", + }); + }, + handler: async (argv) => { + await createPreviewToken(argv.id, argv["expires-at"]); + }, + }) + .command({ + command: "update ", + describe: + "Update the expiration date of a preview token, if no expiration", + builder: (yargs) => { + return yargs + .positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .positional("preview-token-id", { + describe: "ID of the preview token", + type: "string", + demandOption: true, + }) + .option("expires-at", { + alias: "e", + describe: + "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to remove the expiration date.", + type: "string", + }); + }, + handler: async (argv) => { + await updatePreviewToken( + argv["sandbox-id"], + argv["preview-token-id"], + argv["expires-at"] + ); + }, + }) + .command({ + command: "revoke ", + describe: "Revoke preview token(s)", + builder: (yargs) => { + return yargs + .positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .positional("preview-token-id", { + describe: "ID of the preview token", + type: "string", + demandOption: true, + }) + .option("all", { + alias: "a", + describe: "Revoke all preview tokens", + type: "boolean", + }); + }, + handler: async (argv) => { + if (argv.all) { + await revokeAllPreviewTokens(argv["sandbox-id"]); + } else { + await revokePreviewToken( + argv["sandbox-id"], + argv["preview-token-id"] + ); + } + }, + }) + .demandCommand(1, "Please specify a preview-tokens command"); + }, + handler: () => {}, + }) .demandCommand(1, "Please specify a sandbox command"); }, handler: () => {}, diff --git a/src/bin/commands/sandbox/preview-tokens.ts b/src/bin/commands/sandbox/preview-tokens.ts new file mode 100644 index 0000000..d234455 --- /dev/null +++ b/src/bin/commands/sandbox/preview-tokens.ts @@ -0,0 +1,168 @@ +import ora from "ora"; +import Table from "cli-table3"; +import { CodeSandbox } from "../../../"; + +function formatDate(date: Date): string { + return date.toLocaleString(); +} + +export async function listPreviewTokens(sandboxId: string) { + const sdk = new CodeSandbox(); + const spinner = ora("Fetching preview tokens...").start(); + + try { + const tokens = await sdk.sandbox.previewTokens.list(sandboxId); + spinner.stop(); + + if (tokens.length === 0) { + console.log("No preview tokens found"); + return; + } + + const table = new Table({ + head: ["ID", "PREFIX", "LAST USED", "EXPIRES"], + style: { + head: ["bold"], + border: [], + }, + chars: { + top: "", + "top-mid": "", + "top-left": "", + "top-right": "", + bottom: "", + "bottom-mid": "", + "bottom-left": "", + "bottom-right": "", + left: "", + "left-mid": "", + right: "", + "right-mid": "", + mid: "", + "mid-mid": "", + middle: " ", + }, + }); + + tokens.forEach((token) => { + table.push([ + token.tokenId, + token.tokenPrefix, + token.lastUsedAt ? formatDate(token.lastUsedAt) : "Never", + token.expiresAt ? formatDate(token.expiresAt) : "Never", + ]); + }); + + console.log(table.toString()); + } catch (error) { + spinner.fail("Failed to fetch preview tokens"); + throw error; + } +} + +export async function createPreviewToken( + sandboxId: string, + expiresAt?: string +) { + const sdk = new CodeSandbox(); + const spinner = ora("Creating preview token...").start(); + + try { + const token = await sdk.sandbox.previewTokens.create( + sandboxId, + expiresAt ? new Date(expiresAt) : null + ); + spinner.stop(); + + const table = new Table({ + head: ["TOKEN", "ID", "LAST USED", "EXPIRES"], + style: { + head: ["bold"], + border: [], + }, + chars: { + top: "", + "top-mid": "", + "top-left": "", + "top-right": "", + bottom: "", + "bottom-mid": "", + "bottom-left": "", + "bottom-right": "", + left: "", + "left-mid": "", + right: "", + "right-mid": "", + mid: "", + "mid-mid": "", + middle: " ", + }, + }); + + table.push([ + token.token, + token.tokenId, + token.lastUsedAt ? formatDate(token.lastUsedAt) : "Never", + token.expiresAt ? formatDate(token.expiresAt) : "Never", + ]); + + console.log("Preview token created successfully:"); + console.log(table.toString()); + } catch (error) { + spinner.fail("Failed to create preview token"); + throw error; + } +} + +export async function revokePreviewToken( + sandboxId: string, + previewTokenId: string +) { + const sdk = new CodeSandbox(); + const spinner = ora("Revoking preview token...").start(); + + try { + await sdk.sandbox.previewTokens.revoke(sandboxId, previewTokenId); + spinner.stop(); + console.log("Preview token revoked successfully"); + } catch (error) { + spinner.fail("Failed to revoke preview token"); + throw error; + } +} + +export async function updatePreviewToken( + sandboxId: string, + previewTokenId: string, + expiresAt?: string +) { + const sdk = new CodeSandbox(); + const spinner = ora("Updating preview token...").start(); + + try { + await sdk.sandbox.previewTokens.update( + sandboxId, + previewTokenId, + expiresAt ? new Date(expiresAt) : null + ); + spinner.stop(); + console.log("Preview token updated successfully"); + } catch (error) { + spinner.fail("Failed to update preview token"); + throw error; + } +} + +export async function revokeAllPreviewTokens(sandboxId: string) { + const sdk = new CodeSandbox(); + const spinner = ora("Revoking all preview tokens...").start(); + + try { + await sdk.sandbox.previewTokens.revokeAll(sandboxId); + spinner.stop(); + console.log("All preview tokens have been revoked"); + } catch (error) { + spinner.fail("Failed to revoke preview tokens"); + throw error; + } +} diff --git a/src/browser.ts b/src/browser.ts index bea9f8d..39887d8 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -24,8 +24,8 @@ function isStartData( * requires your CodeSandbox API token to be sent with every request. This makes it * unsafe to use from the browser, where you don't want to expose your API token. * - * With this helper function, you can create a connection to a sandbox without - * exposing your API token. + * With this helper function, you can generate a sandbox on the server, and then share a single-use + * token that can be used to create a connection to that sandbox from the browser. * * ## Example * diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index 72a2b4d..75fe33c 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -170,6 +170,86 @@ export const sandboxFork = (options: Optio }); }; +/** + * Revoke preview tokens + * Immediately expires all active preview tokens associated with this sandbox + * + */ +export const previewTokenRevokeAll = (options: Options) => { + return (options.client ?? _heyApiClient).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options + }); +}; + +/** + * List Preview Tokens + * List information about the preview tokens associated with the current sandbox + * + */ +export const previewTokenList = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options + }); +}; + +/** + * Create a Preview Token + * Create a new Preview token that allow access to a private sandbox + * + */ +export const previewTokenCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Update a Preview Token + * Update a Preview token that allow access to a private sandbox + * + */ +export const previewTokenUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens/{token_id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * Hibernate a VM * Suspends a running VM, saving a snapshot of its memory and running processes diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 1d63ab2..a891ef6 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -1,32 +1,61 @@ // This file is auto-generated by @hey-api/openapi-ts -export type _Error = string | { - [key: string]: unknown; +export type Response = { + errors?: Array; + success?: boolean; }; -export type MetaInformation = { - /** - * Meta information about the CodeSandbox API - */ - api: { - latest_version: string; - name: string; +export type PreviewTokenListResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + sandbox_id: string; + tokens: Array; }; +}; + +export type VmUpdateHibernationTimeoutRequest = { /** - * Meta information about the current authentication context + * The new hibernation timeout in seconds. + * + * Must be greater than 0 and less than or equal to 86400 (24 hours). + * */ - auth?: { - scopes: Array; - team: string | null; - version: string; + hibernation_timeout_seconds: number; +}; + +export type PreviewToken = { + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; +}; + +export type VmShutdownResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + [key: string]: unknown; }; }; -export type Response = { +export type PreviewTokenRevokeAllResponse = { errors?: Array; success?: boolean; +} & { + data?: { + [key: string]: unknown; + }; }; export type Sandbox = { @@ -40,85 +69,28 @@ export type Sandbox = { updated_at: string; }; -export type SandboxCreateRequest = { - /** - * Optional text description of the sandbox. Defaults to no description. - */ - description?: string; - /** - * Filename of the entrypoint of the sandbox. - */ - entry?: string; - /** - * Array of strings with external resources to load. - */ - external_resources?: Array; - /** - * Map of `path => file` where each file is a map. - */ - files: { - [key: string]: { - /** - * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. - */ - binary_content?: string; - /** - * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. - */ - code?: string; - /** - * Whether the file contains binary contents. - */ - is_binary?: boolean; - }; - }; - /** - * Whether changes to the sandbox are disallowed. Defaults to `false`. - */ - is_frozen?: boolean; - /** - * Map of dependencies and their version specifications. - */ - npm_dependencies?: { - [key: string]: string; - }; - /** - * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. - */ - path?: string; - /** - * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. - */ - privacy?: number; - /** - * Runtime to use for the sandbox. Defaults to `"browser"`. - */ - runtime?: 'browser' | 'vm'; - /** - * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. - */ - tags?: Array; - /** - * Name of the template from which the sandbox is derived (for example, `"static"`). - */ - template?: string; +export type _Error = string | { + [key: string]: unknown; +}; + +export type VmHibernateRequest = { + [key: string]: unknown; +}; + +export type PreviewTokenCreateRequest = { /** - * Sandbox title. Maximum 255 characters. Defaults to no title. + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. */ - title?: string; + expires_at?: string | null; }; -export type SandboxCreateResponse = { +export type SandboxGetResponse = { errors?: Array; success?: boolean; } & { - data?: { - alias: string; - id: string; - title: string | null; - }; + data?: Sandbox; }; export type SandboxForkRequest = { @@ -173,7 +145,89 @@ export type SandboxForkRequest = { title?: string; }; -export type SandboxForkResponse = { +export type SandboxListResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + pagination: { + current_page: number; + /** + * The number of the next page, if any. If `null`, the current page is the last page of records. + */ + next_page: number | null; + total_records: number; + }; + sandboxes: Array; + }; +}; + +export type MetaInformation = { + /** + * Meta information about the CodeSandbox API + */ + api: { + latest_version: string; + name: string; + }; + /** + * Meta information about the current authentication context + */ + auth?: { + scopes: Array; + team: string | null; + version: string; + }; +}; + +/** + * Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value. + */ +export type TokenUpdateRequest = { + /** + * API Version to use, formatted as YYYY-MM-DD + */ + default_version?: string | null; + /** + * Description of this token, for instance where it will be used. + */ + description?: string | null; + /** + * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. + */ + expires_at?: string | null; + /** + * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + */ + scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; +}; + +export type VmHibernateResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + [key: string]: unknown; + }; +}; + +export type PreviewTokenUpdateResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + sandbox_id: string; + token: PreviewToken; + }; +}; + +export type SandboxCreateResponse = { errors?: Array; @@ -182,71 +236,175 @@ export type SandboxForkResponse = { data?: { alias: string; id: string; - /** - * VM start response. Only present when start_options were provided in the request. - */ - start_response?: { - bootup_type: string; - cluster: string; - id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; - } | null; title: string | null; }; }; -export type SandboxGetResponse = { +export type VmStartRequest = { + /** + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * + */ + hibernation_timeout_seconds?: number; + /** + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + */ + ipcountry?: string; + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; +}; + +export type PreviewTokenUpdateRequest = { + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; +}; + +export type VmShutdownRequest = { + [key: string]: unknown; +}; + +export type VmUpdateSpecsRequest = { + /** + * Determines which specs to update the VM with. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; +}; + +export type WorkspaceCreateRequest = { + /** + * Name for the new workspace. Maximum length 64 characters. + */ + name: string; +}; + +export type VmStartResponse = { errors?: Array; success?: boolean; } & { - data?: Sandbox; + data?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + }; }; -export type SandboxListResponse = { +export type VmUpdateSpecsResponse = { errors?: Array; success?: boolean; } & { data?: { - pagination: { - current_page: number; + id: string; + tier: string; + }; +}; + +export type SandboxCreateRequest = { + /** + * Optional text description of the sandbox. Defaults to no description. + */ + description?: string; + /** + * Filename of the entrypoint of the sandbox. + */ + entry?: string; + /** + * Array of strings with external resources to load. + */ + external_resources?: Array; + /** + * Map of `path => file` where each file is a map. + */ + files: { + [key: string]: { /** - * The number of the next page, if any. If `null`, the current page is the last page of records. + * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. */ - next_page: number | null; - total_records: number; + binary_content?: string; + /** + * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. + */ + code?: string; + /** + * Whether the file contains binary contents. + */ + is_binary?: boolean; }; - sandboxes: Array; }; -}; - -export type TokenCreateRequest = { /** - * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. + * Whether changes to the sandbox are disallowed. Defaults to `false`. + */ + is_frozen?: boolean; + /** + * Map of dependencies and their version specifications. + */ + npm_dependencies?: { + [key: string]: string; + }; + /** + * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. */ - default_version?: string | null; + path?: string; /** - * Description of this token, for instance where it will be used. + * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. */ - description?: string; + privacy?: number; /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + * Runtime to use for the sandbox. Defaults to `"browser"`. */ - expires_at?: string | null; + runtime?: 'browser' | 'vm'; /** - * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. */ - scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; + tags?: Array; + /** + * Name of the template from which the sandbox is derived (for example, `"static"`). + */ + template?: string; + /** + * Sandbox title. Maximum 255 characters. Defaults to no title. + */ + title?: string; +}; + +export type TokenUpdateResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + description: string | null; + expires_at?: string | null; + scopes: Array; + team_id: string; + token_id: string; + }; }; export type TokenCreateResponse = { @@ -265,20 +423,17 @@ export type TokenCreateResponse = { }; }; -/** - * Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value. - */ -export type TokenUpdateRequest = { +export type TokenCreateRequest = { /** - * API Version to use, formatted as YYYY-MM-DD + * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. */ default_version?: string | null; /** * Description of this token, for instance where it will be used. */ - description?: string | null; + description?: string; /** - * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. */ expires_at?: string | null; /** @@ -287,21 +442,6 @@ export type TokenUpdateRequest = { scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; -export type TokenUpdateResponse = { - errors?: Array; - success?: boolean; -} & { - data?: { - description: string | null; - expires_at?: string | null; - scopes: Array; - team_id: string; - token_id: string; - }; -}; - export type VmCreateSessionRequest = { /** * Permission level for the session @@ -349,90 +489,18 @@ export type VmCreateSessionResponse = { }; }; -export type VmHibernateRequest = { - [key: string]: unknown; -}; - -export type VmHibernateResponse = { - errors?: Array; - success?: boolean; -} & { - data?: { - [key: string]: unknown; - }; -}; - -export type VmShutdownRequest = { - [key: string]: unknown; -}; - -export type VmShutdownResponse = { - errors?: Array; - success?: boolean; -} & { - data?: { - [key: string]: unknown; - }; -}; - -export type VmStartRequest = { - /** - * The time in seconds after which the VM will hibernate due to inactivity. - * Must be a positive integer between 1 and 86400 (24 hours). - * Defaults to 300 seconds (5 minutes) if not specified. - * - */ - hibernation_timeout_seconds?: number; - /** - * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). - */ - ipcountry?: string; - /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; -}; - -export type VmStartResponse = { +export type WorkspaceCreateResponse = { errors?: Array; success?: boolean; } & { data?: { - bootup_type: string; - cluster: string; id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; + name: string; }; }; -export type VmUpdateHibernationTimeoutRequest = { - /** - * The new hibernation timeout in seconds. - * - * Must be greater than 0 and less than or equal to 86400 (24 hours). - * - */ - hibernation_timeout_seconds: number; -}; - export type VmUpdateHibernationTimeoutResponse = { errors?: Array; success?: boolean; } & { data?: { + alias: string; id: string; - tier: string; + /** + * VM start response. Only present when start_options were provided in the request. + */ + start_response?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + } | null; + title: string | null; }; }; -export type WorkspaceCreateRequest = { - /** - * Name for the new workspace. Maximum length 64 characters. - */ - name: string; -}; - -export type WorkspaceCreateResponse = { +export type PreviewTokenCreateResponse = { errors?: Array; success?: boolean; } & { data?: { - id: string; - name: string; + sandbox_id: string; + token: { + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; + } & { + token?: string; + }; }; }; @@ -678,6 +753,100 @@ export type SandboxForkResponses = { export type SandboxForkResponse2 = SandboxForkResponses[keyof SandboxForkResponses]; +export type PreviewTokenRevokeAllData = { + body?: never; + path: { + /** + * Shortid of the sandbox to revoke tokens for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; +}; + +export type PreviewTokenRevokeAllResponses = { + /** + * RevokeALlPreviewTokensResponse + */ + 200: PreviewTokenRevokeAllResponse; +}; + +export type PreviewTokenRevokeAllResponse2 = PreviewTokenRevokeAllResponses[keyof PreviewTokenRevokeAllResponses]; + +export type PreviewTokenListData = { + body?: never; + path: { + /** + * Shortid of the sandbox to list the tokens for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; +}; + +export type PreviewTokenListResponses = { + /** + * Token List Response + */ + 201: PreviewTokenListResponse; +}; + +export type PreviewTokenListResponse2 = PreviewTokenListResponses[keyof PreviewTokenListResponses]; + +export type PreviewTokenCreateData = { + /** + * Token Create Request + */ + body?: PreviewTokenCreateRequest; + path: { + /** + * Shortid of the sandbox to create the token for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; +}; + +export type PreviewTokenCreateResponses = { + /** + * Token Create Response + */ + 201: PreviewTokenCreateResponse; +}; + +export type PreviewTokenCreateResponse2 = PreviewTokenCreateResponses[keyof PreviewTokenCreateResponses]; + +export type PreviewTokenUpdateData = { + /** + * Token Update Request + */ + body?: PreviewTokenUpdateRequest; + path: { + /** + * Shortid of the sandbox to create the token for + */ + id: string; + /** + * ID of the token to update. Does not accept the token itself. + */ + token_id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens/{token_id}'; +}; + +export type PreviewTokenUpdateResponses = { + /** + * Token Update Response + */ + 201: PreviewTokenUpdateResponse; +}; + +export type PreviewTokenUpdateResponse2 = PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; + export type VmHibernateData = { /** * VM Hibernate Request diff --git a/src/ports.ts b/src/ports.ts index aada0d4..2ba49b7 100644 --- a/src/ports.ts +++ b/src/ports.ts @@ -9,6 +9,19 @@ export class PortInfo { getPreviewUrl(protocol = "https://"): string { return `${protocol}${this.hostname}`; } + + /** + * Get a signed preview URL using a preview token. Private sandbox previews are inaccessible + * unless a preview token is provided in the URL (or as a header `csb-preview-token` or cookie + * `csb_preview_token`). + * + * @param token - The preview token to sign the URL with + * @param protocol - The protocol to use for the preview URL, defaults to `https://` + * @returns The signed preview URL + */ + getSignedPreviewUrl(token: string, protocol = "https://"): string { + return `${this.getPreviewUrl(protocol)}?preview_token=${token}`; + } } export class Ports extends Disposable { @@ -102,4 +115,25 @@ export class Ports extends Disposable { ); }); } + + /** + * Get a signed preview URL for a port using a preview token. + * + * @param port - The port to get a signed preview URL for + * @param token - The preview token to sign the URL with + * @returns The signed preview URL, or undefined if the port is not open + * @throws {Error} If the port is not open + */ + getSignedPreviewUrl( + port: number, + token: string, + protocol = "https://" + ): string { + const portInfo = this.getOpenedPort(port); + if (!portInfo) { + throw new Error("Port is not open"); + } + + return portInfo.getSignedPreviewUrl(token, protocol); + } } diff --git a/src/preview-tokens.ts b/src/preview-tokens.ts new file mode 100644 index 0000000..762dfee --- /dev/null +++ b/src/preview-tokens.ts @@ -0,0 +1,86 @@ +import { Disposable } from "./utils/disposable"; +import { SandboxClient } from "./sandbox-client"; + +interface BasePreviewTokenInfo { + expiresAt: Date | null; + tokenId: string; + lastUsedAt: Date | null; +} + +export interface PreviewTokenInfo extends BasePreviewTokenInfo { + tokenPrefix: string; +} + +export interface PreviewToken extends BasePreviewTokenInfo { + token: string; +} + +/** + * Provider for generating preview tokens that can be used to access + * private sandbox previews. This provider is only available in environments + * with an authenticated API client (like Node.js). + */ +export class PreviewTokens extends Disposable { + constructor(private sandboxId: string, private sandboxClient: SandboxClient) { + super(); + } + + /** + * Generate a new preview token that can be used to access private sandbox previews. + * + * @param opts - Options + * @param opts.expiresAt - Optional expiration date for the preview token + * @returns A preview token that can be used with Ports.getSignedPreviewUrl + */ + async create(opts: { expiresAt?: Date } = {}): Promise { + return this.sandboxClient.previewTokens.create( + this.sandboxId, + opts.expiresAt + ); + } + + /** + * List all active preview tokens for this sandbox. + * + * @returns A list of preview tokens + */ + async list(): Promise { + return this.sandboxClient.previewTokens.list(this.sandboxId); + } + + /** + * Revoke a single preview token for this sandbox. + * + * @param tokenId - The ID of the token to revoke + */ + async revoke(tokenId: string): Promise { + return this.sandboxClient.previewTokens.revoke(this.sandboxId, tokenId); + } + + /** + * Revoke all active preview tokens for this sandbox. + * This will immediately invalidate all tokens, and they can no longer be used + * to access the sandbox preview. + */ + async revokeAll(): Promise { + return this.sandboxClient.previewTokens.revokeAll(this.sandboxId); + } + + /** + * Update a preview token's expiration date. + * + * @param tokenId - The ID of the token to update + * @param expiresAt - The new expiration date for the token (null for no expiration) + * @returns The updated preview token info + */ + async update( + tokenId: string, + expiresAt: Date | null + ): Promise { + return this.sandboxClient.previewTokens.update( + this.sandboxId, + tokenId, + expiresAt + ); + } +} diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 33d447b..1597d7d 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -11,6 +11,10 @@ import { vmStart, vmUpdateHibernationTimeout, vmUpdateSpecs, + previewTokenCreate, + previewTokenList, + previewTokenRevokeAll, + previewTokenUpdate, } from "./client"; import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; @@ -660,6 +664,162 @@ export class SandboxClient { return connectedSandbox; } + + /** + * Namespace for managing preview tokens that can be used to access private sandbox previews. + */ + public readonly previewTokens = { + /** + * Generate a new preview token that can be used to access private sandbox previews. + * + * @param sandboxId - ID of the sandbox to create the token for + * @param expiresAt - Optional expiration date for the preview token + * @returns A preview token that can be used with Ports.getSignedPreviewUrl + */ + create: async (sandboxId: string, expiresAt: Date | null = null) => { + const response = handleResponse( + await previewTokenCreate({ + client: this.apiClient, + path: { + id: sandboxId, + }, + body: { + expires_at: expiresAt?.toISOString(), + }, + }), + "Failed to create preview token" + ); + + if (!response.token?.token) { + throw new Error("No token returned from API"); + } + + return { + token: response.token.token, + expiresAt: response.token.expires_at + ? new Date(response.token.expires_at) + : null, + tokenId: response.token.token_id, + tokenPrefix: response.token.token_prefix, + lastUsedAt: response.token.last_used_at + ? new Date(response.token.last_used_at) + : null, + }; + }, + + /** + * List all active preview tokens for a sandbox. + * + * @param sandboxId - ID of the sandbox to list tokens for + * @returns A list of preview tokens + */ + list: async (sandboxId: string) => { + const response = handleResponse( + await previewTokenList({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }), + "Failed to list preview tokens" + ); + + if (!response.tokens) { + return []; + } + + return response.tokens.map((token) => ({ + expiresAt: token.expires_at ? new Date(token.expires_at) : null, + tokenId: token.token_id, + tokenPrefix: token.token_prefix, + lastUsedAt: token.last_used_at ? new Date(token.last_used_at) : null, + })); + }, + + /** + * Revoke a single preview token for a sandbox. + * + * @param sandboxId - ID of the sandbox the token belongs to + * @param tokenId - The ID of the token to revoke + */ + revoke: async (sandboxId: string, tokenId: string): Promise => { + handleResponse( + await previewTokenUpdate({ + client: this.apiClient, + path: { + id: sandboxId, + token_id: tokenId, + }, + body: { + expires_at: new Date().toISOString(), + }, + }), + "Failed to revoke preview token" + ); + }, + + /** + * Revoke all active preview tokens for a sandbox. + * This will immediately invalidate all tokens, and they can no longer be used + * to access the sandbox preview. + * + * @param sandboxId - ID of the sandbox to revoke tokens for + */ + revokeAll: async (sandboxId: string): Promise => { + handleResponse( + await previewTokenRevokeAll({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }), + "Failed to revoke preview tokens" + ); + }, + + /** + * Update a preview token's expiration date. + * + * @param sandboxId - ID of the sandbox the token belongs to + * @param tokenId - The ID of the token to update + * @param expiresAt - The new expiration date for the token (null for no expiration) + * @returns The updated preview token info + */ + update: async ( + sandboxId: string, + tokenId: string, + expiresAt: Date | null + ) => { + const response = handleResponse( + await previewTokenUpdate({ + client: this.apiClient, + path: { + id: sandboxId, + token_id: tokenId, + }, + body: { + expires_at: expiresAt?.toISOString(), + }, + }), + "Failed to update preview token" + ); + + if (!response.token) { + throw new Error("No token returned from API"); + } + + return { + expiresAt: response.token.expires_at + ? new Date(response.token.expires_at) + : null, + tokenId: response.token.token_id, + tokenPrefix: response.token.token_prefix, + lastUsedAt: response.token.last_used_at + ? new Date(response.token.last_used_at) + : null, + }; + }, + }; } function privacyToNumber(privacy: SandboxPrivacy): number { diff --git a/src/sandbox.ts b/src/sandbox.ts index e72645e..432c6cb 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -13,6 +13,7 @@ import { Tasks } from "./tasks"; import type { SandboxClient, VMTier } from "."; import { Sessions } from "./sessions"; +import { PreviewTokens } from "./preview-tokens"; export { SessionConnectInfo } from "./sessions"; export { @@ -185,10 +186,30 @@ export class SandboxSession extends Disposable { } export class Sandbox extends SandboxSession { + /** + * Provider for creating new sessions inside the sandbox. These sessions have their own + * filesystem, shells, tasks and permissions. You can read more about sessions in the + * [CodeSandbox docs](https://codesandbox.io/docs/sdk/sessions). + */ public readonly sessions = this.addDisposable( new Sessions(this.id, this.sandboxClient) ); + /** + * Provider for generating preview tokens. These tokens can be used to generate signed + * preview URLs for private sandboxes. + * + * @example + * ```ts + * const sandbox = await sdk.sandbox.create(); + * const previewToken = await sandbox.previewTokens.createToken(); + * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); + * ``` + */ + public readonly previewTokens = this.addDisposable( + new PreviewTokens(this.id, this.sandboxClient) + ); + constructor( private sandboxClient: SandboxClient, pitcherClient: IPitcherClient From 5a5d659525a7d268a12c97d32127d6e5974fbb31 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 18 Feb 2025 20:40:40 +0100 Subject: [PATCH 051/241] chore(main): release 0.8.0 (#64) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8abe63..bd167fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.8.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.7.0...v0.8.0) (2025-02-18) + + +### Features + +* support for private previews and preview tokens ([#63](https://github.com/codesandbox/codesandbox-sdk/issues/63)) ([993e509](https://github.com/codesandbox/codesandbox-sdk/commit/993e50981f907f3a2ccf7421a2fd8e4aba96e9cf)) + ## [0.7.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.2...v0.7.0) (2025-02-15) diff --git a/package-lock.json b/package-lock.json index 6e7efa6..e6dddc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.7.0", + "version": "0.8.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index d3d5481..d378745 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.7.0", + "version": "0.8.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 7b61dcc2ba6aa183fb2597674bccd264dbb8615c Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 21 Feb 2025 13:21:05 +0100 Subject: [PATCH 052/241] fix: don't use env if not env vars are set (#66) --- src/shells.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shells.ts b/src/shells.ts index 4983621..29c20f7 100644 --- a/src/shells.ts +++ b/src/shells.ts @@ -305,9 +305,11 @@ function runCommandAsUser( } // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = `env ${Object.entries(env ?? {}) - .map(([key, value]) => `${key}=${value}`) - .join(" ")} ${command}`; + let commandWithEnv = Object.keys(env ?? {}).length + ? `env ${Object.entries(env ?? {}) + .map(([key, value]) => `${key}=${value}`) + .join(" ")} ${command}` + : command; if (cwd) { commandWithEnv = `cd ${cwd} && ${commandWithEnv}`; From 83cc5492d8d8c7d7d4807dc663b6a1283d7392f9 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 21 Feb 2025 13:21:48 +0100 Subject: [PATCH 053/241] chore(main): release 0.8.1 (#67) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd167fa..6914ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.8.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.0...v0.8.1) (2025-02-21) + + +### Bug Fixes + +* don't use env if not env vars are set ([#66](https://github.com/codesandbox/codesandbox-sdk/issues/66)) ([7b61dcc](https://github.com/codesandbox/codesandbox-sdk/commit/7b61dcc2ba6aa183fb2597674bccd264dbb8615c)) + ## [0.8.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.7.0...v0.8.0) (2025-02-18) diff --git a/package-lock.json b/package-lock.json index e6dddc1..bea8992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index d378745..d86101e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.8.0", + "version": "0.8.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From f0542057345aa0d11251bba24b9420f1b7ae2574 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 26 Feb 2025 02:06:34 +0100 Subject: [PATCH 054/241] feat: add support for `--since` when listing sandboxes (#68) --- src/bin/commands/sandbox/index.ts | 18 ++++++-- src/bin/commands/sandbox/list.ts | 71 +++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/bin/commands/sandbox/index.ts b/src/bin/commands/sandbox/index.ts index 0cc559d..ad51de7 100644 --- a/src/bin/commands/sandbox/index.ts +++ b/src/bin/commands/sandbox/index.ts @@ -54,6 +54,10 @@ export const sandboxCommand: CommandModule = { describe: "Number of items per page", type: "number", }) + .option("since", { + describe: "Filter by creation date", + type: "string", + }) .option("order-by", { describe: "Order results by field", choices: ["inserted_at", "updated_at"], @@ -66,12 +70,19 @@ export const sandboxCommand: CommandModule = { }) .option("limit", { alias: "l", - describe: `Maximum number of sandboxes to list (default: ${DEFAULT_LIMIT})`, + describe: `Maximum number of sandboxes to list (default: ${DEFAULT_LIMIT}, no limit when --since is used)`, type: "number", - default: DEFAULT_LIMIT, }); }, handler: async (argv) => { + // Only set the default limit if --since is not provided + const limit = + argv.limit !== undefined + ? argv.limit + : argv.since + ? undefined + : DEFAULT_LIMIT; + await listSandboxes( argv.output as string | undefined, { @@ -89,9 +100,10 @@ export const sandboxCommand: CommandModule = { pageSize: argv["page-size"], } : undefined, + since: argv.since, }, argv["headers"] as boolean, - argv.limit as number | undefined + limit as number | undefined ); }, }) diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 042d537..9210043 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -43,9 +43,12 @@ function formatAge(date: Date): string { export async function listSandboxes( outputFields?: string, - listOpts: SandboxListOpts & { pagination?: PaginationOpts } = {}, + listOpts: SandboxListOpts & { + pagination?: PaginationOpts; + since?: string; + } = {}, showHeaders = true, - limit = 100 + limit?: number ) { const sdk = new CodeSandbox(); const spinner = ora("Fetching sandboxes...").start(); @@ -56,6 +59,25 @@ export async function listSandboxes( let currentPage = 1; const pageSize = 50; // API's maximum page size + // Default limit to 100 if not specified, unless since is provided + if (limit === undefined) { + limit = listOpts.since ? Infinity : 100; + } + + // If since is provided, ensure we're ordering by inserted_at desc + if (listOpts.since) { + listOpts.orderBy = "inserted_at"; + listOpts.direction = "desc"; + } + + // Parse the since date if provided + const sinceDate = listOpts.since ? new Date(listOpts.since) : null; + if (sinceDate && isNaN(sinceDate.getTime())) { + throw new Error( + `Invalid date format for 'since': ${listOpts.since}. Use ISO format (e.g., '2023-01-01T00:00:00Z').` + ); + } + while (true) { const { sandboxes, @@ -74,17 +96,44 @@ export async function listSandboxes( } totalCount = total; - const newSandboxes = sandboxes.filter( + + // Filter sandboxes by the since date if provided + const filteredSandboxes = sinceDate + ? sandboxes.filter( + (sandbox) => new Date(sandbox.createdAt) >= sinceDate + ) + : sandboxes; + + // If we're using since and ordering by inserted_at desc, we can stop fetching + // once we encounter a sandbox older than the since date + if ( + sinceDate && + listOpts.orderBy === "inserted_at" && + listOpts.direction === "desc" && + sandboxes.some((sandbox) => new Date(sandbox.createdAt) < sinceDate) + ) { + // Add only the filtered sandboxes that match our criteria + const newSandboxes = filteredSandboxes.filter( + (sandbox) => + !allSandboxes.some((existing) => existing.id === sandbox.id) + ); + allSandboxes = [...allSandboxes, ...newSandboxes]; + break; + } + + const newSandboxes = filteredSandboxes.filter( (sandbox) => !allSandboxes.some((existing) => existing.id === sandbox.id) ); allSandboxes = [...allSandboxes, ...newSandboxes]; spinner.text = `Fetching sandboxes... (${allSandboxes.length}${ - limit ? `/${Math.min(limit, totalCount)}` : `/${totalCount}` + limit !== Infinity + ? `/${Math.min(limit, totalCount)}` + : `/${totalCount}` })`; - // Stop if we've reached the total count + // Stop if we've reached the total count or the limit if (allSandboxes.length >= limit || pagination.nextPage == null) { break; } @@ -93,7 +142,7 @@ export async function listSandboxes( } // Apply limit after fetching all sandboxes - if (limit) { + if (limit !== Infinity) { allSandboxes = allSandboxes.slice(0, limit); } @@ -125,7 +174,9 @@ export async function listSandboxes( // eslint-disable-next-line no-console console.error( - `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` + listOpts.since + ? `\nShowing ${allSandboxes.length} sandboxes created since ${listOpts.since}` + : `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` ); return; @@ -175,10 +226,12 @@ export async function listSandboxes( // eslint-disable-next-line no-console console.log(table.toString()); - if (limit && totalCount > allSandboxes.length) { + if (limit !== Infinity && totalCount > allSandboxes.length) { // eslint-disable-next-line no-console console.error( - `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` + listOpts.since + ? `\nShowing ${allSandboxes.length} sandboxes created since ${listOpts.since}` + : `\nShowing ${allSandboxes.length} of ${totalCount} sandboxes` ); } } catch (error) { From 24ab04f13278a34963fcb3e916bd1989ec9915ab Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 26 Feb 2025 02:43:58 +0100 Subject: [PATCH 055/241] chore(main): release 0.9.0 (#69) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6914ec9..505148a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.9.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.1...v0.9.0) (2025-02-26) + + +### Features + +* add support for `--since` when listing sandboxes ([#68](https://github.com/codesandbox/codesandbox-sdk/issues/68)) ([f054205](https://github.com/codesandbox/codesandbox-sdk/commit/f0542057345aa0d11251bba24b9420f1b7ae2574)) + ## [0.8.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.0...v0.8.1) (2025-02-21) diff --git a/package-lock.json b/package-lock.json index bea8992..f818e26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.8.1", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.8.1", + "version": "0.9.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index d86101e..70a8484 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.8.1", + "version": "0.9.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 27c559cb36839ba08b8a2518a45a5ede26b44f8b Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 28 Feb 2025 02:43:53 +0100 Subject: [PATCH 056/241] feat: allow defining timeout when waiting for port (#70) --- src/ports.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/ports.ts b/src/ports.ts index 2ba49b7..77643e3 100644 --- a/src/ports.ts +++ b/src/ports.ts @@ -93,21 +93,44 @@ export class Ports extends Disposable { * Wait for a port to be opened. * * @param port - The port to wait for. + * @param options - Additional options + * @param options.timeoutMs - Optional timeout in milliseconds. If specified, the promise will reject after this time if the port hasn't opened. * @returns A promise that resolves when the port is opened. + * @throws {Error} If the timeout is reached before the port opens */ - async waitForPort(port: number): Promise { + async waitForPort( + port: number, + options?: { timeoutMs?: number } + ): Promise { await this.pitcherClient.clients.port.readyPromise; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { + // Check if port is already open const portInfo = this.getOpenedPorts().find((p) => p.port === port); if (portInfo) { resolve(portInfo); return; } + // Set up timeout if specified + let timeoutId: NodeJS.Timeout | undefined; + if (options?.timeoutMs !== undefined) { + timeoutId = setTimeout(() => { + reject( + new Error( + `Timeout of ${options.timeoutMs}ms exceeded waiting for port ${port} to open` + ) + ); + }, options.timeoutMs); + } + + // Listen for port open events const disposable = this.addDisposable( this.onDidPortOpen((portInfo) => { if (portInfo.port === port) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } resolve(portInfo); disposable.dispose(); } From 25035bbd75f21d3332043770cb83c6deaa3b9b2b Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 28 Feb 2025 02:44:37 +0100 Subject: [PATCH 057/241] chore(main): release 0.10.0 (#71) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 505148a..4f8cfb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.10.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.9.0...v0.10.0) (2025-02-28) + + +### Features + +* allow defining timeout when waiting for port ([#70](https://github.com/codesandbox/codesandbox-sdk/issues/70)) ([27c559c](https://github.com/codesandbox/codesandbox-sdk/commit/27c559cb36839ba08b8a2518a45a5ede26b44f8b)) + ## [0.9.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.1...v0.9.0) (2025-02-26) diff --git a/package-lock.json b/package-lock.json index f818e26..a3938b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.9.0", + "version": "0.10.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index 70a8484..8d1a34f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.9.0", + "version": "0.10.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From c9a3aeaed74ed34ac013a891d3ace7a9c6f99293 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 6 Mar 2025 13:31:51 +0100 Subject: [PATCH 058/241] working rest client --- .gitignore | 1 + openapi-sandbox.json | 239 ++++++++++++++++++++++++++++++++++ package-lock.json | 29 ++++- package.json | 7 +- src/browser.ts | 70 +++------- src/client-rest/client.gen.ts | 5 + src/client-rest/index.ts | 3 + src/client-rest/sdk.gen.ts | 29 +++++ src/client-rest/types.gen.ts | 116 +++++++++++++++++ src/filesystem-rest.ts | 29 +++++ src/index.ts | 7 +- src/rest-client.ts | 84 ++++++++++++ src/sandbox-client.ts | 233 +++++++++++++++++---------------- src/sandbox.ts | 2 +- src/sessions.ts | 2 +- 15 files changed, 680 insertions(+), 176 deletions(-) create mode 100644 openapi-sandbox.json create mode 100644 src/client-rest/client.gen.ts create mode 100644 src/client-rest/index.ts create mode 100644 src/client-rest/sdk.gen.ts create mode 100644 src/client-rest/types.gen.ts create mode 100644 src/filesystem-rest.ts create mode 100644 src/rest-client.ts diff --git a/.gitignore b/.gitignore index 4203c65..60a8d15 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Generated stuff dist tests +test.ts ### macOS ### *.DS_Store diff --git a/openapi-sandbox.json b/openapi-sandbox.json new file mode 100644 index 0000000..5f63dc2 --- /dev/null +++ b/openapi-sandbox.json @@ -0,0 +1,239 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox API", + "description": "API for interacting with sandbox", + "version": "1.0.0" + }, + "paths": { + "/fs/writefile": { + "post": { + "summary": "Write to a file", + "description": "Write content to a file at the specified path", + "operationId": "writeFile", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WriteFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error writing file", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + }, + "required": ["status", "error"] + }, + "DefaultError": { + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/PitcherErrorCode", + "description": "Error code identifying the type of error" + }, + "data": { + "type": "object", + "description": "Additional error details", + "nullable": true + }, + "publicMessage": { + "type": "string", + "description": "Human-readable error message that can be displayed to users", + "nullable": true + } + }, + "required": ["code"] + }, + "RawFsError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [102], + "description": "RAWFS_ERROR code" + }, + "data": { + "type": "object", + "properties": { + "errno": { + "type": ["number", "null"], + "description": "File system error number, or null if not available" + } + }, + "required": ["errno"] + }, + "publicMessage": { + "type": "string", + "description": "Human-readable error message that can be displayed to users", + "nullable": true + } + }, + "required": ["code", "data"] + }, + "PitcherErrorCode": { + "type": "integer", + "description": "Enumeration of error codes", + "enum": [ + 0, 1, 2, 3, 100, 101, 102, 200, 201, 204, 300, 400, 404, 410, 420, + 430, 440, 450, 460, 470, 500, 600, 601, 602, 704, 800, 801, 802, 803, + 814 + ], + "x-enum-descriptions": [ + "CRITICAL_ERROR", + "FEATURE_UNAVAILABLE", + "NO_ACCESS", + "RATE_LIMIT", + "INVALID_ID", + "INVALID_PATH", + "RAWFS_ERROR", + "SHELL_NOT_ACCESSIBLE", + "SHELL_CLOSED", + "SHELL_NOT_FOUND", + "MODEL_NOT_FOUND", + "GIT_OPERATION_IN_PROGRESS", + "GIT_REMOTE_FILE_NOT_FOUND", + "GIT_FETCH_FAIL", + "GIT_PULL_CONFLICT", + "GIT_RESET_LOCAL_REMOTE_ERROR", + "GIT_PUSH_FAIL", + "GIT_RESET_CHECKOUT_INITIAL_BRANCH_FAIL", + "GIT_PULL_FAIL", + "GIT_TRANSPOSE_LINES_FAIL", + "CHANNEL_NOT_FOUND", + "CONFIG_FILE_ALREADY_EXISTS", + "TASK_NOT_FOUND", + "COMMAND_ALREADY_CONFIGURED", + "COMMAND_NOT_FOUND", + "AI_NOT_AVAILABLE", + "PROMPT_TOO_BIG", + "FAILED_TO_RESPOND", + "AI_TOO_FREQUENT_REQUESTS", + "AI_CHAT_NOT_FOUND" + ] + }, + "WriteFileRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path to write to" + }, + "content": { + "type": "string", + "format": "binary", + "description": "File content as binary data (Uint8Array)" + }, + "create": { + "type": "boolean", + "description": "Whether to create the file if it doesn't exist", + "default": false + }, + "overwrite": { + "type": "boolean", + "description": "Whether to overwrite the file if it exists", + "default": false + } + }, + "required": ["path", "content"] + } + } + } +} diff --git a/package-lock.json b/package-lock.json index a3938b6..7074984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "@codesandbox/sdk", "version": "0.10.0", "license": "MIT", + "dependencies": { + "@msgpack/msgpack": "^3.1.0", + "messagepack": "^1.1.12" + }, "bin": { "csb": "dist/bin/codesandbox.cjs" }, @@ -182,6 +186,16 @@ "@msgpack/msgpack": "^2.7.1" } }, + "node_modules/@codesandbox/pitcher-protocol/node_modules/@msgpack/msgpack": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, "node_modules/@codesandbox/sandpack-client": { "version": "2.19.8", "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", @@ -875,13 +889,12 @@ } }, "node_modules/@msgpack/msgpack": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", - "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.0.tgz", + "integrity": "sha512-igBxaq5JHWdJ0lDyKkCo00pDu+bNVuBAs/cHra6a3ndCw6vlZK9BGLuG7Fvmar/DXK2uJ25zvgcAZEl+AvLpjQ==", "license": "ISC", "engines": { - "node": ">= 10" + "node": ">= 18" } }, "node_modules/@open-draft/deferred-promise": { @@ -3017,6 +3030,12 @@ "node": ">= 0.10.0" } }, + "node_modules/messagepack": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/messagepack/-/messagepack-1.1.12.tgz", + "integrity": "sha512-pNB6K4q4VMLRXdvlGZkTtQhmKFntvLisnOQnL0VhKpZooL8B8Wsv5TXuidIJil0bCH6V172p3+Onfyow0usPYQ==", + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", diff --git a/package.json b/package.json index 8d1a34f..d77eb30 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,9 @@ "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", - "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", + "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch && npm run build-openapi-rest", "build-openapi:staging": "rimraf src/client && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", + "build-openapi-rest": "node_modules/.bin/openapi-ts -i ./openapi-sandbox.json -o src/client-rest -c @hey-api/client-fetch", "clean": "rimraf ./dist", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", @@ -103,5 +104,7 @@ "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" }, - "dependencies": {} + "dependencies": { + "@msgpack/msgpack": "^3.1.0" + } } diff --git a/src/browser.ts b/src/browser.ts index 39887d8..91beaa2 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -4,16 +4,8 @@ import { } from "@codesandbox/pitcher-client"; import { SandboxSession } from "./sandbox"; -import { DEFAULT_SUBSCRIPTIONS, type SandboxStartData } from "./sandbox-client"; -import { SessionConnectInfo } from "./sessions"; - -export { SandboxStartData }; - -function isStartData( - data: SandboxStartData | SessionConnectInfo -): data is SandboxStartData { - return "bootup_type" in data; -} +import { DEFAULT_SUBSCRIPTIONS } from "./sandbox-client"; +import { SessionData } from "./sessions"; /** * With this function you can connect to a sandbox from the browser. @@ -51,55 +43,29 @@ function isStartData( * ``` */ export async function connectToSandbox( - startInfo: SandboxStartData | SessionConnectInfo + session: SessionData ): Promise { - const useStartData = isStartData(startInfo); - - let requestPitcherInstance: () => Promise; - if (useStartData) { - requestPitcherInstance = async () => { - const data = startInfo; - - return { - bootupType: data.bootup_type as "RUNNING" | "CLEAN" | "RESUME" | "FORK", - pitcherURL: data.pitcher_url, - workspacePath: data.workspace_path, - userWorkspacePath: data.user_workspace_path, - pitcherManagerVersion: data.pitcher_manager_version, - pitcherVersion: data.pitcher_version, - latestPitcherVersion: data.latest_pitcher_version, - pitcherToken: data.pitcher_token, - cluster: data.cluster, - }; - }; - } else { - requestPitcherInstance = async () => { - const data = startInfo; - - return { - bootupType: "RESUME", - cluster: "session", - id: data.id, - latestPitcherVersion: "1.0.0-session", - pitcherManagerVersion: "1.0.0-session", - pitcherToken: data.pitcher_token, - pitcherURL: data.pitcher_url, - pitcherVersion: "1.0.0-session", - reconnectToken: "", - userWorkspacePath: data.user_workspace_path, - workspacePath: data.user_workspace_path, - }; - }; - } - const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: startInfo.id, + instanceId: session.id, onFocusChange() { return () => {}; }, - requestPitcherInstance, + requestPitcherInstance: () => + Promise.resolve({ + bootupType: "RESUME", + cluster: "session", + id: session.id, + latestPitcherVersion: "1.0.0-session", + pitcherManagerVersion: "1.0.0-session", + pitcherToken: session.pitcher_token, + pitcherURL: session.pitcher_url, + pitcherVersion: "1.0.0-session", + reconnectToken: "", + userWorkspacePath: session.user_workspace_path, + workspacePath: session.user_workspace_path, + }), subscriptions: DEFAULT_SUBSCRIPTIONS, }, () => {} diff --git a/src/client-rest/client.gen.ts b/src/client-rest/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/client-rest/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/client-rest/index.ts b/src/client-rest/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/client-rest/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/client-rest/sdk.gen.ts b/src/client-rest/sdk.gen.ts new file mode 100644 index 0000000..21d5a16 --- /dev/null +++ b/src/client-rest/sdk.gen.ts @@ -0,0 +1,29 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { WriteFileData, WriteFileResponse, WriteFileError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Write to a file + * Write content to a file at the specified path + */ +export const writeFile = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/writefile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/client-rest/types.gen.ts b/src/client-rest/types.gen.ts new file mode 100644 index 0000000..f540b55 --- /dev/null +++ b/src/client-rest/types.gen.ts @@ -0,0 +1,116 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + error: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); +}; + +export type DefaultError = { + code: PitcherErrorCode; + /** + * Additional error details + */ + data?: { + [key: string]: unknown; + } | null; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; +}; + +export type RawFsError = { + /** + * RAWFS_ERROR code + */ + code: 102; + data: { + /** + * File system error number, or null if not available + */ + errno: unknown; + }; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; +}; + +/** + * Enumeration of error codes + */ +export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; + +export type WriteFileRequest = { + /** + * File path to write to + */ + path: string; + /** + * File content as binary data (Uint8Array) + */ + content: Blob | File; + /** + * Whether to create the file if it doesn't exist + */ + create?: boolean; + /** + * Whether to overwrite the file if it exists + */ + overwrite?: boolean; +}; + +export type WriteFileData = { + body: WriteFileRequest; + path?: never; + query?: never; + url: '/fs/writefile'; +}; + +export type WriteFileErrors = { + /** + * Error writing file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; + +export type WriteFileResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; \ No newline at end of file diff --git a/src/filesystem-rest.ts b/src/filesystem-rest.ts new file mode 100644 index 0000000..ee93354 --- /dev/null +++ b/src/filesystem-rest.ts @@ -0,0 +1,29 @@ +import { + WriteFileError, + WriteFileRequest, + WriteFileResponse, +} from "./client-rest"; +import { SessionData } from "./sessions"; +import { RestRequester } from "./rest-client"; + +export class FileSystemRest { + constructor( + private createRequester: (session: SessionData) => RestRequester + ) {} + writeTextFile(session: SessionData, path: string, content: string) { + const request = this.createRequester(session); + + return request( + "fs/writeFile", + { + path, + + // We are not able to generate the correct typing for OpenAPI here + // @ts-expect-error + content: new TextEncoder().encode(content), + create: true, + overwrite: true, + } + ); + } +} diff --git a/src/index.ts b/src/index.ts index bd012aa..7ffd864 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ import { createClient, createConfig } from "@hey-api/client-fetch"; import { SandboxClient, - SandboxStartData, CreateSandboxOpts, VMTier, SandboxListOpts, @@ -13,7 +12,6 @@ import { export { SandboxClient, - SandboxStartData, CreateSandboxOpts, VMTier, SandboxListOpts, @@ -55,14 +53,15 @@ function getBaseUrl(token: string) { return "https://api.together.ai/csb/sdk"; } +export { RestClient } from "./rest-client"; + export class CodeSandbox { private baseUrl: string; private apiToken: string; public readonly apiClient: Client; - public readonly sandbox: SandboxClient; - constructor(apiToken?: string, private readonly opts: ClientOpts = {}) { + constructor(apiToken?: string, readonly opts: ClientOpts = {}) { this.apiToken = apiToken || ensure( diff --git a/src/rest-client.ts b/src/rest-client.ts new file mode 100644 index 0000000..39a0ad0 --- /dev/null +++ b/src/rest-client.ts @@ -0,0 +1,84 @@ +import { createClient, createConfig } from "@hey-api/client-fetch"; +import { SessionData } from "./sessions"; +import { FileSystemRest } from "./filesystem-rest"; +import { decode, encode } from "@msgpack/msgpack"; +import { + SuccessResponse, + ErrorResponse, + WriteFileRequest, +} from "./client-rest"; + +export interface ClientOpts { + /** + * Custom fetch implementation + * + * @default fetch + */ + fetch?: typeof fetch; + + /** + * Additional headers to send with each request + */ + headers?: Record; +} + +export type RestRequester = < + P extends {}, + S extends SuccessResponse, + E extends ErrorResponse +>( + method: string, + params: P +) => Promise; + +export class RestClient { + static id = 0; + constructor(private opts: ClientOpts = {}) {} + private createClient = (session: SessionData): RestRequester => { + const url = new URL(session.pitcher_url); + + url.protocol = "https"; + + const client = createClient( + createConfig({ + bodySerializer: null, + parseAs: "stream", + baseUrl: url.origin, + headers: { + ...(this.opts.headers ?? {}), + "content-type": "application/x-msgpack", + }, + fetch: + this.opts.fetch ?? + // @ts-ignore + ((url, params) => { + console.log("Fetching", url, params); + return fetch(url, params); + }), + }) + ); + + return (method, params) => { + const message = { + id: RestClient.id++, + method, + params, + }; + console.log("Sending message", url.toString(), message); + const encodedMessage = encode(message); + + return client + .post({ + url: `/${session.id}?token=${session.pitcher_token}`, + headers: { + "content-length": encodedMessage.byteLength.toString(), + }, + body: encodedMessage, + }) + .then(async ({ response }) => + decode(await response.arrayBuffer()) + ) as any; + }; + }; + fs = new FileSystemRest(this.createClient); +} diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 1597d7d..07241ab 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -1,7 +1,11 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; import type { Client } from "@hey-api/client-fetch"; -import type { VmStartResponse, VmUpdateSpecsRequest } from "./client"; +import type { + SandboxForkResponse, + VmStartResponse, + VmUpdateSpecsRequest, +} from "./client"; import { sandboxFork, vmCreateSession, @@ -18,10 +22,9 @@ import { } from "./client"; import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; -import { SessionCreateOptions, SessionConnectInfo } from "./sessions"; +import { SessionCreateOptions, SessionData } from "./sessions"; export type SandboxPrivacy = "public" | "unlisted" | "private"; -export type SandboxStartData = Required["data"]; export type SandboxInfo = { id: string; @@ -201,6 +204,7 @@ export class VMTier { function startOptionsFromOpts(opts: StartSandboxOpts | undefined) { if (!opts) return undefined; + return { ipcountry: opts.ipcountry, tier: opts.vmTier?.name, @@ -243,16 +247,64 @@ export type HandledResponse = { response: Response; }; -export class SandboxClient { - constructor(private readonly apiClient: Client) {} +function getDefaultTemplate(client: Client) { + if (client.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } - private get defaultTemplate(): string { - if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { - return "7ngcrf"; - } + return "pcz35m"; +} - return "pcz35m"; +export async function createSandbox( + client: Client, + startVM: true, + opts?: CreateSandboxOpts +): Promise< + SandboxForkResponse["data"] & { + start_response: Required["data"]; } +>; +export async function createSandbox( + client: Client, + startVM: false, + opts?: CreateSandboxOpts +): Promise; +export async function createSandbox( + client: Client, + startVM: boolean, + opts?: CreateSandboxOpts +): Promise { + const templateId = opts?.template || getDefaultTemplate(client); + const privacy = opts?.privacy || "public"; + const tags = opts?.tags || ["sdk"]; + const path = opts?.path || "/SDK"; + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + // An empty object will still start the VM, but with server defaults + const startOptions = startOptionsFromOpts(opts) || {}; + + const result = await sandboxFork({ + client, + body: { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + // We only pass start options if we want to start the VM immediately + start_options: startVM ? startOptions : undefined, + }, + path: { + id: typeof templateId === "string" ? templateId : templateId.id, + }, + }); + + return handleResponse(result, "Failed to create sandbox"); +} + +export class SandboxClient { + constructor(private readonly apiClient: Client) {} /** * Open, start & connect to a sandbox that already exists @@ -261,7 +313,15 @@ export class SandboxClient { id: string, startOpts?: StartSandboxOpts ): Promise { - return this.connectToSandbox(id, () => this.start(id, startOpts)); + const sandbox = await this.start(id, startOpts); + const session: SessionData = { + id: sandbox.id, + pitcher_token: sandbox.pitcher_token, + pitcher_url: sandbox.pitcher_url, + user_workspace_path: sandbox.user_workspace_path, + }; + + return this.connectToSandbox(session); } /** @@ -275,7 +335,7 @@ export class SandboxClient { public async start( id: string, opts?: StartSandboxOpts - ): Promise { + ): Promise { const startResult = await vmStart({ client: this.apiClient, body: startOptionsFromOpts(opts), @@ -294,8 +354,8 @@ export class SandboxClient { * any sandbox/template created on codesandbox.io, even your own templates) or don't pass * in anything and we'll use the default universal template. * - * This function will also start & connect to the VM of the created sandbox, and return a {@link Sandbox} - * that allows you to control the VM. + * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} + * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. * * @param opts Additional options for creating the sandbox * @@ -303,69 +363,48 @@ export class SandboxClient { */ async create( opts: { autoConnect: false } & CreateSandboxOpts - ): Promise; + ): Promise; async create( opts?: { autoConnect?: true } & CreateSandboxOpts ): Promise; async create(opts?: CreateSandboxOpts): Promise; - async create(opts?: CreateSandboxOpts): Promise { - const templateId = opts?.template || this.defaultTemplate; - const privacy = opts?.privacy || "public"; - const tags = opts?.tags || ["sdk"]; - const path = opts?.path || "/SDK"; - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - const result = await sandboxFork({ - client: this.apiClient, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - start_options: - opts?.autoConnect === false - ? undefined - : startOptionsFromOpts(opts || {}), - }, - path: { - id: typeof templateId === "string" ? templateId : templateId.id, - }, - }); - - const sandbox = handleResponse(result, "Failed to create sandbox"); - - return this.connectToSandbox(sandbox.id, () => { - if (sandbox.start_response) { - return Promise.resolve(sandbox.start_response); - } + async create(opts?: CreateSandboxOpts): Promise { + // We always want to start the VM in this context as our intention it to connect immediately, + // or return the session data to manually connect, for example in browser + const sandbox = await createSandbox(this.apiClient, true, opts); + const shouldReturnSessionOnly = opts?.autoConnect === false; + const session: SessionData = { + id: sandbox.id, + pitcher_token: sandbox.start_response.pitcher_token, + pitcher_url: sandbox.start_response.pitcher_url, + user_workspace_path: sandbox.start_response.user_workspace_path, + }; - return this.start(sandbox.id, opts); - }); + return shouldReturnSessionOnly ? session : this.connectToSandbox(session); } /** - * This is the same functionality as {@link SandboxClient.create}, but added to make forking more - * discoverable. + * Creates a sandbox by forking an existing sandbox reference. + * + * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} + * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. + * + * @param opts Additional options for creating the sandbox + * + * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM */ async fork( id: string, opts: { autoConnect: false } & Omit - ): Promise; + ): Promise; async fork( id: string, - opts?: { autoConnect?: true } & Omit + opts: { autoConnect: true } & Omit ): Promise; async fork( id: string, opts?: Omit - ): Promise; - async fork( - id: string, - opts: Omit = {} - ): Promise { + ): Promise { return this.create({ ...opts, template: id }); } @@ -539,25 +578,15 @@ export class SandboxClient { ); } - private async connectToSandbox( - id: string, - startVm: () => Promise< - Required< - Required< - Required>["data"] - >["data"] - >["data"] - > - ): Promise { + private async connectToSandbox(session: SessionData): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: id, + instanceId: session.id, onFocusChange() { return () => {}; }, requestPitcherInstance: async () => { - const data = await startVm(); const headers = this.apiClient.getConfig().headers as Headers; if (headers.get("x-pitcher-manager-url")) { @@ -573,24 +602,20 @@ export class SandboxClient { .baseUrl?.replace("api", "global-scheduler"); await fetch( - `${baseUrl}/api/v1/cluster/${data.id}?preferredManager=${preferredManager}` + `${baseUrl}/api/v1/cluster/${session.id}?preferredManager=${preferredManager}` ).then((res) => res.json()); } return { - bootupType: data.bootup_type as - | "RUNNING" - | "CLEAN" - | "RESUME" - | "FORK", - pitcherURL: data.pitcher_url, - workspacePath: data.workspace_path, - userWorkspacePath: data.user_workspace_path, - pitcherManagerVersion: data.pitcher_manager_version, - pitcherVersion: data.pitcher_version, - latestPitcherVersion: data.latest_pitcher_version, - pitcherToken: data.pitcher_token, - cluster: data.cluster, + bootupType: "RESUME", + pitcherURL: session.pitcher_url, + workspacePath: session.user_workspace_path, + userWorkspacePath: session.user_workspace_path, + pitcherManagerVersion: "1.0.0-session", + pitcherVersion: "1.0.0-session", + latestPitcherVersion: "1.0.0-session", + pitcherToken: session.pitcher_token, + cluster: "session", }; }, subscriptions: DEFAULT_SUBSCRIPTIONS, @@ -605,11 +630,11 @@ export class SandboxClient { sandboxId: string, sessionId: string, options: SessionCreateOptions & { autoConnect: false } - ): Promise; + ): Promise; public async createSession( sandboxId: string, sessionId: string, - options?: SessionCreateOptions & { autoConnect?: true } + options: SessionCreateOptions & { autoConnect: true } ): Promise; public async createSession( sandboxId: string, @@ -620,7 +645,7 @@ export class SandboxClient { sandboxId: string, sessionId: string, options: SessionCreateOptions = {} - ): Promise { + ): Promise { const response = await vmCreateSession({ client: this.apiClient, body: { @@ -637,32 +662,18 @@ export class SandboxClient { `Failed to create session ${sessionId}` ); + const session: SessionData = { + id: sandboxId, + pitcher_token: handledResponse.pitcher_token, + pitcher_url: handledResponse.pitcher_url, + user_workspace_path: handledResponse.user_workspace_path, + }; + if (options.autoConnect === false) { - return { - id: sandboxId, - pitcher_token: handledResponse.pitcher_token, - pitcher_url: handledResponse.pitcher_url, - user_workspace_path: handledResponse.user_workspace_path, - }; + return session; } - const connectedSandbox = await this.connectToSandbox(sandboxId, () => - Promise.resolve({ - bootup_type: "RESUME", - cluster: "session", - id: sandboxId, - latest_pitcher_version: "1.0.0-session", - pitcher_manager_version: "1.0.0-session", - pitcher_token: handledResponse.pitcher_token, - pitcher_url: handledResponse.pitcher_url, - pitcher_version: "1.0.0-session", - reconnect_token: "", - user_workspace_path: handledResponse.user_workspace_path, - workspace_path: handledResponse.user_workspace_path, - }) - ); - - return connectedSandbox; + return this.connectToSandbox(session); } /** diff --git a/src/sandbox.ts b/src/sandbox.ts index 432c6cb..717af85 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -14,7 +14,7 @@ import { Tasks } from "./tasks"; import type { SandboxClient, VMTier } from "."; import { Sessions } from "./sessions"; import { PreviewTokens } from "./preview-tokens"; -export { SessionConnectInfo } from "./sessions"; +export { SessionData } from "./sessions"; export { FSStatResult, diff --git a/src/sessions.ts b/src/sessions.ts index f42932d..e39a6bc 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -7,7 +7,7 @@ export interface SessionCreateOptions { autoConnect?: boolean; } -export type SessionConnectInfo = { +export type SessionData = { id: string; pitcher_token: string; pitcher_url: string; From bc90eab458a6d036778d8b2bb03a052a9a6ff374 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 6 Mar 2025 14:10:12 +0100 Subject: [PATCH 059/241] clean up --- src/sandbox-client.ts | 98 +++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 07241ab..5e0e433 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -24,6 +24,10 @@ import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; import { SessionCreateOptions, SessionData } from "./sessions"; +type SandboxForkResponseWithSession = SandboxForkResponse["data"] & { + start_response: Required["data"]; +}; + export type SandboxPrivacy = "public" | "unlisted" | "private"; export type SandboxInfo = { @@ -247,63 +251,15 @@ export type HandledResponse = { response: Response; }; -function getDefaultTemplate(client: Client) { - if (client.getConfig().baseUrl?.includes("codesandbox.stream")) { - return "7ngcrf"; - } - - return "pcz35m"; -} +export class SandboxClient { + get defaultTemplate() { + if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } -export async function createSandbox( - client: Client, - startVM: true, - opts?: CreateSandboxOpts -): Promise< - SandboxForkResponse["data"] & { - start_response: Required["data"]; + return "pcz35m"; } ->; -export async function createSandbox( - client: Client, - startVM: false, - opts?: CreateSandboxOpts -): Promise; -export async function createSandbox( - client: Client, - startVM: boolean, - opts?: CreateSandboxOpts -): Promise { - const templateId = opts?.template || getDefaultTemplate(client); - const privacy = opts?.privacy || "public"; - const tags = opts?.tags || ["sdk"]; - const path = opts?.path || "/SDK"; - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - // An empty object will still start the VM, but with server defaults - const startOptions = startOptionsFromOpts(opts) || {}; - - const result = await sandboxFork({ - client, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - // We only pass start options if we want to start the VM immediately - start_options: startVM ? startOptions : undefined, - }, - path: { - id: typeof templateId === "string" ? templateId : templateId.id, - }, - }); - return handleResponse(result, "Failed to create sandbox"); -} - -export class SandboxClient { constructor(private readonly apiClient: Client) {} /** @@ -369,9 +325,39 @@ export class SandboxClient { ): Promise; async create(opts?: CreateSandboxOpts): Promise; async create(opts?: CreateSandboxOpts): Promise { - // We always want to start the VM in this context as our intention it to connect immediately, + const templateId = opts?.template || this.defaultTemplate; + const privacy = opts?.privacy || "public"; + const tags = opts?.tags || ["sdk"]; + const path = opts?.path || "/SDK"; + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + // We always want to start the VM in this context as our intention is to connect immediately // or return the session data to manually connect, for example in browser - const sandbox = await createSandbox(this.apiClient, true, opts); + const startOptions = startOptionsFromOpts(opts) || {}; + + const result = await sandboxFork({ + client: this.apiClient, + body: { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + start_options: startOptions, + }, + path: { + id: typeof templateId === "string" ? templateId : templateId.id, + }, + }); + + const sandbox = handleResponse( + result, + "Failed to create sandbox" + // We currently always pass "start_options" to create a session + ) as SandboxForkResponseWithSession; + const shouldReturnSessionOnly = opts?.autoConnect === false; const session: SessionData = { id: sandbox.id, From 318a66c97c136ec721fb3e364b097117d1836d56 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Mon, 10 Mar 2025 16:25:20 +0100 Subject: [PATCH 060/241] chore: fix wrong example in docs (#78) --- src/shells.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shells.ts b/src/shells.ts index 29c20f7..6c9ee04 100644 --- a/src/shells.ts +++ b/src/shells.ts @@ -108,7 +108,7 @@ export class Shells extends Disposable { * ## Example * * ```ts - * const shell = await sandbox.shell.runCommand("echo 'Hello, world!'"); + * const shell = sandbox.shell.run("echo 'Hello, world!'"); * * shell.onOutput((data) => { * console.log(data); From 8c2ef897b03ce2d3f4865f6e68e2c2f07824852c Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 11 Mar 2025 14:15:50 +0100 Subject: [PATCH 061/241] feat: add support for configuring auto-wake behaviour (#79) --- openapi.json | 1154 +++++++++------------------------------ src/client/types.gen.ts | 30 +- src/sandbox-client.ts | 20 + 3 files changed, 319 insertions(+), 885 deletions(-) diff --git a/openapi.json b/openapi.json index dc15321..1e8441f 100644 --- a/openapi.json +++ b/openapi.json @@ -7,21 +7,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -33,21 +26,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -56,20 +42,13 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, + "sandbox_id": { "type": "string" }, "tokens": { - "items": { - "$ref": "#/components/schemas/PreviewToken" - }, + "items": { "$ref": "#/components/schemas/PreviewToken" }, "type": "array" } }, - "required": [ - "tokens", - "sandbox_id" - ], + "required": ["tokens", "sandbox_id"], "type": "object" } }, @@ -89,35 +68,18 @@ "type": "integer" } }, - "required": [ - "hibernation_timeout_seconds" - ], + "required": ["hibernation_timeout_seconds"], "title": "VMUpdateHibernationTimeoutRequest", "type": "object" }, "PreviewToken": { "properties": { - "expires_at": { - "nullable": true, - "type": "string" - }, - "last_used_at": { - "nullable": true, - "type": "string" - }, - "token_id": { - "type": "string" - }, - "token_prefix": { - "type": "string" - } + "expires_at": { "nullable": true, "type": "string" }, + "last_used_at": { "nullable": true, "type": "string" }, + "token_id": { "type": "string" }, + "token_prefix": { "type": "string" } }, - "required": [ - "expires_at", - "last_used_at", - "token_id", - "token_prefix" - ], + "required": ["expires_at", "last_used_at", "token_id", "token_prefix"], "title": "PreviewToken", "type": "object" }, @@ -128,32 +90,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -167,32 +117,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -201,37 +139,14 @@ }, "Sandbox": { "properties": { - "created_at": { - "format": "date-time", - "type": "string" - }, - "description": { - "nullable": true, - "type": "string" - }, - "id": { - "type": "string" - }, - "is_frozen": { - "type": "boolean" - }, - "privacy": { - "type": "integer" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - }, - "title": { - "nullable": true, - "type": "string" - }, - "updated_at": { - "format": "date-time", - "type": "string" - } + "created_at": { "format": "date-time", "type": "string" }, + "description": { "nullable": true, "type": "string" }, + "id": { "type": "string" }, + "is_frozen": { "type": "boolean" }, + "privacy": { "type": "integer" }, + "tags": { "items": { "type": "string" }, "type": "array" }, + "title": { "nullable": true, "type": "string" }, + "updated_at": { "format": "date-time", "type": "string" } }, "required": [ "id", @@ -246,20 +161,12 @@ }, "Error": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, - "VMHibernateRequest": { - "properties": {}, - "title": "VMHibernateRequest" - }, + "VMHibernateRequest": { "properties": {}, "title": "VMHibernateRequest" }, "PreviewTokenCreateRequest": { "properties": { "expires_at": { @@ -279,30 +186,21 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { "properties": { - "data": { - "$ref": "#/components/schemas/Sandbox" - } + "data": { "$ref": "#/components/schemas/Sandbox" } }, "type": "object" } @@ -336,6 +234,22 @@ "start_options": { "description": "Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation.", "properties": { + "automatic_wakeup_config": { + "description": "Configuration for when the VM should automatically wake up from hibernation", + "properties": { + "http": { + "default": true, + "description": "Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests)", + "type": "boolean" + }, + "websocket": { + "default": true, + "description": "Whether the VM should automatically wake up on WebSocket connections", + "type": "boolean" + } + }, + "type": "object" + }, "hibernation_timeout_seconds": { "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", "example": 300, @@ -368,9 +282,7 @@ "tags": { "default": [], "description": "Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "title": { @@ -390,21 +302,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -415,36 +320,23 @@ "properties": { "pagination": { "properties": { - "current_page": { - "type": "integer" - }, + "current_page": { "type": "integer" }, "next_page": { "description": "The number of the next page, if any. If `null`, the current page is the last page of records.", "nullable": true, "type": "integer" }, - "total_records": { - "type": "integer" - } + "total_records": { "type": "integer" } }, - "required": [ - "total_records", - "current_page", - "next_page" - ], + "required": ["total_records", "current_page", "next_page"], "type": "object" }, "sandboxes": { - "items": { - "$ref": "#/components/schemas/Sandbox" - }, + "items": { "$ref": "#/components/schemas/Sandbox" }, "type": "array" } }, - "required": [ - "sandboxes", - "pagination" - ], + "required": ["sandboxes", "pagination"], "type": "object" } }, @@ -459,48 +351,24 @@ "api": { "description": "Meta information about the CodeSandbox API", "properties": { - "latest_version": { - "type": "string" - }, - "name": { - "type": "string" - } + "latest_version": { "type": "string" }, + "name": { "type": "string" } }, - "required": [ - "name", - "latest_version" - ], + "required": ["name", "latest_version"], "type": "object" }, "auth": { "description": "Meta information about the current authentication context", "properties": { - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team": { - "format": "uuid", - "nullable": true, - "type": "string" - }, - "version": { - "type": "string" - } + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team": { "format": "uuid", "nullable": true, "type": "string" }, + "version": { "type": "string" } }, - "required": [ - "scopes", - "team", - "version" - ], + "required": ["scopes", "team", "version"], "type": "object" } }, - "required": [ - "api" - ], + "required": ["api"], "title": "MetaInformation", "type": "object" }, @@ -548,32 +416,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -587,21 +443,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -610,17 +459,10 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, - "token": { - "$ref": "#/components/schemas/PreviewToken" - } + "sandbox_id": { "type": "string" }, + "token": { "$ref": "#/components/schemas/PreviewToken" } }, - "required": [ - "sandbox_id", - "token" - ], + "required": ["sandbox_id", "token"], "type": "object" } }, @@ -637,21 +479,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -660,22 +495,11 @@ "properties": { "data": { "properties": { - "alias": { - "type": "string" - }, - "id": { - "type": "string" - }, - "title": { - "nullable": true, - "type": "string" - } + "alias": { "type": "string" }, + "id": { "type": "string" }, + "title": { "nullable": true, "type": "string" } }, - "required": [ - "alias", - "id", - "title" - ], + "required": ["alias", "id", "title"], "type": "object" } }, @@ -687,6 +511,22 @@ }, "VMStartRequest": { "properties": { + "automatic_wakeup_config": { + "description": "Configuration for when the VM should automatically wake up from hibernation", + "properties": { + "http": { + "default": true, + "description": "Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests)", + "type": "boolean" + }, + "websocket": { + "default": true, + "description": "Whether the VM should automatically wake up on WebSocket connections", + "type": "boolean" + } + }, + "type": "object" + }, "hibernation_timeout_seconds": { "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", "example": 300, @@ -729,10 +569,7 @@ "title": "PreviewTokenUpdateRequest", "type": "object" }, - "VMShutdownRequest": { - "properties": {}, - "title": "VMShutdownRequest" - }, + "VMShutdownRequest": { "properties": {}, "title": "VMShutdownRequest" }, "VMUpdateSpecsRequest": { "properties": { "tier": { @@ -749,9 +586,7 @@ "example": "Micro" } }, - "required": [ - "tier" - ], + "required": ["tier"], "title": "VMUpdateSpecsRequest", "type": "object" }, @@ -763,9 +598,7 @@ "type": "string" } }, - "required": [ - "name" - ], + "required": ["name"], "title": "WorkspaceCreateRequest", "type": "object" }, @@ -776,21 +609,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -799,39 +625,17 @@ "properties": { "data": { "properties": { - "bootup_type": { - "type": "string" - }, - "cluster": { - "type": "string" - }, - "id": { - "type": "string" - }, - "latest_pitcher_version": { - "type": "string" - }, - "pitcher_manager_version": { - "type": "string" - }, - "pitcher_token": { - "type": "string" - }, - "pitcher_url": { - "type": "string" - }, - "pitcher_version": { - "type": "string" - }, - "reconnect_token": { - "type": "string" - }, - "user_workspace_path": { - "type": "string" - }, - "workspace_path": { - "type": "string" - } + "bootup_type": { "type": "string" }, + "cluster": { "type": "string" }, + "id": { "type": "string" }, + "latest_pitcher_version": { "type": "string" }, + "pitcher_manager_version": { "type": "string" }, + "pitcher_token": { "type": "string" }, + "pitcher_url": { "type": "string" }, + "pitcher_version": { "type": "string" }, + "reconnect_token": { "type": "string" }, + "user_workspace_path": { "type": "string" }, + "workspace_path": { "type": "string" } }, "required": [ "bootup_type", @@ -862,21 +666,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -885,17 +682,10 @@ "properties": { "data": { "properties": { - "id": { - "type": "string" - }, - "tier": { - "type": "string" - } + "id": { "type": "string" }, + "tier": { "type": "string" } }, - "required": [ - "id", - "tier" - ], + "required": ["id", "tier"], "type": "object" } }, @@ -917,9 +707,7 @@ }, "external_resources": { "description": "Array of strings with external resources to load.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "files": { @@ -950,9 +738,7 @@ "type": "boolean" }, "npm_dependencies": { - "additionalProperties": { - "type": "string" - }, + "additionalProperties": { "type": "string" }, "description": "Map of dependencies and their version specifications.", "type": "object" }, @@ -971,18 +757,13 @@ "runtime": { "default": "browser", "description": "Runtime to use for the sandbox. Defaults to `\"browser\"`.", - "enum": [ - "browser", - "vm" - ], + "enum": ["browser", "vm"], "type": "string" }, "tags": { "default": [], "description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "template": { @@ -996,9 +777,7 @@ "type": "string" } }, - "required": [ - "files" - ], + "required": ["files"], "title": "SandboxCreateRequest" }, "TokenUpdateResponse": { @@ -1008,21 +787,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1031,34 +803,17 @@ "properties": { "data": { "properties": { - "description": { - "nullable": true, - "type": "string" - }, + "description": { "nullable": true, "type": "string" }, "expires_at": { "format": "date-time", "nullable": true, "type": "string" }, - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team_id": { - "type": "string" - }, - "token_id": { - "type": "string" - } + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team_id": { "type": "string" }, + "token_id": { "type": "string" } }, - "required": [ - "description", - "scopes", - "team_id", - "token_id" - ], + "required": ["description", "scopes", "team_id", "token_id"], "type": "object" } }, @@ -1075,21 +830,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1098,29 +846,12 @@ "properties": { "data": { "properties": { - "description": { - "nullable": true, - "type": "string" - }, - "expires_at": { - "nullable": true, - "type": "string" - }, - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team_id": { - "type": "string" - }, - "token": { - "type": "string" - }, - "token_id": { - "type": "string" - } + "description": { "nullable": true, "type": "string" }, + "expires_at": { "nullable": true, "type": "string" }, + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team_id": { "type": "string" }, + "token": { "type": "string" }, + "token_id": { "type": "string" } }, "required": [ "description", @@ -1178,10 +909,7 @@ "properties": { "permission": { "description": "Permission level for the session", - "enum": [ - "read", - "write" - ], + "enum": ["read", "write"], "example": "write", "type": "string" }, @@ -1192,10 +920,7 @@ "type": "string" } }, - "required": [ - "session_id", - "permission" - ], + "required": ["session_id", "permission"], "title": "VMCreateSessionRequest", "type": "object" }, @@ -1206,21 +931,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1231,9 +949,7 @@ "properties": { "capabilities": { "description": "List of capabilities that Pitcher has", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "permissions": { @@ -1281,21 +997,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1304,17 +1013,10 @@ "properties": { "data": { "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + "id": { "type": "string" }, + "name": { "type": "string" } }, - "required": [ - "id", - "name" - ], + "required": ["id", "name"], "type": "object" } }, @@ -1331,21 +1033,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1354,17 +1049,10 @@ "properties": { "data": { "properties": { - "hibernation_timeout_seconds": { - "type": "integer" - }, - "id": { - "type": "string" - } + "hibernation_timeout_seconds": { "type": "integer" }, + "id": { "type": "string" } }, - "required": [ - "id", - "hibernation_timeout_seconds" - ], + "required": ["id", "hibernation_timeout_seconds"], "type": "object" } }, @@ -1381,21 +1069,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1404,49 +1085,23 @@ "properties": { "data": { "properties": { - "alias": { - "type": "string" - }, - "id": { - "type": "string" - }, + "alias": { "type": "string" }, + "id": { "type": "string" }, "start_response": { "description": "VM start response. Only present when start_options were provided in the request.", "nullable": true, "properties": { - "bootup_type": { - "type": "string" - }, - "cluster": { - "type": "string" - }, - "id": { - "type": "string" - }, - "latest_pitcher_version": { - "type": "string" - }, - "pitcher_manager_version": { - "type": "string" - }, - "pitcher_token": { - "type": "string" - }, - "pitcher_url": { - "type": "string" - }, - "pitcher_version": { - "type": "string" - }, - "reconnect_token": { - "type": "string" - }, - "user_workspace_path": { - "type": "string" - }, - "workspace_path": { - "type": "string" - } + "bootup_type": { "type": "string" }, + "cluster": { "type": "string" }, + "id": { "type": "string" }, + "latest_pitcher_version": { "type": "string" }, + "pitcher_manager_version": { "type": "string" }, + "pitcher_token": { "type": "string" }, + "pitcher_url": { "type": "string" }, + "pitcher_version": { "type": "string" }, + "reconnect_token": { "type": "string" }, + "user_workspace_path": { "type": "string" }, + "workspace_path": { "type": "string" } }, "required": [ "bootup_type", @@ -1463,16 +1118,9 @@ ], "type": "object" }, - "title": { - "nullable": true, - "type": "string" - } + "title": { "nullable": true, "type": "string" } }, - "required": [ - "alias", - "id", - "title" - ], + "required": ["alias", "id", "title"], "type": "object" } }, @@ -1489,21 +1137,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1512,27 +1153,18 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, + "sandbox_id": { "type": "string" }, "token": { "allOf": [ { "properties": { - "expires_at": { - "nullable": true, - "type": "string" - }, + "expires_at": { "nullable": true, "type": "string" }, "last_used_at": { "nullable": true, "type": "string" }, - "token_id": { - "type": "string" - }, - "token_prefix": { - "type": "string" - } + "token_id": { "type": "string" }, + "token_prefix": { "type": "string" } }, "required": [ "expires_at", @@ -1544,24 +1176,15 @@ "type": "object" }, { - "properties": { - "token": { - "type": "string" - } - }, - "required": [ - "token" - ], + "properties": { "token": { "type": "string" } }, + "required": ["token"], "type": "object" } ], "type": "object" } }, - "required": [ - "sandbox_id", - "token" - ], + "required": ["sandbox_id", "token"], "type": "object" } }, @@ -1573,16 +1196,10 @@ } }, "securitySchemes": { - "authorization": { - "scheme": "bearer", - "type": "http" - } + "authorization": { "scheme": "bearer", "type": "http" } } }, - "info": { - "title": "CodeSandbox API", - "version": "2023-07-01" - }, + "info": { "title": "CodeSandbox API", "version": "2023-07-01" }, "openapi": "3.0.0", "paths": { "/meta/info": { @@ -1594,21 +1211,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/MetaInformation" - } + "schema": { "$ref": "#/components/schemas/MetaInformation" } } }, "description": "Meta Info Response" } }, - "security": [ - {} - ], + "security": [{}], "summary": "Metadata about the API", - "tags": [ - "meta" - ] + "tags": ["meta"] } }, "/org/workspace": { @@ -1640,13 +1251,7 @@ "description": "Workspace Create Response" } }, - "security": [ - { - "authorization": [ - "workspace:create" - ] - } - ], + "security": [{ "authorization": ["workspace:create"] }], "summary": "Create a Workspace", "tags": [] } @@ -1663,17 +1268,13 @@ "in": "path", "name": "team_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenCreateRequest" - } + "schema": { "$ref": "#/components/schemas/TokenCreateRequest" } } }, "description": "Token Create Request", @@ -1683,21 +1284,13 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenCreateResponse" - } + "schema": { "$ref": "#/components/schemas/TokenCreateResponse" } } }, "description": "Token Create Response" } }, - "security": [ - { - "authorization": [ - "token:manage" - ] - } - ], + "security": [{ "authorization": ["token:manage"] }], "summary": "Create an API Token", "tags": [] } @@ -1714,9 +1307,7 @@ "in": "path", "name": "team_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "ID of token to update", @@ -1724,17 +1315,13 @@ "in": "path", "name": "token_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenUpdateRequest" - } + "schema": { "$ref": "#/components/schemas/TokenUpdateRequest" } } }, "description": "Token Update Request", @@ -1744,21 +1331,13 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenUpdateResponse" - } + "schema": { "$ref": "#/components/schemas/TokenUpdateResponse" } } }, "description": "Token Update Response" } }, - "security": [ - { - "authorization": [ - "token:manage" - ] - } - ], + "security": [{ "authorization": ["token:manage"] }], "summary": "Update an API Token", "tags": [] } @@ -1775,9 +1354,7 @@ "in": "query", "name": "tags", "required": false, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "Field to order results by", @@ -1787,10 +1364,7 @@ "required": false, "schema": { "default": "updated_at", - "enum": [ - "inserted_at", - "updated_at" - ] + "enum": ["inserted_at", "updated_at"] } }, { @@ -1799,13 +1373,7 @@ "in": "query", "name": "direction", "required": false, - "schema": { - "default": "desc", - "enum": [ - "asc", - "desc" - ] - } + "schema": { "default": "desc", "enum": ["asc", "desc"] } }, { "description": "Maximum number of sandboxes to return in a single response", @@ -1824,47 +1392,29 @@ "in": "query", "name": "page", "required": false, - "schema": { - "default": 1, - "minimum": 1, - "type": "integer" - } + "schema": { "default": 1, "minimum": 1, "type": "integer" } }, { "description": "If true, only returns VMs for which a heartbeat was received in the last 30 seconds.", "in": "query", "name": "status", "required": false, - "schema": { - "enum": [ - "running" - ] - } + "schema": { "enum": ["running"] } } ], "responses": { "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxListResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxListResponse" } } }, "description": "Sandbox List Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read" - ] - } - ], + "security": [{ "authorization": ["sandbox:read"] }], "summary": "List Sandboxes", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] }, "post": { "callbacks": {}, @@ -1874,9 +1424,7 @@ "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxCreateRequest" - } + "schema": { "$ref": "#/components/schemas/SandboxCreateRequest" } } }, "description": "Sandbox Create Request", @@ -1894,17 +1442,9 @@ "description": "Sandbox Create Response" } }, - "security": [ - { - "authorization": [ - "sandbox:create" - ] - } - ], + "security": [{ "authorization": ["sandbox:create"] }], "summary": "Create a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}": { @@ -1919,34 +1459,22 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxGetResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxGetResponse" } } }, "description": "Sandbox Get Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read" - ] - } - ], + "security": [{ "authorization": ["sandbox:read"] }], "summary": "Get a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}/fork": { @@ -1961,17 +1489,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxForkRequest" - } + "schema": { "$ref": "#/components/schemas/SandboxForkRequest" } } }, "description": "Sandbox Fork Request", @@ -1981,25 +1505,15 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxForkResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxForkResponse" } } }, "description": "Sandbox Fork Response" } }, - "security": [ - { - "authorization": [ - "sandbox:create" - ] - } - ], + "security": [{ "authorization": ["sandbox:create"] }], "summary": "Fork a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}/tokens": { @@ -2014,9 +1528,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { @@ -2028,16 +1540,10 @@ } } }, - "description": "RevokeALlPreviewTokensResponse" + "description": "RevokeAllPreviewTokensResponse" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Revoke preview tokens", "tags": [] }, @@ -2052,9 +1558,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { @@ -2069,13 +1573,7 @@ "description": "Token List Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "List Preview Tokens", "tags": [] }, @@ -2090,9 +1588,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2118,13 +1614,7 @@ "description": "Token Create Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Create a Preview Token", "tags": [] } @@ -2141,9 +1631,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "ID of the token to update. Does not accept the token itself.", @@ -2151,9 +1639,7 @@ "in": "path", "name": "token_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2179,13 +1665,7 @@ "description": "Token Update Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Update a Preview Token", "tags": [] } @@ -2202,17 +1682,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMHibernateRequest" - } + "schema": { "$ref": "#/components/schemas/VMHibernateRequest" } } }, "description": "VM Hibernate Request", @@ -2222,25 +1698,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMHibernateResponse" - } + "schema": { "$ref": "#/components/schemas/VMHibernateResponse" } } }, "description": "VM Hibernate Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Hibernate a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/hibernation_timeout": { @@ -2255,9 +1721,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2283,17 +1747,9 @@ "description": "VM Update Hibernation Timeout Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Hibernation Timeout", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/sessions": { @@ -2308,9 +1764,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2336,17 +1790,9 @@ "description": "VM Create Session Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Create a new session on a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/shutdown": { @@ -2361,17 +1807,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMShutdownRequest" - } + "schema": { "$ref": "#/components/schemas/VMShutdownRequest" } } }, "description": "VM Shutdown Request", @@ -2381,25 +1823,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMShutdownResponse" - } + "schema": { "$ref": "#/components/schemas/VMShutdownResponse" } } }, "description": "VM Shutdown Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Shutdown a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/specs": { @@ -2414,17 +1846,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMUpdateSpecsRequest" - } + "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } } }, "description": "VM Update Specs Request", @@ -2442,17 +1870,9 @@ "description": "VM Update Specs Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Specs", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/start": { @@ -2467,17 +1887,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMStartRequest" - } + "schema": { "$ref": "#/components/schemas/VMStartRequest" } } }, "description": "VM Start Request", @@ -2487,26 +1903,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMStartResponse" - } + "schema": { "$ref": "#/components/schemas/VMStartResponse" } } }, "description": "VM Start Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read", - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["sandbox:read", "vm:manage"] }], "summary": "Start a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/update_specs": { @@ -2521,17 +1926,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMUpdateSpecsRequest" - } + "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } } }, "description": "VM Update Specs Request", @@ -2549,26 +1950,13 @@ "description": "VM Update Specs Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Specs", - "tags": [ - "vm" - ] + "tags": ["vm"] } } }, "security": [], - "servers": [ - { - "url": "https://api.codesandbox.stream", - "variables": {} - } - ], + "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], "tags": [] -} \ No newline at end of file +} diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index a891ef6..ce3c5b4 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -114,6 +114,19 @@ export type SandboxForkRequest = { * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. */ start_options?: { + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; /** * The time in seconds after which the VM will hibernate due to inactivity. * Must be a positive integer between 1 and 86400 (24 hours). @@ -241,6 +254,19 @@ export type SandboxCreateResponse = { }; export type VmStartRequest = { + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; /** * The time in seconds after which the VM will hibernate due to inactivity. * Must be a positive integer between 1 and 86400 (24 hours). @@ -556,7 +582,7 @@ export type PreviewTokenCreateResponse = { token_id: string; token_prefix: string; } & { - token?: string; + token: string; }; }; }; @@ -767,7 +793,7 @@ export type PreviewTokenRevokeAllData = { export type PreviewTokenRevokeAllResponses = { /** - * RevokeALlPreviewTokensResponse + * RevokeAllPreviewTokensResponse */ 200: PreviewTokenRevokeAllResponse; }; diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 1597d7d..49355cc 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -205,6 +205,7 @@ function startOptionsFromOpts(opts: StartSandboxOpts | undefined) { ipcountry: opts.ipcountry, tier: opts.vmTier?.name, hibernation_timeout_seconds: opts.hibernationTimeoutSeconds, + automatic_wakeup_config: opts.automaticWakeupConfig, }; } @@ -233,6 +234,25 @@ export interface StartSandboxOpts { * Defaults to 300 seconds for free users, 1800 seconds for pro users. Maximum is 86400 seconds (1 day). */ hibernationTimeoutSeconds?: number; + + /** + * Configuration for when the VM should automatically wake up from hibernation. + */ + automaticWakeupConfig?: { + /** + * Whether the VM should automatically wake up on HTTP requests to preview URLs (excludes WebSocket requests) + * + * @default true + */ + http: boolean; + + /** + * Whether the VM should automatically wake up on WebSocket connections to preview URLs + * + * @default false + */ + websocket: boolean; + }; } export type HandledResponse = { From 6aa5387d9cb457f943d416c4e62af9079b2a8ef4 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 11 Mar 2025 14:16:54 +0100 Subject: [PATCH 062/241] chore(main): release 0.11.0 (#80) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f8cfb1..60a43d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.11.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.10.0...v0.11.0) (2025-03-11) + + +### Features + +* add support for configuring auto-wake behaviour ([#79](https://github.com/codesandbox/codesandbox-sdk/issues/79)) ([8c2ef89](https://github.com/codesandbox/codesandbox-sdk/commit/8c2ef897b03ce2d3f4865f6e68e2c2f07824852c)) + ## [0.10.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.9.0...v0.10.0) (2025-02-28) diff --git a/package-lock.json b/package-lock.json index a3938b6..0280f2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.10.0", + "version": "0.11.0", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index 8d1a34f..d170deb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.10.0", + "version": "0.11.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From ca1f82582d2f12b84cdf379902c596e6d5bfed52 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 13 Mar 2025 16:21:37 +0100 Subject: [PATCH 063/241] fix: support `keepActiveWhileConnected` for browser sessions (#81) --- src/sandbox.ts | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/sandbox.ts b/src/sandbox.ts index 432c6cb..2e59c15 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -183,6 +183,30 @@ export class SandboxSession extends Disposable { this.pitcherClient.disconnect(); this.dispose(); } + + private keepAliveInterval: NodeJS.Timeout | null = null; + /** + * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. + */ + public keepActiveWhileConnected(enabled: boolean) { + if (enabled && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(() => { + this.pitcherClient.clients.system.update(); + }, 1000 * 30); + + this.onWillDispose(() => { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + }); + } else { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + } + } } export class Sandbox extends SandboxSession { @@ -281,28 +305,4 @@ export class Sandbox extends SandboxSession { public async updateHibernationTimeout(timeoutSeconds: number): Promise { await this.sandboxClient.updateHibernationTimeout(this.id, timeoutSeconds); } - - private keepAliveInterval: NodeJS.Timeout | null = null; - /** - * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. - */ - public keepActiveWhileConnected(enabled: boolean) { - if (enabled && !this.keepAliveInterval) { - this.keepAliveInterval = setInterval(() => { - this.pitcherClient.clients.system.update(); - }, 1000 * 30); - - this.onWillDispose(() => { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - }); - } else { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - } - } } From cb67ca6f2a7c35bdb55afdf06304244b62d5c5f6 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 13 Mar 2025 16:22:16 +0100 Subject: [PATCH 064/241] chore(main): release 0.11.1 (#82) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a43d7..a1eb352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.11.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.0...v0.11.1) (2025-03-13) + + +### Bug Fixes + +* support `keepActiveWhileConnected` for browser sessions ([#81](https://github.com/codesandbox/codesandbox-sdk/issues/81)) ([ca1f825](https://github.com/codesandbox/codesandbox-sdk/commit/ca1f82582d2f12b84cdf379902c596e6d5bfed52)) + ## [0.11.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.10.0...v0.11.0) (2025-03-11) diff --git a/package-lock.json b/package-lock.json index 0280f2f..46fdf98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.11.0", + "version": "0.11.1", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index d170deb..5d5bdba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.11.0", + "version": "0.11.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 157691a4e70112de34475ef084aad1eab9ca7f7a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 2 Apr 2025 15:10:53 +0200 Subject: [PATCH 065/241] tend to feedback and add full fs spec --- openapi-sandbox-fs.json | 2005 +++++++++++++++++ openapi-sandbox.json | 239 -- package.json | 7 +- .../client.gen.ts | 0 src/{client-rest => client-rest-fs}/index.ts | 0 src/client-rest-fs/sdk.gen.ts | 280 +++ src/client-rest-fs/types.gen.ts | 1058 +++++++++ src/client-rest/sdk.gen.ts | 29 - src/client-rest/types.gen.ts | 116 - src/filesystem-rest.ts | 29 - src/index.ts | 33 +- src/rest-client.ts | 84 - src/sandbox-client.ts | 51 +- src/sandbox-rest-client.ts | 78 + src/sandbox-rest-filesystem.ts | 34 + 15 files changed, 3510 insertions(+), 533 deletions(-) create mode 100644 openapi-sandbox-fs.json delete mode 100644 openapi-sandbox.json rename src/{client-rest => client-rest-fs}/client.gen.ts (100%) rename src/{client-rest => client-rest-fs}/index.ts (100%) create mode 100644 src/client-rest-fs/sdk.gen.ts create mode 100644 src/client-rest-fs/types.gen.ts delete mode 100644 src/client-rest/sdk.gen.ts delete mode 100644 src/client-rest/types.gen.ts delete mode 100644 src/filesystem-rest.ts delete mode 100644 src/rest-client.ts create mode 100644 src/sandbox-rest-client.ts create mode 100644 src/sandbox-rest-filesystem.ts diff --git a/openapi-sandbox-fs.json b/openapi-sandbox-fs.json new file mode 100644 index 0000000..d7dd0a6 --- /dev/null +++ b/openapi-sandbox-fs.json @@ -0,0 +1,2005 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Rest FS API", + "description": "FS API for interacting with sandbox", + "version": "1.0.0" + }, + "paths": { + "/fs/writefile": { + "post": { + "summary": "Write to a file", + "description": "Write content to a file at the specified path", + "operationId": "writeFile", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WriteFileRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error writing file", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/read": { + "post": { + "summary": "Read file system", + "description": "Retrieve the latest snapshot of the server's MemoryFS file and children list", + "operationId": "fsRead", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSReadResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error reading file system", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/operation": { + "post": { + "summary": "Perform file system operation", + "description": "Send a tree operation reflecting filesystem operations", + "operationId": "fsOperation", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSOperationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSOperationResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error performing operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/search": { + "post": { + "summary": "Search files", + "description": "Search for content in files", + "operationId": "fsSearch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSSearchParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error searching files", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/streamingSearch": { + "post": { + "summary": "Start streaming search", + "description": "Start a streaming search for content in files", + "operationId": "fsStreamingSearch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSStreamingSearchParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "searchId": { + "type": "string", + "description": "ID of the search operation" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error starting streaming search", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/cancelStreamingSearch": { + "post": { + "summary": "Cancel streaming search", + "description": "Cancel an ongoing streaming search", + "operationId": "fsCancelStreamingSearch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "searchId": { + "type": "string", + "description": "ID of the search to cancel" + } + }, + "required": ["searchId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "searchId": { + "type": "string", + "description": "ID of the cancelled search" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error cancelling search", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/pathSearch": { + "post": { + "summary": "Search file paths", + "description": "Search for file paths matching a pattern", + "operationId": "fsPathSearch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PathSearchParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/PathSearchResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error searching paths", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/upload": { + "post": { + "summary": "Upload file", + "description": "Upload a file to the specified parent directory", + "operationId": "fsUpload", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parentId": { + "type": "string", + "description": "ID of the parent directory" + }, + "filename": { + "type": "string", + "description": "Name of the file to create" + }, + "content": { + "type": "string", + "format": "binary", + "description": "File content as binary data" + } + }, + "required": ["parentId", "filename", "content"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "fileId": { + "type": "string", + "description": "ID of the created file" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error uploading file", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/InvalidIdError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/download": { + "post": { + "summary": "Download files", + "description": "Download files at a specified path as a zip", + "operationId": "fsDownload", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to download" + }, + "excludes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns of files/folders to exclude from the download" + } + }, + "required": ["path"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "downloadUrl": { + "type": "string", + "description": "URL to download the files from" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error creating download", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/DefaultError" + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/readFile": { + "post": { + "summary": "Read file content", + "description": "Read the content of a file at the specified path", + "operationId": "fsReadFile", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSReadFileParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSReadFileResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error reading file", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/readdir": { + "post": { + "summary": "Read directory contents", + "description": "List the contents of a directory at the specified path", + "operationId": "fsReadDir", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSReadDirParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSReadDirResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error reading directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/stat": { + "post": { + "summary": "Get file/directory stats", + "description": "Get stats for a file or directory at the specified path", + "operationId": "fsStat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSStatParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSStatResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error getting stats", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/copy": { + "post": { + "summary": "Copy file/directory", + "description": "Copy a file or directory from one location to another", + "operationId": "fsCopy", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSCopyParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error copying file/directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/rename": { + "post": { + "summary": "Rename file/directory", + "description": "Rename a file or directory (move from one location to another)", + "operationId": "fsRename", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSRenameParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error renaming file/directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/remove": { + "post": { + "summary": "Remove file/directory", + "description": "Delete a file or directory at the specified path", + "operationId": "fsRemove", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSRemoveParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error removing file/directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/mkdir": { + "post": { + "summary": "Create directory", + "description": "Create a new directory at the specified path", + "operationId": "fsMkdir", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSMkdirParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error creating directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/watch": { + "post": { + "summary": "Watch file/directory", + "description": "Watch a file or directory for changes", + "operationId": "fsWatch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSWatchParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FSWatchResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error watching file/directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/fs/unwatch": { + "post": { + "summary": "Stop watching file/directory", + "description": "Stop watching a file or directory for changes", + "operationId": "fsUnwatch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FSUnwatchParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error unwatching file/directory", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "oneOf": [ + { + "$ref": "#/components/schemas/DefaultError" + }, + { + "$ref": "#/components/schemas/RawFsError" + } + ], + "discriminator": { + "propertyName": "code" + } + } + }, + "required": ["status", "error"] + }, + "DefaultError": { + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/PitcherErrorCode", + "description": "Error code identifying the type of error" + }, + "data": { + "type": "object", + "description": "Additional error details", + "nullable": true + }, + "publicMessage": { + "type": "string", + "description": "Human-readable error message that can be displayed to users", + "nullable": true + } + }, + "required": ["code"] + }, + "RawFsError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [102], + "description": "RAWFS_ERROR code" + }, + "data": { + "type": "object", + "properties": { + "errno": { + "type": ["number", "null"], + "description": "File system error number, or null if not available" + } + }, + "required": ["errno"] + }, + "publicMessage": { + "type": "string", + "description": "Human-readable error message that can be displayed to users", + "nullable": true + } + }, + "required": ["code", "data"] + }, + "PitcherErrorCode": { + "type": "integer", + "description": "Enumeration of error codes", + "enum": [ + 0, 1, 2, 3, 100, 101, 102, 200, 201, 204, 300, 400, 404, 410, 420, + 430, 440, 450, 460, 470, 500, 600, 601, 602, 704, 800, 801, 802, 803, + 814 + ], + "x-enum-descriptions": [ + "CRITICAL_ERROR", + "FEATURE_UNAVAILABLE", + "NO_ACCESS", + "RATE_LIMIT", + "INVALID_ID", + "INVALID_PATH", + "RAWFS_ERROR", + "SHELL_NOT_ACCESSIBLE", + "SHELL_CLOSED", + "SHELL_NOT_FOUND", + "MODEL_NOT_FOUND", + "GIT_OPERATION_IN_PROGRESS", + "GIT_REMOTE_FILE_NOT_FOUND", + "GIT_FETCH_FAIL", + "GIT_PULL_CONFLICT", + "GIT_RESET_LOCAL_REMOTE_ERROR", + "GIT_PUSH_FAIL", + "GIT_RESET_CHECKOUT_INITIAL_BRANCH_FAIL", + "GIT_PULL_FAIL", + "GIT_TRANSPOSE_LINES_FAIL", + "CHANNEL_NOT_FOUND", + "CONFIG_FILE_ALREADY_EXISTS", + "TASK_NOT_FOUND", + "COMMAND_ALREADY_CONFIGURED", + "COMMAND_NOT_FOUND", + "AI_NOT_AVAILABLE", + "PROMPT_TOO_BIG", + "FAILED_TO_RESPOND", + "AI_TOO_FREQUENT_REQUESTS", + "AI_CHAT_NOT_FOUND" + ] + }, + "WriteFileRequest": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path to write to" + }, + "content": { + "type": "string", + "format": "binary", + "description": "File content as binary data (Uint8Array)" + }, + "create": { + "type": "boolean", + "description": "Whether to create the file if it doesn't exist", + "default": false + }, + "overwrite": { + "type": "boolean", + "description": "Whether to overwrite the file if it exists", + "default": false + } + }, + "required": ["path", "content"] + }, + "FSReadResult": { + "type": "object", + "properties": { + "treeNodes": { + "type": "array", + "items": { + "type": "object", + "description": "JSON representation of a node in the file system" + } + }, + "clock": { + "type": "number", + "description": "Current clock value for the file system" + } + }, + "required": ["treeNodes", "clock"] + }, + "FSOperationRequest": { + "type": "object", + "properties": { + "operation": { + "$ref": "#/components/schemas/FSOperation" + } + }, + "required": ["operation"] + }, + "FSOperation": { + "oneOf": [ + { + "$ref": "#/components/schemas/FSCreateOperation" + }, + { + "$ref": "#/components/schemas/FSDeleteOperation" + }, + { + "$ref": "#/components/schemas/FSMoveOperation" + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "FSCreateOperation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["create"] + }, + "parentId": { + "type": "string", + "description": "ID of the parent directory" + }, + "newEntry": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the new entry" + }, + "type": { + "type": "string", + "enum": ["directory", "file"], + "description": "Type of the node" + }, + "name": { + "type": "string", + "description": "Name of the new entry" + } + }, + "required": ["id", "type", "name"] + } + }, + "required": ["type", "parentId", "newEntry"] + }, + "FSDeleteOperation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["delete"] + }, + "id": { + "type": "string", + "description": "ID of the entry to delete" + } + }, + "required": ["type", "id"] + }, + "FSMoveOperation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["move"] + }, + "id": { + "type": "string", + "description": "ID of the entry to move" + }, + "parentId": { + "type": "string", + "description": "ID of the new parent directory", + "nullable": true + }, + "name": { + "type": "string", + "description": "New name for the entry", + "nullable": true + } + }, + "required": ["type", "id"] + }, + "FSOperationResult": { + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [0], + "description": "Success code" + }, + "clock": { + "type": "number", + "description": "Current clock value" + } + }, + "required": ["code", "clock"] + }, + { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [1], + "description": "Ignored code" + } + }, + "required": ["code"] + } + ], + "discriminator": { + "propertyName": "code" + } + }, + "FSSearchParams": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Text to search for" + }, + "glob": { + "type": "string", + "description": "Glob pattern to filter files", + "nullable": true + }, + "isRegex": { + "type": "boolean", + "description": "Whether to treat the search text as a regular expression", + "nullable": true + }, + "caseSensitivity": { + "type": "string", + "enum": ["smart", "enabled", "disabled"], + "description": "Case sensitivity setting for the search", + "nullable": true + } + }, + "required": ["text"] + }, + "SearchResult": { + "type": "object", + "properties": { + "fileId": { + "type": "string", + "description": "ID of the file containing the match" + }, + "lines": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Text of the line containing the match" + } + }, + "required": ["text"] + }, + "lineNumber": { + "type": "integer", + "description": "Line number of the match" + }, + "absoluteOffset": { + "type": "integer", + "description": "Absolute offset of the match in the file" + }, + "submatches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchSubMatch" + } + } + }, + "required": [ + "fileId", + "lines", + "lineNumber", + "absoluteOffset", + "submatches" + ] + }, + "SearchSubMatch": { + "type": "object", + "properties": { + "match": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Matched text" + } + }, + "required": ["text"] + }, + "start": { + "type": "integer", + "description": "Start position of the match" + }, + "end": { + "type": "integer", + "description": "End position of the match" + } + }, + "required": ["match", "start", "end"] + }, + "FSStreamingSearchParams": { + "type": "object", + "properties": { + "searchId": { + "type": "string", + "description": "ID for the search operation" + }, + "text": { + "type": "string", + "description": "Text to search for" + }, + "glob": { + "type": "string", + "description": "Glob pattern to filter files", + "nullable": true + }, + "isRegex": { + "type": "boolean", + "description": "Whether to treat the search text as a regular expression", + "nullable": true + }, + "caseSensitivity": { + "type": "string", + "enum": ["smart", "enabled", "disabled"], + "description": "Case sensitivity setting for the search", + "nullable": true + }, + "maxResults": { + "type": "integer", + "description": "Maximum number of results to return (default: 10,000)", + "nullable": true + } + }, + "required": ["searchId", "text"] + }, + "PathSearchParams": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Text to search for in file paths" + } + }, + "required": ["text"] + }, + "PathSearchResult": { + "type": "object", + "properties": { + "matches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PathSearchMatch" + } + } + }, + "required": ["matches"] + }, + "PathSearchMatch": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path that matched the search" + }, + "submatches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchSubMatch" + } + } + }, + "required": ["path", "submatches"] + }, + "InvalidIdError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [100], + "description": "INVALID_ID error code" + } + }, + "required": ["code"] + }, + "FSReadFileParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the file to read" + } + }, + "required": ["path"] + }, + "FSReadFileResult": { + "type": "object", + "properties": { + "content": { + "type": "string", + "format": "binary", + "description": "File content as binary data" + } + }, + "required": ["content"] + }, + "FSReadDirParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the directory to read" + } + }, + "required": ["path"] + }, + "FSReadDirResult": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the entry" + }, + "type": { + "type": "string", + "enum": ["directory", "file"], + "description": "Type of the entry" + }, + "isSymlink": { + "type": "boolean", + "description": "Whether the entry is a symlink" + } + }, + "required": ["name", "type", "isSymlink"] + } + } + }, + "required": ["entries"] + }, + "FSStatParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to the file or directory to stat" + } + }, + "required": ["path"] + }, + "FSStatResult": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["directory", "file"], + "description": "Type of the entry" + }, + "isSymlink": { + "type": "boolean", + "description": "Whether the entry is a symlink" + }, + "size": { + "type": "integer", + "description": "Size of the file in bytes" + }, + "mtime": { + "type": "integer", + "description": "Last modified time" + }, + "ctime": { + "type": "integer", + "description": "Creation time" + }, + "atime": { + "type": "integer", + "description": "Last accessed time" + } + }, + "required": ["type", "isSymlink", "size", "mtime", "ctime", "atime"] + }, + "FSCopyParams": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Path to copy from" + }, + "to": { + "type": "string", + "description": "Path to copy to" + }, + "recursive": { + "type": "boolean", + "description": "Whether to copy directories recursively", + "nullable": true + }, + "overwrite": { + "type": "boolean", + "description": "Whether to overwrite existing files", + "nullable": true + } + }, + "required": ["from", "to"] + }, + "FSRenameParams": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Path to rename from" + }, + "to": { + "type": "string", + "description": "Path to rename to" + }, + "overwrite": { + "type": "boolean", + "description": "Whether to overwrite existing files", + "nullable": true + } + }, + "required": ["from", "to"] + }, + "FSRemoveParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to remove" + }, + "recursive": { + "type": "boolean", + "description": "Whether to remove directories recursively", + "nullable": true + } + }, + "required": ["path"] + }, + "FSMkdirParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to create directory at" + }, + "recursive": { + "type": "boolean", + "description": "Whether to create parent directories if they don't exist", + "nullable": true + } + }, + "required": ["path"] + }, + "FSWatchParams": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to watch" + }, + "recursive": { + "type": "boolean", + "description": "Whether to watch directories recursively", + "nullable": true + }, + "excludes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns to exclude from watching", + "nullable": true + } + }, + "required": ["path"] + }, + "FSWatchResult": { + "type": "object", + "properties": { + "watchId": { + "type": "string", + "description": "ID of the watch" + } + }, + "required": ["watchId"] + }, + "FSUnwatchParams": { + "type": "object", + "properties": { + "watchId": { + "type": "string", + "description": "ID of the watch to stop" + } + }, + "required": ["watchId"] + } + } + } +} diff --git a/openapi-sandbox.json b/openapi-sandbox.json deleted file mode 100644 index 5f63dc2..0000000 --- a/openapi-sandbox.json +++ /dev/null @@ -1,239 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox API", - "description": "API for interacting with sandbox", - "version": "1.0.0" - }, - "paths": { - "/fs/writefile": { - "post": { - "summary": "Write to a file", - "description": "Write content to a file at the specified path", - "operationId": "writeFile", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WriteFileRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error writing file", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - }, - "required": ["status", "error"] - }, - "DefaultError": { - "type": "object", - "properties": { - "code": { - "$ref": "#/components/schemas/PitcherErrorCode", - "description": "Error code identifying the type of error" - }, - "data": { - "type": "object", - "description": "Additional error details", - "nullable": true - }, - "publicMessage": { - "type": "string", - "description": "Human-readable error message that can be displayed to users", - "nullable": true - } - }, - "required": ["code"] - }, - "RawFsError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [102], - "description": "RAWFS_ERROR code" - }, - "data": { - "type": "object", - "properties": { - "errno": { - "type": ["number", "null"], - "description": "File system error number, or null if not available" - } - }, - "required": ["errno"] - }, - "publicMessage": { - "type": "string", - "description": "Human-readable error message that can be displayed to users", - "nullable": true - } - }, - "required": ["code", "data"] - }, - "PitcherErrorCode": { - "type": "integer", - "description": "Enumeration of error codes", - "enum": [ - 0, 1, 2, 3, 100, 101, 102, 200, 201, 204, 300, 400, 404, 410, 420, - 430, 440, 450, 460, 470, 500, 600, 601, 602, 704, 800, 801, 802, 803, - 814 - ], - "x-enum-descriptions": [ - "CRITICAL_ERROR", - "FEATURE_UNAVAILABLE", - "NO_ACCESS", - "RATE_LIMIT", - "INVALID_ID", - "INVALID_PATH", - "RAWFS_ERROR", - "SHELL_NOT_ACCESSIBLE", - "SHELL_CLOSED", - "SHELL_NOT_FOUND", - "MODEL_NOT_FOUND", - "GIT_OPERATION_IN_PROGRESS", - "GIT_REMOTE_FILE_NOT_FOUND", - "GIT_FETCH_FAIL", - "GIT_PULL_CONFLICT", - "GIT_RESET_LOCAL_REMOTE_ERROR", - "GIT_PUSH_FAIL", - "GIT_RESET_CHECKOUT_INITIAL_BRANCH_FAIL", - "GIT_PULL_FAIL", - "GIT_TRANSPOSE_LINES_FAIL", - "CHANNEL_NOT_FOUND", - "CONFIG_FILE_ALREADY_EXISTS", - "TASK_NOT_FOUND", - "COMMAND_ALREADY_CONFIGURED", - "COMMAND_NOT_FOUND", - "AI_NOT_AVAILABLE", - "PROMPT_TOO_BIG", - "FAILED_TO_RESPOND", - "AI_TOO_FREQUENT_REQUESTS", - "AI_CHAT_NOT_FOUND" - ] - }, - "WriteFileRequest": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path to write to" - }, - "content": { - "type": "string", - "format": "binary", - "description": "File content as binary data (Uint8Array)" - }, - "create": { - "type": "boolean", - "description": "Whether to create the file if it doesn't exist", - "default": false - }, - "overwrite": { - "type": "boolean", - "description": "Whether to overwrite the file if it exists", - "default": false - } - }, - "required": ["path", "content"] - } - } - } -} diff --git a/package.json b/package.json index d77eb30..298e76f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch && npm run build-openapi-rest", "build-openapi:staging": "rimraf src/client && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", - "build-openapi-rest": "node_modules/.bin/openapi-ts -i ./openapi-sandbox.json -o src/client-rest -c @hey-api/client-fetch", + "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/client-rest-fs -c @hey-api/client-fetch", "clean": "rimraf ./dist", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", @@ -77,6 +77,7 @@ "README.md" ], "devDependencies": { + "@msgpack/msgpack": "^3.1.0", "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", @@ -104,7 +105,5 @@ "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" }, - "dependencies": { - "@msgpack/msgpack": "^3.1.0" - } + "dependencies": {} } diff --git a/src/client-rest/client.gen.ts b/src/client-rest-fs/client.gen.ts similarity index 100% rename from src/client-rest/client.gen.ts rename to src/client-rest-fs/client.gen.ts diff --git a/src/client-rest/index.ts b/src/client-rest-fs/index.ts similarity index 100% rename from src/client-rest/index.ts rename to src/client-rest-fs/index.ts diff --git a/src/client-rest-fs/sdk.gen.ts b/src/client-rest-fs/sdk.gen.ts new file mode 100644 index 0000000..899de24 --- /dev/null +++ b/src/client-rest-fs/sdk.gen.ts @@ -0,0 +1,280 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { WriteFileData, WriteFileResponse, WriteFileError, FsReadData, FsReadResponse, FsReadError, FsOperationData, FsOperationResponse, FsOperationError, FsSearchData, FsSearchResponse, FsSearchError, FsStreamingSearchData, FsStreamingSearchResponse, FsStreamingSearchError, FsCancelStreamingSearchData, FsCancelStreamingSearchResponse, FsCancelStreamingSearchError, FsPathSearchData, FsPathSearchResponse, FsPathSearchError, FsUploadData, FsUploadResponse, FsUploadError, FsDownloadData, FsDownloadResponse, FsDownloadError, FsReadFileData, FsReadFileResponse, FsReadFileError, FsReadDirData, FsReadDirResponse, FsReadDirError, FsStatData, FsStatResponse, FsStatError, FsCopyData, FsCopyResponse, FsCopyError, FsRenameData, FsRenameResponse, FsRenameError, FsRemoveData, FsRemoveResponse, FsRemoveError, FsMkdirData, FsMkdirResponse, FsMkdirError, FsWatchData, FsWatchResponse, FsWatchError, FsUnwatchData, FsUnwatchResponse, FsUnwatchError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Write to a file + * Write content to a file at the specified path + */ +export const writeFile = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/writefile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Read file system + * Retrieve the latest snapshot of the server's MemoryFS file and children list + */ +export const fsRead = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + url: '/fs/read', + ...options + }); +}; + +/** + * Perform file system operation + * Send a tree operation reflecting filesystem operations + */ +export const fsOperation = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/operation', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Search files + * Search for content in files + */ +export const fsSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/search', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Start streaming search + * Start a streaming search for content in files + */ +export const fsStreamingSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/streamingSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Cancel streaming search + * Cancel an ongoing streaming search + */ +export const fsCancelStreamingSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/cancelStreamingSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Search file paths + * Search for file paths matching a pattern + */ +export const fsPathSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/pathSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Upload file + * Upload a file to the specified parent directory + */ +export const fsUpload = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/upload', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Download files + * Download files at a specified path as a zip + */ +export const fsDownload = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/download', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Read file content + * Read the content of a file at the specified path + */ +export const fsReadFile = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/readFile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Read directory contents + * List the contents of a directory at the specified path + */ +export const fsReadDir = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/readdir', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get file/directory stats + * Get stats for a file or directory at the specified path + */ +export const fsStat = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/stat', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Copy file/directory + * Copy a file or directory from one location to another + */ +export const fsCopy = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/copy', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Rename file/directory + * Rename a file or directory (move from one location to another) + */ +export const fsRename = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/rename', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Remove file/directory + * Delete a file or directory at the specified path + */ +export const fsRemove = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/remove', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Create directory + * Create a new directory at the specified path + */ +export const fsMkdir = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/mkdir', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Watch file/directory + * Watch a file or directory for changes + */ +export const fsWatch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/watch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Stop watching file/directory + * Stop watching a file or directory for changes + */ +export const fsUnwatch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/unwatch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/client-rest-fs/types.gen.ts b/src/client-rest-fs/types.gen.ts new file mode 100644 index 0000000..312a8d9 --- /dev/null +++ b/src/client-rest-fs/types.gen.ts @@ -0,0 +1,1058 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + error: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); +}; + +export type DefaultError = { + code: PitcherErrorCode; + /** + * Additional error details + */ + data?: { + [key: string]: unknown; + } | null; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; +}; + +export type RawFsError = { + /** + * RAWFS_ERROR code + */ + code: 102; + data: { + /** + * File system error number, or null if not available + */ + errno: unknown; + }; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; +}; + +/** + * Enumeration of error codes + */ +export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; + +export type WriteFileRequest = { + /** + * File path to write to + */ + path: string; + /** + * File content as binary data (Uint8Array) + */ + content: Blob | File; + /** + * Whether to create the file if it doesn't exist + */ + create?: boolean; + /** + * Whether to overwrite the file if it exists + */ + overwrite?: boolean; +}; + +export type FsReadResult = { + treeNodes: Array<{ + [key: string]: unknown; + }>; + /** + * Current clock value for the file system + */ + clock: number; +}; + +export type FsOperationRequest = { + operation: FsOperation; +}; + +export type FsOperation = ({ + type?: 'FSCreateOperation'; +} & FsCreateOperation) | ({ + type?: 'FSDeleteOperation'; +} & FsDeleteOperation) | ({ + type?: 'FSMoveOperation'; +} & FsMoveOperation); + +export type FsCreateOperation = { + type: 'create'; + /** + * ID of the parent directory + */ + parentId: string; + newEntry: { + /** + * ID of the new entry + */ + id: string; + /** + * Type of the node + */ + type: 'directory' | 'file'; + /** + * Name of the new entry + */ + name: string; + }; +}; + +export type FsDeleteOperation = { + type: 'delete'; + /** + * ID of the entry to delete + */ + id: string; +}; + +export type FsMoveOperation = { + type: 'move'; + /** + * ID of the entry to move + */ + id: string; + /** + * ID of the new parent directory + */ + parentId?: string | null; + /** + * New name for the entry + */ + name?: string | null; +}; + +export type FsOperationResult = { + /** + * Success code + */ + code: 0; + /** + * Current clock value + */ + clock: number; +} | { + /** + * Ignored code + */ + code: 1; +}; + +export type FsSearchParams = { + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: 'smart' | 'enabled' | 'disabled'; +}; + +export type SearchResult = { + /** + * ID of the file containing the match + */ + fileId: string; + lines: { + /** + * Text of the line containing the match + */ + text: string; + }; + /** + * Line number of the match + */ + lineNumber: number; + /** + * Absolute offset of the match in the file + */ + absoluteOffset: number; + submatches: Array; +}; + +export type SearchSubMatch = { + match: { + /** + * Matched text + */ + text: string; + }; + /** + * Start position of the match + */ + start: number; + /** + * End position of the match + */ + end: number; +}; + +export type FsStreamingSearchParams = { + /** + * ID for the search operation + */ + searchId: string; + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: 'smart' | 'enabled' | 'disabled'; + /** + * Maximum number of results to return (default: 10,000) + */ + maxResults?: number | null; +}; + +export type PathSearchParams = { + /** + * Text to search for in file paths + */ + text: string; +}; + +export type PathSearchResult = { + matches: Array; +}; + +export type PathSearchMatch = { + /** + * Path that matched the search + */ + path: string; + submatches: Array; +}; + +export type InvalidIdError = { + /** + * INVALID_ID error code + */ + code: 100; +}; + +export type FsReadFileParams = { + /** + * Path to the file to read + */ + path: string; +}; + +export type FsReadFileResult = { + /** + * File content as binary data + */ + content: Blob | File; +}; + +export type FsReadDirParams = { + /** + * Path to the directory to read + */ + path: string; +}; + +export type FsReadDirResult = { + entries: Array<{ + /** + * Name of the entry + */ + name: string; + /** + * Type of the entry + */ + type: 'directory' | 'file'; + /** + * Whether the entry is a symlink + */ + isSymlink: boolean; + }>; +}; + +export type FsStatParams = { + /** + * Path to the file or directory to stat + */ + path: string; +}; + +export type FsStatResult = { + /** + * Type of the entry + */ + type: 'directory' | 'file'; + /** + * Whether the entry is a symlink + */ + isSymlink: boolean; + /** + * Size of the file in bytes + */ + size: number; + /** + * Last modified time + */ + mtime: number; + /** + * Creation time + */ + ctime: number; + /** + * Last accessed time + */ + atime: number; +}; + +export type FsCopyParams = { + /** + * Path to copy from + */ + from: string; + /** + * Path to copy to + */ + to: string; + /** + * Whether to copy directories recursively + */ + recursive?: boolean | null; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; +}; + +export type FsRenameParams = { + /** + * Path to rename from + */ + from: string; + /** + * Path to rename to + */ + to: string; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; +}; + +export type FsRemoveParams = { + /** + * Path to remove + */ + path: string; + /** + * Whether to remove directories recursively + */ + recursive?: boolean | null; +}; + +export type FsMkdirParams = { + /** + * Path to create directory at + */ + path: string; + /** + * Whether to create parent directories if they don't exist + */ + recursive?: boolean | null; +}; + +export type FsWatchParams = { + /** + * Path to watch + */ + path: string; + /** + * Whether to watch directories recursively + */ + recursive?: boolean | null; + /** + * Glob patterns to exclude from watching + */ + excludes?: Array | null; +}; + +export type FsWatchResult = { + /** + * ID of the watch + */ + watchId: string; +}; + +export type FsUnwatchParams = { + /** + * ID of the watch to stop + */ + watchId: string; +}; + +export type WriteFileData = { + body: WriteFileRequest; + path?: never; + query?: never; + url: '/fs/writefile'; +}; + +export type WriteFileErrors = { + /** + * Error writing file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; + +export type WriteFileResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; + +export type FsReadData = { + body?: never; + path?: never; + query?: never; + url: '/fs/read'; +}; + +export type FsReadErrors = { + /** + * Error reading file system + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsReadError = FsReadErrors[keyof FsReadErrors]; + +export type FsReadResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadResult; + }; +}; + +export type FsReadResponse = FsReadResponses[keyof FsReadResponses]; + +export type FsOperationData = { + body: FsOperationRequest; + path?: never; + query?: never; + url: '/fs/operation'; +}; + +export type FsOperationErrors = { + /** + * Error performing operation + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsOperationError = FsOperationErrors[keyof FsOperationErrors]; + +export type FsOperationResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsOperationResult; + }; +}; + +export type FsOperationResponse = FsOperationResponses[keyof FsOperationResponses]; + +export type FsSearchData = { + body: FsSearchParams; + path?: never; + query?: never; + url: '/fs/search'; +}; + +export type FsSearchErrors = { + /** + * Error searching files + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsSearchError = FsSearchErrors[keyof FsSearchErrors]; + +export type FsSearchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array; + }; +}; + +export type FsSearchResponse = FsSearchResponses[keyof FsSearchResponses]; + +export type FsStreamingSearchData = { + body: FsStreamingSearchParams; + path?: never; + query?: never; + url: '/fs/streamingSearch'; +}; + +export type FsStreamingSearchErrors = { + /** + * Error starting streaming search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsStreamingSearchError = FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; + +export type FsStreamingSearchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the search operation + */ + searchId?: string; + }; + }; +}; + +export type FsStreamingSearchResponse = FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; + +export type FsCancelStreamingSearchData = { + body: { + /** + * ID of the search to cancel + */ + searchId: string; + }; + path?: never; + query?: never; + url: '/fs/cancelStreamingSearch'; +}; + +export type FsCancelStreamingSearchErrors = { + /** + * Error cancelling search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsCancelStreamingSearchError = FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; + +export type FsCancelStreamingSearchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the cancelled search + */ + searchId?: string; + }; + }; +}; + +export type FsCancelStreamingSearchResponse = FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; + +export type FsPathSearchData = { + body: PathSearchParams; + path?: never; + query?: never; + url: '/fs/pathSearch'; +}; + +export type FsPathSearchErrors = { + /** + * Error searching paths + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsPathSearchError = FsPathSearchErrors[keyof FsPathSearchErrors]; + +export type FsPathSearchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: PathSearchResult; + }; +}; + +export type FsPathSearchResponse = FsPathSearchResponses[keyof FsPathSearchResponses]; + +export type FsUploadData = { + body: { + /** + * ID of the parent directory + */ + parentId: string; + /** + * Name of the file to create + */ + filename: string; + /** + * File content as binary data + */ + content: Blob | File; + }; + path?: never; + query?: never; + url: '/fs/upload'; +}; + +export type FsUploadErrors = { + /** + * Error uploading file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'InvalidIdError'; + } & InvalidIdError); + }; +}; + +export type FsUploadError = FsUploadErrors[keyof FsUploadErrors]; + +export type FsUploadResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the created file + */ + fileId?: string; + }; + }; +}; + +export type FsUploadResponse = FsUploadResponses[keyof FsUploadResponses]; + +export type FsDownloadData = { + body: { + /** + * Path to download + */ + path: string; + /** + * Glob patterns of files/folders to exclude from the download + */ + excludes?: Array; + }; + path?: never; + query?: never; + url: '/fs/download'; +}; + +export type FsDownloadErrors = { + /** + * Error creating download + */ + 400: ErrorResponse & { + error?: DefaultError; + }; +}; + +export type FsDownloadError = FsDownloadErrors[keyof FsDownloadErrors]; + +export type FsDownloadResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * URL to download the files from + */ + downloadUrl?: string; + }; + }; +}; + +export type FsDownloadResponse = FsDownloadResponses[keyof FsDownloadResponses]; + +export type FsReadFileData = { + body: FsReadFileParams; + path?: never; + query?: never; + url: '/fs/readFile'; +}; + +export type FsReadFileErrors = { + /** + * Error reading file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsReadFileError = FsReadFileErrors[keyof FsReadFileErrors]; + +export type FsReadFileResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadFileResult; + }; +}; + +export type FsReadFileResponse = FsReadFileResponses[keyof FsReadFileResponses]; + +export type FsReadDirData = { + body: FsReadDirParams; + path?: never; + query?: never; + url: '/fs/readdir'; +}; + +export type FsReadDirErrors = { + /** + * Error reading directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsReadDirError = FsReadDirErrors[keyof FsReadDirErrors]; + +export type FsReadDirResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadDirResult; + }; +}; + +export type FsReadDirResponse = FsReadDirResponses[keyof FsReadDirResponses]; + +export type FsStatData = { + body: FsStatParams; + path?: never; + query?: never; + url: '/fs/stat'; +}; + +export type FsStatErrors = { + /** + * Error getting stats + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsStatError = FsStatErrors[keyof FsStatErrors]; + +export type FsStatResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsStatResult; + }; +}; + +export type FsStatResponse = FsStatResponses[keyof FsStatResponses]; + +export type FsCopyData = { + body: FsCopyParams; + path?: never; + query?: never; + url: '/fs/copy'; +}; + +export type FsCopyErrors = { + /** + * Error copying file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsCopyError = FsCopyErrors[keyof FsCopyErrors]; + +export type FsCopyResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type FsCopyResponse = FsCopyResponses[keyof FsCopyResponses]; + +export type FsRenameData = { + body: FsRenameParams; + path?: never; + query?: never; + url: '/fs/rename'; +}; + +export type FsRenameErrors = { + /** + * Error renaming file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsRenameError = FsRenameErrors[keyof FsRenameErrors]; + +export type FsRenameResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type FsRenameResponse = FsRenameResponses[keyof FsRenameResponses]; + +export type FsRemoveData = { + body: FsRemoveParams; + path?: never; + query?: never; + url: '/fs/remove'; +}; + +export type FsRemoveErrors = { + /** + * Error removing file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsRemoveError = FsRemoveErrors[keyof FsRemoveErrors]; + +export type FsRemoveResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type FsRemoveResponse = FsRemoveResponses[keyof FsRemoveResponses]; + +export type FsMkdirData = { + body: FsMkdirParams; + path?: never; + query?: never; + url: '/fs/mkdir'; +}; + +export type FsMkdirErrors = { + /** + * Error creating directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsMkdirError = FsMkdirErrors[keyof FsMkdirErrors]; + +export type FsMkdirResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type FsMkdirResponse = FsMkdirResponses[keyof FsMkdirResponses]; + +export type FsWatchData = { + body: FsWatchParams; + path?: never; + query?: never; + url: '/fs/watch'; +}; + +export type FsWatchErrors = { + /** + * Error watching file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsWatchError = FsWatchErrors[keyof FsWatchErrors]; + +export type FsWatchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsWatchResult; + }; +}; + +export type FsWatchResponse = FsWatchResponses[keyof FsWatchResponses]; + +export type FsUnwatchData = { + body: FsUnwatchParams; + path?: never; + query?: never; + url: '/fs/unwatch'; +}; + +export type FsUnwatchErrors = { + /** + * Error unwatching file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); + }; +}; + +export type FsUnwatchError = FsUnwatchErrors[keyof FsUnwatchErrors]; + +export type FsUnwatchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; \ No newline at end of file diff --git a/src/client-rest/sdk.gen.ts b/src/client-rest/sdk.gen.ts deleted file mode 100644 index 21d5a16..0000000 --- a/src/client-rest/sdk.gen.ts +++ /dev/null @@ -1,29 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { WriteFileData, WriteFileResponse, WriteFileError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Write to a file - * Write content to a file at the specified path - */ -export const writeFile = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/writefile', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/client-rest/types.gen.ts b/src/client-rest/types.gen.ts deleted file mode 100644 index f540b55..0000000 --- a/src/client-rest/types.gen.ts +++ /dev/null @@ -1,116 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - error: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); -}; - -export type DefaultError = { - code: PitcherErrorCode; - /** - * Additional error details - */ - data?: { - [key: string]: unknown; - } | null; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; -}; - -export type RawFsError = { - /** - * RAWFS_ERROR code - */ - code: 102; - data: { - /** - * File system error number, or null if not available - */ - errno: unknown; - }; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; -}; - -/** - * Enumeration of error codes - */ -export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; - -export type WriteFileRequest = { - /** - * File path to write to - */ - path: string; - /** - * File content as binary data (Uint8Array) - */ - content: Blob | File; - /** - * Whether to create the file if it doesn't exist - */ - create?: boolean; - /** - * Whether to overwrite the file if it exists - */ - overwrite?: boolean; -}; - -export type WriteFileData = { - body: WriteFileRequest; - path?: never; - query?: never; - url: '/fs/writefile'; -}; - -export type WriteFileErrors = { - /** - * Error writing file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; - -export type WriteFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; \ No newline at end of file diff --git a/src/filesystem-rest.ts b/src/filesystem-rest.ts deleted file mode 100644 index ee93354..0000000 --- a/src/filesystem-rest.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - WriteFileError, - WriteFileRequest, - WriteFileResponse, -} from "./client-rest"; -import { SessionData } from "./sessions"; -import { RestRequester } from "./rest-client"; - -export class FileSystemRest { - constructor( - private createRequester: (session: SessionData) => RestRequester - ) {} - writeTextFile(session: SessionData, path: string, content: string) { - const request = this.createRequester(session); - - return request( - "fs/writeFile", - { - path, - - // We are not able to generate the correct typing for OpenAPI here - // @ts-expect-error - content: new TextEncoder().encode(content), - create: true, - overwrite: true, - } - ); - } -} diff --git a/src/index.ts b/src/index.ts index 7ffd864..c609439 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import type { Client } from "@hey-api/client-fetch"; -import { createClient, createConfig } from "@hey-api/client-fetch"; import { SandboxClient, @@ -9,6 +8,8 @@ import { SandboxInfo, PaginationOpts, } from "./sandbox-client"; +import { SandboxRestFileSystem } from "./sandbox-rest-filesystem"; +import { SandboxRestClient } from "./sandbox-rest-client"; export { SandboxClient, @@ -45,24 +46,13 @@ function ensure(value: T | undefined, message: string): T { return value; } -function getBaseUrl(token: string) { - if (token.startsWith("csb_")) { - return "https://api.codesandbox.io"; - } - - return "https://api.together.ai/csb/sdk"; -} - -export { RestClient } from "./rest-client"; +export { SandboxRestClient as RestClient } from "./sandbox-rest-client"; export class CodeSandbox { - private baseUrl: string; - private apiToken: string; - public readonly apiClient: Client; public readonly sandbox: SandboxClient; constructor(apiToken?: string, readonly opts: ClientOpts = {}) { - this.apiToken = + const evaluatedApiToken = apiToken || ensure( typeof process !== "undefined" @@ -70,20 +60,7 @@ export class CodeSandbox { : undefined, "CSB_API_KEY or TOGETHER_API_KEY is not set" ); - this.baseUrl = - process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(this.apiToken); - - this.apiClient = createClient( - createConfig({ - baseUrl: this.baseUrl, - headers: { - Authorization: `Bearer ${this.apiToken}`, - ...(opts.headers ?? {}), - }, - fetch: opts.fetch ?? fetch, - }) - ); - this.sandbox = new SandboxClient(this.apiClient); + this.sandbox = new SandboxClient(evaluatedApiToken, opts); } } diff --git a/src/rest-client.ts b/src/rest-client.ts deleted file mode 100644 index 39a0ad0..0000000 --- a/src/rest-client.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { createClient, createConfig } from "@hey-api/client-fetch"; -import { SessionData } from "./sessions"; -import { FileSystemRest } from "./filesystem-rest"; -import { decode, encode } from "@msgpack/msgpack"; -import { - SuccessResponse, - ErrorResponse, - WriteFileRequest, -} from "./client-rest"; - -export interface ClientOpts { - /** - * Custom fetch implementation - * - * @default fetch - */ - fetch?: typeof fetch; - - /** - * Additional headers to send with each request - */ - headers?: Record; -} - -export type RestRequester = < - P extends {}, - S extends SuccessResponse, - E extends ErrorResponse ->( - method: string, - params: P -) => Promise; - -export class RestClient { - static id = 0; - constructor(private opts: ClientOpts = {}) {} - private createClient = (session: SessionData): RestRequester => { - const url = new URL(session.pitcher_url); - - url.protocol = "https"; - - const client = createClient( - createConfig({ - bodySerializer: null, - parseAs: "stream", - baseUrl: url.origin, - headers: { - ...(this.opts.headers ?? {}), - "content-type": "application/x-msgpack", - }, - fetch: - this.opts.fetch ?? - // @ts-ignore - ((url, params) => { - console.log("Fetching", url, params); - return fetch(url, params); - }), - }) - ); - - return (method, params) => { - const message = { - id: RestClient.id++, - method, - params, - }; - console.log("Sending message", url.toString(), message); - const encodedMessage = encode(message); - - return client - .post({ - url: `/${session.id}?token=${session.pitcher_token}`, - headers: { - "content-length": encodedMessage.byteLength.toString(), - }, - body: encodedMessage, - }) - .then(async ({ response }) => - decode(await response.arrayBuffer()) - ) as any; - }; - }; - fs = new FileSystemRest(this.createClient); -} diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 5e0e433..7828afd 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -1,5 +1,6 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; import type { Client } from "@hey-api/client-fetch"; +import { createClient, createConfig } from "@hey-api/client-fetch"; import type { SandboxForkResponse, @@ -23,6 +24,9 @@ import { import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; import { SessionCreateOptions, SessionData } from "./sessions"; +import { SandboxRestClient } from "./sandbox-rest-client"; +import { SandboxRestFileSystem } from "./sandbox-rest-filesystem"; +import { ClientOpts } from "."; type SandboxForkResponseWithSession = SandboxForkResponse["data"] & { start_response: Required["data"]; @@ -251,7 +255,18 @@ export type HandledResponse = { response: Response; }; +function getBaseUrl(token: string) { + if (token.startsWith("csb_")) { + return "https://api.codesandbox.io"; + } + + return "https://api.together.ai/csb/sdk"; +} + export class SandboxClient { + private apiClient: Client; + private sandboxRestClient: SandboxRestClient; + get defaultTemplate() { if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; @@ -260,7 +275,26 @@ export class SandboxClient { return "pcz35m"; } - constructor(private readonly apiClient: Client) {} + get fs() { + return this.sandboxRestClient.fs; + } + + constructor(apiToken: string, opts: ClientOpts) { + const baseUrl = + process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(apiToken); + + this.sandboxRestClient = new SandboxRestClient(opts); + this.apiClient = this.apiClient = createClient( + createConfig({ + baseUrl, + headers: { + Authorization: `Bearer ${apiToken}`, + ...(opts.headers ?? {}), + }, + fetch: opts.fetch ?? fetch, + }) + ); + } /** * Open, start & connect to a sandbox that already exists @@ -310,7 +344,7 @@ export class SandboxClient { * any sandbox/template created on codesandbox.io, even your own templates) or don't pass * in anything and we'll use the default universal template. * - * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} + * This function will also start & connect to the VM of the created sandbox with a global session, and return a {@link Sandbox} * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. * * @param opts Additional options for creating the sandbox @@ -335,7 +369,8 @@ export class SandboxClient { // We always want to start the VM in this context as our intention is to connect immediately // or return the session data to manually connect, for example in browser - const startOptions = startOptionsFromOpts(opts) || {}; + const startOptions = + opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts); const result = await sandboxFork({ client: this.apiClient, @@ -356,9 +391,17 @@ export class SandboxClient { result, "Failed to create sandbox" // We currently always pass "start_options" to create a session - ) as SandboxForkResponseWithSession; + ); const shouldReturnSessionOnly = opts?.autoConnect === false; + + // HACK: We need to start the sandbox on the correct cluster, which means we can not + // start the sandbox during `sandboxFork`. Normally we would always get a `start_response` here, + // directly from fork endpoint + if (shouldReturnSessionOnly || !sandbox.start_response) { + return this.start(sandbox.id, startOptions); + } + const session: SessionData = { id: sandbox.id, pitcher_token: sandbox.start_response.pitcher_token, diff --git a/src/sandbox-rest-client.ts b/src/sandbox-rest-client.ts new file mode 100644 index 0000000..8eedfe1 --- /dev/null +++ b/src/sandbox-rest-client.ts @@ -0,0 +1,78 @@ +import { createClient, createConfig } from "@hey-api/client-fetch"; +import { SessionData } from "./sessions"; +import { + FileSystemRestRequester, + SandboxRestFileSystem, +} from "./sandbox-rest-filesystem"; +import { decode, encode } from "@msgpack/msgpack"; +import * as fs from "./client-rest-fs"; + +export interface ClientOpts { + /** + * Custom fetch implementation + * + * @default fetch + */ + fetch?: typeof fetch; + + /** + * Additional headers to send with each request + */ + headers?: Record; +} + +export class SandboxRestClient { + static id = 0; + fs: SandboxRestFileSystem; + constructor(opts: ClientOpts = {}) { + const createClient = this.createRestClientFactory(opts); + this.fs = new SandboxRestFileSystem(createClient); + } + private createRestClientFactory(opts: ClientOpts) { + return (session: SessionData): FileSystemRestRequester => { + const url = new URL(session.pitcher_url); + + url.protocol = "https"; + + const client = createClient( + createConfig({ + bodySerializer: null, + parseAs: "stream", + baseUrl: url.origin, + headers: { + ...(opts.headers ?? {}), + "content-type": "application/x-msgpack", + }, + fetch: + opts.fetch ?? + // @ts-ignore + ((url, params) => { + return fetch(url, params); + }), + }) + ); + + return (method, params) => { + const message = { + id: SandboxRestClient.id++, + method, + params, + }; + + const encodedMessage = encode(message); + + return client + .post({ + url: `/${session.id}?token=${session.pitcher_token}`, + headers: { + "content-length": encodedMessage.byteLength.toString(), + }, + body: encodedMessage, + }) + .then(async ({ response }) => + decode(await response.arrayBuffer()) + ) as any; + }; + }; + } +} diff --git a/src/sandbox-rest-filesystem.ts b/src/sandbox-rest-filesystem.ts new file mode 100644 index 0000000..d341720 --- /dev/null +++ b/src/sandbox-rest-filesystem.ts @@ -0,0 +1,34 @@ +import * as fs from "./client-rest-fs"; +import { SessionData } from "./sessions"; + +export type FileSystemRestRequester = < + P extends {}, + S extends fs.SuccessResponse, + E extends fs.ErrorResponse +>( + method: string, + params: P +) => Promise; + +export class SandboxRestFileSystem { + constructor( + private createRequester: (session: SessionData) => FileSystemRestRequester + ) {} + writeTextFile(session: SessionData, path: string, content: string) { + const request = this.createRequester(session); + + return request< + fs.WriteFileRequest, + fs.WriteFileResponse, + fs.WriteFileError + >("fs/writeFile", { + path, + + // We are not able to generate the correct typing for OpenAPI here + // @ts-expect-error + content: new TextEncoder().encode(content), + create: true, + overwrite: true, + }); + } +} From daad8c72262def730363e13db5e2ae446ecaa8cc Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 7 Apr 2025 14:20:27 +0200 Subject: [PATCH 066/241] it is running --- openapi-git-spec.json | 1348 ++++++++++++++++ openapi-git.json | 0 openapi-port.json | 151 ++ openapi-sandbox-container.json | 179 +++ openapi-sandbox-fs.json | 2 +- openapi-sandbox-git.json | 1369 +++++++++++++++++ openapi-sandbox-setup.json | 570 +++++++ openapi-sandbox-shell.json | 916 +++++++++++ openapi-sandbox-system.json | 348 +++++ openapi-sandbox-task.json | 947 ++++++++++++ openapi.json | 1154 ++++---------- package.json | 13 +- src/bin/commands/build.ts | 6 +- .../client-rest-container}/client.gen.ts | 0 .../client-rest-container}/index.ts | 0 src/clients/client-rest-container/sdk.gen.ts | 29 + .../client-rest-container/types.gen.ts | 111 ++ .../client-rest-fs}/client.gen.ts | 0 .../client-rest-fs}/index.ts | 0 src/{ => clients}/client-rest-fs/sdk.gen.ts | 2 +- src/{ => clients}/client-rest-fs/types.gen.ts | 2 +- src/clients/client-rest-git/client.gen.ts | 5 + src/clients/client-rest-git/index.ts | 3 + src/clients/client-rest-git/sdk.gen.ts | 224 +++ src/clients/client-rest-git/types.gen.ts | 725 +++++++++ src/clients/client-rest-setup/client.gen.ts | 5 + src/clients/client-rest-setup/index.ts | 3 + src/clients/client-rest-setup/sdk.gen.ts | 119 ++ src/clients/client-rest-setup/types.gen.ts | 295 ++++ src/clients/client-rest-shell/client.gen.ts | 5 + src/clients/client-rest-shell/index.ts | 3 + src/clients/client-rest-shell/sdk.gen.ts | 149 ++ src/clients/client-rest-shell/types.gen.ts | 443 ++++++ src/clients/client-rest-system/client.gen.ts | 5 + src/clients/client-rest-system/index.ts | 3 + src/clients/client-rest-system/sdk.gen.ts | 59 + src/clients/client-rest-system/types.gen.ts | 207 +++ src/clients/client-rest-task/client.gen.ts | 5 + src/clients/client-rest-task/index.ts | 3 + src/clients/client-rest-task/sdk.gen.ts | 149 ++ src/clients/client-rest-task/types.gen.ts | 507 ++++++ src/clients/client/client.gen.ts | 5 + src/clients/client/index.ts | 3 + src/{ => clients}/client/sdk.gen.ts | 0 src/{ => clients}/client/types.gen.ts | 30 +- src/index.ts | 2 - src/rest/sandbox-rest-container.ts | 21 + src/rest/sandbox-rest-fs.ts | 110 ++ src/rest/sandbox-rest-git.ts | 67 + src/rest/sandbox-rest-shell.ts | 53 + src/rest/sandbox-rest-system.ts | 29 + src/rest/sandbox-rest-task.ts | 59 + src/sandbox-client.ts | 9 +- src/sandbox-rest-client.ts | 110 +- src/sandbox-rest-filesystem.ts | 34 - src/utils/session.ts | 11 + tsconfig.json | 10 +- 57 files changed, 9632 insertions(+), 985 deletions(-) create mode 100644 openapi-git-spec.json create mode 100644 openapi-git.json create mode 100644 openapi-port.json create mode 100644 openapi-sandbox-container.json create mode 100644 openapi-sandbox-git.json create mode 100644 openapi-sandbox-setup.json create mode 100644 openapi-sandbox-shell.json create mode 100644 openapi-sandbox-system.json create mode 100644 openapi-sandbox-task.json rename src/{client-rest-fs => clients/client-rest-container}/client.gen.ts (100%) rename src/{client-rest-fs => clients/client-rest-container}/index.ts (100%) create mode 100644 src/clients/client-rest-container/sdk.gen.ts create mode 100644 src/clients/client-rest-container/types.gen.ts rename src/{client => clients/client-rest-fs}/client.gen.ts (100%) rename src/{client => clients/client-rest-fs}/index.ts (100%) rename src/{ => clients}/client-rest-fs/sdk.gen.ts (99%) rename src/{ => clients}/client-rest-fs/types.gen.ts (99%) create mode 100644 src/clients/client-rest-git/client.gen.ts create mode 100644 src/clients/client-rest-git/index.ts create mode 100644 src/clients/client-rest-git/sdk.gen.ts create mode 100644 src/clients/client-rest-git/types.gen.ts create mode 100644 src/clients/client-rest-setup/client.gen.ts create mode 100644 src/clients/client-rest-setup/index.ts create mode 100644 src/clients/client-rest-setup/sdk.gen.ts create mode 100644 src/clients/client-rest-setup/types.gen.ts create mode 100644 src/clients/client-rest-shell/client.gen.ts create mode 100644 src/clients/client-rest-shell/index.ts create mode 100644 src/clients/client-rest-shell/sdk.gen.ts create mode 100644 src/clients/client-rest-shell/types.gen.ts create mode 100644 src/clients/client-rest-system/client.gen.ts create mode 100644 src/clients/client-rest-system/index.ts create mode 100644 src/clients/client-rest-system/sdk.gen.ts create mode 100644 src/clients/client-rest-system/types.gen.ts create mode 100644 src/clients/client-rest-task/client.gen.ts create mode 100644 src/clients/client-rest-task/index.ts create mode 100644 src/clients/client-rest-task/sdk.gen.ts create mode 100644 src/clients/client-rest-task/types.gen.ts create mode 100644 src/clients/client/client.gen.ts create mode 100644 src/clients/client/index.ts rename src/{ => clients}/client/sdk.gen.ts (100%) rename src/{ => clients}/client/types.gen.ts (96%) create mode 100644 src/rest/sandbox-rest-container.ts create mode 100644 src/rest/sandbox-rest-fs.ts create mode 100644 src/rest/sandbox-rest-git.ts create mode 100644 src/rest/sandbox-rest-shell.ts create mode 100644 src/rest/sandbox-rest-system.ts create mode 100644 src/rest/sandbox-rest-task.ts delete mode 100644 src/sandbox-rest-filesystem.ts create mode 100644 src/utils/session.ts diff --git a/openapi-git-spec.json b/openapi-git-spec.json new file mode 100644 index 0000000..3a8d660 --- /dev/null +++ b/openapi-git-spec.json @@ -0,0 +1,1348 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Git API", + "description": "API for interacting with Git version control in sandboxes", + "version": "1.0.0" + }, + "paths": { + "/git/status": { + "post": { + "summary": "Get git status", + "description": "Retrieve the current git status of the repository", + "operationId": "gitStatus", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitStatus" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving git status", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/remotes": { + "post": { + "summary": "Get git remotes", + "description": "Retrieve the remote repositories configured for the git repository", + "operationId": "gitRemotes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitRemotes" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving git remotes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/targetDiff": { + "post": { + "summary": "Get target diff", + "description": "Retrieve the difference between the current branch and a target branch", + "operationId": "gitTargetDiff", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branch": { + "type": "string", + "description": "Target branch name" + } + }, + "required": ["branch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitTargetDiff" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving target diff", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/pull": { + "post": { + "summary": "Pull changes", + "description": "Pull changes from the remote repository", + "operationId": "gitPull", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branch": { + "type": "string", + "description": "Branch to pull from" + }, + "force": { + "type": "boolean", + "description": "Force pull" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pulling changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/discard": { + "post": { + "summary": "Discard changes", + "description": "Discard changes to specified paths or all changes", + "operationId": "gitDiscard", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths to discard changes for" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths that were discarded" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error discarding changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/commit": { + "post": { + "summary": "Commit changes", + "description": "Commit changes to the local repository", + "operationId": "gitCommit", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths to commit" + }, + "message": { + "type": "string", + "description": "Commit message" + }, + "push": { + "type": "boolean", + "description": "Whether to push after committing" + } + }, + "required": ["message"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "shellId": { + "type": "string", + "description": "ID of the shell process" + } + }, + "required": ["shellId"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error committing changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/push": { + "post": { + "summary": "Push changes", + "description": "Push changes to the remote repository", + "operationId": "gitPush", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pushing changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/pushToRemote": { + "post": { + "summary": "Push to remote", + "description": "Push changes to a specific remote repository and branch", + "operationId": "gitPushToRemote", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the remote repository" + }, + "branch": { + "type": "string", + "description": "Branch to push to" + }, + "squashAllCommits": { + "type": "boolean", + "description": "Whether to squash all commits before pushing" + } + }, + "required": ["url", "branch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pushing to remote", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/renameBranch": { + "post": { + "summary": "Rename branch", + "description": "Rename a branch in the local repository", + "operationId": "gitRenameBranch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "oldBranch": { + "type": "string", + "description": "Name of the branch to rename" + }, + "newBranch": { + "type": "string", + "description": "New name for the branch" + } + }, + "required": ["oldBranch", "newBranch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error renaming branch", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/remoteContent": { + "post": { + "summary": "Get remote content", + "description": "Retrieve the content of a file from a remote branch or commit", + "operationId": "gitRemoteContent", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GitRemoteParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content of the file" + } + }, + "required": ["content"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving remote content", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/diffStatus": { + "post": { + "summary": "Get diff status", + "description": "Retrieve the status of changes between two references", + "operationId": "gitDiffStatus", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GitDiffStatusParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitDiffStatusResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving diff status", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/resetLocalWithRemote": { + "post": { + "summary": "Reset local with remote", + "description": "Reset the local repository to match the remote", + "operationId": "gitResetLocalWithRemote", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error resetting local with remote", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/checkoutInitialBranch": { + "post": { + "summary": "Checkout initial branch", + "description": "Checkout the initial branch of the repository", + "operationId": "gitCheckoutInitialBranch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error checking out initial branch", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/transposeLines": { + "post": { + "summary": "Transpose lines", + "description": "Map line numbers between different git commits", + "operationId": "gitTransposeLines", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sha": { + "type": "string", + "description": "Commit SHA" + }, + "path": { + "type": "string", + "description": "File path" + }, + "line": { + "type": "number", + "description": "Line number" + } + }, + "required": ["sha", "path", "line"] + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path" + }, + "line": { + "type": "number", + "description": "Line number" + } + }, + "required": ["path", "line"], + "nullable": true + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error transposing lines", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "CommonError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code", "message"] + }, + "GitStatusShortFormat": { + "type": "string", + "enum": ["", "M", "A", "D", "R", "C", "U", "?"], + "description": "Git status short format codes" + }, + "GitItem": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path" + }, + "index": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "workingTree": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "isStaged": { + "type": "boolean", + "description": "Whether the file is staged" + }, + "isConflicted": { + "type": "boolean", + "description": "Whether the file has conflicts" + }, + "fileId": { + "type": "string", + "description": "File ID" + } + }, + "required": ["path", "index", "workingTree", "isStaged", "isConflicted"] + }, + "GitChangedFiles": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/GitItem" + }, + "description": "Map of file IDs to GitItems" + }, + "GitBranchProperties": { + "type": "object", + "properties": { + "head": { + "type": "string", + "nullable": true, + "description": "Head commit" + }, + "branch": { + "type": "string", + "nullable": true, + "description": "Branch name" + }, + "ahead": { + "type": "number", + "description": "Number of commits ahead" + }, + "behind": { + "type": "number", + "description": "Number of commits behind" + }, + "safe": { + "type": "boolean", + "description": "Whether the branch is safe to use" + } + }, + "required": ["ahead", "behind", "safe"] + }, + "GitCommit": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "description": "Commit hash" + }, + "date": { + "type": "string", + "description": "Commit date" + }, + "message": { + "type": "string", + "description": "Commit message" + }, + "author": { + "type": "string", + "description": "Commit author" + } + }, + "required": ["hash", "date", "message", "author"] + }, + "GitStatus": { + "type": "object", + "properties": { + "changedFiles": { + "$ref": "#/components/schemas/GitChangedFiles" + }, + "deletedFiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitItem" + } + }, + "conflicts": { + "type": "boolean", + "description": "Whether there are remote conflicts" + }, + "localChanges": { + "type": "boolean", + "description": "Whether there are local changes" + }, + "remote": { + "$ref": "#/components/schemas/GitBranchProperties" + }, + "target": { + "$ref": "#/components/schemas/GitBranchProperties" + }, + "head": { + "type": "string", + "description": "Current HEAD commit" + }, + "commits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitCommit" + } + }, + "branch": { + "type": "string", + "nullable": true, + "description": "Current branch name" + }, + "isMerging": { + "type": "boolean", + "description": "Whether a merge is in progress" + } + }, + "required": [ + "changedFiles", + "deletedFiles", + "conflicts", + "localChanges", + "remote", + "target", + "commits", + "branch", + "isMerging" + ] + }, + "GitTargetDiff": { + "type": "object", + "properties": { + "ahead": { + "type": "number", + "description": "Number of commits ahead of target" + }, + "behind": { + "type": "number", + "description": "Number of commits behind target" + }, + "commits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitCommit" + } + } + }, + "required": ["ahead", "behind", "commits"] + }, + "GitRemotes": { + "type": "object", + "properties": { + "origin": { + "type": "string", + "description": "Origin remote URL" + }, + "upstream": { + "type": "string", + "description": "Upstream remote URL" + } + }, + "required": ["origin", "upstream"] + }, + "GitRemoteParams": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Branch or commit hash" + }, + "path": { + "type": "string", + "description": "File path" + } + }, + "required": ["reference", "path"] + }, + "GitDiffStatusParams": { + "type": "object", + "properties": { + "base": { + "type": "string", + "description": "Base reference for diffing" + }, + "head": { + "type": "string", + "description": "Head reference for diffing" + } + }, + "required": ["base", "head"] + }, + "GitDiffStatusItem": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "path": { + "type": "string", + "description": "File path" + }, + "oldPath": { + "type": "string", + "description": "Original file path (for renames)" + }, + "hunks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "original": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + }, + "modified": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + } + }, + "required": ["original", "modified"] + } + } + }, + "required": ["status", "path", "hunks"] + }, + "GitDiffStatusResult": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitDiffStatusItem" + } + } + }, + "required": ["files"] + } + } + } +} diff --git a/openapi-git.json b/openapi-git.json new file mode 100644 index 0000000..e69de29 diff --git a/openapi-port.json b/openapi-port.json new file mode 100644 index 0000000..176f301 --- /dev/null +++ b/openapi-port.json @@ -0,0 +1,151 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Port API", + "description": "API for managing sandbox port operations", + "version": "1.0.0" + }, + "paths": { + "/port/list": { + "post": { + "summary": "List ports", + "description": "Retrieve a list of available ports and their URLs", + "operationId": "portList", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Port" + }, + "description": "List of available ports" + } + }, + "required": ["list"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error listing ports", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "CommonError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code", "message"] + }, + "Port": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port number" + }, + "url": { + "type": "string", + "description": "URL to access the service on this port" + } + }, + "required": ["port", "url"] + } + } + } +} diff --git a/openapi-sandbox-container.json b/openapi-sandbox-container.json new file mode 100644 index 0000000..f272b37 --- /dev/null +++ b/openapi-sandbox-container.json @@ -0,0 +1,179 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Container API", + "description": "API for managing sandbox container operations", + "version": "1.0.0" + }, + "paths": { + "/container/setup": { + "post": { + "summary": "Setup container", + "description": "Set up a new container based on a template", + "operationId": "containerSetup", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "templateId": { + "type": "string", + "description": "Identifier of the template to use" + }, + "templateArgs": { + "type": "object", + "description": "Arguments for the template", + "additionalProperties": { + "type": "string" + } + }, + "features": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Feature identifier" + }, + "options": { + "type": "object", + "description": "Options for the feature", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["id", "options"] + }, + "nullable": true + } + }, + "required": ["templateId", "templateArgs"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error setting up container", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "ProtocolError": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code", "message"] + }, + "TaskDTO": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Task identifier" + }, + "status": { + "type": "string", + "description": "Task status" + }, + "progress": { + "type": "number", + "description": "Task progress (0-100)" + } + }, + "required": ["id", "status", "progress"] + } + } + } +} diff --git a/openapi-sandbox-fs.json b/openapi-sandbox-fs.json index d7dd0a6..57c3c6c 100644 --- a/openapi-sandbox-fs.json +++ b/openapi-sandbox-fs.json @@ -6,7 +6,7 @@ "version": "1.0.0" }, "paths": { - "/fs/writefile": { + "/fs/writeFile": { "post": { "summary": "Write to a file", "description": "Write content to a file at the specified path", diff --git a/openapi-sandbox-git.json b/openapi-sandbox-git.json new file mode 100644 index 0000000..827a6e9 --- /dev/null +++ b/openapi-sandbox-git.json @@ -0,0 +1,1369 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Git API", + "description": "API for managing git operations in CodeSandbox", + "version": "1.0.0" + }, + "paths": { + "/git/status": { + "post": { + "summary": "Get git status", + "description": "Retrieve current git status including changed files, branch information, and commits", + "operationId": "gitStatus", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitStatus" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving git status", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/remotes": { + "post": { + "summary": "Get git remotes", + "description": "Retrieve git remote information", + "operationId": "gitRemotes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitRemotes" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving git remotes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/targetDiff": { + "post": { + "summary": "Get git target diff", + "description": "Retrieve diff between current branch and target branch", + "operationId": "gitTargetDiff", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branch": { + "type": "string", + "description": "Branch to compare against" + } + }, + "required": ["branch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitTargetDiff" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving git target diff", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/pull": { + "post": { + "summary": "Pull from remote", + "description": "Pull changes from remote repository", + "operationId": "gitPull", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branch": { + "type": "string", + "description": "Branch to pull from" + }, + "force": { + "type": "boolean", + "description": "Force pull even if there are conflicts" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pulling from remote", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/discard": { + "post": { + "summary": "Discard changes", + "description": "Discard local changes for specified paths", + "operationId": "gitDiscard", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths of files to discard changes" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error discarding changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/commit": { + "post": { + "summary": "Commit changes", + "description": "Commit changes to the repository", + "operationId": "gitCommit", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths of files to commit" + }, + "message": { + "type": "string", + "description": "Commit message" + }, + "push": { + "type": "boolean", + "description": "Whether to push the commit immediately" + } + }, + "required": ["message"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "shellId": { + "type": "string", + "description": "ID of the shell process" + } + }, + "required": ["shellId"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error committing changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/push": { + "post": { + "summary": "Push changes", + "description": "Push local commits to remote repository", + "operationId": "gitPush", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pushing changes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/pushToRemote": { + "post": { + "summary": "Push to remote", + "description": "Push to a specific remote repository", + "operationId": "gitPushToRemote", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the remote repository" + }, + "branch": { + "type": "string", + "description": "Branch to push to" + }, + "squashAllCommits": { + "type": "boolean", + "description": "Whether to squash all commits into one" + } + }, + "required": ["url", "branch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error pushing to remote", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/renameBranch": { + "post": { + "summary": "Rename branch", + "description": "Rename a git branch", + "operationId": "gitRenameBranch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "oldBranch": { + "type": "string", + "description": "Current branch name" + }, + "newBranch": { + "type": "string", + "description": "New branch name" + } + }, + "required": ["oldBranch", "newBranch"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error renaming branch", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/remoteContent": { + "post": { + "summary": "Get remote content", + "description": "Retrieve content from a remote repository", + "operationId": "gitRemoteContent", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GitRemoteParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content of the file" + } + }, + "required": ["content"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving remote content", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/diffStatus": { + "post": { + "summary": "Get diff status", + "description": "Retrieve diff status between two git references", + "operationId": "gitDiffStatus", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GitDiffStatusParams" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/GitDiffStatusResult" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving diff status", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/resetLocalWithRemote": { + "post": { + "summary": "Reset local with remote", + "description": "Reset local repository to match the remote state", + "operationId": "gitResetLocalWithRemote", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error resetting local with remote", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/checkoutInitialBranch": { + "post": { + "summary": "Checkout initial branch", + "description": "Checkout the initial branch of the repository", + "operationId": "gitCheckoutInitialBranch", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error checking out initial branch", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/git/transposeLines": { + "post": { + "summary": "Transpose lines", + "description": "Transpose line numbers from one git reference to another", + "operationId": "gitTransposeLines", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sha": { + "type": "string", + "description": "Git commit SHA" + }, + "path": { + "type": "string", + "description": "Path to the file" + }, + "line": { + "type": "number", + "description": "Line number to transpose" + } + }, + "required": ["sha", "path", "line"] + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "line": { + "type": "number" + } + }, + "required": ["path", "line"] + }, + { + "type": "null" + } + ] + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error transposing lines", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "CommonError": { + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "GIT_OPERATION_IN_PROGRESS", + "GIT_REMOTE_FILE_NOT_FOUND" + ], + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + }, + { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Protocol error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data" + } + }, + "required": ["code", "message"] + } + ] + }, + "GitStatusShortFormat": { + "type": "string", + "enum": ["", "M", "A", "D", "R", "C", "U", "?"], + "description": "Git status short format codes" + }, + "GitItem": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path" + }, + "index": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "workingTree": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "isStaged": { + "type": "boolean", + "description": "Whether the file is staged" + }, + "isConflicted": { + "type": "boolean", + "description": "Whether the file has conflicts" + }, + "fileId": { + "type": "string", + "description": "Unique identifier for the file" + } + }, + "required": ["path", "index", "workingTree", "isStaged", "isConflicted"] + }, + "GitChangedFiles": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/GitItem" + }, + "description": "Map of file IDs to Git items" + }, + "GitBranchProperties": { + "type": "object", + "properties": { + "head": { + "type": ["string", "null"], + "description": "Current HEAD reference" + }, + "branch": { + "type": ["string", "null"], + "description": "Current branch name" + }, + "ahead": { + "type": "number", + "description": "Number of commits ahead of the remote" + }, + "behind": { + "type": "number", + "description": "Number of commits behind the remote" + }, + "safe": { + "type": "boolean", + "description": "Whether the branch is safe to operate on" + } + }, + "required": ["ahead", "behind", "safe"] + }, + "GitCommit": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "description": "Commit hash" + }, + "date": { + "type": "string", + "description": "Commit date" + }, + "message": { + "type": "string", + "description": "Commit message" + }, + "author": { + "type": "string", + "description": "Commit author" + } + }, + "required": ["hash", "date", "message", "author"] + }, + "GitStatus": { + "type": "object", + "properties": { + "changedFiles": { + "$ref": "#/components/schemas/GitChangedFiles" + }, + "deletedFiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitItem" + } + }, + "conflicts": { + "type": "boolean", + "description": "Whether there are remote conflicts" + }, + "localChanges": { + "type": "boolean", + "description": "Whether there are local changes" + }, + "remote": { + "$ref": "#/components/schemas/GitBranchProperties" + }, + "target": { + "$ref": "#/components/schemas/GitBranchProperties" + }, + "head": { + "type": "string", + "description": "Current HEAD reference" + }, + "commits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitCommit" + } + }, + "branch": { + "type": ["string", "null"], + "description": "Current branch name" + }, + "isMerging": { + "type": "boolean", + "description": "Whether a merge is in progress" + } + }, + "required": [ + "changedFiles", + "deletedFiles", + "conflicts", + "localChanges", + "remote", + "target", + "commits", + "branch", + "isMerging" + ] + }, + "GitTargetDiff": { + "type": "object", + "properties": { + "ahead": { + "type": "number", + "description": "Number of commits ahead of the target" + }, + "behind": { + "type": "number", + "description": "Number of commits behind the target" + }, + "commits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitCommit" + } + } + }, + "required": ["ahead", "behind", "commits"] + }, + "GitRemotes": { + "type": "object", + "properties": { + "origin": { + "type": "string", + "description": "Origin remote URL" + }, + "upstream": { + "type": "string", + "description": "Upstream remote URL" + } + }, + "required": ["origin", "upstream"] + }, + "GitRemoteParams": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Branch or commit hash" + }, + "path": { + "type": "string", + "description": "Path to the file" + } + }, + "required": ["reference", "path"] + }, + "GitDiffStatusParams": { + "type": "object", + "properties": { + "base": { + "type": "string", + "description": "Base reference used for diffing" + }, + "head": { + "type": "string", + "description": "Head reference used for diffing" + } + }, + "required": ["base", "head"] + }, + "GitDiffStatusItem": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/GitStatusShortFormat" + }, + "path": { + "type": "string", + "description": "Path to the file" + }, + "oldPath": { + "type": "string", + "description": "Original path for renamed files" + }, + "hunks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "original": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + }, + "modified": { + "type": "object", + "properties": { + "start": { + "type": "number" + }, + "end": { + "type": "number" + } + }, + "required": ["start", "end"] + } + }, + "required": ["original", "modified"] + } + } + }, + "required": ["status", "path", "hunks"] + }, + "GitDiffStatusResult": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitDiffStatusItem" + } + } + }, + "required": ["files"] + } + } + } +} diff --git a/openapi-sandbox-setup.json b/openapi-sandbox-setup.json new file mode 100644 index 0000000..bc8b5c4 --- /dev/null +++ b/openapi-sandbox-setup.json @@ -0,0 +1,570 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Setup API", + "description": "API for managing sandbox setup operations", + "version": "1.0.0" + }, + "paths": { + "/setup/get": { + "post": { + "summary": "Get setup progress", + "description": "Retrieve the current setup progress status", + "operationId": "setupGet", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving setup progress", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/skip": { + "post": { + "summary": "Skip setup step", + "description": "Skip a specific step in the setup process", + "operationId": "setupSkipStep", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "stepIndexToSkip": { + "type": "number", + "description": "Index of the step to skip" + } + }, + "required": ["stepIndexToSkip"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error skipping step", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/skipAll": { + "post": { + "summary": "Skip all setup steps", + "description": "Skip all remaining steps in the setup process", + "operationId": "setupSkipAll", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error skipping all steps", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/disable": { + "post": { + "summary": "Disable setup", + "description": "Disable the setup process", + "operationId": "setupDisable", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error disabling setup", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/enable": { + "post": { + "summary": "Enable setup", + "description": "Enable the setup process", + "operationId": "setupEnable", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error enabling setup", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/init": { + "post": { + "summary": "Initialize setup", + "description": "Initialize the setup process", + "operationId": "setupInit", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error initializing setup", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + }, + "/setup/setStep": { + "post": { + "summary": "Set current setup step", + "description": "Set the current step in the setup process (used for restarting)", + "operationId": "setupSetStep", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "stepIndex": { + "type": "number", + "description": "Index of the step to set as current" + } + }, + "required": ["stepIndex"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SetupProgress" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error setting current step", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/ProtocolError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "ProtocolError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code", "message"] + }, + "SetupShellStatus": { + "type": "string", + "enum": ["SUCCEEDED", "FAILED", "SKIPPED"], + "description": "Status of a setup shell step" + }, + "Step": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the setup step" + }, + "command": { + "type": "string", + "description": "Command to execute for this step" + }, + "shellId": { + "type": "string", + "description": "ID of the shell executing the command", + "nullable": true + }, + "finishStatus": { + "$ref": "#/components/schemas/SetupShellStatus", + "nullable": true, + "description": "Status of the step after completion" + } + }, + "required": ["name", "command", "shellId", "finishStatus"] + }, + "SetupProgress": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": ["IDLE", "IN_PROGRESS", "FINISHED", "STOPPED"], + "description": "Current state of the setup process" + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Step" + }, + "description": "List of setup steps" + }, + "currentStepIndex": { + "type": "number", + "description": "Index of the current step being executed" + } + }, + "required": ["state", "steps", "currentStepIndex"] + } + } + } +} diff --git a/openapi-sandbox-shell.json b/openapi-sandbox-shell.json new file mode 100644 index 0000000..e362489 --- /dev/null +++ b/openapi-sandbox-shell.json @@ -0,0 +1,916 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Shell API", + "description": "API for managing terminal and command shells in the sandbox", + "version": "1.0.0" + }, + "paths": { + "/shell/create": { + "post": { + "summary": "Create a new shell", + "description": "Creates a new terminal or command shell", + "operationId": "shellCreate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to execute in the shell" + }, + "cwd": { + "type": "string", + "description": "Working directory for the shell" + }, + "size": { + "$ref": "#/components/schemas/ShellSize", + "description": "Terminal size dimensions" + }, + "type": { + "$ref": "#/components/schemas/ShellProcessType", + "description": "Type of shell to create" + }, + "isSystemShell": { + "type": "boolean", + "description": "Whether this shell is started by the editor itself to run a specific process" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/OpenShellDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error creating shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/in": { + "post": { + "summary": "Send input to shell", + "description": "Sends user input to an active shell", + "operationId": "shellIn", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the target shell" + }, + "input": { + "type": "string", + "description": "Input to send to the shell" + }, + "size": { + "$ref": "#/components/schemas/ShellSize", + "description": "Current terminal dimensions" + } + }, + "required": ["shellId", "input", "size"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error sending input to shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/list": { + "post": { + "summary": "List all shells", + "description": "Retrieves a list of all available shells", + "operationId": "shellList", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "shells": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShellDTO" + } + } + }, + "required": ["shells"] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error listing shells", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/open": { + "post": { + "summary": "Open an existing shell", + "description": "Opens an existing shell and retrieves its buffer", + "operationId": "shellOpen", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to open" + }, + "size": { + "$ref": "#/components/schemas/ShellSize", + "description": "Terminal dimensions" + } + }, + "required": ["shellId", "size"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/OpenShellDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error opening shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/close": { + "post": { + "summary": "Close a shell", + "description": "Closes a shell without terminating the underlying process", + "operationId": "shellClose", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to close" + } + }, + "required": ["shellId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error closing shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/restart": { + "post": { + "summary": "Restart a shell", + "description": "Restarts an existing shell process", + "operationId": "shellRestart", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to restart" + } + }, + "required": ["shellId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error restarting shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/terminate": { + "post": { + "summary": "Terminate a shell", + "description": "Terminates a shell and its underlying process", + "operationId": "shellTerminate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to terminate" + } + }, + "required": ["shellId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/ShellDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error terminating shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/resize": { + "post": { + "summary": "Resize a shell", + "description": "Updates the dimensions of a shell", + "operationId": "shellResize", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to resize" + }, + "size": { + "$ref": "#/components/schemas/ShellSize", + "description": "New terminal dimensions" + } + }, + "required": ["shellId", "size"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error resizing shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/shell/rename": { + "post": { + "summary": "Rename a shell", + "description": "Updates the name of a shell", + "operationId": "shellRename", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId", + "description": "ID of the shell to rename" + }, + "name": { + "type": "string", + "description": "New name for the shell" + } + }, + "required": ["shellId", "name"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error renaming shell", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "ShellId": { + "type": "string", + "description": "Unique identifier for a shell" + }, + "ShellSize": { + "type": "object", + "properties": { + "cols": { + "type": "number", + "description": "Number of columns in the terminal" + }, + "rows": { + "type": "number", + "description": "Number of rows in the terminal" + } + }, + "required": ["cols", "rows"] + }, + "ShellProcessType": { + "type": "string", + "enum": ["TERMINAL", "COMMAND"], + "description": "Type of shell process" + }, + "ShellProcessStatus": { + "type": "string", + "enum": ["RUNNING", "FINISHED", "ERROR", "KILLED", "RESTARTING"], + "description": "Current status of the shell process" + }, + "BaseShellDTO": { + "type": "object", + "properties": { + "shellId": { + "$ref": "#/components/schemas/ShellId" + }, + "name": { + "type": "string", + "description": "Display name of the shell" + }, + "status": { + "$ref": "#/components/schemas/ShellProcessStatus" + }, + "exitCode": { + "type": "number", + "description": "Exit code of the process if it has finished", + "nullable": true + } + }, + "required": ["shellId", "name", "status"] + }, + "CommandShellDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseShellDTO" + }, + { + "type": "object", + "properties": { + "shellType": { + "type": "string", + "enum": ["COMMAND"], + "description": "Indicates this is a command shell" + }, + "startCommand": { + "type": "string", + "description": "The command that was executed to start this shell" + } + }, + "required": ["shellType", "startCommand"] + } + ] + }, + "TerminalShellDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseShellDTO" + }, + { + "type": "object", + "properties": { + "shellType": { + "type": "string", + "enum": ["TERMINAL"], + "description": "Indicates this is a terminal shell" + }, + "ownerUsername": { + "type": "string", + "description": "Username of the shell owner" + }, + "isSystemShell": { + "type": "boolean", + "description": "Whether this is a system shell" + } + }, + "required": ["shellType", "ownerUsername", "isSystemShell"] + } + ] + }, + "ShellDTO": { + "oneOf": [ + { + "$ref": "#/components/schemas/CommandShellDTO" + }, + { + "$ref": "#/components/schemas/TerminalShellDTO" + } + ], + "discriminator": { + "propertyName": "shellType", + "mapping": { + "COMMAND": "#/components/schemas/CommandShellDTO", + "TERMINAL": "#/components/schemas/TerminalShellDTO" + } + } + }, + "OpenCommandShellDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/CommandShellDTO" + }, + { + "type": "object", + "properties": { + "buffer": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Content buffer of the shell" + } + }, + "required": ["buffer"] + } + ] + }, + "OpenTerminalShellDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/TerminalShellDTO" + }, + { + "type": "object", + "properties": { + "buffer": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Content buffer of the shell" + } + }, + "required": ["buffer"] + } + ] + }, + "OpenShellDTO": { + "oneOf": [ + { + "$ref": "#/components/schemas/OpenCommandShellDTO" + }, + { + "$ref": "#/components/schemas/OpenTerminalShellDTO" + } + ], + "discriminator": { + "propertyName": "shellType", + "mapping": { + "COMMAND": "#/components/schemas/OpenCommandShellDTO", + "TERMINAL": "#/components/schemas/OpenTerminalShellDTO" + } + } + }, + "CommonError": { + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["SHELL_NOT_ACCESSIBLE"], + "description": "Error code indicating the shell is not accessible" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + }, + { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Protocol error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + } + ] + } + } + } +} diff --git a/openapi-sandbox-system.json b/openapi-sandbox-system.json new file mode 100644 index 0000000..501f2f5 --- /dev/null +++ b/openapi-sandbox-system.json @@ -0,0 +1,348 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox System API", + "description": "API for managing sandbox system operations", + "version": "1.0.0" + }, + "paths": { + "/system/update": { + "post": { + "summary": "Update system", + "description": "Update the sandbox system", + "operationId": "systemUpdate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": {} + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error updating system", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/SystemError" + } + } + } + ] + } + } + } + } + } + } + }, + "/system/hibernate": { + "post": { + "summary": "Hibernate system", + "description": "Put the sandbox system into hibernation mode", + "operationId": "systemHibernate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error hibernating system", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/SystemError" + } + } + } + ] + } + } + } + } + } + } + }, + "/system/metrics": { + "post": { + "summary": "Get system metrics", + "description": "Retrieve current system metrics including CPU, memory and storage usage", + "operationId": "systemMetrics", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/SystemMetricsStatus" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving system metrics", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/SystemError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "SystemError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code", "message"] + }, + "SystemMetricsStatus": { + "type": "object", + "properties": { + "cpu": { + "type": "object", + "properties": { + "cores": { + "type": "number", + "description": "Number of CPU cores" + }, + "used": { + "type": "number", + "description": "Used CPU resources" + }, + "configured": { + "type": "number", + "description": "Configured CPU resources" + } + }, + "required": ["cores", "used", "configured"] + }, + "memory": { + "type": "object", + "properties": { + "used": { + "type": "number", + "description": "Used memory in bytes" + }, + "total": { + "type": "number", + "description": "Total available memory in bytes" + }, + "configured": { + "type": "number", + "description": "Configured memory limit in bytes" + } + }, + "required": ["used", "total", "configured"] + }, + "storage": { + "type": "object", + "properties": { + "used": { + "type": "number", + "description": "Used storage in bytes" + }, + "total": { + "type": "number", + "description": "Total available storage in bytes" + }, + "configured": { + "type": "number", + "description": "Configured storage limit in bytes" + } + }, + "required": ["used", "total", "configured"] + } + }, + "required": ["cpu", "memory", "storage"] + }, + "InitStatus": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Status message" + }, + "isError": { + "type": "boolean", + "description": "Whether the status represents an error", + "nullable": true + }, + "progress": { + "type": "number", + "description": "Current progress (0-100)", + "minimum": 0, + "maximum": 100 + }, + "nextProgress": { + "type": "number", + "description": "Next progress target (0-100)", + "minimum": 0, + "maximum": 100 + }, + "stdout": { + "type": "string", + "description": "Standard output from the initialization process", + "nullable": true + } + }, + "required": ["message", "progress", "nextProgress"] + } + } + } +} diff --git a/openapi-sandbox-task.json b/openapi-sandbox-task.json new file mode 100644 index 0000000..6b5e320 --- /dev/null +++ b/openapi-sandbox-task.json @@ -0,0 +1,947 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sandbox Task API", + "description": "API for managing tasks in sandbox", + "version": "1.0.0" + }, + "paths": { + "/task/list": { + "post": { + "summary": "List tasks", + "description": "Retrieve a list of all configured tasks", + "operationId": "taskList", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskListDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error retrieving task list", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/CommonError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/run": { + "post": { + "summary": "Run task", + "description": "Start execution of a task by ID", + "operationId": "taskRun", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskId": { + "type": "string", + "description": "ID of the task to run" + } + }, + "required": ["taskId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error running task", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/runCommand": { + "post": { + "summary": "Run command", + "description": "Run a shell command directly, optionally saving it as a task", + "operationId": "taskRunCommand", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to run" + }, + "name": { + "type": "string", + "description": "Optional name for the task", + "nullable": true + }, + "saveToConfig": { + "type": "boolean", + "description": "Whether to save this command as a task in the config", + "nullable": true + } + }, + "required": ["command"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error running command", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/stop": { + "post": { + "summary": "Stop task", + "description": "Stop execution of a running task", + "operationId": "taskStop", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskId": { + "type": "string", + "description": "ID of the task to stop" + } + }, + "required": ["taskId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "oneOf": [ + { + "$ref": "#/components/schemas/TaskDTO" + }, + { + "type": "null", + "description": "Null when stopping an unconfigured task" + } + ] + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error stopping task", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/create": { + "post": { + "summary": "Create task", + "description": "Create a new task configuration", + "operationId": "taskCreate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskFields": { + "$ref": "#/components/schemas/TaskDefinitionDTO" + }, + "startTask": { + "type": "boolean", + "description": "Whether to start the task immediately after creation", + "nullable": true + } + }, + "required": ["taskFields"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskListDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error creating task", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/update": { + "post": { + "summary": "Update task", + "description": "Update an existing task configuration", + "operationId": "taskUpdate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskId": { + "type": "string", + "description": "ID of the task to update" + }, + "taskFields": { + "type": "object", + "description": "Fields to update in the task", + "properties": { + "name": { + "type": "string", + "description": "Name of the task", + "nullable": true + }, + "command": { + "type": "string", + "description": "Command to run", + "nullable": true + }, + "runAtStart": { + "type": "boolean", + "description": "Whether to run the task at sandbox start", + "nullable": true + }, + "preview": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port to use for previewing the task", + "nullable": true + }, + "pr-link": { + "type": "string", + "enum": ["direct", "redirect", "devtool"], + "description": "Type of PR link to use", + "nullable": true + } + }, + "nullable": true + } + } + } + }, + "required": ["taskId", "taskFields"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error updating task", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/saveToConfig": { + "post": { + "summary": "Save task to config", + "description": "Save a runtime task to the configuration file", + "operationId": "taskSaveToConfig", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskId": { + "type": "string", + "description": "ID of the task to save to config" + } + }, + "required": ["taskId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/TaskDTO" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error saving task to config", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/generateConfig": { + "post": { + "summary": "Generate task config", + "description": "Generate a configuration file from current tasks", + "operationId": "taskGenerateConfig", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error generating config", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + }, + "/task/createSetupTasks": { + "post": { + "summary": "Create setup tasks", + "description": "Create tasks that run during sandbox setup", + "operationId": "taskCreateSetupTasks", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskDefinitionDTO" + }, + "description": "Setup tasks to create" + } + }, + "required": ["tasks"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SuccessResponse" + }, + { + "type": "object", + "properties": { + "result": { + "type": "null" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Error creating setup tasks", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorResponse" + }, + { + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/TaskError" + } + } + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [0], + "description": "Status code for successful operations" + }, + "result": { + "type": "object", + "description": "Result payload for the operation" + } + }, + "required": ["status", "result"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "status": { + "type": "number", + "enum": [1], + "description": "Status code for error operations" + }, + "error": { + "type": "object", + "description": "Error details" + } + }, + "required": ["status", "error"] + }, + "CommonError": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Error message" + }, + "data": { + "type": "object", + "description": "Additional error data", + "nullable": true + } + }, + "required": ["code"] + }, + "TaskError": { + "oneOf": [ + { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [600], + "description": "CONFIG_FILE_ALREADY_EXISTS error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + }, + { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [601], + "description": "TASK_NOT_FOUND error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + }, + { + "type": "object", + "properties": { + "code": { + "type": "number", + "enum": [602], + "description": "COMMAND_ALREADY_CONFIGURED error code" + }, + "message": { + "type": "string", + "description": "Error message" + } + }, + "required": ["code", "message"] + }, + { + "$ref": "#/components/schemas/CommonError" + } + ], + "discriminator": { + "propertyName": "code" + } + }, + "TaskDefinitionDTO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the task" + }, + "command": { + "type": "string", + "description": "Command to run for the task" + }, + "runAtStart": { + "type": "boolean", + "description": "Whether the task should run when the sandbox starts", + "nullable": true + }, + "preview": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port to preview from this task", + "nullable": true + }, + "pr-link": { + "type": "string", + "enum": ["direct", "redirect", "devtool"], + "description": "Type of PR link to use", + "nullable": true + } + }, + "nullable": true + } + }, + "required": ["name", "command"] + }, + "CommandShellDTO": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the shell command" + }, + "command": { + "type": "string", + "description": "Command being executed" + }, + "status": { + "type": "string", + "enum": ["initializing", "running", "stopped", "error"], + "description": "Current status of the shell command" + }, + "output": { + "type": "string", + "description": "Current output of the command" + } + }, + "required": ["id", "command", "status", "output"] + }, + "Port": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port number" + }, + "hostname": { + "type": "string", + "description": "Hostname the port is bound to" + }, + "status": { + "type": "string", + "enum": ["open", "closed"], + "description": "Current status of the port" + }, + "taskId": { + "type": "string", + "description": "ID of the task that opened this port", + "nullable": true + } + }, + "required": ["port", "hostname", "status"] + }, + "TaskDTO": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskDefinitionDTO" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique ID of the task" + }, + "unconfigured": { + "type": "boolean", + "description": "Whether this task is unconfigured (not saved in config)", + "nullable": true + }, + "shell": { + "type": "object", + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/CommandShellDTO" + } + ] + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Port" + }, + "description": "Ports opened by this task" + } + }, + "required": ["id", "shell", "ports"] + } + ] + }, + "TaskListDTO": { + "type": "object", + "properties": { + "tasks": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TaskDTO" + }, + "description": "Map of task IDs to task objects" + }, + "setupTasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskDefinitionDTO" + }, + "description": "Tasks that run during sandbox setup" + }, + "validationErrors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Validation errors in the task configuration" + } + }, + "required": ["tasks", "setupTasks", "validationErrors"] + } + } + } +} diff --git a/openapi.json b/openapi.json index dc15321..45365fe 100644 --- a/openapi.json +++ b/openapi.json @@ -7,21 +7,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -33,21 +26,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -56,20 +42,13 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, + "sandbox_id": { "type": "string" }, "tokens": { - "items": { - "$ref": "#/components/schemas/PreviewToken" - }, + "items": { "$ref": "#/components/schemas/PreviewToken" }, "type": "array" } }, - "required": [ - "tokens", - "sandbox_id" - ], + "required": ["tokens", "sandbox_id"], "type": "object" } }, @@ -89,35 +68,18 @@ "type": "integer" } }, - "required": [ - "hibernation_timeout_seconds" - ], + "required": ["hibernation_timeout_seconds"], "title": "VMUpdateHibernationTimeoutRequest", "type": "object" }, "PreviewToken": { "properties": { - "expires_at": { - "nullable": true, - "type": "string" - }, - "last_used_at": { - "nullable": true, - "type": "string" - }, - "token_id": { - "type": "string" - }, - "token_prefix": { - "type": "string" - } + "expires_at": { "nullable": true, "type": "string" }, + "last_used_at": { "nullable": true, "type": "string" }, + "token_id": { "type": "string" }, + "token_prefix": { "type": "string" } }, - "required": [ - "expires_at", - "last_used_at", - "token_id", - "token_prefix" - ], + "required": ["expires_at", "last_used_at", "token_id", "token_prefix"], "title": "PreviewToken", "type": "object" }, @@ -128,32 +90,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -167,32 +117,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -201,37 +139,14 @@ }, "Sandbox": { "properties": { - "created_at": { - "format": "date-time", - "type": "string" - }, - "description": { - "nullable": true, - "type": "string" - }, - "id": { - "type": "string" - }, - "is_frozen": { - "type": "boolean" - }, - "privacy": { - "type": "integer" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - }, - "title": { - "nullable": true, - "type": "string" - }, - "updated_at": { - "format": "date-time", - "type": "string" - } + "created_at": { "format": "date-time", "type": "string" }, + "description": { "nullable": true, "type": "string" }, + "id": { "type": "string" }, + "is_frozen": { "type": "boolean" }, + "privacy": { "type": "integer" }, + "tags": { "items": { "type": "string" }, "type": "array" }, + "title": { "nullable": true, "type": "string" }, + "updated_at": { "format": "date-time", "type": "string" } }, "required": [ "id", @@ -246,20 +161,12 @@ }, "Error": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, - "VMHibernateRequest": { - "properties": {}, - "title": "VMHibernateRequest" - }, + "VMHibernateRequest": { "properties": {}, "title": "VMHibernateRequest" }, "PreviewTokenCreateRequest": { "properties": { "expires_at": { @@ -279,30 +186,21 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { "properties": { - "data": { - "$ref": "#/components/schemas/Sandbox" - } + "data": { "$ref": "#/components/schemas/Sandbox" } }, "type": "object" } @@ -336,6 +234,22 @@ "start_options": { "description": "Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation.", "properties": { + "automatic_wakeup_config": { + "description": "Configuration for when the VM should automatically wake up from hibernation", + "properties": { + "http": { + "default": true, + "description": "Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests)", + "type": "boolean" + }, + "websocket": { + "default": false, + "description": "Whether the VM should automatically wake up on WebSocket connections", + "type": "boolean" + } + }, + "type": "object" + }, "hibernation_timeout_seconds": { "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", "example": 300, @@ -368,9 +282,7 @@ "tags": { "default": [], "description": "Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "title": { @@ -390,21 +302,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -415,36 +320,23 @@ "properties": { "pagination": { "properties": { - "current_page": { - "type": "integer" - }, + "current_page": { "type": "integer" }, "next_page": { "description": "The number of the next page, if any. If `null`, the current page is the last page of records.", "nullable": true, "type": "integer" }, - "total_records": { - "type": "integer" - } + "total_records": { "type": "integer" } }, - "required": [ - "total_records", - "current_page", - "next_page" - ], + "required": ["total_records", "current_page", "next_page"], "type": "object" }, "sandboxes": { - "items": { - "$ref": "#/components/schemas/Sandbox" - }, + "items": { "$ref": "#/components/schemas/Sandbox" }, "type": "array" } }, - "required": [ - "sandboxes", - "pagination" - ], + "required": ["sandboxes", "pagination"], "type": "object" } }, @@ -459,48 +351,24 @@ "api": { "description": "Meta information about the CodeSandbox API", "properties": { - "latest_version": { - "type": "string" - }, - "name": { - "type": "string" - } + "latest_version": { "type": "string" }, + "name": { "type": "string" } }, - "required": [ - "name", - "latest_version" - ], + "required": ["name", "latest_version"], "type": "object" }, "auth": { "description": "Meta information about the current authentication context", "properties": { - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team": { - "format": "uuid", - "nullable": true, - "type": "string" - }, - "version": { - "type": "string" - } + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team": { "format": "uuid", "nullable": true, "type": "string" }, + "version": { "type": "string" } }, - "required": [ - "scopes", - "team", - "version" - ], + "required": ["scopes", "team", "version"], "type": "object" } }, - "required": [ - "api" - ], + "required": ["api"], "title": "MetaInformation", "type": "object" }, @@ -548,32 +416,20 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" }, { - "properties": { - "data": { - "properties": {}, - "type": "object" - } - }, + "properties": { "data": { "properties": {}, "type": "object" } }, "type": "object" } ], @@ -587,21 +443,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -610,17 +459,10 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, - "token": { - "$ref": "#/components/schemas/PreviewToken" - } + "sandbox_id": { "type": "string" }, + "token": { "$ref": "#/components/schemas/PreviewToken" } }, - "required": [ - "sandbox_id", - "token" - ], + "required": ["sandbox_id", "token"], "type": "object" } }, @@ -637,21 +479,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -660,22 +495,11 @@ "properties": { "data": { "properties": { - "alias": { - "type": "string" - }, - "id": { - "type": "string" - }, - "title": { - "nullable": true, - "type": "string" - } + "alias": { "type": "string" }, + "id": { "type": "string" }, + "title": { "nullable": true, "type": "string" } }, - "required": [ - "alias", - "id", - "title" - ], + "required": ["alias", "id", "title"], "type": "object" } }, @@ -687,6 +511,22 @@ }, "VMStartRequest": { "properties": { + "automatic_wakeup_config": { + "description": "Configuration for when the VM should automatically wake up from hibernation", + "properties": { + "http": { + "default": true, + "description": "Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests)", + "type": "boolean" + }, + "websocket": { + "default": false, + "description": "Whether the VM should automatically wake up on WebSocket connections", + "type": "boolean" + } + }, + "type": "object" + }, "hibernation_timeout_seconds": { "description": "The time in seconds after which the VM will hibernate due to inactivity.\nMust be a positive integer between 1 and 86400 (24 hours).\nDefaults to 300 seconds (5 minutes) if not specified.\n", "example": 300, @@ -729,10 +569,7 @@ "title": "PreviewTokenUpdateRequest", "type": "object" }, - "VMShutdownRequest": { - "properties": {}, - "title": "VMShutdownRequest" - }, + "VMShutdownRequest": { "properties": {}, "title": "VMShutdownRequest" }, "VMUpdateSpecsRequest": { "properties": { "tier": { @@ -749,9 +586,7 @@ "example": "Micro" } }, - "required": [ - "tier" - ], + "required": ["tier"], "title": "VMUpdateSpecsRequest", "type": "object" }, @@ -763,9 +598,7 @@ "type": "string" } }, - "required": [ - "name" - ], + "required": ["name"], "title": "WorkspaceCreateRequest", "type": "object" }, @@ -776,21 +609,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -799,39 +625,17 @@ "properties": { "data": { "properties": { - "bootup_type": { - "type": "string" - }, - "cluster": { - "type": "string" - }, - "id": { - "type": "string" - }, - "latest_pitcher_version": { - "type": "string" - }, - "pitcher_manager_version": { - "type": "string" - }, - "pitcher_token": { - "type": "string" - }, - "pitcher_url": { - "type": "string" - }, - "pitcher_version": { - "type": "string" - }, - "reconnect_token": { - "type": "string" - }, - "user_workspace_path": { - "type": "string" - }, - "workspace_path": { - "type": "string" - } + "bootup_type": { "type": "string" }, + "cluster": { "type": "string" }, + "id": { "type": "string" }, + "latest_pitcher_version": { "type": "string" }, + "pitcher_manager_version": { "type": "string" }, + "pitcher_token": { "type": "string" }, + "pitcher_url": { "type": "string" }, + "pitcher_version": { "type": "string" }, + "reconnect_token": { "type": "string" }, + "user_workspace_path": { "type": "string" }, + "workspace_path": { "type": "string" } }, "required": [ "bootup_type", @@ -862,21 +666,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -885,17 +682,10 @@ "properties": { "data": { "properties": { - "id": { - "type": "string" - }, - "tier": { - "type": "string" - } + "id": { "type": "string" }, + "tier": { "type": "string" } }, - "required": [ - "id", - "tier" - ], + "required": ["id", "tier"], "type": "object" } }, @@ -917,9 +707,7 @@ }, "external_resources": { "description": "Array of strings with external resources to load.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "files": { @@ -950,9 +738,7 @@ "type": "boolean" }, "npm_dependencies": { - "additionalProperties": { - "type": "string" - }, + "additionalProperties": { "type": "string" }, "description": "Map of dependencies and their version specifications.", "type": "object" }, @@ -971,18 +757,13 @@ "runtime": { "default": "browser", "description": "Runtime to use for the sandbox. Defaults to `\"browser\"`.", - "enum": [ - "browser", - "vm" - ], + "enum": ["browser", "vm"], "type": "string" }, "tags": { "default": [], "description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "template": { @@ -996,9 +777,7 @@ "type": "string" } }, - "required": [ - "files" - ], + "required": ["files"], "title": "SandboxCreateRequest" }, "TokenUpdateResponse": { @@ -1008,21 +787,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1031,34 +803,17 @@ "properties": { "data": { "properties": { - "description": { - "nullable": true, - "type": "string" - }, + "description": { "nullable": true, "type": "string" }, "expires_at": { "format": "date-time", "nullable": true, "type": "string" }, - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team_id": { - "type": "string" - }, - "token_id": { - "type": "string" - } + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team_id": { "type": "string" }, + "token_id": { "type": "string" } }, - "required": [ - "description", - "scopes", - "team_id", - "token_id" - ], + "required": ["description", "scopes", "team_id", "token_id"], "type": "object" } }, @@ -1075,21 +830,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1098,29 +846,12 @@ "properties": { "data": { "properties": { - "description": { - "nullable": true, - "type": "string" - }, - "expires_at": { - "nullable": true, - "type": "string" - }, - "scopes": { - "items": { - "type": "string" - }, - "type": "array" - }, - "team_id": { - "type": "string" - }, - "token": { - "type": "string" - }, - "token_id": { - "type": "string" - } + "description": { "nullable": true, "type": "string" }, + "expires_at": { "nullable": true, "type": "string" }, + "scopes": { "items": { "type": "string" }, "type": "array" }, + "team_id": { "type": "string" }, + "token": { "type": "string" }, + "token_id": { "type": "string" } }, "required": [ "description", @@ -1178,10 +909,7 @@ "properties": { "permission": { "description": "Permission level for the session", - "enum": [ - "read", - "write" - ], + "enum": ["read", "write"], "example": "write", "type": "string" }, @@ -1192,10 +920,7 @@ "type": "string" } }, - "required": [ - "session_id", - "permission" - ], + "required": ["session_id", "permission"], "title": "VMCreateSessionRequest", "type": "object" }, @@ -1206,21 +931,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1231,9 +949,7 @@ "properties": { "capabilities": { "description": "List of capabilities that Pitcher has", - "items": { - "type": "string" - }, + "items": { "type": "string" }, "type": "array" }, "permissions": { @@ -1281,21 +997,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1304,17 +1013,10 @@ "properties": { "data": { "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + "id": { "type": "string" }, + "name": { "type": "string" } }, - "required": [ - "id", - "name" - ], + "required": ["id", "name"], "type": "object" } }, @@ -1331,21 +1033,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1354,17 +1049,10 @@ "properties": { "data": { "properties": { - "hibernation_timeout_seconds": { - "type": "integer" - }, - "id": { - "type": "string" - } + "hibernation_timeout_seconds": { "type": "integer" }, + "id": { "type": "string" } }, - "required": [ - "id", - "hibernation_timeout_seconds" - ], + "required": ["id", "hibernation_timeout_seconds"], "type": "object" } }, @@ -1381,21 +1069,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1404,49 +1085,23 @@ "properties": { "data": { "properties": { - "alias": { - "type": "string" - }, - "id": { - "type": "string" - }, + "alias": { "type": "string" }, + "id": { "type": "string" }, "start_response": { "description": "VM start response. Only present when start_options were provided in the request.", "nullable": true, "properties": { - "bootup_type": { - "type": "string" - }, - "cluster": { - "type": "string" - }, - "id": { - "type": "string" - }, - "latest_pitcher_version": { - "type": "string" - }, - "pitcher_manager_version": { - "type": "string" - }, - "pitcher_token": { - "type": "string" - }, - "pitcher_url": { - "type": "string" - }, - "pitcher_version": { - "type": "string" - }, - "reconnect_token": { - "type": "string" - }, - "user_workspace_path": { - "type": "string" - }, - "workspace_path": { - "type": "string" - } + "bootup_type": { "type": "string" }, + "cluster": { "type": "string" }, + "id": { "type": "string" }, + "latest_pitcher_version": { "type": "string" }, + "pitcher_manager_version": { "type": "string" }, + "pitcher_token": { "type": "string" }, + "pitcher_url": { "type": "string" }, + "pitcher_version": { "type": "string" }, + "reconnect_token": { "type": "string" }, + "user_workspace_path": { "type": "string" }, + "workspace_path": { "type": "string" } }, "required": [ "bootup_type", @@ -1463,16 +1118,9 @@ ], "type": "object" }, - "title": { - "nullable": true, - "type": "string" - } + "title": { "nullable": true, "type": "string" } }, - "required": [ - "alias", - "id", - "title" - ], + "required": ["alias", "id", "title"], "type": "object" } }, @@ -1489,21 +1137,14 @@ "errors": { "items": { "oneOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } + { "type": "string" }, + { "additionalProperties": true, "type": "object" } ], "title": "Error" }, "type": "array" }, - "success": { - "type": "boolean" - } + "success": { "type": "boolean" } }, "title": "Response", "type": "object" @@ -1512,27 +1153,18 @@ "properties": { "data": { "properties": { - "sandbox_id": { - "type": "string" - }, + "sandbox_id": { "type": "string" }, "token": { "allOf": [ { "properties": { - "expires_at": { - "nullable": true, - "type": "string" - }, + "expires_at": { "nullable": true, "type": "string" }, "last_used_at": { "nullable": true, "type": "string" }, - "token_id": { - "type": "string" - }, - "token_prefix": { - "type": "string" - } + "token_id": { "type": "string" }, + "token_prefix": { "type": "string" } }, "required": [ "expires_at", @@ -1544,24 +1176,15 @@ "type": "object" }, { - "properties": { - "token": { - "type": "string" - } - }, - "required": [ - "token" - ], + "properties": { "token": { "type": "string" } }, + "required": ["token"], "type": "object" } ], "type": "object" } }, - "required": [ - "sandbox_id", - "token" - ], + "required": ["sandbox_id", "token"], "type": "object" } }, @@ -1573,16 +1196,10 @@ } }, "securitySchemes": { - "authorization": { - "scheme": "bearer", - "type": "http" - } + "authorization": { "scheme": "bearer", "type": "http" } } }, - "info": { - "title": "CodeSandbox API", - "version": "2023-07-01" - }, + "info": { "title": "CodeSandbox API", "version": "2023-07-01" }, "openapi": "3.0.0", "paths": { "/meta/info": { @@ -1594,21 +1211,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/MetaInformation" - } + "schema": { "$ref": "#/components/schemas/MetaInformation" } } }, "description": "Meta Info Response" } }, - "security": [ - {} - ], + "security": [{}], "summary": "Metadata about the API", - "tags": [ - "meta" - ] + "tags": ["meta"] } }, "/org/workspace": { @@ -1640,13 +1251,7 @@ "description": "Workspace Create Response" } }, - "security": [ - { - "authorization": [ - "workspace:create" - ] - } - ], + "security": [{ "authorization": ["workspace:create"] }], "summary": "Create a Workspace", "tags": [] } @@ -1663,17 +1268,13 @@ "in": "path", "name": "team_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenCreateRequest" - } + "schema": { "$ref": "#/components/schemas/TokenCreateRequest" } } }, "description": "Token Create Request", @@ -1683,21 +1284,13 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenCreateResponse" - } + "schema": { "$ref": "#/components/schemas/TokenCreateResponse" } } }, "description": "Token Create Response" } }, - "security": [ - { - "authorization": [ - "token:manage" - ] - } - ], + "security": [{ "authorization": ["token:manage"] }], "summary": "Create an API Token", "tags": [] } @@ -1714,9 +1307,7 @@ "in": "path", "name": "team_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "ID of token to update", @@ -1724,17 +1315,13 @@ "in": "path", "name": "token_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenUpdateRequest" - } + "schema": { "$ref": "#/components/schemas/TokenUpdateRequest" } } }, "description": "Token Update Request", @@ -1744,21 +1331,13 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/TokenUpdateResponse" - } + "schema": { "$ref": "#/components/schemas/TokenUpdateResponse" } } }, "description": "Token Update Response" } }, - "security": [ - { - "authorization": [ - "token:manage" - ] - } - ], + "security": [{ "authorization": ["token:manage"] }], "summary": "Update an API Token", "tags": [] } @@ -1775,9 +1354,7 @@ "in": "query", "name": "tags", "required": false, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "Field to order results by", @@ -1787,10 +1364,7 @@ "required": false, "schema": { "default": "updated_at", - "enum": [ - "inserted_at", - "updated_at" - ] + "enum": ["inserted_at", "updated_at"] } }, { @@ -1799,13 +1373,7 @@ "in": "query", "name": "direction", "required": false, - "schema": { - "default": "desc", - "enum": [ - "asc", - "desc" - ] - } + "schema": { "default": "desc", "enum": ["asc", "desc"] } }, { "description": "Maximum number of sandboxes to return in a single response", @@ -1824,47 +1392,29 @@ "in": "query", "name": "page", "required": false, - "schema": { - "default": 1, - "minimum": 1, - "type": "integer" - } + "schema": { "default": 1, "minimum": 1, "type": "integer" } }, { "description": "If true, only returns VMs for which a heartbeat was received in the last 30 seconds.", "in": "query", "name": "status", "required": false, - "schema": { - "enum": [ - "running" - ] - } + "schema": { "enum": ["running"] } } ], "responses": { "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxListResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxListResponse" } } }, "description": "Sandbox List Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read" - ] - } - ], + "security": [{ "authorization": ["sandbox:read"] }], "summary": "List Sandboxes", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] }, "post": { "callbacks": {}, @@ -1874,9 +1424,7 @@ "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxCreateRequest" - } + "schema": { "$ref": "#/components/schemas/SandboxCreateRequest" } } }, "description": "Sandbox Create Request", @@ -1894,17 +1442,9 @@ "description": "Sandbox Create Response" } }, - "security": [ - { - "authorization": [ - "sandbox:create" - ] - } - ], + "security": [{ "authorization": ["sandbox:create"] }], "summary": "Create a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}": { @@ -1919,34 +1459,22 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxGetResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxGetResponse" } } }, "description": "Sandbox Get Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read" - ] - } - ], + "security": [{ "authorization": ["sandbox:read"] }], "summary": "Get a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}/fork": { @@ -1961,17 +1489,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxForkRequest" - } + "schema": { "$ref": "#/components/schemas/SandboxForkRequest" } } }, "description": "Sandbox Fork Request", @@ -1981,25 +1505,15 @@ "201": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboxForkResponse" - } + "schema": { "$ref": "#/components/schemas/SandboxForkResponse" } } }, "description": "Sandbox Fork Response" } }, - "security": [ - { - "authorization": [ - "sandbox:create" - ] - } - ], + "security": [{ "authorization": ["sandbox:create"] }], "summary": "Fork a Sandbox", - "tags": [ - "sandbox" - ] + "tags": ["sandbox"] } }, "/sandbox/{id}/tokens": { @@ -2014,9 +1528,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { @@ -2028,16 +1540,10 @@ } } }, - "description": "RevokeALlPreviewTokensResponse" + "description": "RevokeAllPreviewTokensResponse" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Revoke preview tokens", "tags": [] }, @@ -2052,9 +1558,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "responses": { @@ -2069,13 +1573,7 @@ "description": "Token List Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "List Preview Tokens", "tags": [] }, @@ -2090,9 +1588,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2118,13 +1614,7 @@ "description": "Token Create Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Create a Preview Token", "tags": [] } @@ -2141,9 +1631,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } }, { "description": "ID of the token to update. Does not accept the token itself.", @@ -2151,9 +1639,7 @@ "in": "path", "name": "token_id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2179,13 +1665,7 @@ "description": "Token Update Response" } }, - "security": [ - { - "authorization": [ - "preview_token:manage" - ] - } - ], + "security": [{ "authorization": ["preview_token:manage"] }], "summary": "Update a Preview Token", "tags": [] } @@ -2202,17 +1682,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMHibernateRequest" - } + "schema": { "$ref": "#/components/schemas/VMHibernateRequest" } } }, "description": "VM Hibernate Request", @@ -2222,25 +1698,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMHibernateResponse" - } + "schema": { "$ref": "#/components/schemas/VMHibernateResponse" } } }, "description": "VM Hibernate Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Hibernate a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/hibernation_timeout": { @@ -2255,9 +1721,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2283,17 +1747,9 @@ "description": "VM Update Hibernation Timeout Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Hibernation Timeout", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/sessions": { @@ -2308,9 +1764,7 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { @@ -2336,17 +1790,9 @@ "description": "VM Create Session Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Create a new session on a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/shutdown": { @@ -2361,17 +1807,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMShutdownRequest" - } + "schema": { "$ref": "#/components/schemas/VMShutdownRequest" } } }, "description": "VM Shutdown Request", @@ -2381,25 +1823,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMShutdownResponse" - } + "schema": { "$ref": "#/components/schemas/VMShutdownResponse" } } }, "description": "VM Shutdown Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Shutdown a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/specs": { @@ -2414,17 +1846,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMUpdateSpecsRequest" - } + "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } } }, "description": "VM Update Specs Request", @@ -2442,17 +1870,9 @@ "description": "VM Update Specs Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Specs", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/start": { @@ -2467,17 +1887,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMStartRequest" - } + "schema": { "$ref": "#/components/schemas/VMStartRequest" } } }, "description": "VM Start Request", @@ -2487,26 +1903,15 @@ "200": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMStartResponse" - } + "schema": { "$ref": "#/components/schemas/VMStartResponse" } } }, "description": "VM Start Response" } }, - "security": [ - { - "authorization": [ - "sandbox:read", - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["sandbox:read", "vm:manage"] }], "summary": "Start a VM", - "tags": [ - "vm" - ] + "tags": ["vm"] } }, "/vm/{id}/update_specs": { @@ -2521,17 +1926,13 @@ "in": "path", "name": "id", "required": true, - "schema": { - "type": "string" - } + "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/VMUpdateSpecsRequest" - } + "schema": { "$ref": "#/components/schemas/VMUpdateSpecsRequest" } } }, "description": "VM Update Specs Request", @@ -2549,26 +1950,13 @@ "description": "VM Update Specs Response" } }, - "security": [ - { - "authorization": [ - "vm:manage" - ] - } - ], + "security": [{ "authorization": ["vm:manage"] }], "summary": "Update VM Specs", - "tags": [ - "vm" - ] + "tags": ["vm"] } } }, "security": [], - "servers": [ - { - "url": "https://api.codesandbox.stream", - "variables": {} - } - ], + "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], "tags": [] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 298e76f..9d0d881 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,16 @@ "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", - "build-openapi": "rimraf src/client && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch && npm run build-openapi-rest", - "build-openapi:staging": "rimraf src/client && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/client -c @hey-api/client-fetch", - "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/client-rest-fs -c @hey-api/client-fetch", + "build-openapi": "rimraf src/clients && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", + "build-openapi:staging": "rimraf src/clients && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", + "build-openapi-rest": "npm run build-openapi-rest-fs && npm run build-openapi-rest-task && npm run build-openapi-rest-container && npm run build-openapi-rest-git && npm run build-openapi-rest-setup && npm run build-openapi-rest-shell && npm run build-openapi-rest-system", + "build-openapi-rest-container": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-container.json -o src/clients/client-rest-container -c @hey-api/client-fetch", + "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/clients/client-rest-fs -c @hey-api/client-fetch", + "build-openapi-rest-git": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-git.json -o src/clients/client-rest-git -c @hey-api/client-fetch", + "build-openapi-rest-setup": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-setup.json -o src/clients/client-rest-setup -c @hey-api/client-fetch", + "build-openapi-rest-shell": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-shell.json -o src/clients/client-rest-shell -c @hey-api/client-fetch", + "build-openapi-rest-system": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-system.json -o src/clients/client-rest-system -c @hey-api/client-fetch", + "build-openapi-rest-task": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-task.json -o src/clients/client-rest-task -c @hey-api/client-fetch", "clean": "rimraf ./dist", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index e019b73..a7e63ad 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -9,7 +9,11 @@ import type * as yargs from "yargs"; import { SetupProgress, VMTier } from "../../"; import { CodeSandbox, PortInfo } from "../../"; -import { sandboxCreate, sandboxFork, VmUpdateSpecsRequest } from "../../client"; +import { + sandboxCreate, + sandboxFork, + VmUpdateSpecsRequest, +} from "../../clients/client"; import { handleResponse } from "../../utils/handle-response"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; diff --git a/src/client-rest-fs/client.gen.ts b/src/clients/client-rest-container/client.gen.ts similarity index 100% rename from src/client-rest-fs/client.gen.ts rename to src/clients/client-rest-container/client.gen.ts diff --git a/src/client-rest-fs/index.ts b/src/clients/client-rest-container/index.ts similarity index 100% rename from src/client-rest-fs/index.ts rename to src/clients/client-rest-container/index.ts diff --git a/src/clients/client-rest-container/sdk.gen.ts b/src/clients/client-rest-container/sdk.gen.ts new file mode 100644 index 0000000..bfa7256 --- /dev/null +++ b/src/clients/client-rest-container/sdk.gen.ts @@ -0,0 +1,29 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { ContainerSetupData, ContainerSetupResponse, ContainerSetupError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Setup container + * Set up a new container based on a template + */ +export const containerSetup = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/container/setup', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-container/types.gen.ts b/src/clients/client-rest-container/types.gen.ts new file mode 100644 index 0000000..b2fa4a4 --- /dev/null +++ b/src/clients/client-rest-container/types.gen.ts @@ -0,0 +1,111 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type ProtocolError = { + /** + * Error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type TaskDto = { + /** + * Task identifier + */ + id: string; + /** + * Task status + */ + status: string; + /** + * Task progress (0-100) + */ + progress: number; +}; + +export type ContainerSetupData = { + body: { + /** + * Identifier of the template to use + */ + templateId: string; + /** + * Arguments for the template + */ + templateArgs: { + [key: string]: string; + }; + features?: Array<{ + /** + * Feature identifier + */ + id: string; + /** + * Options for the feature + */ + options: { + [key: string]: string; + }; + }> | null; + }; + path?: never; + query?: never; + url: '/container/setup'; +}; + +export type ContainerSetupErrors = { + /** + * Error setting up container + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type ContainerSetupError = ContainerSetupErrors[keyof ContainerSetupErrors]; + +export type ContainerSetupResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; +}; + +export type ContainerSetupResponse = ContainerSetupResponses[keyof ContainerSetupResponses]; \ No newline at end of file diff --git a/src/client/client.gen.ts b/src/clients/client-rest-fs/client.gen.ts similarity index 100% rename from src/client/client.gen.ts rename to src/clients/client-rest-fs/client.gen.ts diff --git a/src/client/index.ts b/src/clients/client-rest-fs/index.ts similarity index 100% rename from src/client/index.ts rename to src/clients/client-rest-fs/index.ts diff --git a/src/client-rest-fs/sdk.gen.ts b/src/clients/client-rest-fs/sdk.gen.ts similarity index 99% rename from src/client-rest-fs/sdk.gen.ts rename to src/clients/client-rest-fs/sdk.gen.ts index 899de24..83a4cab 100644 --- a/src/client-rest-fs/sdk.gen.ts +++ b/src/clients/client-rest-fs/sdk.gen.ts @@ -19,7 +19,7 @@ export type Options(options: Options) => { return (options.client ?? _heyApiClient).post({ - url: '/fs/writefile', + url: '/fs/writeFile', ...options, headers: { 'Content-Type': 'application/json', diff --git a/src/client-rest-fs/types.gen.ts b/src/clients/client-rest-fs/types.gen.ts similarity index 99% rename from src/client-rest-fs/types.gen.ts rename to src/clients/client-rest-fs/types.gen.ts index 312a8d9..624cd45 100644 --- a/src/client-rest-fs/types.gen.ts +++ b/src/clients/client-rest-fs/types.gen.ts @@ -436,7 +436,7 @@ export type WriteFileData = { body: WriteFileRequest; path?: never; query?: never; - url: '/fs/writefile'; + url: '/fs/writeFile'; }; export type WriteFileErrors = { diff --git a/src/clients/client-rest-git/client.gen.ts b/src/clients/client-rest-git/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client-rest-git/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client-rest-git/index.ts b/src/clients/client-rest-git/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client-rest-git/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/clients/client-rest-git/sdk.gen.ts b/src/clients/client-rest-git/sdk.gen.ts new file mode 100644 index 0000000..0e3b272 --- /dev/null +++ b/src/clients/client-rest-git/sdk.gen.ts @@ -0,0 +1,224 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { GitStatusData, GitStatusResponse, GitStatusError, GitRemotesData, GitRemotesResponse, GitRemotesError, GitTargetDiffData, GitTargetDiffResponse, GitTargetDiffError, GitPullData, GitPullResponse, GitPullError, GitDiscardData, GitDiscardResponse, GitDiscardError, GitCommitData, GitCommitResponse, GitCommitError, GitPushData, GitPushResponse, GitPushError, GitPushToRemoteData, GitPushToRemoteResponse, GitPushToRemoteError, GitRenameBranchData, GitRenameBranchResponse, GitRenameBranchError, GitRemoteContentData, GitRemoteContentResponse, GitRemoteContentError, GitDiffStatusData, GitDiffStatusResponse, GitDiffStatusError, GitResetLocalWithRemoteData, GitResetLocalWithRemoteResponse, GitResetLocalWithRemoteError, GitCheckoutInitialBranchData, GitCheckoutInitialBranchResponse, GitCheckoutInitialBranchError, GitTransposeLinesData, GitTransposeLinesResponse, GitTransposeLinesError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Get git status + * Retrieve current git status including changed files, branch information, and commits + */ +export const gitStatus = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/status', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get git remotes + * Retrieve git remote information + */ +export const gitRemotes = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/remotes', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get git target diff + * Retrieve diff between current branch and target branch + */ +export const gitTargetDiff = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/targetDiff', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Pull from remote + * Pull changes from remote repository + */ +export const gitPull = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/pull', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Discard changes + * Discard local changes for specified paths + */ +export const gitDiscard = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/discard', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Commit changes + * Commit changes to the repository + */ +export const gitCommit = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/commit', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Push changes + * Push local commits to remote repository + */ +export const gitPush = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/push', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Push to remote + * Push to a specific remote repository + */ +export const gitPushToRemote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/pushToRemote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Rename branch + * Rename a git branch + */ +export const gitRenameBranch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/renameBranch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get remote content + * Retrieve content from a remote repository + */ +export const gitRemoteContent = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/remoteContent', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get diff status + * Retrieve diff status between two git references + */ +export const gitDiffStatus = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/diffStatus', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Reset local with remote + * Reset local repository to match the remote state + */ +export const gitResetLocalWithRemote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/resetLocalWithRemote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Checkout initial branch + * Checkout the initial branch of the repository + */ +export const gitCheckoutInitialBranch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/checkoutInitialBranch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Transpose lines + * Transpose line numbers from one git reference to another + */ +export const gitTransposeLines = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/transposeLines', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-git/types.gen.ts b/src/clients/client-rest-git/types.gen.ts new file mode 100644 index 0000000..dce87ba --- /dev/null +++ b/src/clients/client-rest-git/types.gen.ts @@ -0,0 +1,725 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type CommonError = { + /** + * Error code + */ + code: 'GIT_OPERATION_IN_PROGRESS' | 'GIT_REMOTE_FILE_NOT_FOUND'; + /** + * Error message + */ + message: string; +} | { + /** + * Protocol error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + }; +}; + +/** + * Git status short format codes + */ +export type GitStatusShortFormat = '' | 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'; + +export type GitItem = { + /** + * File path + */ + path: string; + index: GitStatusShortFormat; + workingTree: GitStatusShortFormat; + /** + * Whether the file is staged + */ + isStaged: boolean; + /** + * Whether the file has conflicts + */ + isConflicted: boolean; + /** + * Unique identifier for the file + */ + fileId?: string; +}; + +/** + * Map of file IDs to Git items + */ +export type GitChangedFiles = { + [key: string]: GitItem; +}; + +export type GitBranchProperties = { + /** + * Current HEAD reference + */ + head?: unknown; + /** + * Current branch name + */ + branch?: unknown; + /** + * Number of commits ahead of the remote + */ + ahead: number; + /** + * Number of commits behind the remote + */ + behind: number; + /** + * Whether the branch is safe to operate on + */ + safe: boolean; +}; + +export type GitCommit = { + /** + * Commit hash + */ + hash: string; + /** + * Commit date + */ + date: string; + /** + * Commit message + */ + message: string; + /** + * Commit author + */ + author: string; +}; + +export type GitStatus = { + changedFiles: GitChangedFiles; + deletedFiles: Array; + /** + * Whether there are remote conflicts + */ + conflicts: boolean; + /** + * Whether there are local changes + */ + localChanges: boolean; + remote: GitBranchProperties; + target: GitBranchProperties; + /** + * Current HEAD reference + */ + head?: string; + commits: Array; + /** + * Current branch name + */ + branch: unknown; + /** + * Whether a merge is in progress + */ + isMerging: boolean; +}; + +export type GitTargetDiff = { + /** + * Number of commits ahead of the target + */ + ahead: number; + /** + * Number of commits behind the target + */ + behind: number; + commits: Array; +}; + +export type GitRemotes = { + /** + * Origin remote URL + */ + origin: string; + /** + * Upstream remote URL + */ + upstream: string; +}; + +export type GitRemoteParams = { + /** + * Branch or commit hash + */ + reference: string; + /** + * Path to the file + */ + path: string; +}; + +export type GitDiffStatusParams = { + /** + * Base reference used for diffing + */ + base: string; + /** + * Head reference used for diffing + */ + head: string; +}; + +export type GitDiffStatusItem = { + status: GitStatusShortFormat; + /** + * Path to the file + */ + path: string; + /** + * Original path for renamed files + */ + oldPath?: string; + hunks: Array<{ + original: { + start: number; + end: number; + }; + modified: { + start: number; + end: number; + }; + }>; +}; + +export type GitDiffStatusResult = { + files: Array; +}; + +export type GitStatusData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/status'; +}; + +export type GitStatusErrors = { + /** + * Error retrieving git status + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitStatusError = GitStatusErrors[keyof GitStatusErrors]; + +export type GitStatusResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitStatus; + }; +}; + +export type GitStatusResponse = GitStatusResponses[keyof GitStatusResponses]; + +export type GitRemotesData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/remotes'; +}; + +export type GitRemotesErrors = { + /** + * Error retrieving git remotes + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitRemotesError = GitRemotesErrors[keyof GitRemotesErrors]; + +export type GitRemotesResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitRemotes; + }; +}; + +export type GitRemotesResponse = GitRemotesResponses[keyof GitRemotesResponses]; + +export type GitTargetDiffData = { + body: { + /** + * Branch to compare against + */ + branch: string; + }; + path?: never; + query?: never; + url: '/git/targetDiff'; +}; + +export type GitTargetDiffErrors = { + /** + * Error retrieving git target diff + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitTargetDiffError = GitTargetDiffErrors[keyof GitTargetDiffErrors]; + +export type GitTargetDiffResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitTargetDiff; + }; +}; + +export type GitTargetDiffResponse = GitTargetDiffResponses[keyof GitTargetDiffResponses]; + +export type GitPullData = { + body: { + /** + * Branch to pull from + */ + branch?: string; + /** + * Force pull even if there are conflicts + */ + force?: boolean; + }; + path?: never; + query?: never; + url: '/git/pull'; +}; + +export type GitPullErrors = { + /** + * Error pulling from remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitPullError = GitPullErrors[keyof GitPullErrors]; + +export type GitPullResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitPullResponse = GitPullResponses[keyof GitPullResponses]; + +export type GitDiscardData = { + body: { + /** + * Paths of files to discard changes + */ + paths?: Array; + }; + path?: never; + query?: never; + url: '/git/discard'; +}; + +export type GitDiscardErrors = { + /** + * Error discarding changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitDiscardError = GitDiscardErrors[keyof GitDiscardErrors]; + +export type GitDiscardResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + paths?: Array; + }; + }; +}; + +export type GitDiscardResponse = GitDiscardResponses[keyof GitDiscardResponses]; + +export type GitCommitData = { + body: { + /** + * Paths of files to commit + */ + paths?: Array; + /** + * Commit message + */ + message: string; + /** + * Whether to push the commit immediately + */ + push?: boolean; + }; + path?: never; + query?: never; + url: '/git/commit'; +}; + +export type GitCommitErrors = { + /** + * Error committing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitCommitError = GitCommitErrors[keyof GitCommitErrors]; + +export type GitCommitResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the shell process + */ + shellId: string; + }; + }; +}; + +export type GitCommitResponse = GitCommitResponses[keyof GitCommitResponses]; + +export type GitPushData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/push'; +}; + +export type GitPushErrors = { + /** + * Error pushing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitPushError = GitPushErrors[keyof GitPushErrors]; + +export type GitPushResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitPushResponse = GitPushResponses[keyof GitPushResponses]; + +export type GitPushToRemoteData = { + body: { + /** + * URL of the remote repository + */ + url: string; + /** + * Branch to push to + */ + branch: string; + /** + * Whether to squash all commits into one + */ + squashAllCommits?: boolean; + }; + path?: never; + query?: never; + url: '/git/pushToRemote'; +}; + +export type GitPushToRemoteErrors = { + /** + * Error pushing to remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitPushToRemoteError = GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; + +export type GitPushToRemoteResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitPushToRemoteResponse = GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; + +export type GitRenameBranchData = { + body: { + /** + * Current branch name + */ + oldBranch: string; + /** + * New branch name + */ + newBranch: string; + }; + path?: never; + query?: never; + url: '/git/renameBranch'; +}; + +export type GitRenameBranchErrors = { + /** + * Error renaming branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitRenameBranchError = GitRenameBranchErrors[keyof GitRenameBranchErrors]; + +export type GitRenameBranchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitRenameBranchResponse = GitRenameBranchResponses[keyof GitRenameBranchResponses]; + +export type GitRemoteContentData = { + body: GitRemoteParams; + path?: never; + query?: never; + url: '/git/remoteContent'; +}; + +export type GitRemoteContentErrors = { + /** + * Error retrieving remote content + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitRemoteContentError = GitRemoteContentErrors[keyof GitRemoteContentErrors]; + +export type GitRemoteContentResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * Content of the file + */ + content: string; + }; + }; +}; + +export type GitRemoteContentResponse = GitRemoteContentResponses[keyof GitRemoteContentResponses]; + +export type GitDiffStatusData = { + body: GitDiffStatusParams; + path?: never; + query?: never; + url: '/git/diffStatus'; +}; + +export type GitDiffStatusErrors = { + /** + * Error retrieving diff status + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitDiffStatusError = GitDiffStatusErrors[keyof GitDiffStatusErrors]; + +export type GitDiffStatusResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitDiffStatusResult; + }; +}; + +export type GitDiffStatusResponse = GitDiffStatusResponses[keyof GitDiffStatusResponses]; + +export type GitResetLocalWithRemoteData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/resetLocalWithRemote'; +}; + +export type GitResetLocalWithRemoteErrors = { + /** + * Error resetting local with remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitResetLocalWithRemoteError = GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; + +export type GitResetLocalWithRemoteResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitResetLocalWithRemoteResponse = GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; + +export type GitCheckoutInitialBranchData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/checkoutInitialBranch'; +}; + +export type GitCheckoutInitialBranchErrors = { + /** + * Error checking out initial branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitCheckoutInitialBranchError = GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; + +export type GitCheckoutInitialBranchResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type GitCheckoutInitialBranchResponse = GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; + +export type GitTransposeLinesData = { + body: Array<{ + /** + * Git commit SHA + */ + sha: string; + /** + * Path to the file + */ + path: string; + /** + * Line number to transpose + */ + line: number; + }>; + path?: never; + query?: never; + url: '/git/transposeLines'; +}; + +export type GitTransposeLinesErrors = { + /** + * Error transposing lines + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type GitTransposeLinesError = GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; + +export type GitTransposeLinesResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array<{ + path: string; + line: number; + } | unknown>; + }; +}; + +export type GitTransposeLinesResponse = GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; \ No newline at end of file diff --git a/src/clients/client-rest-setup/client.gen.ts b/src/clients/client-rest-setup/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client-rest-setup/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client-rest-setup/index.ts b/src/clients/client-rest-setup/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client-rest-setup/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/clients/client-rest-setup/sdk.gen.ts b/src/clients/client-rest-setup/sdk.gen.ts new file mode 100644 index 0000000..1a3ae61 --- /dev/null +++ b/src/clients/client-rest-setup/sdk.gen.ts @@ -0,0 +1,119 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { SetupGetData, SetupGetResponse, SetupGetError, SetupSkipStepData, SetupSkipStepResponse, SetupSkipStepError, SetupSkipAllData, SetupSkipAllResponse, SetupSkipAllError, SetupDisableData, SetupDisableResponse, SetupDisableError, SetupEnableData, SetupEnableResponse, SetupEnableError, SetupInitData, SetupInitResponse, SetupInitError, SetupSetStepData, SetupSetStepResponse, SetupSetStepError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Get setup progress + * Retrieve the current setup progress status + */ +export const setupGet = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/get', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Skip setup step + * Skip a specific step in the setup process + */ +export const setupSkipStep = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/skip', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Skip all setup steps + * Skip all remaining steps in the setup process + */ +export const setupSkipAll = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/skipAll', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Disable setup + * Disable the setup process + */ +export const setupDisable = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/disable', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Enable setup + * Enable the setup process + */ +export const setupEnable = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/enable', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Initialize setup + * Initialize the setup process + */ +export const setupInit = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/init', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Set current setup step + * Set the current step in the setup process (used for restarting) + */ +export const setupSetStep = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/setStep', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-setup/types.gen.ts b/src/clients/client-rest-setup/types.gen.ts new file mode 100644 index 0000000..43425e7 --- /dev/null +++ b/src/clients/client-rest-setup/types.gen.ts @@ -0,0 +1,295 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type ProtocolError = { + /** + * Error code + */ + code: number; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +/** + * Status of a setup shell step + */ +export type SetupShellStatus = 'SUCCEEDED' | 'FAILED' | 'SKIPPED'; + +export type Step = { + /** + * Name of the setup step + */ + name: string; + /** + * Command to execute for this step + */ + command: string; + /** + * ID of the shell executing the command + */ + shellId: string | null; + finishStatus: SetupShellStatus; +}; + +export type SetupProgress = { + /** + * Current state of the setup process + */ + state: 'IDLE' | 'IN_PROGRESS' | 'FINISHED' | 'STOPPED'; + /** + * List of setup steps + */ + steps: Array; + /** + * Index of the current step being executed + */ + currentStepIndex: number; +}; + +export type SetupGetData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/setup/get'; +}; + +export type SetupGetErrors = { + /** + * Error retrieving setup progress + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupGetError = SetupGetErrors[keyof SetupGetErrors]; + +export type SetupGetResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupGetResponse = SetupGetResponses[keyof SetupGetResponses]; + +export type SetupSkipStepData = { + body: { + /** + * Index of the step to skip + */ + stepIndexToSkip: number; + }; + path?: never; + query?: never; + url: '/setup/skip'; +}; + +export type SetupSkipStepErrors = { + /** + * Error skipping step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupSkipStepError = SetupSkipStepErrors[keyof SetupSkipStepErrors]; + +export type SetupSkipStepResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupSkipStepResponse = SetupSkipStepResponses[keyof SetupSkipStepResponses]; + +export type SetupSkipAllData = { + body: unknown; + path?: never; + query?: never; + url: '/setup/skipAll'; +}; + +export type SetupSkipAllErrors = { + /** + * Error skipping all steps + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupSkipAllError = SetupSkipAllErrors[keyof SetupSkipAllErrors]; + +export type SetupSkipAllResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupSkipAllResponse = SetupSkipAllResponses[keyof SetupSkipAllResponses]; + +export type SetupDisableData = { + body: unknown; + path?: never; + query?: never; + url: '/setup/disable'; +}; + +export type SetupDisableErrors = { + /** + * Error disabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupDisableError = SetupDisableErrors[keyof SetupDisableErrors]; + +export type SetupDisableResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupDisableResponse = SetupDisableResponses[keyof SetupDisableResponses]; + +export type SetupEnableData = { + body: unknown; + path?: never; + query?: never; + url: '/setup/enable'; +}; + +export type SetupEnableErrors = { + /** + * Error enabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupEnableError = SetupEnableErrors[keyof SetupEnableErrors]; + +export type SetupEnableResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupEnableResponse = SetupEnableResponses[keyof SetupEnableResponses]; + +export type SetupInitData = { + body: unknown; + path?: never; + query?: never; + url: '/setup/init'; +}; + +export type SetupInitErrors = { + /** + * Error initializing setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupInitError = SetupInitErrors[keyof SetupInitErrors]; + +export type SetupInitResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupInitResponse = SetupInitResponses[keyof SetupInitResponses]; + +export type SetupSetStepData = { + body: { + /** + * Index of the step to set as current + */ + stepIndex: number; + }; + path?: never; + query?: never; + url: '/setup/setStep'; +}; + +export type SetupSetStepErrors = { + /** + * Error setting current step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; +}; + +export type SetupSetStepError = SetupSetStepErrors[keyof SetupSetStepErrors]; + +export type SetupSetStepResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; +}; + +export type SetupSetStepResponse = SetupSetStepResponses[keyof SetupSetStepResponses]; \ No newline at end of file diff --git a/src/clients/client-rest-shell/client.gen.ts b/src/clients/client-rest-shell/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client-rest-shell/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client-rest-shell/index.ts b/src/clients/client-rest-shell/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client-rest-shell/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/clients/client-rest-shell/sdk.gen.ts b/src/clients/client-rest-shell/sdk.gen.ts new file mode 100644 index 0000000..319634a --- /dev/null +++ b/src/clients/client-rest-shell/sdk.gen.ts @@ -0,0 +1,149 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { ShellCreateData, ShellCreateResponse, ShellCreateError, ShellInData, ShellInResponse, ShellInError, ShellListData, ShellListResponse, ShellListError, ShellOpenData, ShellOpenResponse, ShellOpenError, ShellCloseData, ShellCloseResponse, ShellCloseError, ShellRestartData, ShellRestartResponse, ShellRestartError, ShellTerminateData, ShellTerminateResponse, ShellTerminateError, ShellResizeData, ShellResizeResponse, ShellResizeError, ShellRenameData, ShellRenameResponse, ShellRenameError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Create a new shell + * Creates a new terminal or command shell + */ +export const shellCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/create', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Send input to shell + * Sends user input to an active shell + */ +export const shellIn = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/in', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * List all shells + * Retrieves a list of all available shells + */ +export const shellList = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/list', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Open an existing shell + * Opens an existing shell and retrieves its buffer + */ +export const shellOpen = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/open', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Close a shell + * Closes a shell without terminating the underlying process + */ +export const shellClose = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/close', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Restart a shell + * Restarts an existing shell process + */ +export const shellRestart = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/restart', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Terminate a shell + * Terminates a shell and its underlying process + */ +export const shellTerminate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/terminate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Resize a shell + * Updates the dimensions of a shell + */ +export const shellResize = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/resize', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Rename a shell + * Updates the name of a shell + */ +export const shellRename = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/rename', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-shell/types.gen.ts b/src/clients/client-rest-shell/types.gen.ts new file mode 100644 index 0000000..45aa947 --- /dev/null +++ b/src/clients/client-rest-shell/types.gen.ts @@ -0,0 +1,443 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +/** + * Unique identifier for a shell + */ +export type ShellId = string; + +export type ShellSize = { + /** + * Number of columns in the terminal + */ + cols: number; + /** + * Number of rows in the terminal + */ + rows: number; +}; + +/** + * Type of shell process + */ +export type ShellProcessType = 'TERMINAL' | 'COMMAND'; + +/** + * Current status of the shell process + */ +export type ShellProcessStatus = 'RUNNING' | 'FINISHED' | 'ERROR' | 'KILLED' | 'RESTARTING'; + +export type BaseShellDto = { + shellId: ShellId; + /** + * Display name of the shell + */ + name: string; + status: ShellProcessStatus; + /** + * Exit code of the process if it has finished + */ + exitCode?: number | null; +}; + +export type CommandShellDto = BaseShellDto & { + /** + * Indicates this is a command shell + */ + shellType: 'COMMAND'; + /** + * The command that was executed to start this shell + */ + startCommand: string; +}; + +export type TerminalShellDto = BaseShellDto & { + /** + * Indicates this is a terminal shell + */ + shellType: 'TERMINAL'; + /** + * Username of the shell owner + */ + ownerUsername: string; + /** + * Whether this is a system shell + */ + isSystemShell: boolean; +}; + +export type ShellDto = ({ + shellType?: 'COMMAND'; +} & CommandShellDto) | ({ + shellType?: 'TERMINAL'; +} & TerminalShellDto); + +export type OpenCommandShellDto = CommandShellDto & { + /** + * Content buffer of the shell + */ + buffer: Array; +}; + +export type OpenTerminalShellDto = TerminalShellDto & { + /** + * Content buffer of the shell + */ + buffer: Array; +}; + +export type OpenShellDto = ({ + shellType?: 'COMMAND'; +} & OpenCommandShellDto) | ({ + shellType?: 'TERMINAL'; +} & OpenTerminalShellDto); + +export type CommonError = { + /** + * Error code indicating the shell is not accessible + */ + code: 'SHELL_NOT_ACCESSIBLE'; + /** + * Error message + */ + message: string; +} | { + /** + * Protocol error code + */ + code: string; + /** + * Error message + */ + message: string; +}; + +export type ShellCreateData = { + body: { + /** + * Command to execute in the shell + */ + command?: string; + /** + * Working directory for the shell + */ + cwd?: string; + size?: ShellSize; + type?: ShellProcessType; + /** + * Whether this shell is started by the editor itself to run a specific process + */ + isSystemShell?: boolean; + }; + path?: never; + query?: never; + url: '/shell/create'; +}; + +export type ShellCreateErrors = { + /** + * Error creating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellCreateError = ShellCreateErrors[keyof ShellCreateErrors]; + +export type ShellCreateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; +}; + +export type ShellCreateResponse = ShellCreateResponses[keyof ShellCreateResponses]; + +export type ShellInData = { + body: { + shellId: ShellId; + /** + * Input to send to the shell + */ + input: string; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/in'; +}; + +export type ShellInErrors = { + /** + * Error sending input to shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellInError = ShellInErrors[keyof ShellInErrors]; + +export type ShellInResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type ShellInResponse = ShellInResponses[keyof ShellInResponses]; + +export type ShellListData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/shell/list'; +}; + +export type ShellListErrors = { + /** + * Error listing shells + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellListError = ShellListErrors[keyof ShellListErrors]; + +export type ShellListResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + shells: Array; + }; + }; +}; + +export type ShellListResponse = ShellListResponses[keyof ShellListResponses]; + +export type ShellOpenData = { + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/open'; +}; + +export type ShellOpenErrors = { + /** + * Error opening shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellOpenError = ShellOpenErrors[keyof ShellOpenErrors]; + +export type ShellOpenResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; +}; + +export type ShellOpenResponse = ShellOpenResponses[keyof ShellOpenResponses]; + +export type ShellCloseData = { + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/close'; +}; + +export type ShellCloseErrors = { + /** + * Error closing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellCloseError = ShellCloseErrors[keyof ShellCloseErrors]; + +export type ShellCloseResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type ShellCloseResponse = ShellCloseResponses[keyof ShellCloseResponses]; + +export type ShellRestartData = { + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/restart'; +}; + +export type ShellRestartErrors = { + /** + * Error restarting shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellRestartError = ShellRestartErrors[keyof ShellRestartErrors]; + +export type ShellRestartResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type ShellRestartResponse = ShellRestartResponses[keyof ShellRestartResponses]; + +export type ShellTerminateData = { + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/terminate'; +}; + +export type ShellTerminateErrors = { + /** + * Error terminating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellTerminateError = ShellTerminateErrors[keyof ShellTerminateErrors]; + +export type ShellTerminateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: ShellDto; + }; +}; + +export type ShellTerminateResponse = ShellTerminateResponses[keyof ShellTerminateResponses]; + +export type ShellResizeData = { + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/resize'; +}; + +export type ShellResizeErrors = { + /** + * Error resizing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellResizeError = ShellResizeErrors[keyof ShellResizeErrors]; + +export type ShellResizeResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type ShellResizeResponse = ShellResizeResponses[keyof ShellResizeResponses]; + +export type ShellRenameData = { + body: { + shellId: ShellId; + /** + * New name for the shell + */ + name: string; + }; + path?: never; + query?: never; + url: '/shell/rename'; +}; + +export type ShellRenameErrors = { + /** + * Error renaming shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type ShellRenameError = ShellRenameErrors[keyof ShellRenameErrors]; + +export type ShellRenameResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type ShellRenameResponse = ShellRenameResponses[keyof ShellRenameResponses]; \ No newline at end of file diff --git a/src/clients/client-rest-system/client.gen.ts b/src/clients/client-rest-system/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client-rest-system/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client-rest-system/index.ts b/src/clients/client-rest-system/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client-rest-system/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/clients/client-rest-system/sdk.gen.ts b/src/clients/client-rest-system/sdk.gen.ts new file mode 100644 index 0000000..d1f450e --- /dev/null +++ b/src/clients/client-rest-system/sdk.gen.ts @@ -0,0 +1,59 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { SystemUpdateData, SystemUpdateResponse, SystemUpdateError, SystemHibernateData, SystemHibernateResponse, SystemHibernateError, SystemMetricsData, SystemMetricsResponse, SystemMetricsError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * Update system + * Update the sandbox system + */ +export const systemUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/update', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Hibernate system + * Put the sandbox system into hibernation mode + */ +export const systemHibernate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/hibernate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Get system metrics + * Retrieve current system metrics including CPU, memory and storage usage + */ +export const systemMetrics = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/metrics', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-system/types.gen.ts b/src/clients/client-rest-system/types.gen.ts new file mode 100644 index 0000000..f23b7f1 --- /dev/null +++ b/src/clients/client-rest-system/types.gen.ts @@ -0,0 +1,207 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type SystemError = { + /** + * Error code + */ + code: number; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type SystemMetricsStatus = { + cpu: { + /** + * Number of CPU cores + */ + cores: number; + /** + * Used CPU resources + */ + used: number; + /** + * Configured CPU resources + */ + configured: number; + }; + memory: { + /** + * Used memory in bytes + */ + used: number; + /** + * Total available memory in bytes + */ + total: number; + /** + * Configured memory limit in bytes + */ + configured: number; + }; + storage: { + /** + * Used storage in bytes + */ + used: number; + /** + * Total available storage in bytes + */ + total: number; + /** + * Configured storage limit in bytes + */ + configured: number; + }; +}; + +export type InitStatus = { + /** + * Status message + */ + message: string; + /** + * Whether the status represents an error + */ + isError?: boolean | null; + /** + * Current progress (0-100) + */ + progress: number; + /** + * Next progress target (0-100) + */ + nextProgress: number; + /** + * Standard output from the initialization process + */ + stdout?: string | null; +}; + +export type SystemUpdateData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/update'; +}; + +export type SystemUpdateErrors = { + /** + * Error updating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; +}; + +export type SystemUpdateError = SystemUpdateErrors[keyof SystemUpdateErrors]; + +export type SystemUpdateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; + }; +}; + +export type SystemUpdateResponse = SystemUpdateResponses[keyof SystemUpdateResponses]; + +export type SystemHibernateData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/hibernate'; +}; + +export type SystemHibernateErrors = { + /** + * Error hibernating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; +}; + +export type SystemHibernateError = SystemHibernateErrors[keyof SystemHibernateErrors]; + +export type SystemHibernateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type SystemHibernateResponse = SystemHibernateResponses[keyof SystemHibernateResponses]; + +export type SystemMetricsData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/metrics'; +}; + +export type SystemMetricsErrors = { + /** + * Error retrieving system metrics + */ + 400: ErrorResponse & { + error?: SystemError; + }; +}; + +export type SystemMetricsError = SystemMetricsErrors[keyof SystemMetricsErrors]; + +export type SystemMetricsResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SystemMetricsStatus; + }; +}; + +export type SystemMetricsResponse = SystemMetricsResponses[keyof SystemMetricsResponses]; \ No newline at end of file diff --git a/src/clients/client-rest-task/client.gen.ts b/src/clients/client-rest-task/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client-rest-task/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client-rest-task/index.ts b/src/clients/client-rest-task/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client-rest-task/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/clients/client-rest-task/sdk.gen.ts b/src/clients/client-rest-task/sdk.gen.ts new file mode 100644 index 0000000..133e8f8 --- /dev/null +++ b/src/clients/client-rest-task/sdk.gen.ts @@ -0,0 +1,149 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { TaskListData, TaskListResponse, TaskListError, TaskRunData, TaskRunResponse, TaskRunError, TaskRunCommandData, TaskRunCommandResponse, TaskRunCommandError, TaskStopData, TaskStopResponse, TaskStopError, TaskCreateData, TaskCreateResponse, TaskCreateError, TaskUpdateData, TaskUpdateResponse, TaskUpdateError, TaskSaveToConfigData, TaskSaveToConfigResponse, TaskSaveToConfigError, TaskGenerateConfigData, TaskGenerateConfigResponse, TaskGenerateConfigError, TaskCreateSetupTasksData, TaskCreateSetupTasksResponse, TaskCreateSetupTasksError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; +}; + +/** + * List tasks + * Retrieve a list of all configured tasks + */ +export const taskList = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/list', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Run task + * Start execution of a task by ID + */ +export const taskRun = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/run', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Run command + * Run a shell command directly, optionally saving it as a task + */ +export const taskRunCommand = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/runCommand', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Stop task + * Stop execution of a running task + */ +export const taskStop = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/stop', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Create task + * Create a new task configuration + */ +export const taskCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/create', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Update task + * Update an existing task configuration + */ +export const taskUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/update', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Save task to config + * Save a runtime task to the configuration file + */ +export const taskSaveToConfig = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/saveToConfig', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Generate task config + * Generate a configuration file from current tasks + */ +export const taskGenerateConfig = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/generateConfig', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Create setup tasks + * Create tasks that run during sandbox setup + */ +export const taskCreateSetupTasks = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/createSetupTasks', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/clients/client-rest-task/types.gen.ts b/src/clients/client-rest-task/types.gen.ts new file mode 100644 index 0000000..012bd28 --- /dev/null +++ b/src/clients/client-rest-task/types.gen.ts @@ -0,0 +1,507 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SuccessResponse = { + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; +}; + +export type ErrorResponse = { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type CommonError = { + /** + * Error code + */ + code: number; + /** + * Error message + */ + message?: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type TaskError = { + /** + * CONFIG_FILE_ALREADY_EXISTS error code + */ + code: 600; + /** + * Error message + */ + message: string; +} | { + /** + * TASK_NOT_FOUND error code + */ + code: 601; + /** + * Error message + */ + message: string; +} | { + /** + * COMMAND_ALREADY_CONFIGURED error code + */ + code: 602; + /** + * Error message + */ + message: string; +} | ({ + code?: 'CommonError'; +} & CommonError); + +export type TaskDefinitionDto = { + /** + * Name of the task + */ + name: string; + /** + * Command to run for the task + */ + command: string; + /** + * Whether the task should run when the sandbox starts + */ + runAtStart?: boolean | null; + preview?: { + /** + * Port to preview from this task + */ + port?: number | null; + /** + * Type of PR link to use + */ + 'pr-link'?: 'direct' | 'redirect' | 'devtool'; + } | null; +}; + +export type CommandShellDto = { + /** + * ID of the shell command + */ + id: string; + /** + * Command being executed + */ + command: string; + /** + * Current status of the shell command + */ + status: 'initializing' | 'running' | 'stopped' | 'error'; + /** + * Current output of the command + */ + output: string; +}; + +export type Port = { + /** + * Port number + */ + port: number; + /** + * Hostname the port is bound to + */ + hostname: string; + /** + * Current status of the port + */ + status: 'open' | 'closed'; + /** + * ID of the task that opened this port + */ + taskId?: string | null; +}; + +export type TaskDto = TaskDefinitionDto & { + /** + * Unique ID of the task + */ + id: string; + /** + * Whether this task is unconfigured (not saved in config) + */ + unconfigured?: boolean | null; + shell: CommandShellDto | null; + /** + * Ports opened by this task + */ + ports: Array; +}; + +export type TaskListDto = { + /** + * Map of task IDs to task objects + */ + tasks: { + [key: string]: TaskDto; + }; + /** + * Tasks that run during sandbox setup + */ + setupTasks: Array; + /** + * Validation errors in the task configuration + */ + validationErrors: Array; +}; + +export type TaskListData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/task/list'; +}; + +export type TaskListErrors = { + /** + * Error retrieving task list + */ + 400: ErrorResponse & { + error?: CommonError; + }; +}; + +export type TaskListError = TaskListErrors[keyof TaskListErrors]; + +export type TaskListResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; +}; + +export type TaskListResponse = TaskListResponses[keyof TaskListResponses]; + +export type TaskRunData = { + body: { + /** + * ID of the task to run + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/run'; +}; + +export type TaskRunErrors = { + /** + * Error running task + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskRunError = TaskRunErrors[keyof TaskRunErrors]; + +export type TaskRunResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; +}; + +export type TaskRunResponse = TaskRunResponses[keyof TaskRunResponses]; + +export type TaskRunCommandData = { + body: { + /** + * Command to run + */ + command: string; + /** + * Optional name for the task + */ + name?: string | null; + /** + * Whether to save this command as a task in the config + */ + saveToConfig?: boolean | null; + }; + path?: never; + query?: never; + url: '/task/runCommand'; +}; + +export type TaskRunCommandErrors = { + /** + * Error running command + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskRunCommandError = TaskRunCommandErrors[keyof TaskRunCommandErrors]; + +export type TaskRunCommandResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; +}; + +export type TaskRunCommandResponse = TaskRunCommandResponses[keyof TaskRunCommandResponses]; + +export type TaskStopData = { + body: { + /** + * ID of the task to stop + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/stop'; +}; + +export type TaskStopErrors = { + /** + * Error stopping task + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskStopError = TaskStopErrors[keyof TaskStopErrors]; + +export type TaskStopResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto | unknown; + }; +}; + +export type TaskStopResponse = TaskStopResponses[keyof TaskStopResponses]; + +export type TaskCreateData = { + body: { + taskFields: TaskDefinitionDto; + /** + * Whether to start the task immediately after creation + */ + startTask?: boolean | null; + }; + path?: never; + query?: never; + url: '/task/create'; +}; + +export type TaskCreateErrors = { + /** + * Error creating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskCreateError = TaskCreateErrors[keyof TaskCreateErrors]; + +export type TaskCreateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; +}; + +export type TaskCreateResponse = TaskCreateResponses[keyof TaskCreateResponses]; + +export type TaskUpdateData = { + body: { + /** + * ID of the task to update + */ + taskId: string; + /** + * Fields to update in the task + */ + taskFields: { + /** + * Name of the task + */ + name?: string | null; + /** + * Command to run + */ + command?: string | null; + /** + * Whether to run the task at sandbox start + */ + runAtStart?: boolean | null; + preview?: { + /** + * Port to use for previewing the task + */ + port?: number | null; + /** + * Type of PR link to use + */ + 'pr-link'?: 'direct' | 'redirect' | 'devtool'; + } | null; + }; + }; + path?: never; + query?: never; + url: '/task/update'; +}; + +export type TaskUpdateErrors = { + /** + * Error updating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskUpdateError = TaskUpdateErrors[keyof TaskUpdateErrors]; + +export type TaskUpdateResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; +}; + +export type TaskUpdateResponse = TaskUpdateResponses[keyof TaskUpdateResponses]; + +export type TaskSaveToConfigData = { + body: { + /** + * ID of the task to save to config + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/saveToConfig'; +}; + +export type TaskSaveToConfigErrors = { + /** + * Error saving task to config + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskSaveToConfigError = TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; + +export type TaskSaveToConfigResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; +}; + +export type TaskSaveToConfigResponse = TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; + +export type TaskGenerateConfigData = { + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/task/generateConfig'; +}; + +export type TaskGenerateConfigErrors = { + /** + * Error generating config + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskGenerateConfigError = TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; + +export type TaskGenerateConfigResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type TaskGenerateConfigResponse = TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; + +export type TaskCreateSetupTasksData = { + body: { + /** + * Setup tasks to create + */ + tasks: Array; + }; + path?: never; + query?: never; + url: '/task/createSetupTasks'; +}; + +export type TaskCreateSetupTasksErrors = { + /** + * Error creating setup tasks + */ + 400: ErrorResponse & { + error?: TaskError; + }; +}; + +export type TaskCreateSetupTasksError = TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; + +export type TaskCreateSetupTasksResponses = { + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; +}; + +export type TaskCreateSetupTasksResponse = TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; \ No newline at end of file diff --git a/src/clients/client/client.gen.ts b/src/clients/client/client.gen.ts new file mode 100644 index 0000000..1822a95 --- /dev/null +++ b/src/clients/client/client.gen.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig } from '@hey-api/client-fetch'; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/clients/client/index.ts b/src/clients/client/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/clients/client/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/client/sdk.gen.ts b/src/clients/client/sdk.gen.ts similarity index 100% rename from src/client/sdk.gen.ts rename to src/clients/client/sdk.gen.ts diff --git a/src/client/types.gen.ts b/src/clients/client/types.gen.ts similarity index 96% rename from src/client/types.gen.ts rename to src/clients/client/types.gen.ts index a891ef6..ce3c5b4 100644 --- a/src/client/types.gen.ts +++ b/src/clients/client/types.gen.ts @@ -114,6 +114,19 @@ export type SandboxForkRequest = { * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. */ start_options?: { + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; /** * The time in seconds after which the VM will hibernate due to inactivity. * Must be a positive integer between 1 and 86400 (24 hours). @@ -241,6 +254,19 @@ export type SandboxCreateResponse = { }; export type VmStartRequest = { + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; /** * The time in seconds after which the VM will hibernate due to inactivity. * Must be a positive integer between 1 and 86400 (24 hours). @@ -556,7 +582,7 @@ export type PreviewTokenCreateResponse = { token_id: string; token_prefix: string; } & { - token?: string; + token: string; }; }; }; @@ -767,7 +793,7 @@ export type PreviewTokenRevokeAllData = { export type PreviewTokenRevokeAllResponses = { /** - * RevokeALlPreviewTokensResponse + * RevokeAllPreviewTokensResponse */ 200: PreviewTokenRevokeAllResponse; }; diff --git a/src/index.ts b/src/index.ts index c609439..574aab7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,8 +8,6 @@ import { SandboxInfo, PaginationOpts, } from "./sandbox-client"; -import { SandboxRestFileSystem } from "./sandbox-rest-filesystem"; -import { SandboxRestClient } from "./sandbox-rest-client"; export { SandboxClient, diff --git a/src/rest/sandbox-rest-container.ts b/src/rest/sandbox-rest-container.ts new file mode 100644 index 0000000..5153ac9 --- /dev/null +++ b/src/rest/sandbox-rest-container.ts @@ -0,0 +1,21 @@ +import { Client } from "@hey-api/client-fetch"; +import * as container from "../clients/client-rest-container"; +import { SessionData } from "../sessions"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestContainer { + constructor(private client: Client) {} + + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + + setup(session: SessionData, body: container.ContainerSetupData["body"]) { + return container.containerSetup(this.createRestParams(session, body)); + } +} diff --git a/src/rest/sandbox-rest-fs.ts b/src/rest/sandbox-rest-fs.ts new file mode 100644 index 0000000..cc05fd5 --- /dev/null +++ b/src/rest/sandbox-rest-fs.ts @@ -0,0 +1,110 @@ +import { Client } from "@hey-api/client-fetch"; +import * as fs from "../clients/client-rest-fs"; +import { SessionData } from "../sessions"; +import { join } from "path"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestFS { + constructor(private client: Client) {} + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + writeTextFile( + session: SessionData, + body: Omit & { content: string } + ) { + return fs.writeFile( + this.createRestParams(session, { + ...body, + // OpenAPI does not have UINT8Array, so we need to cast it to a Blob + content: new TextEncoder().encode(body.content) as unknown as Blob, + path: join(session.user_workspace_path, body.path), + create: body.create === false ? false : true, + }) + ); + } + writeFile(session: SessionData, body: fs.WriteFileData["body"]) { + return fs.writeFile( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + create: body.create === false ? false : true, + }) + ); + } + readFile(session: SessionData, body: fs.FsReadFileData["body"]) { + return fs.fsReadFile( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + }) + ); + } + search(session: SessionData, body: fs.FsSearchData["body"]) { + return fs.fsSearch(this.createRestParams(session, body)); + } + pathSearch(session: SessionData, body: fs.FsPathSearchData["body"]) { + return fs.fsPathSearch(this.createRestParams(session, body)); + } + upload(session: SessionData, body: fs.FsUploadData["body"]) { + return fs.fsUpload(this.createRestParams(session, body)); + } + download(session: SessionData, body: fs.FsDownloadData["body"]) { + return fs.fsDownload(this.createRestParams(session, body)); + } + readDir(session: SessionData, body: fs.FsReadDirData["body"]) { + return fs.fsReadDir( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + }) + ); + } + stat(session: SessionData, body: fs.FsStatData["body"]) { + return fs.fsStat( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + }) + ); + } + copy(session: SessionData, body: fs.FsCopyData["body"]) { + return fs.fsCopy( + this.createRestParams(session, { + ...body, + from: join(session.user_workspace_path, body.from), + to: join(session.user_workspace_path, body.to), + }) + ); + } + rename(session: SessionData, body: fs.FsRenameData["body"]) { + return fs.fsRename( + this.createRestParams(session, { + ...body, + from: join(session.user_workspace_path, body.from), + to: join(session.user_workspace_path, body.to), + }) + ); + } + remove(session: SessionData, body: fs.FsRemoveData["body"]) { + return fs.fsRemove( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + }) + ); + } + mkdir(session: SessionData, body: fs.FsMkdirData["body"]) { + return fs.fsMkdir( + this.createRestParams(session, { + ...body, + path: join(session.user_workspace_path, body.path), + }) + ); + } +} diff --git a/src/rest/sandbox-rest-git.ts b/src/rest/sandbox-rest-git.ts new file mode 100644 index 0000000..7e1882e --- /dev/null +++ b/src/rest/sandbox-rest-git.ts @@ -0,0 +1,67 @@ +import { Client } from "@hey-api/client-fetch"; +import * as git from "../clients/client-rest-git"; +import { SessionData } from "../sessions"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestGit { + constructor(private client: Client) {} + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + + status(session: SessionData, body: git.GitStatusData["body"]) { + return git.gitStatus(this.createRestParams(session, body)); + } + + remotes(session: SessionData, body: git.GitRemotesData["body"]) { + return git.gitRemotes(this.createRestParams(session, body)); + } + + targetDiff(session: SessionData, body: git.GitTargetDiffData["body"]) { + return git.gitTargetDiff(this.createRestParams(session, body)); + } + + pull(session: SessionData, body: git.GitPullData["body"]) { + return git.gitPull(this.createRestParams(session, body)); + } + + discard(session: SessionData, body: git.GitDiscardData["body"]) { + return git.gitDiscard(this.createRestParams(session, body)); + } + + commit(session: SessionData, body: git.GitCommitData["body"]) { + return git.gitCommit(this.createRestParams(session, body)); + } + + push(session: SessionData, body: git.GitPushData["body"]) { + return git.gitPush(this.createRestParams(session, body)); + } + + pushToRemote(session: SessionData, body: git.GitPushToRemoteData["body"]) { + return git.gitPushToRemote(this.createRestParams(session, body)); + } + + renameBranch(session: SessionData, body: git.GitRenameBranchData["body"]) { + return git.gitRenameBranch(this.createRestParams(session, body)); + } + + remoteContent(session: SessionData, body: git.GitRemoteContentData["body"]) { + return git.gitRemoteContent(this.createRestParams(session, body)); + } + + diffStatus(session: SessionData, body: git.GitDiffStatusData["body"]) { + return git.gitDiffStatus(this.createRestParams(session, body)); + } + + resetLocalWithRemote( + session: SessionData, + body: git.GitResetLocalWithRemoteData["body"] + ) { + return git.gitResetLocalWithRemote(this.createRestParams(session, body)); + } +} diff --git a/src/rest/sandbox-rest-shell.ts b/src/rest/sandbox-rest-shell.ts new file mode 100644 index 0000000..0c7a183 --- /dev/null +++ b/src/rest/sandbox-rest-shell.ts @@ -0,0 +1,53 @@ +import { Client } from "@hey-api/client-fetch"; +import * as shell from "../clients/client-rest-shell"; +import { SessionData } from "../sessions"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestShell { + constructor(private client: Client) {} + + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + + create(session: SessionData, body: shell.ShellCreateData["body"]) { + return shell.shellCreate(this.createRestParams(session, body)); + } + + in(session: SessionData, body: shell.ShellInData["body"]) { + return shell.shellIn(this.createRestParams(session, body)); + } + + list(session: SessionData, body: shell.ShellListData["body"]) { + return shell.shellList(this.createRestParams(session, body)); + } + + open(session: SessionData, body: shell.ShellOpenData["body"]) { + return shell.shellOpen(this.createRestParams(session, body)); + } + + close(session: SessionData, body: shell.ShellCloseData["body"]) { + return shell.shellClose(this.createRestParams(session, body)); + } + + restart(session: SessionData, body: shell.ShellRestartData["body"]) { + return shell.shellRestart(this.createRestParams(session, body)); + } + + terminate(session: SessionData, body: shell.ShellTerminateData["body"]) { + return shell.shellTerminate(this.createRestParams(session, body)); + } + + resize(session: SessionData, body: shell.ShellResizeData["body"]) { + return shell.shellResize(this.createRestParams(session, body)); + } + + rename(session: SessionData, body: shell.ShellRenameData["body"]) { + return shell.shellRename(this.createRestParams(session, body)); + } +} diff --git a/src/rest/sandbox-rest-system.ts b/src/rest/sandbox-rest-system.ts new file mode 100644 index 0000000..7339785 --- /dev/null +++ b/src/rest/sandbox-rest-system.ts @@ -0,0 +1,29 @@ +import { Client } from "@hey-api/client-fetch"; +import * as system from "../clients/client-rest-system"; +import { SessionData } from "../sessions"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestSystem { + constructor(private client: Client) {} + + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + + update(session: SessionData, body: system.SystemUpdateData["body"]) { + return system.systemUpdate(this.createRestParams(session, body)); + } + + hibernate(session: SessionData, body: system.SystemHibernateData["body"]) { + return system.systemHibernate(this.createRestParams(session, body)); + } + + metrics(session: SessionData, body: system.SystemMetricsData["body"]) { + return system.systemMetrics(this.createRestParams(session, body)); + } +} diff --git a/src/rest/sandbox-rest-task.ts b/src/rest/sandbox-rest-task.ts new file mode 100644 index 0000000..a8ccb0c --- /dev/null +++ b/src/rest/sandbox-rest-task.ts @@ -0,0 +1,59 @@ +import { Client } from "@hey-api/client-fetch"; +import * as task from "../clients/client-rest-task"; +import { SessionData } from "../sessions"; +import { getSessionUrl } from "../utils/session"; + +export class SandboxRestTask { + constructor(private client: Client) {} + + private createRestParams(session: SessionData, body: T) { + return { + baseUrl: getSessionUrl(session), + client: this.client, + body, + throwOnError: true, + }; + } + + list(session: SessionData, body: task.TaskListData["body"] = {}) { + return task.taskList(this.createRestParams(session, body)); + } + + run(session: SessionData, body: task.TaskRunData["body"]) { + return task.taskRun(this.createRestParams(session, body)); + } + + runCommand(session: SessionData, body: task.TaskRunCommandData["body"]) { + return task.taskRunCommand(this.createRestParams(session, body)); + } + + stop(session: SessionData, body: task.TaskStopData["body"]) { + return task.taskStop(this.createRestParams(session, body)); + } + + create(session: SessionData, body: task.TaskCreateData["body"]) { + return task.taskCreate(this.createRestParams(session, body)); + } + + update(session: SessionData, body: task.TaskUpdateData["body"]) { + return task.taskUpdate(this.createRestParams(session, body)); + } + + saveToConfig(session: SessionData, body: task.TaskSaveToConfigData["body"]) { + return task.taskSaveToConfig(this.createRestParams(session, body)); + } + + generateConfig( + session: SessionData, + body: task.TaskGenerateConfigData["body"] = {} + ) { + return task.taskGenerateConfig(this.createRestParams(session, body)); + } + + createSetupTasks( + session: SessionData, + body: task.TaskCreateSetupTasksData["body"] + ) { + return task.taskCreateSetupTasks(this.createRestParams(session, body)); + } +} diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 7828afd..58d4d5a 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -6,7 +6,7 @@ import type { SandboxForkResponse, VmStartResponse, VmUpdateSpecsRequest, -} from "./client"; +} from "./clients/client"; import { sandboxFork, vmCreateSession, @@ -20,18 +20,13 @@ import { previewTokenList, previewTokenRevokeAll, previewTokenUpdate, -} from "./client"; +} from "./clients/client"; import { Sandbox, SandboxSession } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; import { SessionCreateOptions, SessionData } from "./sessions"; import { SandboxRestClient } from "./sandbox-rest-client"; -import { SandboxRestFileSystem } from "./sandbox-rest-filesystem"; import { ClientOpts } from "."; -type SandboxForkResponseWithSession = SandboxForkResponse["data"] & { - start_response: Required["data"]; -}; - export type SandboxPrivacy = "public" | "unlisted" | "private"; export type SandboxInfo = { diff --git a/src/sandbox-rest-client.ts b/src/sandbox-rest-client.ts index 8eedfe1..a80ea76 100644 --- a/src/sandbox-rest-client.ts +++ b/src/sandbox-rest-client.ts @@ -1,78 +1,90 @@ -import { createClient, createConfig } from "@hey-api/client-fetch"; -import { SessionData } from "./sessions"; -import { - FileSystemRestRequester, - SandboxRestFileSystem, -} from "./sandbox-rest-filesystem"; +import { Client, createClient, createConfig } from "@hey-api/client-fetch"; import { decode, encode } from "@msgpack/msgpack"; -import * as fs from "./client-rest-fs"; +import { SandboxRestFS } from "./rest/sandbox-rest-fs"; +import { SandboxRestContainer } from "./rest/sandbox-rest-container"; +import { SandboxRestGit } from "./rest/sandbox-rest-git"; +import { SandboxRestShell } from "./rest/sandbox-rest-shell"; +import { SandboxRestSystem } from "./rest/sandbox-rest-system"; +import { SandboxRestTask } from "./rest/sandbox-rest-task"; export interface ClientOpts { - /** - * Custom fetch implementation - * - * @default fetch - */ fetch?: typeof fetch; - - /** - * Additional headers to send with each request - */ headers?: Record; } export class SandboxRestClient { static id = 0; - fs: SandboxRestFileSystem; + fs: SandboxRestFS; + container: SandboxRestContainer; + git: SandboxRestGit; + shell: SandboxRestShell; + system: SandboxRestSystem; + task: SandboxRestTask; constructor(opts: ClientOpts = {}) { - const createClient = this.createRestClientFactory(opts); - this.fs = new SandboxRestFileSystem(createClient); + const client = this.createRestClient(opts); + this.fs = new SandboxRestFS(client); + this.container = new SandboxRestContainer(client); + this.git = new SandboxRestGit(client); + this.shell = new SandboxRestShell(client); + this.system = new SandboxRestSystem(client); + this.task = new SandboxRestTask(client); } - private createRestClientFactory(opts: ClientOpts) { - return (session: SessionData): FileSystemRestRequester => { - const url = new URL(session.pitcher_url); - - url.protocol = "https"; + private createRestClient(opts: ClientOpts): Client { + const client = createClient( + createConfig({ + bodySerializer: null, + parseAs: "stream", + headers: { + ...(opts.headers ?? {}), + "content-type": "application/x-msgpack", + }, + fetch: + opts.fetch ?? + // @ts-ignore + ((url, params) => { + return fetch(url, params); + }), + }) + ); - const client = createClient( - createConfig({ - bodySerializer: null, - parseAs: "stream", - baseUrl: url.origin, - headers: { - ...(opts.headers ?? {}), - "content-type": "application/x-msgpack", - }, - fetch: - opts.fetch ?? - // @ts-ignore - ((url, params) => { - return fetch(url, params); - }), - }) - ); + return { + post(opts) { + const method = opts.url.substring(1); - return (method, params) => { const message = { id: SandboxRestClient.id++, method, - params, + params: opts.body, }; const encodedMessage = encode(message); + if (!opts.baseUrl) { + throw new Error("You have to pass baseURL to the rest client"); + } + + // This is a hack to properly build the url. As "url" defaults to "/" in openapi client and breaks + const urlParts = opts.baseUrl.split("/"); + const url = urlParts.pop()!; + const baseUrl = urlParts.join("/"); + return client .post({ - url: `/${session.id}?token=${session.pitcher_token}`, + baseUrl, + url, headers: { "content-length": encodedMessage.byteLength.toString(), }, body: encodedMessage, }) - .then(async ({ response }) => - decode(await response.arrayBuffer()) - ) as any; - }; - }; + .then(async ({ response, error }) => { + if (error) { + throw error; + } + + return decode(await response.arrayBuffer()); + }) as any; + }, + } as Client; } } diff --git a/src/sandbox-rest-filesystem.ts b/src/sandbox-rest-filesystem.ts deleted file mode 100644 index d341720..0000000 --- a/src/sandbox-rest-filesystem.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as fs from "./client-rest-fs"; -import { SessionData } from "./sessions"; - -export type FileSystemRestRequester = < - P extends {}, - S extends fs.SuccessResponse, - E extends fs.ErrorResponse ->( - method: string, - params: P -) => Promise; - -export class SandboxRestFileSystem { - constructor( - private createRequester: (session: SessionData) => FileSystemRestRequester - ) {} - writeTextFile(session: SessionData, path: string, content: string) { - const request = this.createRequester(session); - - return request< - fs.WriteFileRequest, - fs.WriteFileResponse, - fs.WriteFileError - >("fs/writeFile", { - path, - - // We are not able to generate the correct typing for OpenAPI here - // @ts-expect-error - content: new TextEncoder().encode(content), - create: true, - overwrite: true, - }); - } -} diff --git a/src/utils/session.ts b/src/utils/session.ts new file mode 100644 index 0000000..507cd7a --- /dev/null +++ b/src/utils/session.ts @@ -0,0 +1,11 @@ +import { SessionData } from "../sessions"; + +export function getSessionUrl(session: SessionData) { + const url = new URL(session.pitcher_url); + + url.protocol = "https"; + url.pathname = `/${session.id}`; + url.search = `token=${session.pitcher_token}`; + + return url.toString(); +} diff --git a/tsconfig.json b/tsconfig.json index bcaa894..dac88a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,5 +18,13 @@ "strict": true }, "include": ["src"], - "exclude": ["node_modules", "dist", "es", "lib", "cjs", "src/bin"] + "exclude": [ + "node_modules", + "dist", + "es", + "lib", + "cjs", + "src/bin", + "src/clients" + ] } From c5a246997a07834fe86a73e15762c1e76d552d58 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 15 Apr 2025 13:52:48 +0200 Subject: [PATCH 067/241] fix(build): consider port opened for faulty status codes (#84) --- src/bin/commands/build.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index e019b73..0522034 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -270,15 +270,15 @@ export const buildCommand: yargs.CommandModule< // eslint-disable-next-line no-constant-condition while (true) { const res = await fetch(portInfo.getPreviewUrl()); - if (res.status < 400) { + if (res.status !== 502 && res.status !== 503) { spinner.succeed(`Port ${port} is open (status ${res.status})`); break; - } else { - spinner.fail( - `Port ${port} is not open yet (status ${res.status}), retrying in 1 second...` - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); } + + spinner.fail( + `Port ${port} is not open yet (status ${res.status}), retrying in 1 second...` + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); } tasksWithPorts = tasksWithPorts.filter((t) => t.id !== task.id); From 0030774a3f26eb43a6e037e01482e326d6a71f93 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 15 Apr 2025 13:54:08 +0200 Subject: [PATCH 068/241] chore(main): release 0.11.2 (#85) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1eb352..c720c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.11.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.1...v0.11.2) (2025-04-15) + + +### Bug Fixes + +* **build:** consider port opened for faulty status codes ([#84](https://github.com/codesandbox/codesandbox-sdk/issues/84)) ([c5a2469](https://github.com/codesandbox/codesandbox-sdk/commit/c5a246997a07834fe86a73e15762c1e76d552d58)) + ## [0.11.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.0...v0.11.1) (2025-03-13) diff --git a/package-lock.json b/package-lock.json index 46fdf98..dcebca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.11.1", + "version": "0.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.11.1", + "version": "0.11.2", "license": "MIT", "bin": { "csb": "dist/bin/codesandbox.cjs" diff --git a/package.json b/package.json index 5d5bdba..51ff704 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.11.1", + "version": "0.11.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 92ec41927433c837c52323fb7c2b054869e3b810 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 24 Apr 2025 14:31:49 +0200 Subject: [PATCH 069/241] add property to check if VM is up to date --- src/sandbox.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sandbox.ts b/src/sandbox.ts index a5f5d19..d3649d7 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -111,6 +111,13 @@ export class SandboxSession extends Disposable { this.addDisposable(this.pitcherClient); } + /** + * Check if the VM process is up to date. To update a restart is required + */ + get isUpToDate() { + return this.pitcherClient.isUpToDate(); + } + /** * The ID of the sandbox. */ From 54681826891f374136baa8aee3ae1208e0fb1ed5 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 24 Apr 2025 14:39:40 +0200 Subject: [PATCH 070/241] Update src/sandbox.ts Co-authored-by: Ives van Hoorne --- src/sandbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sandbox.ts b/src/sandbox.ts index d3649d7..ce84dab 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -112,7 +112,7 @@ export class SandboxSession extends Disposable { } /** - * Check if the VM process is up to date. To update a restart is required + * Check if the VM agent process is up to date. To update a restart is required */ get isUpToDate() { return this.pitcherClient.isUpToDate(); From d7a2902748df98c0c094f9b88e8906b17d22cfa1 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Thu, 24 Apr 2025 14:50:57 +0200 Subject: [PATCH 071/241] chore(main): release 0.12.0 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c720c5a..539bcaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.12.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.2...v0.12.0) (2025-04-24) + + +### Features + +* add property to check if VM is up to date ([a44c842](https://github.com/codesandbox/codesandbox-sdk/commit/a44c8424dedee731b172ea2cdbbb6fe3ade0f2f5)) +* Rest client ([ec8f5eb](https://github.com/codesandbox/codesandbox-sdk/commit/ec8f5ebc8ab5d4e3540b1985a3a98ecfb64b0c7f)) + ## [0.11.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.1...v0.11.2) (2025-04-15) diff --git a/package-lock.json b/package-lock.json index 54788fe..e593153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "0.11.2", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.11.2", + "version": "0.12.0", "license": "MIT", "dependencies": { "@msgpack/msgpack": "^3.1.0", diff --git a/package.json b/package.json index d1f5b03..60216c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.11.2", + "version": "0.12.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 6b6b0adf264c6b1ca17e011d398b2545b1ead8ae Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 28 Apr 2025 08:34:59 +0200 Subject: [PATCH 072/241] WIP --- TODO.md | 106 +++++++++ package-lock.json | 12 +- src/browser.ts | 27 +-- src/rest/sandbox-rest-container.ts | 9 +- src/rest/sandbox-rest-fs.ts | 30 +-- src/rest/sandbox-rest-git.ts | 37 +-- src/rest/sandbox-rest-shell.ts | 25 ++- src/rest/sandbox-rest-system.ts | 13 +- src/rest/sandbox-rest-task.ts | 28 ++- src/sandbox-client.ts | 350 ++++++++++++++--------------- src/sandbox.ts | 17 +- src/sessions.ts | 51 ++--- src/utils/session.ts | 4 +- 13 files changed, 389 insertions(+), 320 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c5e993a --- /dev/null +++ b/TODO.md @@ -0,0 +1,106 @@ +## QUESTIONS + +- Please explain again why we always start the VM from "forkSandbox", but we also handle non start_response... how do we handle actually correct cluster? Is it not by always using the `start` method? +- You say sessions allows users to edit files without affecting each other, but above you state that all files are shared? +- Should we really call it GIT, it only supports GitHub? + +# 1 New API + +```ts +const sdk = new CodeSandbox(apiToken); + +const sandbox = sdk.sandbox.create(SandboxOption & StartOptions); +const sandbox = sdk.sandbox.fork( + id, + Omit & StartOptions +); + +sandbox.restart(); +sandbox.hibernate(); +sandbox.shutdown(); +sandbox.isUpToDate(); +sandbox.resume(); + +sandbox.createSession({ + type: "websocket", + username: "anonymous", + permission: "read", +}); +sandbox.createSession({ + type: "browser", + username: "anonymous", + permission: "read", +}); +sandbox.createSession({ + type: "rest", + username: "anonymous", + permission: "read", +}); +sandbox.createSession({ + type: "ssh", + username: "anonymous", + permission: "read", +}); +``` + +# 2 Git clone support + +```ts +// Factory.ai +// Create base template +// const sbx = await sdk.sandbox.create(); +// sbx.git.clone(); +// -> /project/sandbox +// git set-remote origin ... +// git pull + +// /project/sandbox/.git -> /persisted/.git + +/** + * sdk.sandbox.create({ source: { + * type: 'git', + * url: 'https://github.com/sandbox-git/sandbox-git.git', + * branch: 'main', + * gitAccessToken: '...' + * } }) + * 1. create sandbox + * 2. ... + * 3. clone + * + * // Source = Dropbox + * // Source = Zip + * + * + * // API create zip + * + * sandbox.create({ + * source: { + * type: 'zip', + * url: 'https://example.com/my-zip-file.zip' + * } + * }); + */ + +await sandbox.git.clone({ + url: "https://github.com/sandbox-git/sandbox-git.git", + branch: "main", +}); + +// rm -rf /project/sandbox/* +// + +await sandbox.git.pull(); +await sandbox.git.checkout("main"); + +// +``` + +# 3 Snapshot Tagging + +```ts +sdk.sandbox.create({ + files: {}, +}); +``` + +# Export types properly diff --git a/package-lock.json b/package-lock.json index e593153..ec7dc4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,6 @@ "name": "@codesandbox/sdk", "version": "0.12.0", "license": "MIT", - "dependencies": { - "@msgpack/msgpack": "^3.1.0", - "messagepack": "^1.1.12" - }, "bin": { "csb": "dist/bin/codesandbox.cjs" }, @@ -21,6 +17,7 @@ "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", + "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", @@ -892,6 +889,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.0.tgz", "integrity": "sha512-igBxaq5JHWdJ0lDyKkCo00pDu+bNVuBAs/cHra6a3ndCw6vlZK9BGLuG7Fvmar/DXK2uJ25zvgcAZEl+AvLpjQ==", + "dev": true, "license": "ISC", "engines": { "node": ">= 18" @@ -3030,12 +3028,6 @@ "node": ">= 0.10.0" } }, - "node_modules/messagepack": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/messagepack/-/messagepack-1.1.12.tgz", - "integrity": "sha512-pNB6K4q4VMLRXdvlGZkTtQhmKFntvLisnOQnL0VhKpZooL8B8Wsv5TXuidIJil0bCH6V172p3+Onfyow0usPYQ==", - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", diff --git a/src/browser.ts b/src/browser.ts index 91beaa2..ab51173 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,11 +1,8 @@ -import { - initPitcherClient, - PitcherManagerResponse, -} from "@codesandbox/pitcher-client"; +import { initPitcherClient } from "@codesandbox/pitcher-client"; -import { SandboxSession } from "./sandbox"; +import { SandboxSession } from "./sessions"; import { DEFAULT_SUBSCRIPTIONS } from "./sandbox-client"; -import { SessionData } from "./sessions"; +import { UniversalSandbox } from "."; /** * With this function you can connect to a sandbox from the browser. @@ -43,12 +40,12 @@ import { SessionData } from "./sessions"; * ``` */ export async function connectToSandbox( - session: SessionData -): Promise { + session: SandboxSession +): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: session.id, + instanceId: session.sandboxId, onFocusChange() { return () => {}; }, @@ -56,20 +53,20 @@ export async function connectToSandbox( Promise.resolve({ bootupType: "RESUME", cluster: "session", - id: session.id, + id: session.sandboxId, latestPitcherVersion: "1.0.0-session", pitcherManagerVersion: "1.0.0-session", - pitcherToken: session.pitcher_token, - pitcherURL: session.pitcher_url, + pitcherToken: session.pitcherToken, + pitcherURL: session.pitcherUrl, pitcherVersion: "1.0.0-session", reconnectToken: "", - userWorkspacePath: session.user_workspace_path, - workspacePath: session.user_workspace_path, + userWorkspacePath: session.userWorkspacePath, + workspacePath: session.userWorkspacePath, }), subscriptions: DEFAULT_SUBSCRIPTIONS, }, () => {} ); - return new SandboxSession(pitcherClient); + return new UniversalSandbox(pitcherClient); } diff --git a/src/rest/sandbox-rest-container.ts b/src/rest/sandbox-rest-container.ts index 5153ac9..0c27f97 100644 --- a/src/rest/sandbox-rest-container.ts +++ b/src/rest/sandbox-rest-container.ts @@ -1,12 +1,12 @@ import { Client } from "@hey-api/client-fetch"; import * as container from "../clients/client-rest-container"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { getSessionUrl } from "../utils/session"; export class SandboxRestContainer { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -15,7 +15,10 @@ export class SandboxRestContainer { }; } - setup(session: SessionData, body: container.ContainerSetupData["body"]) { + setup( + session: SandboxSessionData, + body: container.ContainerSetupData["body"] + ) { return container.containerSetup(this.createRestParams(session, body)); } } diff --git a/src/rest/sandbox-rest-fs.ts b/src/rest/sandbox-rest-fs.ts index cc05fd5..d85614d 100644 --- a/src/rest/sandbox-rest-fs.ts +++ b/src/rest/sandbox-rest-fs.ts @@ -1,12 +1,12 @@ import { Client } from "@hey-api/client-fetch"; import * as fs from "../clients/client-rest-fs"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { join } from "path"; import { getSessionUrl } from "../utils/session"; export class SandboxRestFS { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -15,7 +15,7 @@ export class SandboxRestFS { }; } writeTextFile( - session: SessionData, + session: SandboxSessionData, body: Omit & { content: string } ) { return fs.writeFile( @@ -28,7 +28,7 @@ export class SandboxRestFS { }) ); } - writeFile(session: SessionData, body: fs.WriteFileData["body"]) { + writeFile(session: SandboxSessionData, body: fs.WriteFileData["body"]) { return fs.writeFile( this.createRestParams(session, { ...body, @@ -37,7 +37,7 @@ export class SandboxRestFS { }) ); } - readFile(session: SessionData, body: fs.FsReadFileData["body"]) { + readFile(session: SandboxSessionData, body: fs.FsReadFileData["body"]) { return fs.fsReadFile( this.createRestParams(session, { ...body, @@ -45,19 +45,19 @@ export class SandboxRestFS { }) ); } - search(session: SessionData, body: fs.FsSearchData["body"]) { + search(session: SandboxSessionData, body: fs.FsSearchData["body"]) { return fs.fsSearch(this.createRestParams(session, body)); } - pathSearch(session: SessionData, body: fs.FsPathSearchData["body"]) { + pathSearch(session: SandboxSessionData, body: fs.FsPathSearchData["body"]) { return fs.fsPathSearch(this.createRestParams(session, body)); } - upload(session: SessionData, body: fs.FsUploadData["body"]) { + upload(session: SandboxSessionData, body: fs.FsUploadData["body"]) { return fs.fsUpload(this.createRestParams(session, body)); } - download(session: SessionData, body: fs.FsDownloadData["body"]) { + download(session: SandboxSessionData, body: fs.FsDownloadData["body"]) { return fs.fsDownload(this.createRestParams(session, body)); } - readDir(session: SessionData, body: fs.FsReadDirData["body"]) { + readDir(session: SandboxSessionData, body: fs.FsReadDirData["body"]) { return fs.fsReadDir( this.createRestParams(session, { ...body, @@ -65,7 +65,7 @@ export class SandboxRestFS { }) ); } - stat(session: SessionData, body: fs.FsStatData["body"]) { + stat(session: SandboxSessionData, body: fs.FsStatData["body"]) { return fs.fsStat( this.createRestParams(session, { ...body, @@ -73,7 +73,7 @@ export class SandboxRestFS { }) ); } - copy(session: SessionData, body: fs.FsCopyData["body"]) { + copy(session: SandboxSessionData, body: fs.FsCopyData["body"]) { return fs.fsCopy( this.createRestParams(session, { ...body, @@ -82,7 +82,7 @@ export class SandboxRestFS { }) ); } - rename(session: SessionData, body: fs.FsRenameData["body"]) { + rename(session: SandboxSessionData, body: fs.FsRenameData["body"]) { return fs.fsRename( this.createRestParams(session, { ...body, @@ -91,7 +91,7 @@ export class SandboxRestFS { }) ); } - remove(session: SessionData, body: fs.FsRemoveData["body"]) { + remove(session: SandboxSessionData, body: fs.FsRemoveData["body"]) { return fs.fsRemove( this.createRestParams(session, { ...body, @@ -99,7 +99,7 @@ export class SandboxRestFS { }) ); } - mkdir(session: SessionData, body: fs.FsMkdirData["body"]) { + mkdir(session: SandboxSessionData, body: fs.FsMkdirData["body"]) { return fs.fsMkdir( this.createRestParams(session, { ...body, diff --git a/src/rest/sandbox-rest-git.ts b/src/rest/sandbox-rest-git.ts index 7e1882e..0dd10db 100644 --- a/src/rest/sandbox-rest-git.ts +++ b/src/rest/sandbox-rest-git.ts @@ -1,11 +1,11 @@ import { Client } from "@hey-api/client-fetch"; import * as git from "../clients/client-rest-git"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { getSessionUrl } from "../utils/session"; export class SandboxRestGit { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -14,52 +14,61 @@ export class SandboxRestGit { }; } - status(session: SessionData, body: git.GitStatusData["body"]) { + status(session: SandboxSessionData, body: git.GitStatusData["body"]) { return git.gitStatus(this.createRestParams(session, body)); } - remotes(session: SessionData, body: git.GitRemotesData["body"]) { + remotes(session: SandboxSessionData, body: git.GitRemotesData["body"]) { return git.gitRemotes(this.createRestParams(session, body)); } - targetDiff(session: SessionData, body: git.GitTargetDiffData["body"]) { + targetDiff(session: SandboxSessionData, body: git.GitTargetDiffData["body"]) { return git.gitTargetDiff(this.createRestParams(session, body)); } - pull(session: SessionData, body: git.GitPullData["body"]) { + pull(session: SandboxSessionData, body: git.GitPullData["body"]) { return git.gitPull(this.createRestParams(session, body)); } - discard(session: SessionData, body: git.GitDiscardData["body"]) { + discard(session: SandboxSessionData, body: git.GitDiscardData["body"]) { return git.gitDiscard(this.createRestParams(session, body)); } - commit(session: SessionData, body: git.GitCommitData["body"]) { + commit(session: SandboxSessionData, body: git.GitCommitData["body"]) { return git.gitCommit(this.createRestParams(session, body)); } - push(session: SessionData, body: git.GitPushData["body"]) { + push(session: SandboxSessionData, body: git.GitPushData["body"]) { return git.gitPush(this.createRestParams(session, body)); } - pushToRemote(session: SessionData, body: git.GitPushToRemoteData["body"]) { + pushToRemote( + session: SandboxSessionData, + body: git.GitPushToRemoteData["body"] + ) { return git.gitPushToRemote(this.createRestParams(session, body)); } - renameBranch(session: SessionData, body: git.GitRenameBranchData["body"]) { + renameBranch( + session: SandboxSessionData, + body: git.GitRenameBranchData["body"] + ) { return git.gitRenameBranch(this.createRestParams(session, body)); } - remoteContent(session: SessionData, body: git.GitRemoteContentData["body"]) { + remoteContent( + session: SandboxSessionData, + body: git.GitRemoteContentData["body"] + ) { return git.gitRemoteContent(this.createRestParams(session, body)); } - diffStatus(session: SessionData, body: git.GitDiffStatusData["body"]) { + diffStatus(session: SandboxSessionData, body: git.GitDiffStatusData["body"]) { return git.gitDiffStatus(this.createRestParams(session, body)); } resetLocalWithRemote( - session: SessionData, + session: SandboxSessionData, body: git.GitResetLocalWithRemoteData["body"] ) { return git.gitResetLocalWithRemote(this.createRestParams(session, body)); diff --git a/src/rest/sandbox-rest-shell.ts b/src/rest/sandbox-rest-shell.ts index 0c7a183..067e869 100644 --- a/src/rest/sandbox-rest-shell.ts +++ b/src/rest/sandbox-rest-shell.ts @@ -1,12 +1,12 @@ import { Client } from "@hey-api/client-fetch"; import * as shell from "../clients/client-rest-shell"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { getSessionUrl } from "../utils/session"; export class SandboxRestShell { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -15,39 +15,42 @@ export class SandboxRestShell { }; } - create(session: SessionData, body: shell.ShellCreateData["body"]) { + create(session: SandboxSessionData, body: shell.ShellCreateData["body"]) { return shell.shellCreate(this.createRestParams(session, body)); } - in(session: SessionData, body: shell.ShellInData["body"]) { + in(session: SandboxSessionData, body: shell.ShellInData["body"]) { return shell.shellIn(this.createRestParams(session, body)); } - list(session: SessionData, body: shell.ShellListData["body"]) { + list(session: SandboxSessionData, body: shell.ShellListData["body"]) { return shell.shellList(this.createRestParams(session, body)); } - open(session: SessionData, body: shell.ShellOpenData["body"]) { + open(session: SandboxSessionData, body: shell.ShellOpenData["body"]) { return shell.shellOpen(this.createRestParams(session, body)); } - close(session: SessionData, body: shell.ShellCloseData["body"]) { + close(session: SandboxSessionData, body: shell.ShellCloseData["body"]) { return shell.shellClose(this.createRestParams(session, body)); } - restart(session: SessionData, body: shell.ShellRestartData["body"]) { + restart(session: SandboxSessionData, body: shell.ShellRestartData["body"]) { return shell.shellRestart(this.createRestParams(session, body)); } - terminate(session: SessionData, body: shell.ShellTerminateData["body"]) { + terminate( + session: SandboxSessionData, + body: shell.ShellTerminateData["body"] + ) { return shell.shellTerminate(this.createRestParams(session, body)); } - resize(session: SessionData, body: shell.ShellResizeData["body"]) { + resize(session: SandboxSessionData, body: shell.ShellResizeData["body"]) { return shell.shellResize(this.createRestParams(session, body)); } - rename(session: SessionData, body: shell.ShellRenameData["body"]) { + rename(session: SandboxSessionData, body: shell.ShellRenameData["body"]) { return shell.shellRename(this.createRestParams(session, body)); } } diff --git a/src/rest/sandbox-rest-system.ts b/src/rest/sandbox-rest-system.ts index 7339785..71e03d1 100644 --- a/src/rest/sandbox-rest-system.ts +++ b/src/rest/sandbox-rest-system.ts @@ -1,12 +1,12 @@ import { Client } from "@hey-api/client-fetch"; import * as system from "../clients/client-rest-system"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { getSessionUrl } from "../utils/session"; export class SandboxRestSystem { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -15,15 +15,18 @@ export class SandboxRestSystem { }; } - update(session: SessionData, body: system.SystemUpdateData["body"]) { + update(session: SandboxSessionData, body: system.SystemUpdateData["body"]) { return system.systemUpdate(this.createRestParams(session, body)); } - hibernate(session: SessionData, body: system.SystemHibernateData["body"]) { + hibernate( + session: SandboxSessionData, + body: system.SystemHibernateData["body"] + ) { return system.systemHibernate(this.createRestParams(session, body)); } - metrics(session: SessionData, body: system.SystemMetricsData["body"]) { + metrics(session: SandboxSessionData, body: system.SystemMetricsData["body"]) { return system.systemMetrics(this.createRestParams(session, body)); } } diff --git a/src/rest/sandbox-rest-task.ts b/src/rest/sandbox-rest-task.ts index a8ccb0c..6a2d52e 100644 --- a/src/rest/sandbox-rest-task.ts +++ b/src/rest/sandbox-rest-task.ts @@ -1,12 +1,12 @@ import { Client } from "@hey-api/client-fetch"; import * as task from "../clients/client-rest-task"; -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; import { getSessionUrl } from "../utils/session"; export class SandboxRestTask { constructor(private client: Client) {} - private createRestParams(session: SessionData, body: T) { + private createRestParams(session: SandboxSessionData, body: T) { return { baseUrl: getSessionUrl(session), client: this.client, @@ -15,43 +15,49 @@ export class SandboxRestTask { }; } - list(session: SessionData, body: task.TaskListData["body"] = {}) { + list(session: SandboxSessionData, body: task.TaskListData["body"] = {}) { return task.taskList(this.createRestParams(session, body)); } - run(session: SessionData, body: task.TaskRunData["body"]) { + run(session: SandboxSessionData, body: task.TaskRunData["body"]) { return task.taskRun(this.createRestParams(session, body)); } - runCommand(session: SessionData, body: task.TaskRunCommandData["body"]) { + runCommand( + session: SandboxSessionData, + body: task.TaskRunCommandData["body"] + ) { return task.taskRunCommand(this.createRestParams(session, body)); } - stop(session: SessionData, body: task.TaskStopData["body"]) { + stop(session: SandboxSessionData, body: task.TaskStopData["body"]) { return task.taskStop(this.createRestParams(session, body)); } - create(session: SessionData, body: task.TaskCreateData["body"]) { + create(session: SandboxSessionData, body: task.TaskCreateData["body"]) { return task.taskCreate(this.createRestParams(session, body)); } - update(session: SessionData, body: task.TaskUpdateData["body"]) { + update(session: SandboxSessionData, body: task.TaskUpdateData["body"]) { return task.taskUpdate(this.createRestParams(session, body)); } - saveToConfig(session: SessionData, body: task.TaskSaveToConfigData["body"]) { + saveToConfig( + session: SandboxSessionData, + body: task.TaskSaveToConfigData["body"] + ) { return task.taskSaveToConfig(this.createRestParams(session, body)); } generateConfig( - session: SessionData, + session: SandboxSessionData, body: task.TaskGenerateConfigData["body"] = {} ) { return task.taskGenerateConfig(this.createRestParams(session, body)); } createSetupTasks( - session: SessionData, + session: SandboxSessionData, body: task.TaskCreateSetupTasksData["body"] ) { return task.taskCreateSetupTasks(this.createRestParams(session, body)); diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts index 03b0e3c..641de75 100644 --- a/src/sandbox-client.ts +++ b/src/sandbox-client.ts @@ -2,11 +2,7 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; import type { Client } from "@hey-api/client-fetch"; import { createClient, createConfig } from "@hey-api/client-fetch"; -import type { - SandboxForkResponse, - VmStartResponse, - VmUpdateSpecsRequest, -} from "./clients/client"; +import type { VmUpdateSpecsRequest } from "./clients/client"; import { sandboxFork, vmCreateSession, @@ -21,9 +17,9 @@ import { previewTokenRevokeAll, previewTokenUpdate, } from "./clients/client"; -import { Sandbox, SandboxSession } from "./sandbox"; +import { Sandbox } from "./sandbox"; import { handleResponse } from "./utils/handle-response"; -import { SessionCreateOptions, SessionData } from "./sessions"; +import { SessionCreateOptions, SandboxSession } from "./sessions"; import { SandboxRestClient } from "./sandbox-rest-client"; import { ClientOpts } from "."; @@ -92,7 +88,9 @@ export const DEFAULT_SUBSCRIPTIONS = { }, }; -export type CreateSandboxOpts = { +export type CreateSandboxTemplateOpts = { + source: "template"; + /** * What template to fork from, this is the id of another sandbox. Defaults to our * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). @@ -114,13 +112,6 @@ export type CreateSandboxOpts = { */ description?: string; - /** - * Whether to automatically connect to the sandbox after creation. If this is set to `false`, - * the sandbox will not be connected to, and you will have to call {@link SandboxClient.start} - * yourself or pass the returned start data to the browser. - */ - autoConnect?: boolean; - /** * Which tags to add to the sandbox, can be used for categorization and filtering. Max 10 tags. */ @@ -130,7 +121,19 @@ export type CreateSandboxOpts = { * In which folder to put the sandbox in (inside your workspace). */ path?: string; -} & StartSandboxOpts; +}; + +export type CreateSandboxOpts = + | CreateSandboxTemplateOpts + | { + source: "git"; + url: string; + branch: string; + } + | { + source: "json"; + files: Record; + }; /** * A VM tier is how we classify the specs of a VM. You can use this to request a VM with specific @@ -205,17 +208,6 @@ export class VMTier { } } -function startOptionsFromOpts(opts: StartSandboxOpts | undefined) { - if (!opts) return undefined; - - return { - ipcountry: opts.ipcountry, - tier: opts.vmTier?.name, - hibernation_timeout_seconds: opts.hibernationTimeoutSeconds, - automatic_wakeup_config: opts.automaticWakeupConfig, - }; -} - export interface StartSandboxOpts { /** * Country, served as a hint on where you want the sandbox to be scheduled. For example, if "NL" is given @@ -311,47 +303,66 @@ export class SandboxClient { ); } - /** - * Open, start & connect to a sandbox that already exists - */ - public async open( + private async start( id: string, startOpts?: StartSandboxOpts - ): Promise { - const sandbox = await this.start(id, startOpts); - const session: SessionData = { - id: sandbox.id, - pitcher_token: sandbox.pitcher_token, - pitcher_url: sandbox.pitcher_url, - user_workspace_path: sandbox.user_workspace_path, - }; + ): Promise { + const startResult = await vmStart({ + client: this.apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id, + }, + }); - return this.connectToSandbox(session); + const response = handleResponse( + startResult, + `Failed to start sandbox ${id}` + ); + + return { + sandboxId: id, + pitcherToken: response.pitcher_token, + pitcherUrl: response.pitcher_url, + userWorkspacePath: response.user_workspace_path, + }; } - /** - * Try to start a sandbox that already exists, it will return the data of the started - * VM, which you can pass to the browser. In the browser you can call `connectToSandbox` with this - * data to control the VM without sharing your CodeSandbox API token in the browser. - * - * @param id the ID of the sandbox - * @returns The start data, contains a single use token to connect to the VM - */ - public async start( - id: string, - opts?: StartSandboxOpts - ): Promise { - const startResult = await vmStart({ + private async createSession( + sandboxId: string, + options: SessionCreateOptions + ): Promise { + const response = await vmCreateSession({ client: this.apiClient, - body: startOptionsFromOpts(opts), + body: { + session_id: options.id, + permission: options.permission ?? "write", + }, path: { - id, + id: sandboxId, }, }); - const data = handleResponse(startResult, `Failed to start sandbox ${id}`); + const handledResponse = handleResponse( + response, + `Failed to create session ${options.id}` + ); + + const session: SandboxSession = { + sandboxId, + pitcherToken: handledResponse.pitcher_token, + pitcherUrl: handledResponse.pitcher_url, + userWorkspacePath: handledResponse.user_workspace_path, + }; - return data; + return session; } /** @@ -367,64 +378,60 @@ export class SandboxClient { * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM */ async create( - opts: { autoConnect: false } & CreateSandboxOpts - ): Promise; - async create( - opts?: { autoConnect?: true } & CreateSandboxOpts - ): Promise; - async create(opts?: CreateSandboxOpts): Promise; - async create(opts?: CreateSandboxOpts): Promise { - const templateId = opts?.template || this.defaultTemplate; - const privacy = opts?.privacy || "public"; - const tags = opts?.tags || ["sdk"]; - const path = opts?.path || "/SDK"; - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - // We always want to start the VM in this context as our intention is to connect immediately - // or return the session data to manually connect, for example in browser - const startOptions = - opts?.autoConnect === false ? undefined : startOptionsFromOpts(opts); - - const result = await sandboxFork({ - client: this.apiClient, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - start_options: startOptions, - }, - path: { - id: typeof templateId === "string" ? templateId : templateId.id, - }, - }); - - const sandbox = handleResponse( - result, - "Failed to create sandbox" - // We currently always pass "start_options" to create a session - ); + opts: CreateSandboxOpts = { source: "template" }, + sessionOpts?: SessionCreateOptions, + startOpts?: StartSandboxOpts + ): Promise { + switch (opts.source) { + case "git": { + throw new Error("Not implemented"); + } + case "json": { + throw new Error("Not implemented"); + } + case "template": { + const templateId = opts.template || this.defaultTemplate; + const privacy = opts.privacy || "public"; + const tags = opts.tags || ["sdk"]; + const path = opts.path || "/SDK"; + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + // We never want to start the Sandbox as part of this call. The reason is that we currently need to + // call an explicit START to start it in the correct cluster + const result = await sandboxFork({ + client: this.apiClient, + body: { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + }, + path: { + id: typeof templateId === "string" ? templateId : templateId.id, + }, + }); - const shouldReturnSessionOnly = opts?.autoConnect === false; + const sandbox = handleResponse( + result, + "Failed to create sandbox" + // We currently always pass "start_options" to create a session + ); - // HACK: We need to start the sandbox on the correct cluster, which means we can not - // start the sandbox during `sandboxFork`. Normally we would always get a `start_response` here, - // directly from fork endpoint - if (shouldReturnSessionOnly || !sandbox.start_response) { - return this.start(sandbox.id, startOptions); - } + // HACK: We need to start the sandbox on the correct cluster, which means we can not + // start the sandbox during `sandboxFork`. This creates a "global" session + let session = await this.start(sandbox.id, startOpts); - const session: SessionData = { - id: sandbox.id, - pitcher_token: sandbox.start_response.pitcher_token, - pitcher_url: sandbox.start_response.pitcher_url, - user_workspace_path: sandbox.start_response.user_workspace_path, - }; + // We can only create a custom session after the Sandbox is started + if (sessionOpts) { + session = await this.createSession(sandbox.id, sessionOpts); + } - return shouldReturnSessionOnly ? session : this.connectToSandbox(session); + return session; + } + } } /** @@ -438,51 +445,70 @@ export class SandboxClient { * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM */ async fork( + sandboxId: string, + opts: Omit = { source: "template" }, + sessionOpts?: SessionCreateOptions, + startOpts?: StartSandboxOpts + ): Promise { + return this.create( + { ...opts, template: sandboxId }, + sessionOpts, + startOpts + ); + } + + /** + * Try to start a sandbox that already exists, it will return the data of the started + * VM, which you can pass to the browser. In the browser you can call `connectToSandbox` with this + * data to control the VM without sharing your CodeSandbox API token in the browser. + * + * @param id the ID of the sandbox + * @returns The start data, contains a single use token to connect to the VM + */ + public async resume( id: string, - opts: { autoConnect: false } & Omit - ): Promise; - async fork( - id: string, - opts: { autoConnect: true } & Omit - ): Promise; - async fork( - id: string, - opts?: Omit - ): Promise { - return this.create({ ...opts, template: id }); + sessionOpts?: SessionCreateOptions + ): Promise { + const globalSession = this.start(id); + + if (sessionOpts) { + return this.createSession(id, sessionOpts); + } + + return globalSession; } /** * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. * - * @param id The ID of the sandbox to shutdown + * @param sandboxId The ID of the sandbox to shutdown */ - async shutdown(id: string): Promise { + async shutdown(sandboxId: string): Promise { const response = await vmShutdown({ client: this.apiClient, path: { - id, + id: sandboxId, }, }); - handleResponse(response, `Failed to shutdown sandbox ${id}`); + handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); } /** * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time * you start the sandbox it will be resumed from the last state it was in. * - * @param id The ID of the sandbox to hibernate + * @param sandboxId The ID of the sandbox to hibernate */ - async hibernate(id: string): Promise { + async hibernate(sandboxId: string): Promise { const response = await vmHibernate({ client: this.apiClient, path: { - id, + id: sandboxId, }, }); - handleResponse(response, `Failed to hibernate sandbox ${id}`); + handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); } /** @@ -622,11 +648,11 @@ export class SandboxClient { ); } - private async connectToSandbox(session: SessionData): Promise { + async connect(session: SandboxSession): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: session.id, + instanceId: session.sandboxId, onFocusChange() { return () => {}; }, @@ -646,19 +672,19 @@ export class SandboxClient { .baseUrl?.replace("api", "global-scheduler"); await fetch( - `${baseUrl}/api/v1/cluster/${session.id}?preferredManager=${preferredManager}` + `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` ).then((res) => res.json()); } return { bootupType: "RESUME", - pitcherURL: session.pitcher_url, - workspacePath: session.user_workspace_path, - userWorkspacePath: session.user_workspace_path, + pitcherURL: session.pitcherUrl, + workspacePath: session.userWorkspacePath, + userWorkspacePath: session.userWorkspacePath, pitcherManagerVersion: "1.0.0-session", pitcherVersion: "1.0.0-session", latestPitcherVersion: "1.0.0-session", - pitcherToken: session.pitcher_token, + pitcherToken: session.pitcherToken, cluster: "session", }; }, @@ -670,56 +696,6 @@ export class SandboxClient { return new Sandbox(this, pitcherClient); } - public async createSession( - sandboxId: string, - sessionId: string, - options: SessionCreateOptions & { autoConnect: false } - ): Promise; - public async createSession( - sandboxId: string, - sessionId: string, - options: SessionCreateOptions & { autoConnect: true } - ): Promise; - public async createSession( - sandboxId: string, - sessionId: string, - options?: SessionCreateOptions - ): Promise; - public async createSession( - sandboxId: string, - sessionId: string, - options: SessionCreateOptions = {} - ): Promise { - const response = await vmCreateSession({ - client: this.apiClient, - body: { - session_id: sessionId, - permission: options.permission ?? "write", - }, - path: { - id: sandboxId, - }, - }); - - const handledResponse = handleResponse( - response, - `Failed to create session ${sessionId}` - ); - - const session: SessionData = { - id: sandboxId, - pitcher_token: handledResponse.pitcher_token, - pitcher_url: handledResponse.pitcher_url, - user_workspace_path: handledResponse.user_workspace_path, - }; - - if (options.autoConnect === false) { - return session; - } - - return this.connectToSandbox(session); - } - /** * Namespace for managing preview tokens that can be used to access private sandbox previews. */ diff --git a/src/sandbox.ts b/src/sandbox.ts index ce84dab..ffbb65a 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,5 +1,4 @@ import { - Barrier, Disposable, type IPitcherClient, type protocol as _protocol, @@ -14,7 +13,6 @@ import { Tasks } from "./tasks"; import type { SandboxClient, VMTier } from "."; import { Sessions } from "./sessions"; import { PreviewTokens } from "./preview-tokens"; -export { SessionData } from "./sessions"; export { FSStatResult, @@ -58,7 +56,7 @@ export interface SystemMetricsStatus { }; } -export class SandboxSession extends Disposable { +export class UniversalSandbox extends Disposable { /** * Namespace for all filesystem operations on this sandbox. */ @@ -216,7 +214,7 @@ export class SandboxSession extends Disposable { } } -export class Sandbox extends SandboxSession { +export class Sandbox extends UniversalSandbox { /** * Provider for creating new sessions inside the sandbox. These sessions have their own * filesystem, shells, tasks and permissions. You can read more about sessions in the @@ -253,9 +251,11 @@ export class Sandbox extends SandboxSession { * that running processes will continue to run in the forked sandbox. */ public async fork(): Promise { - return this.sandboxClient.create({ + const session = await this.sandboxClient.create({ template: this.id, }); + + return this.sandboxClient.connect(session); } /** @@ -284,15 +284,14 @@ export class Sandbox extends SandboxSession { } /** - * Reboot the sandbox. This will shutdown the sandbox, and then start it again. Files in + * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in * the project directory (`/project/sandbox`) will be preserved. * * Will resolve once the sandbox is rebooted. */ - public async reboot(): Promise { + public async restart(): Promise { await this.shutdown(); - const newSandbox = await this.sandboxClient.open(this.id); - Object.assign(this, newSandbox); + await this.sandboxClient.resume(this.id); } /** diff --git a/src/sessions.ts b/src/sessions.ts index e39a6bc..39e3982 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -1,19 +1,26 @@ import { Disposable } from "./utils/disposable"; import { SandboxClient } from "./sandbox-client"; -import { SandboxSession } from "."; export interface SessionCreateOptions { + id: string; permission?: "read" | "write"; - autoConnect?: boolean; + gitAccessToken?: string; } -export type SessionData = { +export type SandboxSessionData = { id: string; pitcher_token: string; pitcher_url: string; user_workspace_path: string; }; +export type SandboxSession = { + sandboxId: string; + pitcherToken: string; + pitcherUrl: string; + userWorkspacePath: string; +}; + export class Sessions extends Disposable { constructor( private readonly id: string, @@ -32,40 +39,8 @@ export class Sessions extends Disposable { * @returns if `autoConnect` is true, returns a `SandboxSession` object (which can be used to connect), otherwise returns * a connected session. */ - async create( - sessionId: string, - options: SessionCreateOptions & { autoConnect: false } - ): Promise<{ - pitcher_token: string; - pitcher_url: string; - user_workspace_path: string; - }>; - async create( - sessionId: string, - options?: SessionCreateOptions & { autoConnect?: true } - ): Promise; - async create( - sessionId: string, - options: SessionCreateOptions = {} - ): Promise< - | SandboxSession - | { - pitcher_token: string; - pitcher_url: string; - user_workspace_path: string; - } - > { - const defaultOptions: SessionCreateOptions = { - permission: "write", - autoConnect: true, - }; - - const mergedOptions = { - ...defaultOptions, - ...options, - }; - - return this.apiClient.createSession(this.id, sessionId, mergedOptions); + async create(opts: SessionCreateOptions): Promise { + return this.apiClient["createSession"](this.id, opts); } /** @@ -75,6 +50,6 @@ export class Sessions extends Disposable { * @returns The new session */ async createReadOnly(): Promise { - return this.create("anonymous", { permission: "read" }); + return this.create({ id: "anonymous", permission: "read" }); } } diff --git a/src/utils/session.ts b/src/utils/session.ts index 507cd7a..af38381 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -1,6 +1,6 @@ -import { SessionData } from "../sessions"; +import { SandboxSessionData } from "../sessions"; -export function getSessionUrl(session: SessionData) { +export function getSessionUrl(session: SandboxSessionData) { const url = new URL(session.pitcher_url); url.protocol = "https"; From 80875baa1def7964ca30bba40e4de269c0b4a6b4 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 28 Apr 2025 14:19:24 +0200 Subject: [PATCH 073/241] working state --- TODO.md | 22 +- src/PreviewTokens.ts | 179 ++++ src/SandboxClient.ts | 228 +++++ src/VMTier.ts | 74 ++ src/bin/commands/build.ts | 84 +- src/browser.ts | 10 +- src/index.ts | 43 +- src/preview-tokens.ts | 86 -- src/rest/sandbox-rest-container.ts | 24 - src/rest/sandbox-rest-fs.ts | 110 --- src/rest/sandbox-rest-git.ts | 76 -- src/rest/sandbox-rest-shell.ts | 56 -- src/rest/sandbox-rest-system.ts | 32 - src/rest/sandbox-rest-task.ts | 65 -- src/sandbox-client.ts | 878 ------------------ src/sandbox.ts | 449 ++++----- src/sessions.ts | 55 -- .../RestSession/index.ts} | 40 +- .../RestSession/sandbox-rest-container.ts | 12 + src/sessions/RestSession/sandbox-rest-fs.ts | 111 +++ src/sessions/RestSession/sandbox-rest-git.ts | 53 ++ .../RestSession/sandbox-rest-shell.ts | 42 + .../RestSession/sandbox-rest-system.ts | 18 + src/sessions/RestSession/sandbox-rest-task.ts | 42 + .../WebSocketSession}/filesystem.ts | 4 +- src/sessions/WebSocketSession/index.ts | 225 +++++ src/{ => sessions/WebSocketSession}/ports.ts | 4 +- src/{ => sessions/WebSocketSession}/setup.ts | 4 +- src/{ => sessions/WebSocketSession}/shells.ts | 6 +- src/{ => sessions/WebSocketSession}/tasks.ts | 2 +- src/types.ts | 223 +++++ src/utils/{handle-response.ts => api.ts} | 16 + src/utils/session.ts | 11 - 33 files changed, 1524 insertions(+), 1760 deletions(-) create mode 100644 src/PreviewTokens.ts create mode 100644 src/SandboxClient.ts create mode 100644 src/VMTier.ts delete mode 100644 src/preview-tokens.ts delete mode 100644 src/rest/sandbox-rest-container.ts delete mode 100644 src/rest/sandbox-rest-fs.ts delete mode 100644 src/rest/sandbox-rest-git.ts delete mode 100644 src/rest/sandbox-rest-shell.ts delete mode 100644 src/rest/sandbox-rest-system.ts delete mode 100644 src/rest/sandbox-rest-task.ts delete mode 100644 src/sandbox-client.ts delete mode 100644 src/sessions.ts rename src/{sandbox-rest-client.ts => sessions/RestSession/index.ts} (64%) create mode 100644 src/sessions/RestSession/sandbox-rest-container.ts create mode 100644 src/sessions/RestSession/sandbox-rest-fs.ts create mode 100644 src/sessions/RestSession/sandbox-rest-git.ts create mode 100644 src/sessions/RestSession/sandbox-rest-shell.ts create mode 100644 src/sessions/RestSession/sandbox-rest-system.ts create mode 100644 src/sessions/RestSession/sandbox-rest-task.ts rename src/{ => sessions/WebSocketSession}/filesystem.ts (98%) create mode 100644 src/sessions/WebSocketSession/index.ts rename src/{ => sessions/WebSocketSession}/ports.ts (98%) rename src/{ => sessions/WebSocketSession}/setup.ts (94%) rename src/{ => sessions/WebSocketSession}/shells.ts (98%) rename src/{ => sessions/WebSocketSession}/tasks.ts (96%) create mode 100644 src/types.ts rename src/utils/{handle-response.ts => api.ts} (77%) delete mode 100644 src/utils/session.ts diff --git a/TODO.md b/TODO.md index c5e993a..449c5f7 100644 --- a/TODO.md +++ b/TODO.md @@ -3,41 +3,43 @@ - Please explain again why we always start the VM from "forkSandbox", but we also handle non start_response... how do we handle actually correct cluster? Is it not by always using the `start` method? - You say sessions allows users to edit files without affecting each other, but above you state that all files are shared? - Should we really call it GIT, it only supports GitHub? +- Can we pass custom session to `vmStart`? +- Using start options is not reliable + +## TODO # 1 New API ```ts const sdk = new CodeSandbox(apiToken); -const sandbox = sdk.sandbox.create(SandboxOption & StartOptions); +const sandbox = sdk.sandbox.create(SandboxOptions & StartOptions); const sandbox = sdk.sandbox.fork( id, Omit & StartOptions ); -sandbox.restart(); +sandbox.restart(StartOptions); sandbox.hibernate(); sandbox.shutdown(); sandbox.isUpToDate(); sandbox.resume(); +sandbox.updateTier(); -sandbox.createSession({ - type: "websocket", +sandbox.session({ username: "anonymous", permission: "read", + gitAccessToken: "", }); -sandbox.createSession({ - type: "browser", +sandbox.connect({ username: "anonymous", permission: "read", }); -sandbox.createSession({ - type: "rest", +sandbox.rest({ username: "anonymous", permission: "read", }); -sandbox.createSession({ - type: "ssh", +sandbox.ssh({ username: "anonymous", permission: "read", }); diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts new file mode 100644 index 0000000..dc22d22 --- /dev/null +++ b/src/PreviewTokens.ts @@ -0,0 +1,179 @@ +import { Disposable } from "./utils/disposable"; +import { SandboxClient } from "./SandboxClient"; +import { Client } from "@hey-api/client-fetch"; +import { handleResponse } from "./utils/api"; +import { + previewTokenCreate, + previewTokenList, + previewTokenRevokeAll, + previewTokenUpdate, +} from "./clients/client"; + +interface BasePreviewTokenInfo { + expiresAt: Date | null; + tokenId: string; + lastUsedAt: Date | null; +} + +export interface PreviewTokenInfo extends BasePreviewTokenInfo { + tokenPrefix: string; +} + +export interface PreviewToken extends BasePreviewTokenInfo { + token: string; +} + +/** + * Provider for generating preview tokens that can be used to access + * private sandbox previews. This provider is only available in environments + * with an authenticated API client (like Node.js). + */ +export class PreviewTokens extends Disposable { + constructor(private sandboxId: string, private apiClient: Client) { + super(); + } + + /** + * Generate a new preview token that can be used to access private sandbox previews. + * + * @param opts - Options + * @param opts.expiresAt - Optional expiration date for the preview token + * @returns A preview token that can be used with Ports.getSignedPreviewUrl + */ + async create(opts: { expiresAt?: Date } = {}): Promise { + const response = handleResponse( + await previewTokenCreate({ + client: this.apiClient, + path: { + id: this.sandboxId, + }, + body: { + expires_at: opts.expiresAt?.toISOString(), + }, + }), + "Failed to create preview token" + ); + + if (!response.token?.token) { + throw new Error("No token returned from API"); + } + + return { + token: response.token.token, + expiresAt: response.token.expires_at + ? new Date(response.token.expires_at) + : null, + tokenId: response.token.token_id, + lastUsedAt: response.token.last_used_at + ? new Date(response.token.last_used_at) + : null, + }; + } + + /** + * List all active preview tokens for this sandbox. + * + * @returns A list of preview tokens + */ + async list(): Promise { + const response = handleResponse( + await previewTokenList({ + client: this.apiClient, + path: { + id: this.sandboxId, + }, + }), + "Failed to list preview tokens" + ); + + if (!response.tokens) { + return []; + } + + return response.tokens.map((token) => ({ + expiresAt: token.expires_at ? new Date(token.expires_at) : null, + tokenId: token.token_id, + tokenPrefix: token.token_prefix, + lastUsedAt: token.last_used_at ? new Date(token.last_used_at) : null, + })); + } + + /** + * Revoke a single preview token for this sandbox. + * + * @param tokenId - The ID of the token to revoke + */ + async revoke(tokenId: string): Promise { + handleResponse( + await previewTokenUpdate({ + client: this.apiClient, + path: { + id: this.sandboxId, + token_id: tokenId, + }, + body: { + expires_at: new Date().toISOString(), + }, + }), + "Failed to revoke preview token" + ); + } + + /** + * Revoke all active preview tokens for this sandbox. + * This will immediately invalidate all tokens, and they can no longer be used + * to access the sandbox preview. + */ + async revokeAll(): Promise { + handleResponse( + await previewTokenRevokeAll({ + client: this.apiClient, + path: { + id: this.sandboxId, + }, + }), + "Failed to revoke preview tokens" + ); + } + + /** + * Update a preview token's expiration date. + * + * @param tokenId - The ID of the token to update + * @param expiresAt - The new expiration date for the token (null for no expiration) + * @returns The updated preview token info + */ + async update( + tokenId: string, + expiresAt: Date | null + ): Promise { + const response = handleResponse( + await previewTokenUpdate({ + client: this.apiClient, + path: { + id: this.sandboxId, + token_id: tokenId, + }, + body: { + expires_at: expiresAt?.toISOString(), + }, + }), + "Failed to update preview token" + ); + + if (!response.token) { + throw new Error("No token returned from API"); + } + + return { + expiresAt: response.token.expires_at + ? new Date(response.token.expires_at) + : null, + tokenId: response.token.token_id, + tokenPrefix: response.token.token_prefix, + lastUsedAt: response.token.last_used_at + ? new Date(response.token.last_used_at) + : null, + }; + } +} diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts new file mode 100644 index 0000000..0249269 --- /dev/null +++ b/src/SandboxClient.ts @@ -0,0 +1,228 @@ +import type { Client } from "@hey-api/client-fetch"; +import { createClient, createConfig } from "@hey-api/client-fetch"; + +import { sandboxFork, sandboxList } from "./clients/client"; +import { Sandbox } from "./Sandbox"; +import { getBaseUrl, handleResponse } from "./utils/api"; +import { ClientOpts } from "."; +import { + CreateSandboxOpts, + PaginationOpts, + SandboxInfo, + SandboxListOpts, + SandboxListResponse, + SandboxPrivacy, + StartSandboxOpts, +} from "./types"; + +export class SandboxClient { + private apiClient: Client; + + get defaultTemplateId() { + if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } + + return "pcz35m"; + } + + constructor(apiToken: string, opts: ClientOpts) { + const baseUrl = + process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(apiToken); + + this.apiClient = this.apiClient = createClient( + createConfig({ + baseUrl, + headers: { + Authorization: `Bearer ${apiToken}`, + ...(opts.headers ?? {}), + }, + fetch: opts.fetch ?? fetch, + }) + ); + } + + /** + * + */ + ref(sandboxId: string) { + return new Sandbox(sandboxId, this); + } + + /** + * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from + * any sandbox/template created on codesandbox.io, even your own templates) or don't pass + * in anything and we'll use the default universal template. + * + * This function will also start & connect to the VM of the created sandbox with a global session, and return a {@link Sandbox} + * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. + * + * @param opts Additional options for creating the sandbox + * + * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM + */ + async create( + opts: CreateSandboxOpts & StartSandboxOpts = { source: "template" } + ): Promise { + switch (opts.source) { + case "git": { + throw new Error("Not implemented"); + } + case "json": { + throw new Error("Not implemented"); + } + case "template": { + const templateId = opts.id || this.defaultTemplateId; + const privacy = opts.privacy || "public"; + const tags = opts.tags || ["sdk"]; + const path = opts.path || "/SDK"; + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + // We never want to start the Sandbox as part of this call. The reason is that we currently need to + // call an explicit START to start it in the correct cluster + const result = await sandboxFork({ + client: this.apiClient, + body: { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + }, + path: { + id: templateId, + }, + }); + + const sandbox = handleResponse( + result, + "Failed to create sandbox" + // We currently always pass "start_options" to create a session + ); + + return new Sandbox(sandbox.id, this); + } + } + } + + /** + * List sandboxes from the current workspace with optional filters. + * + * This method supports two modes of operation: + * 1. Simple limit-based fetching (default): + * ```ts + * // Get up to 50 sandboxes (default) + * const { sandboxes, totalCount } = await client.list(); + * + * // Get up to 200 sandboxes + * const { sandboxes, totalCount } = await client.list({ limit: 200 }); + * ``` + * + * 2. Manual pagination: + * ```ts + * // Get first page + * const { sandboxes, pagination } = await client.list({ + * pagination: { page: 1, pageSize: 50 } + * }); + * // pagination = { currentPage: 1, nextPage: 2, pageSize: 50 } + * + * // Get next page if available + * if (pagination.nextPage) { + * const { sandboxes, pagination: nextPagination } = await client.list({ + * pagination: { page: pagination.nextPage, pageSize: 50 } + * }); + * } + * ``` + */ + async list( + opts: SandboxListOpts & { + limit?: number; + pagination?: PaginationOpts; + } = {} + ): Promise { + const limit = opts.limit ?? 50; + let allSandboxes: SandboxInfo[] = []; + let currentPage = opts.pagination?.page ?? 1; + let pageSize = opts.pagination?.pageSize ?? limit; + let totalCount = 0; + let nextPage: number | null = null; + + while (true) { + const response = await sandboxList({ + client: this.apiClient, + query: { + tags: opts.tags?.join(","), + page: currentPage, + page_size: pageSize, + order_by: opts.orderBy, + direction: opts.direction, + status: opts.status, + }, + }); + + const info = handleResponse(response, "Failed to list sandboxes"); + totalCount = info.pagination.total_records; + nextPage = info.pagination.next_page; + + const sandboxes = info.sandboxes.map((sandbox) => ({ + id: sandbox.id, + createdAt: new Date(sandbox.created_at), + updatedAt: new Date(sandbox.updated_at), + title: sandbox.title ?? undefined, + description: sandbox.description ?? undefined, + privacy: privacyFromNumber(sandbox.privacy), + tags: sandbox.tags, + })); + + const newSandboxes = sandboxes.filter( + (sandbox) => + !allSandboxes.some((existing) => existing.id === sandbox.id) + ); + allSandboxes = [...allSandboxes, ...newSandboxes]; + + // Stop if we've hit the limit or there are no more pages + if (!nextPage || allSandboxes.length >= limit) { + break; + } + + currentPage = nextPage; + } + + return { + sandboxes: allSandboxes, + hasMore: totalCount > allSandboxes.length, + totalCount, + pagination: { + currentPage, + nextPage: allSandboxes.length >= limit ? nextPage : null, + pageSize, + }, + }; + } +} + +function privacyToNumber(privacy: SandboxPrivacy): number { + switch (privacy) { + case "public": + return 0; + case "unlisted": + return 1; + case "private": + return 2; + } +} + +function privacyFromNumber(privacy: number): SandboxPrivacy { + switch (privacy) { + case 0: + return "public"; + case 1: + return "unlisted"; + case 2: + return "private"; + } + + throw new Error(`Invalid privacy number: ${privacy}`); +} diff --git a/src/VMTier.ts b/src/VMTier.ts new file mode 100644 index 0000000..85bf926 --- /dev/null +++ b/src/VMTier.ts @@ -0,0 +1,74 @@ +import { VmUpdateSpecsRequest } from "./clients/client"; + +/** + * A VM tier is how we classify the specs of a VM. You can use this to request a VM with specific + * specs. + * + * You can either get a tier by its name, or by specifying the minimum specs you need. + * + * ## Example + * + * ```ts + * const tier = VMTier.Pico; + * ``` + * + * ```ts + * const tier = VMTier.fromSpecs(16, 32, 40); + * ``` + */ +export class VMTier { + /** 1 CPU, 2GiB RAM */ + public static readonly Pico = new VMTier("Pico", 1, 2, 20); + /** 2 CPU, 4GiB RAM */ + public static readonly Nano = new VMTier("Nano", 2, 4, 20); + /** 4 CPU, 8GiB RAM */ + public static readonly Micro = new VMTier("Micro", 4, 8, 20); + /** 8 CPU, 16GiB RAM */ + public static readonly Small = new VMTier("Small", 8, 16, 30); + /** 16 CPU, 32GiB RAM */ + public static readonly Medium = new VMTier("Medium", 16, 32, 40); + /** 32 CPU, 64GiB RAM */ + public static readonly Large = new VMTier("Large", 32, 64, 50); + /** 64 CPU, 128GiB RAM */ + public static readonly XLarge = new VMTier("XLarge", 64, 128, 50); + + public static readonly All = [ + VMTier.Pico, + VMTier.Nano, + VMTier.Micro, + VMTier.Small, + VMTier.Medium, + VMTier.Large, + VMTier.XLarge, + ]; + + private constructor( + public readonly name: VmUpdateSpecsRequest["tier"], + public readonly cpuCores: number, + public readonly memoryGiB: number, + public readonly diskGB: number + ) {} + + public static fromName(name: VmUpdateSpecsRequest["tier"]): VMTier { + return VMTier[name]; + } + + /** + * Returns the tier that complies to the given minimum specs. + * @param cpuCores Amount of CPU cores needed + * @param memoryGiB Amount of memory needed in GiB + * @param diskGB Amount of disk space needed in GB + */ + public static fromSpecs(specs: { + cpu: number; + memGiB: number; + diskGB?: number; + }): VMTier | undefined { + return Object.values(VMTier).find( + (tier) => + tier.cpuCores >= specs.cpu && + tier.memoryGiB >= specs.memGiB && + (specs.diskGB === undefined || tier.diskGB >= specs.diskGB) + ); + } +} diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index a53a414..dbe8a4e 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -7,14 +7,14 @@ import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { SetupProgress, VMTier } from "../../"; -import { CodeSandbox, PortInfo } from "../../"; +import { WebSocketSession, VMTier, CodeSandbox } from "../../"; + import { sandboxCreate, sandboxFork, VmUpdateSpecsRequest, } from "../../clients/client"; -import { handleResponse } from "../../utils/handle-response"; +import { handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; @@ -113,15 +113,17 @@ export const buildCommand: yargs.CommandModule< const tag = `sha:${shortHash}-${cluster || ""}`; spinner.start(`Creating or updating sandbox...`); - const { sandboxId, filesIncluded } = await createSandbox( + const { sandboxId, filesIncluded } = await createSandbox({ apiClient, - tag, + shaTag: tag, filePaths, - argv.directory, - argv.fromSandbox, - argv.path, - argv.name - ); + rootPath: argv.directory, + fromSandbox: argv.fromSandbox, + collectionPath: argv.path, + name: argv.name, + ipcountry: argv.ipCountry, + vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, + }); if (argv.fromSandbox) { spinner.succeed( @@ -137,10 +139,8 @@ export const buildCommand: yargs.CommandModule< spinner.start(`Starting sandbox...`); } - const sandbox = await sdk.sandbox.open(sandboxId, { - ipcountry: argv.ipCountry, - vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, - }); + const sandbox = sdk.sandbox.ref(sandboxId); + const session = await sandbox.connect(); spinner.succeed("Sandbox opened"); if (!argv.skipFiles && !filesIncluded) { @@ -152,8 +152,8 @@ export const buildCommand: yargs.CommandModule< const fullPath = path.join(argv.directory, filePath); const content = await fs.readFile(fullPath); const dirname = path.dirname(filePath); - await sandbox.fs.mkdir(dirname, true); - await sandbox.fs.writeFile(filePath, content, { + await session.fs.mkdir(dirname, true); + await session.fs.writeFile(filePath, content, { create: true, overwrite: true, }); @@ -161,12 +161,14 @@ export const buildCommand: yargs.CommandModule< spinner.succeed("Files written to sandbox"); spinner.start("Rebooting sandbox..."); - await sandbox.reboot(); - spinner.succeed("Sandbox rebooted"); + await sandbox.restart(); + spinner.succeed("Sandbox restarted"); } const disposableStore = new DisposableStore(); - const handleProgress = async (progress: SetupProgress) => { + const handleProgress = async ( + progress: WebSocketSession.SetupProgress + ) => { if (progress.state === "IN_PROGRESS" && progress.steps.length > 0) { const step = progress.steps[progress.currentStepIndex]; if (!step) { @@ -181,7 +183,7 @@ export const buildCommand: yargs.CommandModule< const shellId = step.shellId; if (shellId) { - const shell = await sandbox.shells.open(shellId, { + const shell = await session.shells.open(shellId, { ptySize: { cols: process.stderr.columns, rows: process.stderr.rows, @@ -209,16 +211,16 @@ export const buildCommand: yargs.CommandModule< } }; - const progress = await sandbox.setup.getProgress(); + const progress = await session.setup.getProgress(); await handleProgress(progress); - disposableStore.add(sandbox.setup.onSetupProgressUpdate(handleProgress)); + disposableStore.add(session.setup.onSetupProgressUpdate(handleProgress)); - await sandbox.setup.waitForFinish(); + await session.setup.waitForFinish(); disposableStore.dispose(); spinner.succeed("Sandbox built"); - const tasksWithStart = (await sandbox.tasks.getTasks()).filter( + const tasksWithStart = (await session.tasks.getTasks()).filter( (t) => t.runAtStart === true ); let tasksWithPorts = tasksWithStart.filter((t) => t.preview?.port); @@ -251,7 +253,7 @@ export const buildCommand: yargs.CommandModule< let timeout; const portInfo = await Promise.race([ - sandbox.ports.waitForPort(port), + session.ports.waitForPort(port), new Promise( (_, reject) => (timeout = setTimeout( @@ -267,7 +269,7 @@ export const buildCommand: yargs.CommandModule< ]); clearTimeout(timeout); - if (!(portInfo instanceof PortInfo)) { + if (!(portInfo instanceof WebSocketSession.PortInfo)) { throw portInfo; } @@ -315,15 +317,27 @@ export const buildCommand: yargs.CommandModule< }, }; -async function createSandbox( - apiClient: Client, - shaTag: string, - filePaths: string[], - rootPath: string, - fromSandbox?: string, - collectionPath?: string, - name?: string -): Promise<{ +type CreateSandboxParams = { + apiClient: Client; + shaTag: string; + filePaths: string[]; + rootPath: string; + fromSandbox?: string; + collectionPath?: string; + name?: string; + vmTier?: VMTier; + ipcountry?: string; +}; + +async function createSandbox({ + apiClient, + filePaths, + rootPath, + shaTag, + collectionPath, + fromSandbox, + name, +}: CreateSandboxParams): Promise<{ sandboxId: string; filesIncluded: boolean; }> { diff --git a/src/browser.ts b/src/browser.ts index ab51173..b4cadfe 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,8 +1,6 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; - -import { SandboxSession } from "./sessions"; -import { DEFAULT_SUBSCRIPTIONS } from "./sandbox-client"; -import { UniversalSandbox } from "."; +import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "./types"; +import { WebSocketSession } from "./sessions/WebSocketSession"; /** * With this function you can connect to a sandbox from the browser. @@ -41,7 +39,7 @@ import { UniversalSandbox } from "."; */ export async function connectToSandbox( session: SandboxSession -): Promise { +): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -68,5 +66,5 @@ export async function connectToSandbox( () => {} ); - return new UniversalSandbox(pitcherClient); + return new WebSocketSession(pitcherClient); } diff --git a/src/index.ts b/src/index.ts index 574aab7..d7e1b22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,40 +1,15 @@ -import type { Client } from "@hey-api/client-fetch"; +import { SandboxClient } from "./SandboxClient"; +import { ClientOpts } from "./types"; -import { - SandboxClient, - CreateSandboxOpts, - VMTier, - SandboxListOpts, - SandboxInfo, - PaginationOpts, -} from "./sandbox-client"; +export { SandboxClient }; -export { - SandboxClient, - CreateSandboxOpts, - VMTier, - SandboxListOpts, - SandboxInfo, - PaginationOpts, -}; -export * from "./sandbox"; +export { VMTier } from "./VMTier"; -export interface ClientOpts { - baseUrl?: string; - /** - * Custom fetch implementation - * - * @default fetch - */ - fetch?: typeof fetch; +export * from "./Sandbox"; +export * from "./types"; +import * as WebSocketSession from "./sessions/WebSocketSession"; - /** - * Additional headers to send with each request - */ - headers?: Record; -} - -export type SandboxPrivacy = "public" | "unlisted" | "private"; +export { WebSocketSession }; function ensure(value: T | undefined, message: string): T { if (!value) { @@ -44,7 +19,7 @@ function ensure(value: T | undefined, message: string): T { return value; } -export { SandboxRestClient as RestClient } from "./sandbox-rest-client"; +export { RestSession as RestClient } from "./sessions/RestSession"; export class CodeSandbox { public readonly sandbox: SandboxClient; diff --git a/src/preview-tokens.ts b/src/preview-tokens.ts deleted file mode 100644 index 762dfee..0000000 --- a/src/preview-tokens.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Disposable } from "./utils/disposable"; -import { SandboxClient } from "./sandbox-client"; - -interface BasePreviewTokenInfo { - expiresAt: Date | null; - tokenId: string; - lastUsedAt: Date | null; -} - -export interface PreviewTokenInfo extends BasePreviewTokenInfo { - tokenPrefix: string; -} - -export interface PreviewToken extends BasePreviewTokenInfo { - token: string; -} - -/** - * Provider for generating preview tokens that can be used to access - * private sandbox previews. This provider is only available in environments - * with an authenticated API client (like Node.js). - */ -export class PreviewTokens extends Disposable { - constructor(private sandboxId: string, private sandboxClient: SandboxClient) { - super(); - } - - /** - * Generate a new preview token that can be used to access private sandbox previews. - * - * @param opts - Options - * @param opts.expiresAt - Optional expiration date for the preview token - * @returns A preview token that can be used with Ports.getSignedPreviewUrl - */ - async create(opts: { expiresAt?: Date } = {}): Promise { - return this.sandboxClient.previewTokens.create( - this.sandboxId, - opts.expiresAt - ); - } - - /** - * List all active preview tokens for this sandbox. - * - * @returns A list of preview tokens - */ - async list(): Promise { - return this.sandboxClient.previewTokens.list(this.sandboxId); - } - - /** - * Revoke a single preview token for this sandbox. - * - * @param tokenId - The ID of the token to revoke - */ - async revoke(tokenId: string): Promise { - return this.sandboxClient.previewTokens.revoke(this.sandboxId, tokenId); - } - - /** - * Revoke all active preview tokens for this sandbox. - * This will immediately invalidate all tokens, and they can no longer be used - * to access the sandbox preview. - */ - async revokeAll(): Promise { - return this.sandboxClient.previewTokens.revokeAll(this.sandboxId); - } - - /** - * Update a preview token's expiration date. - * - * @param tokenId - The ID of the token to update - * @param expiresAt - The new expiration date for the token (null for no expiration) - * @returns The updated preview token info - */ - async update( - tokenId: string, - expiresAt: Date | null - ): Promise { - return this.sandboxClient.previewTokens.update( - this.sandboxId, - tokenId, - expiresAt - ); - } -} diff --git a/src/rest/sandbox-rest-container.ts b/src/rest/sandbox-rest-container.ts deleted file mode 100644 index 0c27f97..0000000 --- a/src/rest/sandbox-rest-container.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as container from "../clients/client-rest-container"; -import { SandboxSessionData } from "../sessions"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestContainer { - constructor(private client: Client) {} - - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - - setup( - session: SandboxSessionData, - body: container.ContainerSetupData["body"] - ) { - return container.containerSetup(this.createRestParams(session, body)); - } -} diff --git a/src/rest/sandbox-rest-fs.ts b/src/rest/sandbox-rest-fs.ts deleted file mode 100644 index d85614d..0000000 --- a/src/rest/sandbox-rest-fs.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as fs from "../clients/client-rest-fs"; -import { SandboxSessionData } from "../sessions"; -import { join } from "path"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestFS { - constructor(private client: Client) {} - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - writeTextFile( - session: SandboxSessionData, - body: Omit & { content: string } - ) { - return fs.writeFile( - this.createRestParams(session, { - ...body, - // OpenAPI does not have UINT8Array, so we need to cast it to a Blob - content: new TextEncoder().encode(body.content) as unknown as Blob, - path: join(session.user_workspace_path, body.path), - create: body.create === false ? false : true, - }) - ); - } - writeFile(session: SandboxSessionData, body: fs.WriteFileData["body"]) { - return fs.writeFile( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - create: body.create === false ? false : true, - }) - ); - } - readFile(session: SandboxSessionData, body: fs.FsReadFileData["body"]) { - return fs.fsReadFile( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - }) - ); - } - search(session: SandboxSessionData, body: fs.FsSearchData["body"]) { - return fs.fsSearch(this.createRestParams(session, body)); - } - pathSearch(session: SandboxSessionData, body: fs.FsPathSearchData["body"]) { - return fs.fsPathSearch(this.createRestParams(session, body)); - } - upload(session: SandboxSessionData, body: fs.FsUploadData["body"]) { - return fs.fsUpload(this.createRestParams(session, body)); - } - download(session: SandboxSessionData, body: fs.FsDownloadData["body"]) { - return fs.fsDownload(this.createRestParams(session, body)); - } - readDir(session: SandboxSessionData, body: fs.FsReadDirData["body"]) { - return fs.fsReadDir( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - }) - ); - } - stat(session: SandboxSessionData, body: fs.FsStatData["body"]) { - return fs.fsStat( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - }) - ); - } - copy(session: SandboxSessionData, body: fs.FsCopyData["body"]) { - return fs.fsCopy( - this.createRestParams(session, { - ...body, - from: join(session.user_workspace_path, body.from), - to: join(session.user_workspace_path, body.to), - }) - ); - } - rename(session: SandboxSessionData, body: fs.FsRenameData["body"]) { - return fs.fsRename( - this.createRestParams(session, { - ...body, - from: join(session.user_workspace_path, body.from), - to: join(session.user_workspace_path, body.to), - }) - ); - } - remove(session: SandboxSessionData, body: fs.FsRemoveData["body"]) { - return fs.fsRemove( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - }) - ); - } - mkdir(session: SandboxSessionData, body: fs.FsMkdirData["body"]) { - return fs.fsMkdir( - this.createRestParams(session, { - ...body, - path: join(session.user_workspace_path, body.path), - }) - ); - } -} diff --git a/src/rest/sandbox-rest-git.ts b/src/rest/sandbox-rest-git.ts deleted file mode 100644 index 0dd10db..0000000 --- a/src/rest/sandbox-rest-git.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as git from "../clients/client-rest-git"; -import { SandboxSessionData } from "../sessions"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestGit { - constructor(private client: Client) {} - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - - status(session: SandboxSessionData, body: git.GitStatusData["body"]) { - return git.gitStatus(this.createRestParams(session, body)); - } - - remotes(session: SandboxSessionData, body: git.GitRemotesData["body"]) { - return git.gitRemotes(this.createRestParams(session, body)); - } - - targetDiff(session: SandboxSessionData, body: git.GitTargetDiffData["body"]) { - return git.gitTargetDiff(this.createRestParams(session, body)); - } - - pull(session: SandboxSessionData, body: git.GitPullData["body"]) { - return git.gitPull(this.createRestParams(session, body)); - } - - discard(session: SandboxSessionData, body: git.GitDiscardData["body"]) { - return git.gitDiscard(this.createRestParams(session, body)); - } - - commit(session: SandboxSessionData, body: git.GitCommitData["body"]) { - return git.gitCommit(this.createRestParams(session, body)); - } - - push(session: SandboxSessionData, body: git.GitPushData["body"]) { - return git.gitPush(this.createRestParams(session, body)); - } - - pushToRemote( - session: SandboxSessionData, - body: git.GitPushToRemoteData["body"] - ) { - return git.gitPushToRemote(this.createRestParams(session, body)); - } - - renameBranch( - session: SandboxSessionData, - body: git.GitRenameBranchData["body"] - ) { - return git.gitRenameBranch(this.createRestParams(session, body)); - } - - remoteContent( - session: SandboxSessionData, - body: git.GitRemoteContentData["body"] - ) { - return git.gitRemoteContent(this.createRestParams(session, body)); - } - - diffStatus(session: SandboxSessionData, body: git.GitDiffStatusData["body"]) { - return git.gitDiffStatus(this.createRestParams(session, body)); - } - - resetLocalWithRemote( - session: SandboxSessionData, - body: git.GitResetLocalWithRemoteData["body"] - ) { - return git.gitResetLocalWithRemote(this.createRestParams(session, body)); - } -} diff --git a/src/rest/sandbox-rest-shell.ts b/src/rest/sandbox-rest-shell.ts deleted file mode 100644 index 067e869..0000000 --- a/src/rest/sandbox-rest-shell.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as shell from "../clients/client-rest-shell"; -import { SandboxSessionData } from "../sessions"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestShell { - constructor(private client: Client) {} - - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - - create(session: SandboxSessionData, body: shell.ShellCreateData["body"]) { - return shell.shellCreate(this.createRestParams(session, body)); - } - - in(session: SandboxSessionData, body: shell.ShellInData["body"]) { - return shell.shellIn(this.createRestParams(session, body)); - } - - list(session: SandboxSessionData, body: shell.ShellListData["body"]) { - return shell.shellList(this.createRestParams(session, body)); - } - - open(session: SandboxSessionData, body: shell.ShellOpenData["body"]) { - return shell.shellOpen(this.createRestParams(session, body)); - } - - close(session: SandboxSessionData, body: shell.ShellCloseData["body"]) { - return shell.shellClose(this.createRestParams(session, body)); - } - - restart(session: SandboxSessionData, body: shell.ShellRestartData["body"]) { - return shell.shellRestart(this.createRestParams(session, body)); - } - - terminate( - session: SandboxSessionData, - body: shell.ShellTerminateData["body"] - ) { - return shell.shellTerminate(this.createRestParams(session, body)); - } - - resize(session: SandboxSessionData, body: shell.ShellResizeData["body"]) { - return shell.shellResize(this.createRestParams(session, body)); - } - - rename(session: SandboxSessionData, body: shell.ShellRenameData["body"]) { - return shell.shellRename(this.createRestParams(session, body)); - } -} diff --git a/src/rest/sandbox-rest-system.ts b/src/rest/sandbox-rest-system.ts deleted file mode 100644 index 71e03d1..0000000 --- a/src/rest/sandbox-rest-system.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as system from "../clients/client-rest-system"; -import { SandboxSessionData } from "../sessions"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestSystem { - constructor(private client: Client) {} - - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - - update(session: SandboxSessionData, body: system.SystemUpdateData["body"]) { - return system.systemUpdate(this.createRestParams(session, body)); - } - - hibernate( - session: SandboxSessionData, - body: system.SystemHibernateData["body"] - ) { - return system.systemHibernate(this.createRestParams(session, body)); - } - - metrics(session: SandboxSessionData, body: system.SystemMetricsData["body"]) { - return system.systemMetrics(this.createRestParams(session, body)); - } -} diff --git a/src/rest/sandbox-rest-task.ts b/src/rest/sandbox-rest-task.ts deleted file mode 100644 index 6a2d52e..0000000 --- a/src/rest/sandbox-rest-task.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as task from "../clients/client-rest-task"; -import { SandboxSessionData } from "../sessions"; -import { getSessionUrl } from "../utils/session"; - -export class SandboxRestTask { - constructor(private client: Client) {} - - private createRestParams(session: SandboxSessionData, body: T) { - return { - baseUrl: getSessionUrl(session), - client: this.client, - body, - throwOnError: true, - }; - } - - list(session: SandboxSessionData, body: task.TaskListData["body"] = {}) { - return task.taskList(this.createRestParams(session, body)); - } - - run(session: SandboxSessionData, body: task.TaskRunData["body"]) { - return task.taskRun(this.createRestParams(session, body)); - } - - runCommand( - session: SandboxSessionData, - body: task.TaskRunCommandData["body"] - ) { - return task.taskRunCommand(this.createRestParams(session, body)); - } - - stop(session: SandboxSessionData, body: task.TaskStopData["body"]) { - return task.taskStop(this.createRestParams(session, body)); - } - - create(session: SandboxSessionData, body: task.TaskCreateData["body"]) { - return task.taskCreate(this.createRestParams(session, body)); - } - - update(session: SandboxSessionData, body: task.TaskUpdateData["body"]) { - return task.taskUpdate(this.createRestParams(session, body)); - } - - saveToConfig( - session: SandboxSessionData, - body: task.TaskSaveToConfigData["body"] - ) { - return task.taskSaveToConfig(this.createRestParams(session, body)); - } - - generateConfig( - session: SandboxSessionData, - body: task.TaskGenerateConfigData["body"] = {} - ) { - return task.taskGenerateConfig(this.createRestParams(session, body)); - } - - createSetupTasks( - session: SandboxSessionData, - body: task.TaskCreateSetupTasksData["body"] - ) { - return task.taskCreateSetupTasks(this.createRestParams(session, body)); - } -} diff --git a/src/sandbox-client.ts b/src/sandbox-client.ts deleted file mode 100644 index 641de75..0000000 --- a/src/sandbox-client.ts +++ /dev/null @@ -1,878 +0,0 @@ -import { initPitcherClient } from "@codesandbox/pitcher-client"; -import type { Client } from "@hey-api/client-fetch"; -import { createClient, createConfig } from "@hey-api/client-fetch"; - -import type { VmUpdateSpecsRequest } from "./clients/client"; -import { - sandboxFork, - vmCreateSession, - sandboxList, - vmHibernate, - vmShutdown, - vmStart, - vmUpdateHibernationTimeout, - vmUpdateSpecs, - previewTokenCreate, - previewTokenList, - previewTokenRevokeAll, - previewTokenUpdate, -} from "./clients/client"; -import { Sandbox } from "./sandbox"; -import { handleResponse } from "./utils/handle-response"; -import { SessionCreateOptions, SandboxSession } from "./sessions"; -import { SandboxRestClient } from "./sandbox-rest-client"; -import { ClientOpts } from "."; - -export type SandboxPrivacy = "public" | "unlisted" | "private"; - -export type SandboxInfo = { - id: string; - createdAt: Date; - updatedAt: Date; - title?: string; - description?: string; - privacy: SandboxPrivacy; - tags: string[]; -}; - -export type SandboxListOpts = { - tags?: string[]; - orderBy?: "inserted_at" | "updated_at"; - direction?: "asc" | "desc"; - status?: "running"; -}; - -export interface SandboxListResponse { - sandboxes: SandboxInfo[]; - hasMore: boolean; - totalCount: number; - pagination: { - currentPage: number; - nextPage: number | null; - pageSize: number; - }; -} - -export type PaginationOpts = { - page?: number; - pageSize?: number; -}; - -export const DEFAULT_SUBSCRIPTIONS = { - client: { - status: true, - }, - file: { - status: true, - selection: true, - ot: true, - }, - fs: { - operations: true, - }, - git: { - status: true, - operations: true, - }, - port: { - status: true, - }, - setup: { - progress: true, - }, - shell: { - status: true, - }, - system: { - metrics: true, - }, -}; - -export type CreateSandboxTemplateOpts = { - source: "template"; - - /** - * What template to fork from, this is the id of another sandbox. Defaults to our - * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). - */ - template?: string | Sandbox; - - /** - * What the privacy of the new sandbox should be. Defaults to "public". - */ - privacy?: SandboxPrivacy; - - /** - * The title of the new sandbox. - */ - title?: string; - - /** - * The description of the new sandbox. - */ - description?: string; - - /** - * Which tags to add to the sandbox, can be used for categorization and filtering. Max 10 tags. - */ - tags?: string[]; - - /** - * In which folder to put the sandbox in (inside your workspace). - */ - path?: string; -}; - -export type CreateSandboxOpts = - | CreateSandboxTemplateOpts - | { - source: "git"; - url: string; - branch: string; - } - | { - source: "json"; - files: Record; - }; - -/** - * A VM tier is how we classify the specs of a VM. You can use this to request a VM with specific - * specs. - * - * You can either get a tier by its name, or by specifying the minimum specs you need. - * - * ## Example - * - * ```ts - * const tier = VMTier.Pico; - * ``` - * - * ```ts - * const tier = VMTier.fromSpecs(16, 32, 40); - * ``` - */ -export class VMTier { - /** 1 CPU, 2GiB RAM */ - public static readonly Pico = new VMTier("Pico", 1, 2, 20); - /** 2 CPU, 4GiB RAM */ - public static readonly Nano = new VMTier("Nano", 2, 4, 20); - /** 4 CPU, 8GiB RAM */ - public static readonly Micro = new VMTier("Micro", 4, 8, 20); - /** 8 CPU, 16GiB RAM */ - public static readonly Small = new VMTier("Small", 8, 16, 30); - /** 16 CPU, 32GiB RAM */ - public static readonly Medium = new VMTier("Medium", 16, 32, 40); - /** 32 CPU, 64GiB RAM */ - public static readonly Large = new VMTier("Large", 32, 64, 50); - /** 64 CPU, 128GiB RAM */ - public static readonly XLarge = new VMTier("XLarge", 64, 128, 50); - - public static readonly All = [ - VMTier.Pico, - VMTier.Nano, - VMTier.Micro, - VMTier.Small, - VMTier.Medium, - VMTier.Large, - VMTier.XLarge, - ]; - - private constructor( - public readonly name: VmUpdateSpecsRequest["tier"], - public readonly cpuCores: number, - public readonly memoryGiB: number, - public readonly diskGB: number - ) {} - - public static fromName(name: VmUpdateSpecsRequest["tier"]): VMTier { - return VMTier[name]; - } - - /** - * Returns the tier that complies to the given minimum specs. - * @param cpuCores Amount of CPU cores needed - * @param memoryGiB Amount of memory needed in GiB - * @param diskGB Amount of disk space needed in GB - */ - public static fromSpecs(specs: { - cpu: number; - memGiB: number; - diskGB?: number; - }): VMTier | undefined { - return Object.values(VMTier).find( - (tier) => - tier.cpuCores >= specs.cpu && - tier.memoryGiB >= specs.memGiB && - (specs.diskGB === undefined || tier.diskGB >= specs.diskGB) - ); - } -} - -export interface StartSandboxOpts { - /** - * Country, served as a hint on where you want the sandbox to be scheduled. For example, if "NL" is given - * as a country, the sandbox will be scheduled in a cluster inside Europe. Note that this is not a guarantee, - * and the sandbox might end up in a different region based on availability and scheduling decisions. - * - * Follows ISO 3166-1 alpha-2 codes. - */ - ipcountry?: string; - - /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * Check {@link VMTier} for available tiers. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. - * Specifying a VM tier for someone else's sandbox will return an error. - */ - vmTier?: VMTier; - - /** - * The amount of seconds to wait before hibernating the sandbox after inactivity. - * - * Defaults to 300 seconds for free users, 1800 seconds for pro users. Maximum is 86400 seconds (1 day). - */ - hibernationTimeoutSeconds?: number; - - /** - * Configuration for when the VM should automatically wake up from hibernation. - */ - automaticWakeupConfig?: { - /** - * Whether the VM should automatically wake up on HTTP requests to preview URLs (excludes WebSocket requests) - * - * @default true - */ - http: boolean; - - /** - * Whether the VM should automatically wake up on WebSocket connections to preview URLs - * - * @default false - */ - websocket: boolean; - }; -} - -export type HandledResponse = { - data?: { - data?: D; - }; - error?: E; - response: Response; -}; - -function getBaseUrl(token: string) { - if (token.startsWith("csb_")) { - return "https://api.codesandbox.io"; - } - - return "https://api.together.ai/csb/sdk"; -} - -export class SandboxClient { - private apiClient: Client; - private sandboxRestClient: SandboxRestClient; - - get defaultTemplate() { - if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { - return "7ngcrf"; - } - - return "pcz35m"; - } - - get fs() { - return this.sandboxRestClient.fs; - } - - constructor(apiToken: string, opts: ClientOpts) { - const baseUrl = - process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(apiToken); - - this.sandboxRestClient = new SandboxRestClient(opts); - this.apiClient = this.apiClient = createClient( - createConfig({ - baseUrl, - headers: { - Authorization: `Bearer ${apiToken}`, - ...(opts.headers ?? {}), - }, - fetch: opts.fetch ?? fetch, - }) - ); - } - - private async start( - id: string, - startOpts?: StartSandboxOpts - ): Promise { - const startResult = await vmStart({ - client: this.apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${id}` - ); - - return { - sandboxId: id, - pitcherToken: response.pitcher_token, - pitcherUrl: response.pitcher_url, - userWorkspacePath: response.user_workspace_path, - }; - } - - private async createSession( - sandboxId: string, - options: SessionCreateOptions - ): Promise { - const response = await vmCreateSession({ - client: this.apiClient, - body: { - session_id: options.id, - permission: options.permission ?? "write", - }, - path: { - id: sandboxId, - }, - }); - - const handledResponse = handleResponse( - response, - `Failed to create session ${options.id}` - ); - - const session: SandboxSession = { - sandboxId, - pitcherToken: handledResponse.pitcher_token, - pitcherUrl: handledResponse.pitcher_url, - userWorkspacePath: handledResponse.user_workspace_path, - }; - - return session; - } - - /** - * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from - * any sandbox/template created on codesandbox.io, even your own templates) or don't pass - * in anything and we'll use the default universal template. - * - * This function will also start & connect to the VM of the created sandbox with a global session, and return a {@link Sandbox} - * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. - * - * @param opts Additional options for creating the sandbox - * - * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM - */ - async create( - opts: CreateSandboxOpts = { source: "template" }, - sessionOpts?: SessionCreateOptions, - startOpts?: StartSandboxOpts - ): Promise { - switch (opts.source) { - case "git": { - throw new Error("Not implemented"); - } - case "json": { - throw new Error("Not implemented"); - } - case "template": { - const templateId = opts.template || this.defaultTemplate; - const privacy = opts.privacy || "public"; - const tags = opts.tags || ["sdk"]; - const path = opts.path || "/SDK"; - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - // We never want to start the Sandbox as part of this call. The reason is that we currently need to - // call an explicit START to start it in the correct cluster - const result = await sandboxFork({ - client: this.apiClient, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - }, - path: { - id: typeof templateId === "string" ? templateId : templateId.id, - }, - }); - - const sandbox = handleResponse( - result, - "Failed to create sandbox" - // We currently always pass "start_options" to create a session - ); - - // HACK: We need to start the sandbox on the correct cluster, which means we can not - // start the sandbox during `sandboxFork`. This creates a "global" session - let session = await this.start(sandbox.id, startOpts); - - // We can only create a custom session after the Sandbox is started - if (sessionOpts) { - session = await this.createSession(sandbox.id, sessionOpts); - } - - return session; - } - } - } - - /** - * Creates a sandbox by forking an existing sandbox reference. - * - * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} - * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. - * - * @param opts Additional options for creating the sandbox - * - * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM - */ - async fork( - sandboxId: string, - opts: Omit = { source: "template" }, - sessionOpts?: SessionCreateOptions, - startOpts?: StartSandboxOpts - ): Promise { - return this.create( - { ...opts, template: sandboxId }, - sessionOpts, - startOpts - ); - } - - /** - * Try to start a sandbox that already exists, it will return the data of the started - * VM, which you can pass to the browser. In the browser you can call `connectToSandbox` with this - * data to control the VM without sharing your CodeSandbox API token in the browser. - * - * @param id the ID of the sandbox - * @returns The start data, contains a single use token to connect to the VM - */ - public async resume( - id: string, - sessionOpts?: SessionCreateOptions - ): Promise { - const globalSession = this.start(id); - - if (sessionOpts) { - return this.createSession(id, sessionOpts); - } - - return globalSession; - } - - /** - * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. - * - * @param sandboxId The ID of the sandbox to shutdown - */ - async shutdown(sandboxId: string): Promise { - const response = await vmShutdown({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }); - - handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); - } - - /** - * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time - * you start the sandbox it will be resumed from the last state it was in. - * - * @param sandboxId The ID of the sandbox to hibernate - */ - async hibernate(sandboxId: string): Promise { - const response = await vmHibernate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }); - - handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); - } - - /** - * List sandboxes from the current workspace with optional filters. - * - * This method supports two modes of operation: - * 1. Simple limit-based fetching (default): - * ```ts - * // Get up to 50 sandboxes (default) - * const { sandboxes, totalCount } = await client.list(); - * - * // Get up to 200 sandboxes - * const { sandboxes, totalCount } = await client.list({ limit: 200 }); - * ``` - * - * 2. Manual pagination: - * ```ts - * // Get first page - * const { sandboxes, pagination } = await client.list({ - * pagination: { page: 1, pageSize: 50 } - * }); - * // pagination = { currentPage: 1, nextPage: 2, pageSize: 50 } - * - * // Get next page if available - * if (pagination.nextPage) { - * const { sandboxes, pagination: nextPagination } = await client.list({ - * pagination: { page: pagination.nextPage, pageSize: 50 } - * }); - * } - * ``` - */ - async list( - opts: SandboxListOpts & { - limit?: number; - pagination?: PaginationOpts; - } = {} - ): Promise { - const limit = opts.limit ?? 50; - let allSandboxes: SandboxInfo[] = []; - let currentPage = opts.pagination?.page ?? 1; - let pageSize = opts.pagination?.pageSize ?? limit; - let totalCount = 0; - let nextPage: number | null = null; - - while (true) { - const response = await sandboxList({ - client: this.apiClient, - query: { - tags: opts.tags?.join(","), - page: currentPage, - page_size: pageSize, - order_by: opts.orderBy, - direction: opts.direction, - status: opts.status, - }, - }); - - const info = handleResponse(response, "Failed to list sandboxes"); - totalCount = info.pagination.total_records; - nextPage = info.pagination.next_page; - - const sandboxes = info.sandboxes.map((sandbox) => ({ - id: sandbox.id, - createdAt: new Date(sandbox.created_at), - updatedAt: new Date(sandbox.updated_at), - title: sandbox.title ?? undefined, - description: sandbox.description ?? undefined, - privacy: privacyFromNumber(sandbox.privacy), - tags: sandbox.tags, - })); - - const newSandboxes = sandboxes.filter( - (sandbox) => - !allSandboxes.some((existing) => existing.id === sandbox.id) - ); - allSandboxes = [...allSandboxes, ...newSandboxes]; - - // Stop if we've hit the limit or there are no more pages - if (!nextPage || allSandboxes.length >= limit) { - break; - } - - currentPage = nextPage; - } - - return { - sandboxes: allSandboxes, - hasMore: totalCount > allSandboxes.length, - totalCount, - pagination: { - currentPage, - nextPage: allSandboxes.length >= limit ? nextPage : null, - pageSize, - }, - }; - } - - /** - * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the - * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory - * than it can scale down to, it can become very slow. - * - * @param id The ID of the sandbox to update - * @param tier The new VM tier - */ - async updateTier(id: string, tier: VMTier): Promise { - const response = await vmUpdateSpecs({ - client: this.apiClient, - path: { id }, - body: { - tier: tier.name, - }, - }); - - handleResponse(response, `Failed to update sandbox tier ${id}`); - } - - /** - * Updates the hibernation timeout of a sandbox. - * - * @param id The ID of the sandbox to update - * @param timeoutSeconds The new hibernation timeout in seconds - */ - async updateHibernationTimeout( - id: string, - timeoutSeconds: number - ): Promise { - const response = await vmUpdateHibernationTimeout({ - client: this.apiClient, - path: { id }, - body: { hibernation_timeout_seconds: timeoutSeconds }, - }); - - handleResponse( - response, - `Failed to update hibernation timeout for sandbox ${id}` - ); - } - - async connect(session: SandboxSession): Promise { - const pitcherClient = await initPitcherClient( - { - appId: "sdk", - instanceId: session.sandboxId, - onFocusChange() { - return () => {}; - }, - requestPitcherInstance: async () => { - const headers = this.apiClient.getConfig().headers as Headers; - - if (headers.get("x-pitcher-manager-url")) { - // This is a hack, we need to tell the global scheduler that the VM is running - // in a different cluster than the one it'd like to default to. - - const preferredManager = headers - .get("x-pitcher-manager-url") - ?.replace("/api/v1", "") - .replace("https://", ""); - const baseUrl = this.apiClient - .getConfig() - .baseUrl?.replace("api", "global-scheduler"); - - await fetch( - `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` - ).then((res) => res.json()); - } - - return { - bootupType: "RESUME", - pitcherURL: session.pitcherUrl, - workspacePath: session.userWorkspacePath, - userWorkspacePath: session.userWorkspacePath, - pitcherManagerVersion: "1.0.0-session", - pitcherVersion: "1.0.0-session", - latestPitcherVersion: "1.0.0-session", - pitcherToken: session.pitcherToken, - cluster: "session", - }; - }, - subscriptions: DEFAULT_SUBSCRIPTIONS, - }, - () => {} - ); - - return new Sandbox(this, pitcherClient); - } - - /** - * Namespace for managing preview tokens that can be used to access private sandbox previews. - */ - public readonly previewTokens = { - /** - * Generate a new preview token that can be used to access private sandbox previews. - * - * @param sandboxId - ID of the sandbox to create the token for - * @param expiresAt - Optional expiration date for the preview token - * @returns A preview token that can be used with Ports.getSignedPreviewUrl - */ - create: async (sandboxId: string, expiresAt: Date | null = null) => { - const response = handleResponse( - await previewTokenCreate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - body: { - expires_at: expiresAt?.toISOString(), - }, - }), - "Failed to create preview token" - ); - - if (!response.token?.token) { - throw new Error("No token returned from API"); - } - - return { - token: response.token.token, - expiresAt: response.token.expires_at - ? new Date(response.token.expires_at) - : null, - tokenId: response.token.token_id, - tokenPrefix: response.token.token_prefix, - lastUsedAt: response.token.last_used_at - ? new Date(response.token.last_used_at) - : null, - }; - }, - - /** - * List all active preview tokens for a sandbox. - * - * @param sandboxId - ID of the sandbox to list tokens for - * @returns A list of preview tokens - */ - list: async (sandboxId: string) => { - const response = handleResponse( - await previewTokenList({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - "Failed to list preview tokens" - ); - - if (!response.tokens) { - return []; - } - - return response.tokens.map((token) => ({ - expiresAt: token.expires_at ? new Date(token.expires_at) : null, - tokenId: token.token_id, - tokenPrefix: token.token_prefix, - lastUsedAt: token.last_used_at ? new Date(token.last_used_at) : null, - })); - }, - - /** - * Revoke a single preview token for a sandbox. - * - * @param sandboxId - ID of the sandbox the token belongs to - * @param tokenId - The ID of the token to revoke - */ - revoke: async (sandboxId: string, tokenId: string): Promise => { - handleResponse( - await previewTokenUpdate({ - client: this.apiClient, - path: { - id: sandboxId, - token_id: tokenId, - }, - body: { - expires_at: new Date().toISOString(), - }, - }), - "Failed to revoke preview token" - ); - }, - - /** - * Revoke all active preview tokens for a sandbox. - * This will immediately invalidate all tokens, and they can no longer be used - * to access the sandbox preview. - * - * @param sandboxId - ID of the sandbox to revoke tokens for - */ - revokeAll: async (sandboxId: string): Promise => { - handleResponse( - await previewTokenRevokeAll({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - "Failed to revoke preview tokens" - ); - }, - - /** - * Update a preview token's expiration date. - * - * @param sandboxId - ID of the sandbox the token belongs to - * @param tokenId - The ID of the token to update - * @param expiresAt - The new expiration date for the token (null for no expiration) - * @returns The updated preview token info - */ - update: async ( - sandboxId: string, - tokenId: string, - expiresAt: Date | null - ) => { - const response = handleResponse( - await previewTokenUpdate({ - client: this.apiClient, - path: { - id: sandboxId, - token_id: tokenId, - }, - body: { - expires_at: expiresAt?.toISOString(), - }, - }), - "Failed to update preview token" - ); - - if (!response.token) { - throw new Error("No token returned from API"); - } - - return { - expiresAt: response.token.expires_at - ? new Date(response.token.expires_at) - : null, - tokenId: response.token.token_id, - tokenPrefix: response.token.token_prefix, - lastUsedAt: response.token.last_used_at - ? new Date(response.token.last_used_at) - : null, - }; - }, - }; -} - -function privacyToNumber(privacy: SandboxPrivacy): number { - switch (privacy) { - case "public": - return 0; - case "unlisted": - return 1; - case "private": - return 2; - } -} - -function privacyFromNumber(privacy: number): SandboxPrivacy { - switch (privacy) { - case 0: - return "public"; - case 1: - return "unlisted"; - case 2: - return "private"; - } - - throw new Error(`Invalid privacy number: ${privacy}`); -} diff --git a/src/sandbox.ts b/src/sandbox.ts index ffbb65a..68039f5 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,286 +1,247 @@ import { Disposable, - type IPitcherClient, + initPitcherClient, type protocol as _protocol, } from "@codesandbox/pitcher-client"; - -import { FileSystem } from "./filesystem"; -import { Ports } from "./ports"; -import { Setup } from "./setup"; -import { Shells } from "./shells"; -import { Tasks } from "./tasks"; - -import type { SandboxClient, VMTier } from "."; -import { Sessions } from "./sessions"; -import { PreviewTokens } from "./preview-tokens"; - -export { - FSStatResult, - WriteFileOpts, - ReaddirEntry, - WatchOpts, - WatchEvent, - Watcher, -} from "./filesystem"; - -export { PortInfo } from "./ports"; - -export { SetupProgress, Step, SetupShellStatus } from "./setup"; - -export { - RunningCommand, - ShellSize, - ShellStatus, - ShellCreateOpts, - ShellRunOpts, - ShellOpenOpts, -} from "./shells"; - -export { Task, TaskDefinition } from "./tasks"; - -export interface SystemMetricsStatus { - cpu: { - cores: number; - used: number; - configured: number; - }; - memory: { - usedKiB: number; - totalKiB: number; - configuredKiB: number; - }; - storage: { - usedKB: number; - totalKB: number; - configuredKB: number; - }; -} - -export class UniversalSandbox extends Disposable { - /** - * Namespace for all filesystem operations on this sandbox. - */ - public readonly fs = this.addDisposable(new FileSystem(this.pitcherClient)); - - /** - * Namespace for running shell commands on this sandbox. - */ - public readonly shells = this.addDisposable(new Shells(this.pitcherClient)); - - /** - * Namespace for detecting open ports on this sandbox, and getting preview URLs for - * them. - */ - public readonly ports = this.addDisposable(new Ports(this.pitcherClient)); - - /** - * Namespace for all setup operations on this sandbox (installing dependencies, etc). - * - * This provider is *experimental*, it might get changes or completely be removed - * if it is not used. - */ - public readonly setup = this.addDisposable(new Setup(this.pitcherClient)); - +import type { + SandboxSession, + SessionCreateOptions, + StartSandboxOpts, + CreateSandboxBaseOpts, +} from "./types"; +import { PreviewTokens } from "./PreviewTokens"; +import { Client } from "@hey-api/client-fetch"; +import { + vmCreateSession, + vmHibernate, + vmShutdown, + vmStart, + VmStartResponse, + vmUpdateHibernationTimeout, + vmUpdateSpecs, +} from "./clients/client"; +import { handleResponse } from "./utils/api"; +import { SandboxClient } from "./SandboxClient"; +import { VMTier } from "./VMTier"; +import { WebSocketSession } from "./sessions/WebSocketSession"; +import { ClientOpts, RestSession } from "./sessions/RestSession"; + +export class Sandbox extends Disposable { + private apiClient: Client; /** - * Namespace for all task operations on a sandbox. This includes running tasks, - * getting tasks, and stopping tasks. - * - * In CodeSandbox, you can create tasks and manage them by creating a `.codesandbox/tasks.json` - * in the sandbox. These tasks become available under this namespace, this way you can manage - * tasks that you will need to run more often (like a dev server). - * - * More documentation: https://codesandbox.io/docs/learn/devboxes/task#adding-and-configuring-tasks + * Provider for generating preview tokens. These tokens can be used to generate signed + * preview URLs for private sandboxes. * - * This provider is *experimental*, it might get changes or completely be removed - * if it is not used. + * @example + * ```ts + * const sandbox = await sdk.sandbox.create(); + * const previewToken = await sandbox.previewTokens.createToken(); + * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); + * ``` */ - public readonly tasks = this.addDisposable(new Tasks(this.pitcherClient)); + public readonly previewTokens: PreviewTokens; - constructor(protected pitcherClient: IPitcherClient) { + constructor(public id: string, private sandboxClient: SandboxClient) { super(); + this.apiClient = sandboxClient["apiClient"]; + this.previewTokens = this.addDisposable( + new PreviewTokens(this.id, this.apiClient) + ); + } + + private async start(startOpts?: StartSandboxOpts) { + const startResult = await vmStart({ + client: this.apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: this.id, + }, + }); - // TODO: Bring this back once metrics polling does not reset inactivity - // const metricsDisposable = { - // dispose: - // this.pitcherClient.clients.system.startMetricsPollingAtInterval(5000), - // }; + const response = handleResponse( + startResult, + `Failed to start sandbox ${this.id}` + ); - // this.addDisposable(metricsDisposable); - this.addDisposable(this.pitcherClient); + return response; } - /** - * Check if the VM agent process is up to date. To update a restart is required - */ - get isUpToDate() { - return this.pitcherClient.isUpToDate(); + private async createSession( + options: SessionCreateOptions, + globalSession: SandboxSession + ): Promise { + const response = await vmCreateSession({ + client: this.apiClient, + body: { + session_id: options.id, + permission: options.permission ?? "write", + }, + path: { + id: this.id, + }, + }); + + const handledResponse = handleResponse( + response, + `Failed to create session ${options.id}` + ); + + const session: SandboxSession = { + bootupType: globalSession.bootupType, + cluster: globalSession.cluster, + sandboxId: this.id, + pitcherToken: handledResponse.pitcher_token, + pitcherUrl: handledResponse.pitcher_url, + userWorkspacePath: handledResponse.user_workspace_path, + }; + + return session; } /** - * The ID of the sandbox. + * Creates a sandbox by forking an existing sandbox reference. + * + * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} + * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. + * + * @param opts Additional options for creating the sandbox + * + * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM */ - get id(): string { - return this.pitcherClient.instanceId; + async fork(opts: CreateSandboxBaseOpts & StartSandboxOpts): Promise { + return this.sandboxClient.create({ + ...opts, + source: "template", + id: this.id, + }); } /** - * Get the URL to the editor for this sandbox. Keep in mind that this URL is not - * available if the sandbox is private, and the user opening this sandbox does not - * have access to the sandbox. + * Try to start a sandbox that already exists, it will return the data of the started + * VM, which you can pass to the browser. In the browser you can call `connectToSandbox` with this + * data to control the VM without sharing your CodeSandbox API token in the browser. + * + * @param id the ID of the sandbox + * @returns The start data, contains a single use token to connect to the VM */ - get editorUrl(): string { - return `https://codesandbox.io/p/devbox/${this.id}`; + public async resume(): Promise { + await this.start(); } - // TODO: Bring this back once metrics polling does not reset inactivity - // /** - // * Get the current system metrics. This return type may change in the future. - // */ - // public async getMetrics(): Promise { - // await this.pitcherClient.clients.system.update(); - - // const barrier = new Barrier<_protocol.system.SystemMetricsStatus>(); - // const initialMetrics = this.pitcherClient.clients.system.getMetrics(); - // if (!initialMetrics) { - // const disposable = this.pitcherClient.clients.system.onMetricsUpdated( - // (metrics) => { - // if (metrics) { - // barrier.open(metrics); - // } - // } - // ); - // disposable.dispose(); - // } else { - // barrier.open(initialMetrics); - // } - - // const barrierResult = await barrier.wait(); - // if (barrierResult.status === "disposed") { - // throw new Error("Metrics not available"); - // } - - // const metrics = barrierResult.value; - - // return { - // cpu: { - // cores: metrics.cpu.cores, - // used: metrics.cpu.used / 100, - // configured: metrics.cpu.configured, - // }, - // memory: { - // usedKiB: metrics.memory.used * 1024 * 1024, - // totalKiB: metrics.memory.total * 1024 * 1024, - // configuredKiB: metrics.memory.total * 1024 * 1024, - // }, - // storage: { - // usedKB: metrics.storage.used * 1000 * 1000, - // totalKB: metrics.storage.total * 1000 * 1000, - // configuredKB: metrics.storage.configured * 1000 * 1000, - // }, - // }; - // } - /** - * Disconnect from the sandbox, this does not hibernate the sandbox (but it will - * automatically hibernate after an inactivity timer). + * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. + * + * @param sandboxId The ID of the sandbox to shutdown */ - public disconnect() { - this.pitcherClient.disconnect(); + async shutdown(): Promise { this.dispose(); + const response = await vmShutdown({ + client: this.apiClient, + path: { + id: this.id, + }, + }); + + handleResponse(response, `Failed to shutdown sandbox ${this.id}`); } - private keepAliveInterval: NodeJS.Timeout | null = null; /** - * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. + * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time + * you start the sandbox it will be resumed from the last state it was in. + * + * @param sandboxId The ID of the sandbox to hibernate */ - public keepActiveWhileConnected(enabled: boolean) { - if (enabled && !this.keepAliveInterval) { - this.keepAliveInterval = setInterval(() => { - this.pitcherClient.clients.system.update(); - }, 1000 * 30); + async hibernate(): Promise { + const response = await vmHibernate({ + client: this.apiClient, + path: { + id: this.id, + }, + }); - this.onWillDispose(() => { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - }); - } else { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - } + handleResponse(response, `Failed to hibernate sandbox ${this.id}`); } -} -export class Sandbox extends UniversalSandbox { /** - * Provider for creating new sessions inside the sandbox. These sessions have their own - * filesystem, shells, tasks and permissions. You can read more about sessions in the - * [CodeSandbox docs](https://codesandbox.io/docs/sdk/sessions). - */ - public readonly sessions = this.addDisposable( - new Sessions(this.id, this.sandboxClient) - ); - - /** - * Provider for generating preview tokens. These tokens can be used to generate signed - * preview URLs for private sandboxes. + * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the + * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory + * than it can scale down to, it can become very slow. * - * @example - * ```ts - * const sandbox = await sdk.sandbox.create(); - * const previewToken = await sandbox.previewTokens.createToken(); - * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); - * ``` + * @param id The ID of the sandbox to update + * @param tier The new VM tier */ - public readonly previewTokens = this.addDisposable( - new PreviewTokens(this.id, this.sandboxClient) - ); + async updateTier(tier: VMTier): Promise { + const response = await vmUpdateSpecs({ + client: this.apiClient, + path: { id: this.id }, + body: { + tier: tier.name, + }, + }); - constructor( - private sandboxClient: SandboxClient, - pitcherClient: IPitcherClient - ) { - super(pitcherClient); + handleResponse(response, `Failed to update sandbox tier ${this.id}`); } /** - * This creates a copy of the current sandbox, both memory and disk is copied, which means - * that running processes will continue to run in the forked sandbox. + * Updates the hibernation timeout of a sandbox. + * + * @param id The ID of the sandbox to update + * @param timeoutSeconds The new hibernation timeout in seconds */ - public async fork(): Promise { - const session = await this.sandboxClient.create({ - template: this.id, + async updateHibernationTimeout(timeoutSeconds: number): Promise { + const response = await vmUpdateHibernationTimeout({ + client: this.apiClient, + path: { id: this.id }, + body: { hibernation_timeout_seconds: timeoutSeconds }, }); - return this.sandboxClient.connect(session); + handleResponse( + response, + `Failed to update hibernation timeout for sandbox ${this.id}` + ); } - /** - * Hibernate the sandbox. This will snapshot the disk and memory of the sandbox, so it - * can be restored later from the exact current state. Will resolve once the sandbox is hibernated. - */ - public async hibernate(): Promise { - this.dispose(); - this.pitcherClient.disconnect(); + async session(opts?: SessionCreateOptions): Promise { + const startData = await this.start(); + let session: SandboxSession = { + bootupType: startData.bootup_type as SandboxSession["bootupType"], + cluster: startData.cluster, + sandboxId: this.id, + pitcherToken: startData.pitcher_token, + pitcherUrl: startData.pitcher_url, + userWorkspacePath: startData.user_workspace_path, + }; + + if (opts) { + session = await this.createSession(opts, session); + } - await this.sandboxClient.hibernate(this.id); + return session; } - /** - * Shutdown the sandbox. This will stop all running processes and stop the sandbox. When you - * start the sandbox next time, you will still have the same files and state as when you - * shut down the sandbox. - * - * Will resolve once the sandbox is shutdown. - */ - public async shutdown(): Promise { - this.dispose(); - this.pitcherClient.disconnect(); + async connect(opts?: SessionCreateOptions): Promise { + const session = await this.session(opts); - await this.sandboxClient.shutdown(this.id); + return WebSocketSession.init(session, this.apiClient); + } + + async isUpToDate() { + const startData = await this.start(); + + return startData.latest_pitcher_version === startData.pitcher_version; + } + + async rest(opts?: SessionCreateOptions & ClientOpts) { + const session = await this.session(opts); + + return new RestSession(session, opts); } /** @@ -291,24 +252,6 @@ export class Sandbox extends UniversalSandbox { */ public async restart(): Promise { await this.shutdown(); - await this.sandboxClient.resume(this.id); - } - - /** - * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the - * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory - * than it can scale down to, it can become very slow. - */ - public async updateTier(tier: VMTier): Promise { - await this.sandboxClient.updateTier(this.id, tier); - } - - /** - * Updates the hibernation timeout of a sandbox. - * - * @param timeoutSeconds The new hibernation timeout in seconds - */ - public async updateHibernationTimeout(timeoutSeconds: number): Promise { - await this.sandboxClient.updateHibernationTimeout(this.id, timeoutSeconds); + await this.resume(); } } diff --git a/src/sessions.ts b/src/sessions.ts deleted file mode 100644 index 39e3982..0000000 --- a/src/sessions.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Disposable } from "./utils/disposable"; -import { SandboxClient } from "./sandbox-client"; - -export interface SessionCreateOptions { - id: string; - permission?: "read" | "write"; - gitAccessToken?: string; -} - -export type SandboxSessionData = { - id: string; - pitcher_token: string; - pitcher_url: string; - user_workspace_path: string; -}; - -export type SandboxSession = { - sandboxId: string; - pitcherToken: string; - pitcherUrl: string; - userWorkspacePath: string; -}; - -export class Sessions extends Disposable { - constructor( - private readonly id: string, - private readonly apiClient: SandboxClient - ) { - super(); - } - - /** - * Create a new session inside the VM. This is a new Linux user (inside the VM) with its - * own home directory and permissions. - * - * @param sessionId The id of the session, this will also be used for the username - * @param options Optional settings including permissions - * - * @returns if `autoConnect` is true, returns a `SandboxSession` object (which can be used to connect), otherwise returns - * a connected session. - */ - async create(opts: SessionCreateOptions): Promise { - return this.apiClient["createSession"](this.id, opts); - } - - /** - * Creates or reuses a session inside the VM with read-only permissions. Because read-only sessions - * cannot affect each-other, we use the same session id for all read-only sessions ("anonymous"). - * - * @returns The new session - */ - async createReadOnly(): Promise { - return this.create({ id: "anonymous", permission: "read" }); - } -} diff --git a/src/sandbox-rest-client.ts b/src/sessions/RestSession/index.ts similarity index 64% rename from src/sandbox-rest-client.ts rename to src/sessions/RestSession/index.ts index a80ea76..81f999e 100644 --- a/src/sandbox-rest-client.ts +++ b/src/sessions/RestSession/index.ts @@ -1,18 +1,19 @@ import { Client, createClient, createConfig } from "@hey-api/client-fetch"; import { decode, encode } from "@msgpack/msgpack"; -import { SandboxRestFS } from "./rest/sandbox-rest-fs"; -import { SandboxRestContainer } from "./rest/sandbox-rest-container"; -import { SandboxRestGit } from "./rest/sandbox-rest-git"; -import { SandboxRestShell } from "./rest/sandbox-rest-shell"; -import { SandboxRestSystem } from "./rest/sandbox-rest-system"; -import { SandboxRestTask } from "./rest/sandbox-rest-task"; +import { SandboxRestFS } from "./sandbox-rest-fs"; +import { SandboxRestContainer } from "./sandbox-rest-container"; +import { SandboxRestGit } from "./sandbox-rest-git"; +import { SandboxRestShell } from "./sandbox-rest-shell"; +import { SandboxRestSystem } from "./sandbox-rest-system"; +import { SandboxRestTask } from "./sandbox-rest-task"; +import { SandboxSession } from "../../types"; export interface ClientOpts { fetch?: typeof fetch; headers?: Record; } -export class SandboxRestClient { +export class RestSession { static id = 0; fs: SandboxRestFS; container: SandboxRestContainer; @@ -20,9 +21,9 @@ export class SandboxRestClient { shell: SandboxRestShell; system: SandboxRestSystem; task: SandboxRestTask; - constructor(opts: ClientOpts = {}) { + constructor(private session: SandboxSession, opts: ClientOpts = {}) { const client = this.createRestClient(opts); - this.fs = new SandboxRestFS(client); + this.fs = new SandboxRestFS(session, client); this.container = new SandboxRestContainer(client); this.git = new SandboxRestGit(client); this.shell = new SandboxRestShell(client); @@ -38,6 +39,7 @@ export class SandboxRestClient { ...(opts.headers ?? {}), "content-type": "application/x-msgpack", }, + throwOnError: true, fetch: opts.fetch ?? // @ts-ignore @@ -47,31 +49,31 @@ export class SandboxRestClient { }) ); + const session = this.session; + return { post(opts) { const method = opts.url.substring(1); const message = { - id: SandboxRestClient.id++, + id: RestSession.id++, method, params: opts.body, }; const encodedMessage = encode(message); - if (!opts.baseUrl) { - throw new Error("You have to pass baseURL to the rest client"); - } + // We have to create a baseUrl, because openapi fetcher always prefixes the url + // with "/" + const baseUrl = new URL(session.pitcherUrl); - // This is a hack to properly build the url. As "url" defaults to "/" in openapi client and breaks - const urlParts = opts.baseUrl.split("/"); - const url = urlParts.pop()!; - const baseUrl = urlParts.join("/"); + baseUrl.protocol = "https"; + baseUrl.pathname = ""; return client .post({ - baseUrl, - url, + baseUrl: baseUrl.origin, + url: `${session.sandboxId}?token=${session.pitcherToken}`, headers: { "content-length": encodedMessage.byteLength.toString(), }, diff --git a/src/sessions/RestSession/sandbox-rest-container.ts b/src/sessions/RestSession/sandbox-rest-container.ts new file mode 100644 index 0000000..065127e --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-container.ts @@ -0,0 +1,12 @@ +import { Client } from "@hey-api/client-fetch"; +import * as container from "../../clients/client-rest-container"; + +export class SandboxRestContainer { + constructor(private client: Client) {} + setup(body: container.ContainerSetupData["body"]) { + return container.containerSetup({ + body, + client: this.client, + }); + } +} diff --git a/src/sessions/RestSession/sandbox-rest-fs.ts b/src/sessions/RestSession/sandbox-rest-fs.ts new file mode 100644 index 0000000..5d82ca9 --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-fs.ts @@ -0,0 +1,111 @@ +import { Client } from "@hey-api/client-fetch"; +import * as fs from "../../clients/client-rest-fs"; +import { join } from "path"; + +import { SandboxSession } from "../../types"; + +export class SandboxRestFS { + constructor(private session: SandboxSession, private client: Client) {} + writeTextFile( + body: Omit & { content: string } + ) { + return fs.writeFile({ + client: this.client, + body: { + ...body, + // OpenAPI does not have UINT8Array, so we need to cast it to a Blob + content: new TextEncoder().encode(body.content) as unknown as Blob, + path: join(this.session.userWorkspacePath, body.path), + create: body.create === false ? false : true, + overwrite: body.overwrite === false ? false : true, + }, + }); + } + writeFile(body: fs.WriteFileData["body"]) { + return fs.writeFile({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + create: body.create === false ? false : true, + }, + }); + } + readFile(body: fs.FsReadFileData["body"]) { + return fs.fsReadFile({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + }, + }); + } + search(body: fs.FsSearchData["body"]) { + return fs.fsSearch({ client: this.client, body }); + } + pathSearch(body: fs.FsPathSearchData["body"]) { + return fs.fsPathSearch({ client: this.client, body }); + } + upload(body: fs.FsUploadData["body"]) { + return fs.fsUpload({ client: this.client, body }); + } + download(body: fs.FsDownloadData["body"]) { + return fs.fsDownload({ client: this.client, body }); + } + readDir(body: fs.FsReadDirData["body"]) { + return fs.fsReadDir({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + }, + }); + } + stat(body: fs.FsStatData["body"]) { + return fs.fsStat({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + }, + }); + } + copy(body: fs.FsCopyData["body"]) { + return fs.fsCopy({ + client: this.client, + body: { + ...body, + from: join(this.session.userWorkspacePath, body.from), + to: join(this.session.userWorkspacePath, body.to), + }, + }); + } + rename(body: fs.FsRenameData["body"]) { + return fs.fsRename({ + client: this.client, + body: { + ...body, + from: join(this.session.userWorkspacePath, body.from), + to: join(this.session.userWorkspacePath, body.to), + }, + }); + } + remove(body: fs.FsRemoveData["body"]) { + return fs.fsRemove({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + }, + }); + } + mkdir(body: fs.FsMkdirData["body"]) { + return fs.fsMkdir({ + client: this.client, + body: { + ...body, + path: join(this.session.userWorkspacePath, body.path), + }, + }); + } +} diff --git a/src/sessions/RestSession/sandbox-rest-git.ts b/src/sessions/RestSession/sandbox-rest-git.ts new file mode 100644 index 0000000..eec36ee --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-git.ts @@ -0,0 +1,53 @@ +import { Client } from "@hey-api/client-fetch"; +import * as git from "../../clients/client-rest-git"; + +export class SandboxRestGit { + constructor(private client: Client) {} + status(body: git.GitStatusData["body"]) { + return git.gitStatus({ client: this.client, body }); + } + + remotes(body: git.GitRemotesData["body"]) { + return git.gitRemotes({ client: this.client, body }); + } + + targetDiff(body: git.GitTargetDiffData["body"]) { + return git.gitTargetDiff({ client: this.client, body }); + } + + pull(body: git.GitPullData["body"]) { + return git.gitPull({ client: this.client, body }); + } + + discard(body: git.GitDiscardData["body"]) { + return git.gitDiscard({ client: this.client, body }); + } + + commit(body: git.GitCommitData["body"]) { + return git.gitCommit({ client: this.client, body }); + } + + push(body: git.GitPushData["body"]) { + return git.gitPush({ client: this.client, body }); + } + + pushToRemote(body: git.GitPushToRemoteData["body"]) { + return git.gitPushToRemote({ client: this.client, body }); + } + + renameBranch(body: git.GitRenameBranchData["body"]) { + return git.gitRenameBranch({ client: this.client, body }); + } + + remoteContent(body: git.GitRemoteContentData["body"]) { + return git.gitRemoteContent({ client: this.client, body }); + } + + diffStatus(body: git.GitDiffStatusData["body"]) { + return git.gitDiffStatus({ client: this.client, body }); + } + + resetLocalWithRemote(body: git.GitResetLocalWithRemoteData["body"]) { + return git.gitResetLocalWithRemote({ client: this.client, body }); + } +} diff --git a/src/sessions/RestSession/sandbox-rest-shell.ts b/src/sessions/RestSession/sandbox-rest-shell.ts new file mode 100644 index 0000000..c39bf34 --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-shell.ts @@ -0,0 +1,42 @@ +import { Client } from "@hey-api/client-fetch"; +import * as shell from "../../clients/client-rest-shell"; + +export class SandboxRestShell { + constructor(private client: Client) {} + + create(body: shell.ShellCreateData["body"]) { + return shell.shellCreate({ client: this.client, body }); + } + + in(body: shell.ShellInData["body"]) { + return shell.shellIn({ client: this.client, body }); + } + + list(body: shell.ShellListData["body"]) { + return shell.shellList({ client: this.client, body }); + } + + open(body: shell.ShellOpenData["body"]) { + return shell.shellOpen({ client: this.client, body }); + } + + close(body: shell.ShellCloseData["body"]) { + return shell.shellClose({ client: this.client, body }); + } + + restart(body: shell.ShellRestartData["body"]) { + return shell.shellRestart({ client: this.client, body }); + } + + terminate(body: shell.ShellTerminateData["body"]) { + return shell.shellTerminate({ client: this.client, body }); + } + + resize(body: shell.ShellResizeData["body"]) { + return shell.shellResize({ client: this.client, body }); + } + + rename(body: shell.ShellRenameData["body"]) { + return shell.shellRename({ client: this.client, body }); + } +} diff --git a/src/sessions/RestSession/sandbox-rest-system.ts b/src/sessions/RestSession/sandbox-rest-system.ts new file mode 100644 index 0000000..e0d6dd7 --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-system.ts @@ -0,0 +1,18 @@ +import { Client } from "@hey-api/client-fetch"; +import * as system from "../../clients/client-rest-system"; + +export class SandboxRestSystem { + constructor(private client: Client) {} + + update(body: system.SystemUpdateData["body"]) { + return system.systemUpdate({ client: this.client, body }); + } + + hibernate(body: system.SystemHibernateData["body"]) { + return system.systemHibernate({ client: this.client, body }); + } + + metrics(body: system.SystemMetricsData["body"]) { + return system.systemMetrics({ client: this.client, body }); + } +} diff --git a/src/sessions/RestSession/sandbox-rest-task.ts b/src/sessions/RestSession/sandbox-rest-task.ts new file mode 100644 index 0000000..f669e27 --- /dev/null +++ b/src/sessions/RestSession/sandbox-rest-task.ts @@ -0,0 +1,42 @@ +import { Client } from "@hey-api/client-fetch"; +import * as task from "../../clients/client-rest-task"; + +export class SandboxRestTask { + constructor(private client: Client) {} + + list(body: task.TaskListData["body"] = {}) { + return task.taskList({ client: this.client, body }); + } + + run(body: task.TaskRunData["body"]) { + return task.taskRun({ client: this.client, body }); + } + + runCommand(body: task.TaskRunCommandData["body"]) { + return task.taskRunCommand({ client: this.client, body }); + } + + stop(body: task.TaskStopData["body"]) { + return task.taskStop({ client: this.client, body }); + } + + create(body: task.TaskCreateData["body"]) { + return task.taskCreate({ client: this.client, body }); + } + + update(body: task.TaskUpdateData["body"]) { + return task.taskUpdate({ client: this.client, body }); + } + + saveToConfig(body: task.TaskSaveToConfigData["body"]) { + return task.taskSaveToConfig({ client: this.client, body }); + } + + generateConfig(body: task.TaskGenerateConfigData["body"] = {}) { + return task.taskGenerateConfig({ client: this.client, body }); + } + + createSetupTasks(body: task.TaskCreateSetupTasksData["body"]) { + return task.taskCreateSetupTasks({ client: this.client, body }); + } +} diff --git a/src/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts similarity index 98% rename from src/filesystem.ts rename to src/sessions/WebSocketSession/filesystem.ts index aa85172..69620c3 100644 --- a/src/filesystem.ts +++ b/src/sessions/WebSocketSession/filesystem.ts @@ -1,7 +1,7 @@ import { type IPitcherClient } from "@codesandbox/pitcher-client"; -import { Disposable } from "./utils/disposable"; -import { Emitter, type Event } from "./utils/event"; +import { Disposable } from "../../utils/disposable"; +import { Emitter, type Event } from "../../utils/event"; export type FSStatResult = { type: "file" | "directory"; diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts new file mode 100644 index 0000000..5a21a88 --- /dev/null +++ b/src/sessions/WebSocketSession/index.ts @@ -0,0 +1,225 @@ +import { initPitcherClient } from "@codesandbox/pitcher-client"; +import { + Disposable, + protocol as _protocol, + type IPitcherClient, +} from "@codesandbox/pitcher-client"; + +import { FileSystem } from "./filesystem"; +import { Ports } from "./ports"; +import { Setup } from "./setup"; +import { Shells } from "./shells"; +import { Tasks } from "./tasks"; +import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../../types"; +import { Client } from "@hey-api/client-fetch"; + +export * from "./filesystem"; +export * from "./ports"; +export * from "./setup"; +export * from "./shells"; +export * from "./tasks"; + +export class WebSocketSession extends Disposable { + static async init(session: SandboxSession, apiClient: Client) { + const pitcherClient = await initPitcherClient( + { + appId: "sdk", + instanceId: session.sandboxId, + onFocusChange() { + return () => {}; + }, + requestPitcherInstance: async () => { + const headers = apiClient.getConfig().headers as Headers; + + if (headers.get("x-pitcher-manager-url")) { + // This is a hack, we need to tell the global scheduler that the VM is running + // in a different cluster than the one it'd like to default to. + + const preferredManager = headers + .get("x-pitcher-manager-url") + ?.replace("/api/v1", "") + .replace("https://", ""); + const baseUrl = apiClient + .getConfig() + .baseUrl?.replace("api", "global-scheduler"); + + await fetch( + `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` + ).then((res) => res.json()); + } + + return { + bootupType: "RESUME", + pitcherURL: session.pitcherUrl, + workspacePath: session.userWorkspacePath, + userWorkspacePath: session.userWorkspacePath, + pitcherManagerVersion: "1.0.0-session", + pitcherVersion: "1.0.0-session", + latestPitcherVersion: "1.0.0-session", + pitcherToken: session.pitcherToken, + cluster: "session", + }; + }, + subscriptions: DEFAULT_SUBSCRIPTIONS, + }, + () => {} + ); + + return new WebSocketSession(pitcherClient); + } + /** + * Namespace for all filesystem operations on this sandbox. + */ + public readonly fs = this.addDisposable(new FileSystem(this.pitcherClient)); + + /** + * Namespace for running shell commands on this sandbox. + */ + public readonly shells = this.addDisposable(new Shells(this.pitcherClient)); + + /** + * Namespace for detecting open ports on this sandbox, and getting preview URLs for + * them. + */ + public readonly ports = this.addDisposable(new Ports(this.pitcherClient)); + + /** + * Namespace for all setup operations on this sandbox (installing dependencies, etc). + * + * This provider is *experimental*, it might get changes or completely be removed + * if it is not used. + */ + public readonly setup = this.addDisposable(new Setup(this.pitcherClient)); + + /** + * Namespace for all task operations on a sandbox. This includes running tasks, + * getting tasks, and stopping tasks. + * + * In CodeSandbox, you can create tasks and manage them by creating a `.codesandbox/tasks.json` + * in the sandbox. These tasks become available under this namespace, this way you can manage + * tasks that you will need to run more often (like a dev server). + * + * More documentation: https://codesandbox.io/docs/learn/devboxes/task#adding-and-configuring-tasks + * + * This provider is *experimental*, it might get changes or completely be removed + * if it is not used. + */ + public readonly tasks = this.addDisposable(new Tasks(this.pitcherClient)); + + constructor(protected pitcherClient: IPitcherClient) { + super(); + + // TODO: Bring this back once metrics polling does not reset inactivity + // const metricsDisposable = { + // dispose: + // this.pitcherClient.clients.system.startMetricsPollingAtInterval(5000), + // }; + + // this.addDisposable(metricsDisposable); + this.addDisposable(this.pitcherClient); + } + + /** + * Check if the VM agent process is up to date. To update a restart is required + */ + get isUpToDate() { + return this.pitcherClient.isUpToDate(); + } + + /** + * The ID of the sandbox. + */ + get id(): string { + return this.pitcherClient.instanceId; + } + + /** + * Get the URL to the editor for this sandbox. Keep in mind that this URL is not + * available if the sandbox is private, and the user opening this sandbox does not + * have access to the sandbox. + */ + get editorUrl(): string { + return `https://codesandbox.io/p/devbox/${this.id}`; + } + + // TODO: Bring this back once metrics polling does not reset inactivity + // /** + // * Get the current system metrics. This return type may change in the future. + // */ + // public async getMetrics(): Promise { + // await this.pitcherClient.clients.system.update(); + + // const barrier = new Barrier<_protocol.system.SystemMetricsStatus>(); + // const initialMetrics = this.pitcherClient.clients.system.getMetrics(); + // if (!initialMetrics) { + // const disposable = this.pitcherClient.clients.system.onMetricsUpdated( + // (metrics) => { + // if (metrics) { + // barrier.open(metrics); + // } + // } + // ); + // disposable.dispose(); + // } else { + // barrier.open(initialMetrics); + // } + + // const barrierResult = await barrier.wait(); + // if (barrierResult.status === "disposed") { + // throw new Error("Metrics not available"); + // } + + // const metrics = barrierResult.value; + + // return { + // cpu: { + // cores: metrics.cpu.cores, + // used: metrics.cpu.used / 100, + // configured: metrics.cpu.configured, + // }, + // memory: { + // usedKiB: metrics.memory.used * 1024 * 1024, + // totalKiB: metrics.memory.total * 1024 * 1024, + // configuredKiB: metrics.memory.total * 1024 * 1024, + // }, + // storage: { + // usedKB: metrics.storage.used * 1000 * 1000, + // totalKB: metrics.storage.total * 1000 * 1000, + // configuredKB: metrics.storage.configured * 1000 * 1000, + // }, + // }; + // } + + /** + * Disconnect from the sandbox, this does not hibernate the sandbox (but it will + * automatically hibernate after an inactivity timer). + */ + public disconnect() { + this.pitcherClient.disconnect(); + this.dispose(); + } + + private keepAliveInterval: NodeJS.Timeout | null = null; + /** + * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. + */ + public keepActiveWhileConnected(enabled: boolean) { + if (enabled && !this.keepAliveInterval) { + this.keepAliveInterval = setInterval(() => { + this.pitcherClient.clients.system.update(); + }, 1000 * 30); + + this.onWillDispose(() => { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + }); + } else { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + } + } +} diff --git a/src/ports.ts b/src/sessions/WebSocketSession/ports.ts similarity index 98% rename from src/ports.ts rename to src/sessions/WebSocketSession/ports.ts index 77643e3..6237317 100644 --- a/src/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -1,7 +1,7 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; -import { Disposable } from "./utils/disposable"; -import { Emitter } from "./utils/event"; +import { Disposable } from "../../utils/disposable"; +import { Emitter } from "../../utils/event"; export class PortInfo { constructor(public port: number, public hostname: string) {} diff --git a/src/setup.ts b/src/sessions/WebSocketSession/setup.ts similarity index 94% rename from src/setup.ts rename to src/sessions/WebSocketSession/setup.ts index cbbfcfb..8eace3f 100644 --- a/src/setup.ts +++ b/src/sessions/WebSocketSession/setup.ts @@ -1,8 +1,8 @@ import type { Id, IPitcherClient } from "@codesandbox/pitcher-client"; import { listenOnce } from "@codesandbox/pitcher-common/dist/event"; -import { Disposable } from "./utils/disposable"; -import { Emitter } from "./utils/event"; +import { Disposable } from "../../utils/disposable"; +import { Emitter } from "../../utils/event"; export class Setup extends Disposable { private readonly onSetupProgressUpdateEmitter = this.addDisposable( diff --git a/src/shells.ts b/src/sessions/WebSocketSession/shells.ts similarity index 98% rename from src/shells.ts rename to src/sessions/WebSocketSession/shells.ts index 6c9ee04..b66ebf1 100644 --- a/src/shells.ts +++ b/src/sessions/WebSocketSession/shells.ts @@ -3,8 +3,8 @@ import type { Id } from "@codesandbox/pitcher-common"; import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; import type { OpenShellDTO } from "@codesandbox/pitcher-protocol/dist/src/messages/shell"; -import { Disposable } from "./utils/disposable"; -import { Emitter, type Event } from "./utils/event"; +import { Disposable } from "../../utils/disposable"; +import { Emitter, type Event } from "../../utils/event"; export interface RunningCommand extends Promise<{ output: string; exitCode?: number }> { @@ -188,7 +188,7 @@ class LanguageInterpreter { } } -class ShellInstance extends Disposable { +export class ShellInstance extends Disposable { // TODO: differentiate between stdout and stderr, also send back bytes instead of // strings private onShellOutputEmitter = this.addDisposable(new Emitter()); diff --git a/src/tasks.ts b/src/sessions/WebSocketSession/tasks.ts similarity index 96% rename from src/tasks.ts rename to src/sessions/WebSocketSession/tasks.ts index bbaa36a..1ac6016 100644 --- a/src/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -1,7 +1,7 @@ import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { PortInfo } from "./ports"; -import { Disposable } from "./utils/disposable"; +import { Disposable } from "../../utils/disposable"; export type TaskDefinition = { name: string; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ea8653e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,223 @@ +import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +import { VMTier } from "./VMTier"; + +export interface SystemMetricsStatus { + cpu: { + cores: number; + used: number; + configured: number; + }; + memory: { + usedKiB: number; + totalKiB: number; + configuredKiB: number; + }; + storage: { + usedKB: number; + totalKB: number; + configuredKB: number; + }; +} + +export type SandboxPrivacy = "public" | "unlisted" | "private"; + +export type SandboxInfo = { + id: string; + createdAt: Date; + updatedAt: Date; + title?: string; + description?: string; + privacy: SandboxPrivacy; + tags: string[]; +}; + +export type SandboxListOpts = { + tags?: string[]; + orderBy?: "inserted_at" | "updated_at"; + direction?: "asc" | "desc"; + status?: "running"; +}; + +export interface SandboxListResponse { + sandboxes: SandboxInfo[]; + hasMore: boolean; + totalCount: number; + pagination: { + currentPage: number; + nextPage: number | null; + pageSize: number; + }; +} + +export type PaginationOpts = { + page?: number; + pageSize?: number; +}; + +export interface ClientOpts { + baseUrl?: string; + /** + * Custom fetch implementation + * + * @default fetch + */ + fetch?: typeof fetch; + + /** + * Additional headers to send with each request + */ + headers?: Record; +} + +export const DEFAULT_SUBSCRIPTIONS = { + client: { + status: true, + }, + file: { + status: true, + selection: true, + ot: true, + }, + fs: { + operations: true, + }, + git: { + status: true, + operations: true, + }, + port: { + status: true, + }, + setup: { + progress: true, + }, + shell: { + status: true, + }, + system: { + metrics: true, + }, +}; + +export interface StartSandboxOpts { + /** + * Country, served as a hint on where you want the sandbox to be scheduled. For example, if "NL" is given + * as a country, the sandbox will be scheduled in a cluster inside Europe. Note that this is not a guarantee, + * and the sandbox might end up in a different region based on availability and scheduling decisions. + * + * Follows ISO 3166-1 alpha-2 codes. + */ + ipcountry?: string; + + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * Check {@link VMTier} for available tiers. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. + * Specifying a VM tier for someone else's sandbox will return an error. + */ + vmTier?: VMTier; + + /** + * The amount of seconds to wait before hibernating the sandbox after inactivity. + * + * Defaults to 300 seconds for free users, 1800 seconds for pro users. Maximum is 86400 seconds (1 day). + */ + hibernationTimeoutSeconds?: number; + + /** + * Configuration for when the VM should automatically wake up from hibernation. + */ + automaticWakeupConfig?: { + /** + * Whether the VM should automatically wake up on HTTP requests to preview URLs (excludes WebSocket requests) + * + * @default true + */ + http: boolean; + + /** + * Whether the VM should automatically wake up on WebSocket connections to preview URLs + * + * @default false + */ + websocket: boolean; + }; +} + +export type CreateSandboxBaseOpts = { + /** + * What the privacy of the new sandbox should be. Defaults to "public". + */ + privacy?: SandboxPrivacy; + + /** + * The title of the new sandbox. + */ + title?: string; + + /** + * The description of the new sandbox. + */ + description?: string; + + /** + * Which tags to add to the sandbox, can be used for categorization and filtering. Max 10 tags. + */ + tags?: string[]; + + /** + * In which folder to put the sandbox in (inside your workspace). + */ + path?: string; +}; + +export type CreateSandboxTemplateSourceOpts = { + source: "template"; + /** + * What template to fork from, this is the id of another sandbox. Defaults to our + * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). + */ + id?: string; +}; + +export type SandboxSessionData = { + id: string; + pitcher_token: string; + pitcher_url: string; + user_workspace_path: string; +}; + +export interface SessionCreateOptions { + id: string; + permission?: "read" | "write"; + gitAccessToken?: string; +} + +export type SandboxSession = { + bootupType: PitcherManagerResponse["bootupType"]; + cluster: string; + sandboxId: string; + pitcherToken: string; + pitcherUrl: string; + userWorkspacePath: string; +}; + +export type CreateSandboxGitSourceOpts = { + source: "git"; + url: string; + branch?: string; + accessToken: string; +}; + +export type CreateSandboxJSONSourceOpts = { + source: "json"; + files: Record; +}; + +export type CreateSandboxOpts = CreateSandboxBaseOpts & + ( + | CreateSandboxTemplateSourceOpts + | CreateSandboxGitSourceOpts + | CreateSandboxJSONSourceOpts + ); diff --git a/src/utils/handle-response.ts b/src/utils/api.ts similarity index 77% rename from src/utils/handle-response.ts rename to src/utils/api.ts index 4a04189..cc3fa1b 100644 --- a/src/utils/handle-response.ts +++ b/src/utils/api.ts @@ -1,5 +1,21 @@ import { RateLimitError } from "./rate-limit"; +export type HandledResponse = { + data?: { + data?: D; + }; + error?: E; + response: Response; +}; + +export function getBaseUrl(token: string) { + if (token.startsWith("csb_")) { + return "https://api.codesandbox.io"; + } + + return "https://api.together.ai/csb/sdk"; +} + export function handleResponse( result: Awaited<{ data?: { data?: D }; error?: E; response: Response }>, errorPrefix: string diff --git a/src/utils/session.ts b/src/utils/session.ts deleted file mode 100644 index af38381..0000000 --- a/src/utils/session.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SandboxSessionData } from "../sessions"; - -export function getSessionUrl(session: SandboxSessionData) { - const url = new URL(session.pitcher_url); - - url.protocol = "https"; - url.pathname = `/${session.id}`; - url.search = `token=${session.pitcher_token}`; - - return url.toString(); -} From c2067651153d6097bdbe90199c4f001221c30b78 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 29 Apr 2025 13:13:24 +0200 Subject: [PATCH 074/241] more cleanup --- TODO.md | 47 +++--- package.json | 18 +-- src/PreviewTokens.ts | 2 +- src/SandboxClient.ts | 51 ++++++- src/VMTier.ts | 2 +- .../client-rest-container/client.gen.ts | 0 .../client-rest-container/index.ts | 0 .../client-rest-container/sdk.gen.ts | 0 .../client-rest-container/types.gen.ts | 0 .../client-rest-fs/client.gen.ts | 0 .../client-rest-fs/index.ts | 0 .../client-rest-fs/sdk.gen.ts | 0 .../client-rest-fs/types.gen.ts | 0 .../client-rest-git/client.gen.ts | 0 .../client-rest-git/index.ts | 0 .../client-rest-git/sdk.gen.ts | 0 .../client-rest-git/types.gen.ts | 0 .../client-rest-setup/client.gen.ts | 0 .../client-rest-setup/index.ts | 0 .../client-rest-setup/sdk.gen.ts | 0 .../client-rest-setup/types.gen.ts | 0 .../client-rest-shell/client.gen.ts | 0 .../client-rest-shell/index.ts | 0 .../client-rest-shell/sdk.gen.ts | 0 .../client-rest-shell/types.gen.ts | 0 .../client-rest-system/client.gen.ts | 0 .../client-rest-system/index.ts | 0 .../client-rest-system/sdk.gen.ts | 0 .../client-rest-system/types.gen.ts | 0 .../client-rest-task/client.gen.ts | 0 .../client-rest-task/index.ts | 0 .../client-rest-task/sdk.gen.ts | 0 .../client-rest-task/types.gen.ts | 0 .../client/client.gen.ts | 0 src/{clients => api-clients}/client/index.ts | 0 .../client/sdk.gen.ts | 0 .../client/types.gen.ts | 0 src/bin/commands/build.ts | 4 +- src/browser.ts | 6 +- .../RestClient}/index.ts | 4 +- .../RestClient}/sandbox-rest-container.ts | 2 +- .../RestClient}/sandbox-rest-fs.ts | 2 +- .../RestClient}/sandbox-rest-git.ts | 2 +- .../RestClient}/sandbox-rest-shell.ts | 2 +- .../RestClient}/sandbox-rest-system.ts | 2 +- .../RestClient}/sandbox-rest-task.ts | 2 +- .../WebSocketClient}/filesystem.ts | 0 .../WebSocketClient}/index.ts | 4 +- .../WebSocketClient}/ports.ts | 0 .../WebSocketClient}/setup.ts | 0 .../WebSocketClient}/shells.ts | 0 .../WebSocketClient}/tasks.ts | 0 src/index.ts | 4 +- src/sandbox.ts | 134 +++++++----------- src/types.ts | 16 ++- tsconfig.json | 2 +- 56 files changed, 154 insertions(+), 152 deletions(-) rename src/{clients => api-clients}/client-rest-container/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-container/index.ts (100%) rename src/{clients => api-clients}/client-rest-container/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-container/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-fs/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-fs/index.ts (100%) rename src/{clients => api-clients}/client-rest-fs/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-fs/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-git/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-git/index.ts (100%) rename src/{clients => api-clients}/client-rest-git/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-git/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-setup/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-setup/index.ts (100%) rename src/{clients => api-clients}/client-rest-setup/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-setup/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-shell/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-shell/index.ts (100%) rename src/{clients => api-clients}/client-rest-shell/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-shell/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-system/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-system/index.ts (100%) rename src/{clients => api-clients}/client-rest-system/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-system/types.gen.ts (100%) rename src/{clients => api-clients}/client-rest-task/client.gen.ts (100%) rename src/{clients => api-clients}/client-rest-task/index.ts (100%) rename src/{clients => api-clients}/client-rest-task/sdk.gen.ts (100%) rename src/{clients => api-clients}/client-rest-task/types.gen.ts (100%) rename src/{clients => api-clients}/client/client.gen.ts (100%) rename src/{clients => api-clients}/client/index.ts (100%) rename src/{clients => api-clients}/client/sdk.gen.ts (100%) rename src/{clients => api-clients}/client/types.gen.ts (100%) rename src/{sessions/RestSession => clients/RestClient}/index.ts (97%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-container.ts (79%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-fs.ts (98%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-git.ts (96%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-shell.ts (94%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-system.ts (88%) rename src/{sessions/RestSession => clients/RestClient}/sandbox-rest-task.ts (95%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/filesystem.ts (100%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/index.ts (98%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/ports.ts (100%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/setup.ts (100%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/shells.ts (100%) rename src/{sessions/WebSocketSession => clients/WebSocketClient}/tasks.ts (100%) diff --git a/TODO.md b/TODO.md index 449c5f7..a31d417 100644 --- a/TODO.md +++ b/TODO.md @@ -1,48 +1,39 @@ ## QUESTIONS -- Please explain again why we always start the VM from "forkSandbox", but we also handle non start_response... how do we handle actually correct cluster? Is it not by always using the `start` method? -- You say sessions allows users to edit files without affecting each other, but above you state that all files are shared? -- Should we really call it GIT, it only supports GitHub? -- Can we pass custom session to `vmStart`? -- Using start options is not reliable - ## TODO +- Improve data passed to browser with a BrowserSession, also ensure reconnect token works + # 1 New API ```ts const sdk = new CodeSandbox(apiToken); -const sandbox = sdk.sandbox.create(SandboxOptions & StartOptions); -const sandbox = sdk.sandbox.fork( - id, - Omit & StartOptions -); +const sandbox = await sdk.sandbox.resume(id); +const sandbox = await sdk.sandbox.create(SandboxOptions & StartOptions); +sandbox.isUpToDate; +sandbox.bootupType; +sandbox.cluster; +sandbox.globalSession; +sandbox.fork(Omit & StartOptions); sandbox.restart(StartOptions); sandbox.hibernate(); sandbox.shutdown(); -sandbox.isUpToDate(); sandbox.resume(); sandbox.updateTier(); -sandbox.session({ - username: "anonymous", - permission: "read", - gitAccessToken: "", -}); -sandbox.connect({ - username: "anonymous", - permission: "read", -}); -sandbox.rest({ - username: "anonymous", - permission: "read", -}); -sandbox.ssh({ - username: "anonymous", - permission: "read", +const client = await sandbox.connect(); +const client = await sandbox.createRestClient(); +const browserSession = await sandbox.globalSession; + +const session = await sandbox.createSession({ + id: "some-user-name", + permission: "write", }); +const client = sandbox.connect(session); +const client = sandbox.createRestClient(session); +const browserSession = session; ``` # 2 Git clone support diff --git a/package.json b/package.json index 60216c5..f72f78c 100644 --- a/package.json +++ b/package.json @@ -57,16 +57,16 @@ "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", - "build-openapi": "rimraf src/clients && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", - "build-openapi:staging": "rimraf src/clients && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", + "build-openapi": "rimraf src/api-clients && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/api-clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", + "build-openapi:staging": "rimraf src/api-clients && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/api-clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", "build-openapi-rest": "npm run build-openapi-rest-fs && npm run build-openapi-rest-task && npm run build-openapi-rest-container && npm run build-openapi-rest-git && npm run build-openapi-rest-setup && npm run build-openapi-rest-shell && npm run build-openapi-rest-system", - "build-openapi-rest-container": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-container.json -o src/clients/client-rest-container -c @hey-api/client-fetch", - "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/clients/client-rest-fs -c @hey-api/client-fetch", - "build-openapi-rest-git": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-git.json -o src/clients/client-rest-git -c @hey-api/client-fetch", - "build-openapi-rest-setup": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-setup.json -o src/clients/client-rest-setup -c @hey-api/client-fetch", - "build-openapi-rest-shell": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-shell.json -o src/clients/client-rest-shell -c @hey-api/client-fetch", - "build-openapi-rest-system": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-system.json -o src/clients/client-rest-system -c @hey-api/client-fetch", - "build-openapi-rest-task": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-task.json -o src/clients/client-rest-task -c @hey-api/client-fetch", + "build-openapi-rest-container": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-container.json -o src/api-clients/client-rest-container -c @hey-api/client-fetch", + "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/api-clients/client-rest-fs -c @hey-api/client-fetch", + "build-openapi-rest-git": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-git.json -o src/api-clients/client-rest-git -c @hey-api/client-fetch", + "build-openapi-rest-setup": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-setup.json -o src/api-clients/client-rest-setup -c @hey-api/client-fetch", + "build-openapi-rest-shell": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-shell.json -o src/api-clients/client-rest-shell -c @hey-api/client-fetch", + "build-openapi-rest-system": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-system.json -o src/api-clients/client-rest-system -c @hey-api/client-fetch", + "build-openapi-rest-task": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-task.json -o src/api-clients/client-rest-task -c @hey-api/client-fetch", "clean": "rimraf ./dist", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts index dc22d22..b1ab18e 100644 --- a/src/PreviewTokens.ts +++ b/src/PreviewTokens.ts @@ -7,7 +7,7 @@ import { previewTokenList, previewTokenRevokeAll, previewTokenUpdate, -} from "./clients/client"; +} from "./api-clients/client"; interface BasePreviewTokenInfo { expiresAt: Date | null; diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index 0249269..e6e094e 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -1,7 +1,7 @@ import type { Client } from "@hey-api/client-fetch"; import { createClient, createConfig } from "@hey-api/client-fetch"; -import { sandboxFork, sandboxList } from "./clients/client"; +import { sandboxFork, sandboxList, vmStart } from "./api-clients/client"; import { Sandbox } from "./Sandbox"; import { getBaseUrl, handleResponse } from "./utils/api"; import { ClientOpts } from "."; @@ -11,6 +11,7 @@ import { SandboxInfo, SandboxListOpts, SandboxListResponse, + SandboxOpts, SandboxPrivacy, StartSandboxOpts, } from "./types"; @@ -42,11 +43,50 @@ export class SandboxClient { ); } + private async start( + sandboxId: string, + startOpts?: StartSandboxOpts + ): Promise { + const startResult = await vmStart({ + client: this.apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }); + + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); + + return { + id: sandboxId, + bootupType: response.bootup_type as SandboxOpts["bootupType"], + cluster: response.cluster, + isUpToDate: response.latest_pitcher_version === response.pitcher_version, + globalSession: { + sandboxId, + pitcherToken: response.pitcher_token, + pitcherUrl: response.pitcher_url, + userWorkspacePath: response.user_workspace_path, + }, + }; + } + /** * */ - ref(sandboxId: string) { - return new Sandbox(sandboxId, this); + async resume(sandboxId: string) { + const sandboxOpts = await this.start(sandboxId); + return new Sandbox(sandboxOpts, this); } /** @@ -68,7 +108,7 @@ export class SandboxClient { case "git": { throw new Error("Not implemented"); } - case "json": { + case "files": { throw new Error("Not implemented"); } case "template": { @@ -101,8 +141,9 @@ export class SandboxClient { "Failed to create sandbox" // We currently always pass "start_options" to create a session ); + const sandboxOpts = await this.start(sandbox.id, opts); - return new Sandbox(sandbox.id, this); + return new Sandbox(sandboxOpts, this); } } } diff --git a/src/VMTier.ts b/src/VMTier.ts index 85bf926..6eeaa76 100644 --- a/src/VMTier.ts +++ b/src/VMTier.ts @@ -1,4 +1,4 @@ -import { VmUpdateSpecsRequest } from "./clients/client"; +import { VmUpdateSpecsRequest } from "./api-clients/client"; /** * A VM tier is how we classify the specs of a VM. You can use this to request a VM with specific diff --git a/src/clients/client-rest-container/client.gen.ts b/src/api-clients/client-rest-container/client.gen.ts similarity index 100% rename from src/clients/client-rest-container/client.gen.ts rename to src/api-clients/client-rest-container/client.gen.ts diff --git a/src/clients/client-rest-container/index.ts b/src/api-clients/client-rest-container/index.ts similarity index 100% rename from src/clients/client-rest-container/index.ts rename to src/api-clients/client-rest-container/index.ts diff --git a/src/clients/client-rest-container/sdk.gen.ts b/src/api-clients/client-rest-container/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-container/sdk.gen.ts rename to src/api-clients/client-rest-container/sdk.gen.ts diff --git a/src/clients/client-rest-container/types.gen.ts b/src/api-clients/client-rest-container/types.gen.ts similarity index 100% rename from src/clients/client-rest-container/types.gen.ts rename to src/api-clients/client-rest-container/types.gen.ts diff --git a/src/clients/client-rest-fs/client.gen.ts b/src/api-clients/client-rest-fs/client.gen.ts similarity index 100% rename from src/clients/client-rest-fs/client.gen.ts rename to src/api-clients/client-rest-fs/client.gen.ts diff --git a/src/clients/client-rest-fs/index.ts b/src/api-clients/client-rest-fs/index.ts similarity index 100% rename from src/clients/client-rest-fs/index.ts rename to src/api-clients/client-rest-fs/index.ts diff --git a/src/clients/client-rest-fs/sdk.gen.ts b/src/api-clients/client-rest-fs/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-fs/sdk.gen.ts rename to src/api-clients/client-rest-fs/sdk.gen.ts diff --git a/src/clients/client-rest-fs/types.gen.ts b/src/api-clients/client-rest-fs/types.gen.ts similarity index 100% rename from src/clients/client-rest-fs/types.gen.ts rename to src/api-clients/client-rest-fs/types.gen.ts diff --git a/src/clients/client-rest-git/client.gen.ts b/src/api-clients/client-rest-git/client.gen.ts similarity index 100% rename from src/clients/client-rest-git/client.gen.ts rename to src/api-clients/client-rest-git/client.gen.ts diff --git a/src/clients/client-rest-git/index.ts b/src/api-clients/client-rest-git/index.ts similarity index 100% rename from src/clients/client-rest-git/index.ts rename to src/api-clients/client-rest-git/index.ts diff --git a/src/clients/client-rest-git/sdk.gen.ts b/src/api-clients/client-rest-git/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-git/sdk.gen.ts rename to src/api-clients/client-rest-git/sdk.gen.ts diff --git a/src/clients/client-rest-git/types.gen.ts b/src/api-clients/client-rest-git/types.gen.ts similarity index 100% rename from src/clients/client-rest-git/types.gen.ts rename to src/api-clients/client-rest-git/types.gen.ts diff --git a/src/clients/client-rest-setup/client.gen.ts b/src/api-clients/client-rest-setup/client.gen.ts similarity index 100% rename from src/clients/client-rest-setup/client.gen.ts rename to src/api-clients/client-rest-setup/client.gen.ts diff --git a/src/clients/client-rest-setup/index.ts b/src/api-clients/client-rest-setup/index.ts similarity index 100% rename from src/clients/client-rest-setup/index.ts rename to src/api-clients/client-rest-setup/index.ts diff --git a/src/clients/client-rest-setup/sdk.gen.ts b/src/api-clients/client-rest-setup/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-setup/sdk.gen.ts rename to src/api-clients/client-rest-setup/sdk.gen.ts diff --git a/src/clients/client-rest-setup/types.gen.ts b/src/api-clients/client-rest-setup/types.gen.ts similarity index 100% rename from src/clients/client-rest-setup/types.gen.ts rename to src/api-clients/client-rest-setup/types.gen.ts diff --git a/src/clients/client-rest-shell/client.gen.ts b/src/api-clients/client-rest-shell/client.gen.ts similarity index 100% rename from src/clients/client-rest-shell/client.gen.ts rename to src/api-clients/client-rest-shell/client.gen.ts diff --git a/src/clients/client-rest-shell/index.ts b/src/api-clients/client-rest-shell/index.ts similarity index 100% rename from src/clients/client-rest-shell/index.ts rename to src/api-clients/client-rest-shell/index.ts diff --git a/src/clients/client-rest-shell/sdk.gen.ts b/src/api-clients/client-rest-shell/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-shell/sdk.gen.ts rename to src/api-clients/client-rest-shell/sdk.gen.ts diff --git a/src/clients/client-rest-shell/types.gen.ts b/src/api-clients/client-rest-shell/types.gen.ts similarity index 100% rename from src/clients/client-rest-shell/types.gen.ts rename to src/api-clients/client-rest-shell/types.gen.ts diff --git a/src/clients/client-rest-system/client.gen.ts b/src/api-clients/client-rest-system/client.gen.ts similarity index 100% rename from src/clients/client-rest-system/client.gen.ts rename to src/api-clients/client-rest-system/client.gen.ts diff --git a/src/clients/client-rest-system/index.ts b/src/api-clients/client-rest-system/index.ts similarity index 100% rename from src/clients/client-rest-system/index.ts rename to src/api-clients/client-rest-system/index.ts diff --git a/src/clients/client-rest-system/sdk.gen.ts b/src/api-clients/client-rest-system/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-system/sdk.gen.ts rename to src/api-clients/client-rest-system/sdk.gen.ts diff --git a/src/clients/client-rest-system/types.gen.ts b/src/api-clients/client-rest-system/types.gen.ts similarity index 100% rename from src/clients/client-rest-system/types.gen.ts rename to src/api-clients/client-rest-system/types.gen.ts diff --git a/src/clients/client-rest-task/client.gen.ts b/src/api-clients/client-rest-task/client.gen.ts similarity index 100% rename from src/clients/client-rest-task/client.gen.ts rename to src/api-clients/client-rest-task/client.gen.ts diff --git a/src/clients/client-rest-task/index.ts b/src/api-clients/client-rest-task/index.ts similarity index 100% rename from src/clients/client-rest-task/index.ts rename to src/api-clients/client-rest-task/index.ts diff --git a/src/clients/client-rest-task/sdk.gen.ts b/src/api-clients/client-rest-task/sdk.gen.ts similarity index 100% rename from src/clients/client-rest-task/sdk.gen.ts rename to src/api-clients/client-rest-task/sdk.gen.ts diff --git a/src/clients/client-rest-task/types.gen.ts b/src/api-clients/client-rest-task/types.gen.ts similarity index 100% rename from src/clients/client-rest-task/types.gen.ts rename to src/api-clients/client-rest-task/types.gen.ts diff --git a/src/clients/client/client.gen.ts b/src/api-clients/client/client.gen.ts similarity index 100% rename from src/clients/client/client.gen.ts rename to src/api-clients/client/client.gen.ts diff --git a/src/clients/client/index.ts b/src/api-clients/client/index.ts similarity index 100% rename from src/clients/client/index.ts rename to src/api-clients/client/index.ts diff --git a/src/clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts similarity index 100% rename from src/clients/client/sdk.gen.ts rename to src/api-clients/client/sdk.gen.ts diff --git a/src/clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts similarity index 100% rename from src/clients/client/types.gen.ts rename to src/api-clients/client/types.gen.ts diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index dbe8a4e..21c0658 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -13,7 +13,7 @@ import { sandboxCreate, sandboxFork, VmUpdateSpecsRequest, -} from "../../clients/client"; +} from "../../api-clients/client"; import { handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; @@ -139,7 +139,7 @@ export const buildCommand: yargs.CommandModule< spinner.start(`Starting sandbox...`); } - const sandbox = sdk.sandbox.ref(sandboxId); + const sandbox = await sdk.sandbox.resume(sandboxId); const session = await sandbox.connect(); spinner.succeed("Sandbox opened"); diff --git a/src/browser.ts b/src/browser.ts index b4cadfe..3d09928 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,6 +1,6 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "./types"; -import { WebSocketSession } from "./sessions/WebSocketSession"; +import { WebSocketClient } from "./clients/WebSocketClient"; /** * With this function you can connect to a sandbox from the browser. @@ -39,7 +39,7 @@ import { WebSocketSession } from "./sessions/WebSocketSession"; */ export async function connectToSandbox( session: SandboxSession -): Promise { +): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -66,5 +66,5 @@ export async function connectToSandbox( () => {} ); - return new WebSocketSession(pitcherClient); + return new WebSocketClient(pitcherClient); } diff --git a/src/sessions/RestSession/index.ts b/src/clients/RestClient/index.ts similarity index 97% rename from src/sessions/RestSession/index.ts rename to src/clients/RestClient/index.ts index 81f999e..e9444db 100644 --- a/src/sessions/RestSession/index.ts +++ b/src/clients/RestClient/index.ts @@ -13,7 +13,7 @@ export interface ClientOpts { headers?: Record; } -export class RestSession { +export class RestClient { static id = 0; fs: SandboxRestFS; container: SandboxRestContainer; @@ -56,7 +56,7 @@ export class RestSession { const method = opts.url.substring(1); const message = { - id: RestSession.id++, + id: RestClient.id++, method, params: opts.body, }; diff --git a/src/sessions/RestSession/sandbox-rest-container.ts b/src/clients/RestClient/sandbox-rest-container.ts similarity index 79% rename from src/sessions/RestSession/sandbox-rest-container.ts rename to src/clients/RestClient/sandbox-rest-container.ts index 065127e..32833a8 100644 --- a/src/sessions/RestSession/sandbox-rest-container.ts +++ b/src/clients/RestClient/sandbox-rest-container.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as container from "../../clients/client-rest-container"; +import * as container from "../../api-clients/client-rest-container"; export class SandboxRestContainer { constructor(private client: Client) {} diff --git a/src/sessions/RestSession/sandbox-rest-fs.ts b/src/clients/RestClient/sandbox-rest-fs.ts similarity index 98% rename from src/sessions/RestSession/sandbox-rest-fs.ts rename to src/clients/RestClient/sandbox-rest-fs.ts index 5d82ca9..42256fb 100644 --- a/src/sessions/RestSession/sandbox-rest-fs.ts +++ b/src/clients/RestClient/sandbox-rest-fs.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as fs from "../../clients/client-rest-fs"; +import * as fs from "../../api-clients/client-rest-fs"; import { join } from "path"; import { SandboxSession } from "../../types"; diff --git a/src/sessions/RestSession/sandbox-rest-git.ts b/src/clients/RestClient/sandbox-rest-git.ts similarity index 96% rename from src/sessions/RestSession/sandbox-rest-git.ts rename to src/clients/RestClient/sandbox-rest-git.ts index eec36ee..7a5e709 100644 --- a/src/sessions/RestSession/sandbox-rest-git.ts +++ b/src/clients/RestClient/sandbox-rest-git.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as git from "../../clients/client-rest-git"; +import * as git from "../../api-clients/client-rest-git"; export class SandboxRestGit { constructor(private client: Client) {} diff --git a/src/sessions/RestSession/sandbox-rest-shell.ts b/src/clients/RestClient/sandbox-rest-shell.ts similarity index 94% rename from src/sessions/RestSession/sandbox-rest-shell.ts rename to src/clients/RestClient/sandbox-rest-shell.ts index c39bf34..ddd24c1 100644 --- a/src/sessions/RestSession/sandbox-rest-shell.ts +++ b/src/clients/RestClient/sandbox-rest-shell.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as shell from "../../clients/client-rest-shell"; +import * as shell from "../../api-clients/client-rest-shell"; export class SandboxRestShell { constructor(private client: Client) {} diff --git a/src/sessions/RestSession/sandbox-rest-system.ts b/src/clients/RestClient/sandbox-rest-system.ts similarity index 88% rename from src/sessions/RestSession/sandbox-rest-system.ts rename to src/clients/RestClient/sandbox-rest-system.ts index e0d6dd7..6ebce84 100644 --- a/src/sessions/RestSession/sandbox-rest-system.ts +++ b/src/clients/RestClient/sandbox-rest-system.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as system from "../../clients/client-rest-system"; +import * as system from "../../api-clients/client-rest-system"; export class SandboxRestSystem { constructor(private client: Client) {} diff --git a/src/sessions/RestSession/sandbox-rest-task.ts b/src/clients/RestClient/sandbox-rest-task.ts similarity index 95% rename from src/sessions/RestSession/sandbox-rest-task.ts rename to src/clients/RestClient/sandbox-rest-task.ts index f669e27..4ed4b00 100644 --- a/src/sessions/RestSession/sandbox-rest-task.ts +++ b/src/clients/RestClient/sandbox-rest-task.ts @@ -1,5 +1,5 @@ import { Client } from "@hey-api/client-fetch"; -import * as task from "../../clients/client-rest-task"; +import * as task from "../../api-clients/client-rest-task"; export class SandboxRestTask { constructor(private client: Client) {} diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/clients/WebSocketClient/filesystem.ts similarity index 100% rename from src/sessions/WebSocketSession/filesystem.ts rename to src/clients/WebSocketClient/filesystem.ts diff --git a/src/sessions/WebSocketSession/index.ts b/src/clients/WebSocketClient/index.ts similarity index 98% rename from src/sessions/WebSocketSession/index.ts rename to src/clients/WebSocketClient/index.ts index 5a21a88..ad99205 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/clients/WebSocketClient/index.ts @@ -19,7 +19,7 @@ export * from "./setup"; export * from "./shells"; export * from "./tasks"; -export class WebSocketSession extends Disposable { +export class WebSocketClient extends Disposable { static async init(session: SandboxSession, apiClient: Client) { const pitcherClient = await initPitcherClient( { @@ -65,7 +65,7 @@ export class WebSocketSession extends Disposable { () => {} ); - return new WebSocketSession(pitcherClient); + return new WebSocketClient(pitcherClient); } /** * Namespace for all filesystem operations on this sandbox. diff --git a/src/sessions/WebSocketSession/ports.ts b/src/clients/WebSocketClient/ports.ts similarity index 100% rename from src/sessions/WebSocketSession/ports.ts rename to src/clients/WebSocketClient/ports.ts diff --git a/src/sessions/WebSocketSession/setup.ts b/src/clients/WebSocketClient/setup.ts similarity index 100% rename from src/sessions/WebSocketSession/setup.ts rename to src/clients/WebSocketClient/setup.ts diff --git a/src/sessions/WebSocketSession/shells.ts b/src/clients/WebSocketClient/shells.ts similarity index 100% rename from src/sessions/WebSocketSession/shells.ts rename to src/clients/WebSocketClient/shells.ts diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/clients/WebSocketClient/tasks.ts similarity index 100% rename from src/sessions/WebSocketSession/tasks.ts rename to src/clients/WebSocketClient/tasks.ts diff --git a/src/index.ts b/src/index.ts index d7e1b22..22c4062 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; -import * as WebSocketSession from "./sessions/WebSocketSession"; +import * as WebSocketSession from "./clients/WebSocketClient"; export { WebSocketSession }; @@ -19,7 +19,7 @@ function ensure(value: T | undefined, message: string): T { return value; } -export { RestSession as RestClient } from "./sessions/RestSession"; +export { RestClient as RestClient } from "./clients/RestClient"; export class CodeSandbox { public readonly sandbox: SandboxClient; diff --git a/src/sandbox.ts b/src/sandbox.ts index 68039f5..c947761 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -8,6 +8,7 @@ import type { SessionCreateOptions, StartSandboxOpts, CreateSandboxBaseOpts, + SandboxOpts, } from "./types"; import { PreviewTokens } from "./PreviewTokens"; import { Client } from "@hey-api/client-fetch"; @@ -19,12 +20,12 @@ import { VmStartResponse, vmUpdateHibernationTimeout, vmUpdateSpecs, -} from "./clients/client"; +} from "./api-clients/client"; import { handleResponse } from "./utils/api"; import { SandboxClient } from "./SandboxClient"; import { VMTier } from "./VMTier"; -import { WebSocketSession } from "./sessions/WebSocketSession"; -import { ClientOpts, RestSession } from "./sessions/RestSession"; +import { WebSocketClient } from "./clients/WebSocketClient"; +import { RestClient } from "./clients/RestClient"; export class Sandbox extends Disposable { private apiClient: Client; @@ -41,70 +42,30 @@ export class Sandbox extends Disposable { */ public readonly previewTokens: PreviewTokens; - constructor(public id: string, private sandboxClient: SandboxClient) { + get id() { + return this.opts.id; + } + get bootupType() { + return this.opts.bootupType; + } + get cluster() { + return this.opts.cluster; + } + get isUpToDate() { + return this.opts.isUpToDate; + } + get globalSession() { + return this.opts.globalSession; + } + constructor(private opts: SandboxOpts, private sandboxClient: SandboxClient) { super(); + this.apiClient = sandboxClient["apiClient"]; this.previewTokens = this.addDisposable( new PreviewTokens(this.id, this.apiClient) ); } - private async start(startOpts?: StartSandboxOpts) { - const startResult = await vmStart({ - client: this.apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: this.id, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${this.id}` - ); - - return response; - } - - private async createSession( - options: SessionCreateOptions, - globalSession: SandboxSession - ): Promise { - const response = await vmCreateSession({ - client: this.apiClient, - body: { - session_id: options.id, - permission: options.permission ?? "write", - }, - path: { - id: this.id, - }, - }); - - const handledResponse = handleResponse( - response, - `Failed to create session ${options.id}` - ); - - const session: SandboxSession = { - bootupType: globalSession.bootupType, - cluster: globalSession.cluster, - sandboxId: this.id, - pitcherToken: handledResponse.pitcher_token, - pitcherUrl: handledResponse.pitcher_url, - userWorkspacePath: handledResponse.user_workspace_path, - }; - - return session; - } - /** * Creates a sandbox by forking an existing sandbox reference. * @@ -132,7 +93,7 @@ export class Sandbox extends Disposable { * @returns The start data, contains a single use token to connect to the VM */ public async resume(): Promise { - await this.start(); + this.opts = await this.sandboxClient["start"](this.id); } /** @@ -208,40 +169,43 @@ export class Sandbox extends Disposable { ); } - async session(opts?: SessionCreateOptions): Promise { - const startData = await this.start(); - let session: SandboxSession = { - bootupType: startData.bootup_type as SandboxSession["bootupType"], - cluster: startData.cluster, + async createSession(opts: SessionCreateOptions): Promise { + const response = await vmCreateSession({ + client: this.apiClient, + body: { + session_id: opts.id, + permission: opts.permission ?? "write", + }, + path: { + id: this.id, + }, + }); + + const handledResponse = handleResponse( + response, + `Failed to create session ${opts.id}` + ); + + const session: SandboxSession = { sandboxId: this.id, - pitcherToken: startData.pitcher_token, - pitcherUrl: startData.pitcher_url, - userWorkspacePath: startData.user_workspace_path, + pitcherToken: handledResponse.pitcher_token, + pitcherUrl: handledResponse.pitcher_url, + userWorkspacePath: handledResponse.user_workspace_path, }; - if (opts) { - session = await this.createSession(opts, session); - } - return session; } - async connect(opts?: SessionCreateOptions): Promise { - const session = await this.session(opts); - - return WebSocketSession.init(session, this.apiClient); - } - - async isUpToDate() { - const startData = await this.start(); + async connect(customSession?: SandboxSession): Promise { + const session = customSession || this.globalSession; - return startData.latest_pitcher_version === startData.pitcher_version; + return WebSocketClient.init(session, this.apiClient); } - async rest(opts?: SessionCreateOptions & ClientOpts) { - const session = await this.session(opts); + async createRestClient(customSession?: SandboxSession) { + const session = customSession || this.globalSession; - return new RestSession(session, opts); + return new RestClient(session); } /** diff --git a/src/types.ts b/src/types.ts index ea8653e..be09711 100644 --- a/src/types.ts +++ b/src/types.ts @@ -195,8 +195,6 @@ export interface SessionCreateOptions { } export type SandboxSession = { - bootupType: PitcherManagerResponse["bootupType"]; - cluster: string; sandboxId: string; pitcherToken: string; pitcherUrl: string; @@ -210,8 +208,8 @@ export type CreateSandboxGitSourceOpts = { accessToken: string; }; -export type CreateSandboxJSONSourceOpts = { - source: "json"; +export type CreateSandboxFilesSourceOpts = { + source: "files"; files: Record; }; @@ -219,5 +217,13 @@ export type CreateSandboxOpts = CreateSandboxBaseOpts & ( | CreateSandboxTemplateSourceOpts | CreateSandboxGitSourceOpts - | CreateSandboxJSONSourceOpts + | CreateSandboxFilesSourceOpts ); + +export type SandboxOpts = { + id: string; + bootupType: PitcherManagerResponse["bootupType"]; + cluster: string; + isUpToDate: boolean; + globalSession: SandboxSession; +}; diff --git a/tsconfig.json b/tsconfig.json index dac88a9..01567c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,6 @@ "lib", "cjs", "src/bin", - "src/clients" + "src/api-clients" ] } From 51f46bfb3f8b4c2dc93a51a690fd72fa96d6d307 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 29 Apr 2025 13:47:44 +0200 Subject: [PATCH 075/241] add git sandbox --- TODO.md | 5 ++ src/SandboxClient.ts | 100 +++++++++++++++++++---------- src/clients/WebSocketClient/git.ts | 14 ++++ src/types.ts | 36 +++++------ 4 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 src/clients/WebSocketClient/git.ts diff --git a/TODO.md b/TODO.md index a31d417..3079746 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,10 @@ ## QUESTIONS +- Should we always require a `gitAccessToken` for git sandboxes? +- If `gitAccessToken` should we create a temp session to clone and then still require + a `createSession` to interact with the repo? +- Should we allow `gitAccessToken` with global user on `vm/{id}/start`? + ## TODO - Improve data passed to browser with a BrowserSession, also ensure reconnect token works diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index e6e094e..c665dce 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -6,7 +6,9 @@ import { Sandbox } from "./Sandbox"; import { getBaseUrl, handleResponse } from "./utils/api"; import { ClientOpts } from "."; import { + CreateSandboxGitSourceOpts, CreateSandboxOpts, + CreateSandboxTemplateSourceOpts, PaginationOpts, SandboxInfo, SandboxListOpts, @@ -89,6 +91,69 @@ export class SandboxClient { return new Sandbox(sandboxOpts, this); } + private async createGitSandbox( + opts: CreateSandboxGitSourceOpts & StartSandboxOpts + ) { + const sandbox = await this.createTemplateSandbox({ + ...opts, + source: "template", + id: this.defaultTemplateId, + }); + + const client = await sandbox.connect(); + + await client.shells.run( + [ + "rm -rf .git", + "git init", + `git remote add origin ${opts.url}`, + "git fetch origin", + `git checkout -b ${opts.branch}`, + `git reset --hard origin/${opts.branch}`, + ].join("&&") + ); + + client.disconnect(); + + return sandbox; + } + + private async createTemplateSandbox( + opts: CreateSandboxTemplateSourceOpts & StartSandboxOpts + ) { + const templateId = opts.id || this.defaultTemplateId; + const privacy = opts.privacy || "public"; + const tags = opts.tags || ["sdk"]; + const path = opts.path || "/SDK"; + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + // We never want to start the Sandbox as part of this call. The reason is that we currently need to + // call an explicit START to start it in the correct cluster + const result = await sandboxFork({ + client: this.apiClient, + body: { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + }, + path: { + id: templateId, + }, + }); + + const sandbox = handleResponse( + result, + "Failed to create sandbox" + // We currently always pass "start_options" to create a session + ); + const sandboxOpts = await this.start(sandbox.id, opts); + + return new Sandbox(sandboxOpts, this); + } /** * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from * any sandbox/template created on codesandbox.io, even your own templates) or don't pass @@ -106,44 +171,13 @@ export class SandboxClient { ): Promise { switch (opts.source) { case "git": { - throw new Error("Not implemented"); + return this.createGitSandbox(opts); } case "files": { throw new Error("Not implemented"); } case "template": { - const templateId = opts.id || this.defaultTemplateId; - const privacy = opts.privacy || "public"; - const tags = opts.tags || ["sdk"]; - const path = opts.path || "/SDK"; - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - // We never want to start the Sandbox as part of this call. The reason is that we currently need to - // call an explicit START to start it in the correct cluster - const result = await sandboxFork({ - client: this.apiClient, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - }, - path: { - id: templateId, - }, - }); - - const sandbox = handleResponse( - result, - "Failed to create sandbox" - // We currently always pass "start_options" to create a session - ); - const sandboxOpts = await this.start(sandbox.id, opts); - - return new Sandbox(sandboxOpts, this); + return this.createTemplateSandbox(opts); } } } diff --git a/src/clients/WebSocketClient/git.ts b/src/clients/WebSocketClient/git.ts new file mode 100644 index 0000000..3dd52b5 --- /dev/null +++ b/src/clients/WebSocketClient/git.ts @@ -0,0 +1,14 @@ +import type { Id, IPitcherClient } from "@codesandbox/pitcher-client"; +import { listenOnce } from "@codesandbox/pitcher-common/dist/event"; + +import { Disposable } from "../../utils/disposable"; +import { Emitter } from "../../utils/event"; + +export class Git extends Disposable { + constructor(private pitcherClient: IPitcherClient) { + super(); + } + pull() { + return this.pitcherClient.clients.git.pull(); + } +} diff --git a/src/types.ts b/src/types.ts index be09711..30ad241 100644 --- a/src/types.ts +++ b/src/types.ts @@ -172,15 +172,6 @@ export type CreateSandboxBaseOpts = { path?: string; }; -export type CreateSandboxTemplateSourceOpts = { - source: "template"; - /** - * What template to fork from, this is the id of another sandbox. Defaults to our - * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). - */ - id?: string; -}; - export type SandboxSessionData = { id: string; pitcher_token: string; @@ -201,24 +192,31 @@ export type SandboxSession = { userWorkspacePath: string; }; -export type CreateSandboxGitSourceOpts = { +export type CreateSandboxTemplateSourceOpts = CreateSandboxBaseOpts & { + source: "template"; + /** + * What template to fork from, this is the id of another sandbox. Defaults to our + * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). + */ + id?: string; +}; + +export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { source: "git"; url: string; - branch?: string; - accessToken: string; + branch: string; + gitAccessToken?: string; }; -export type CreateSandboxFilesSourceOpts = { +export type CreateSandboxFilesSourceOpts = CreateSandboxBaseOpts & { source: "files"; files: Record; }; -export type CreateSandboxOpts = CreateSandboxBaseOpts & - ( - | CreateSandboxTemplateSourceOpts - | CreateSandboxGitSourceOpts - | CreateSandboxFilesSourceOpts - ); +export type CreateSandboxOpts = + | CreateSandboxTemplateSourceOpts + | CreateSandboxGitSourceOpts + | CreateSandboxFilesSourceOpts; export type SandboxOpts = { id: string; From df496178f048890f127800b5492b8ad78259479c Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 29 Apr 2025 14:10:59 +0200 Subject: [PATCH 076/241] fix browser sessions --- TODO.md | 14 ++------- src/SandboxClient.ts | 28 +++++++++--------- src/browser.ts | 35 +++++++++-------------- src/sandbox.ts | 68 +++++++++++++++++++++++++++++++++++--------- src/types.ts | 4 +++ 5 files changed, 89 insertions(+), 60 deletions(-) diff --git a/TODO.md b/TODO.md index 3079746..ae4ae6b 100644 --- a/TODO.md +++ b/TODO.md @@ -28,17 +28,9 @@ sandbox.shutdown(); sandbox.resume(); sandbox.updateTier(); -const client = await sandbox.connect(); -const client = await sandbox.createRestClient(); -const browserSession = await sandbox.globalSession; - -const session = await sandbox.createSession({ - id: "some-user-name", - permission: "write", -}); -const client = sandbox.connect(session); -const client = sandbox.createRestClient(session); -const browserSession = session; +const session = await sandbox.createBrowserSession(); +const client = sandbox.connect(); +const client = sandbox.createRestClient(); ``` # 2 Git clone support diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index c665dce..cb2e3d5 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -17,6 +17,7 @@ import { SandboxPrivacy, StartSandboxOpts, } from "./types"; +import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; export class SandboxClient { private apiClient: Client; @@ -48,7 +49,7 @@ export class SandboxClient { private async start( sandboxId: string, startOpts?: StartSandboxOpts - ): Promise { + ): Promise { const startResult = await vmStart({ client: this.apiClient, body: startOpts @@ -70,16 +71,15 @@ export class SandboxClient { ); return { - id: sandboxId, - bootupType: response.bootup_type as SandboxOpts["bootupType"], + bootupType: response.bootup_type as PitcherManagerResponse["bootupType"], cluster: response.cluster, - isUpToDate: response.latest_pitcher_version === response.pitcher_version, - globalSession: { - sandboxId, - pitcherToken: response.pitcher_token, - pitcherUrl: response.pitcher_url, - userWorkspacePath: response.user_workspace_path, - }, + pitcherURL: response.pitcher_url, + workspacePath: response.workspace_path, + userWorkspacePath: response.user_workspace_path, + pitcherManagerVersion: response.pitcher_manager_version, + pitcherVersion: response.pitcher_version, + latestPitcherVersion: response.latest_pitcher_version, + pitcherToken: response.pitcher_token, }; } @@ -87,8 +87,8 @@ export class SandboxClient { * */ async resume(sandboxId: string) { - const sandboxOpts = await this.start(sandboxId); - return new Sandbox(sandboxOpts, this); + const startResponse = await this.start(sandboxId); + return new Sandbox(sandboxId, startResponse, this); } private async createGitSandbox( @@ -150,9 +150,9 @@ export class SandboxClient { "Failed to create sandbox" // We currently always pass "start_options" to create a session ); - const sandboxOpts = await this.start(sandbox.id, opts); + const startResponse = await this.start(sandbox.id, opts); - return new Sandbox(sandboxOpts, this); + return new Sandbox(sandbox.id, startResponse, this); } /** * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from diff --git a/src/browser.ts b/src/browser.ts index 3d09928..d444827 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,5 +1,5 @@ -import { initPitcherClient } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "./types"; +import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; +import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; import { WebSocketClient } from "./clients/WebSocketClient"; /** @@ -38,32 +38,25 @@ import { WebSocketClient } from "./clients/WebSocketClient"; * ``` */ export async function connectToSandbox( - session: SandboxSession + session: SandboxBrowserSession, + options: { + onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; + initStatusCb?: (event: protocol.system.InitStatus) => void; + } = {} ): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: session.sandboxId, - onFocusChange() { - return () => {}; - }, - requestPitcherInstance: () => - Promise.resolve({ - bootupType: "RESUME", - cluster: "session", - id: session.sandboxId, - latestPitcherVersion: "1.0.0-session", - pitcherManagerVersion: "1.0.0-session", - pitcherToken: session.pitcherToken, - pitcherURL: session.pitcherUrl, - pitcherVersion: "1.0.0-session", - reconnectToken: "", - userWorkspacePath: session.userWorkspacePath, - workspacePath: session.userWorkspacePath, + instanceId: session.id, + onFocusChange: + options.onFocusChange || + (() => { + return () => {}; }), + requestPitcherInstance: () => Promise.resolve(session), subscriptions: DEFAULT_SUBSCRIPTIONS, }, - () => {} + options.initStatusCb || (() => {}) ); return new WebSocketClient(pitcherClient); diff --git a/src/sandbox.ts b/src/sandbox.ts index c947761..6373769 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,6 +1,7 @@ import { Disposable, initPitcherClient, + PitcherManagerResponse, type protocol as _protocol, } from "@codesandbox/pitcher-client"; import type { @@ -9,6 +10,7 @@ import type { StartSandboxOpts, CreateSandboxBaseOpts, SandboxOpts, + SandboxBrowserSession, } from "./types"; import { PreviewTokens } from "./PreviewTokens"; import { Client } from "@hey-api/client-fetch"; @@ -42,22 +44,31 @@ export class Sandbox extends Disposable { */ public readonly previewTokens: PreviewTokens; - get id() { - return this.opts.id; - } get bootupType() { - return this.opts.bootupType; + return this.pitcherManagerResponse.bootupType; } get cluster() { - return this.opts.cluster; + return this.pitcherManagerResponse.cluster; } get isUpToDate() { - return this.opts.isUpToDate; + return ( + this.pitcherManagerResponse.latestPitcherVersion === + this.pitcherManagerResponse.pitcherVersion + ); } get globalSession() { - return this.opts.globalSession; + return { + sandboxId: this.id, + pitcherToken: this.pitcherManagerResponse.pitcherToken, + pitcherUrl: this.pitcherManagerResponse.pitcherURL, + userWorkspacePath: this.pitcherManagerResponse.userWorkspacePath, + }; } - constructor(private opts: SandboxOpts, private sandboxClient: SandboxClient) { + constructor( + public id: string, + private pitcherManagerResponse: PitcherManagerResponse, + private sandboxClient: SandboxClient + ) { super(); this.apiClient = sandboxClient["apiClient"]; @@ -93,7 +104,7 @@ export class Sandbox extends Disposable { * @returns The start data, contains a single use token to connect to the VM */ public async resume(): Promise { - this.opts = await this.sandboxClient["start"](this.id); + this.pitcherManagerResponse = await this.sandboxClient["start"](this.id); } /** @@ -169,7 +180,9 @@ export class Sandbox extends Disposable { ); } - async createSession(opts: SessionCreateOptions): Promise { + private async createSession( + opts: SessionCreateOptions + ): Promise { const response = await vmCreateSession({ client: this.apiClient, body: { @@ -196,18 +209,45 @@ export class Sandbox extends Disposable { return session; } - async connect(customSession?: SandboxSession): Promise { - const session = customSession || this.globalSession; + async connect( + customSession?: SessionCreateOptions + ): Promise { + const session = customSession + ? await this.createSession(customSession) + : this.globalSession; return WebSocketClient.init(session, this.apiClient); } - async createRestClient(customSession?: SandboxSession) { - const session = customSession || this.globalSession; + async createRestClient(customSession?: SessionCreateOptions) { + const session = customSession + ? await this.createSession(customSession) + : this.globalSession; return new RestClient(session); } + async createBrowserSession( + customSession?: SessionCreateOptions + ): Promise { + const session = customSession + ? await this.createSession(customSession) + : this.globalSession; + + return { + id: this.id, + bootupType: this.bootupType, + cluster: this.cluster, + latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion, + pitcherManagerVersion: this.pitcherManagerResponse.pitcherManagerVersion, + pitcherToken: session.pitcherToken, + pitcherURL: session.pitcherUrl, + userWorkspacePath: session.userWorkspacePath, + workspacePath: this.pitcherManagerResponse.workspacePath, + pitcherVersion: this.pitcherManagerResponse.pitcherVersion, + }; + } + /** * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in * the project directory (`/project/sandbox`) will be preserved. diff --git a/src/types.ts b/src/types.ts index 30ad241..099accf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -225,3 +225,7 @@ export type SandboxOpts = { isUpToDate: boolean; globalSession: SandboxSession; }; + +export type SandboxBrowserSession = PitcherManagerResponse & { + id: string; +}; From 58985d1c340ac89dd7bce715cb05361e7069211f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 10:15:59 +0200 Subject: [PATCH 077/241] fixed ports and other stuff --- TODO.md | 11 ++++- src/PreviewTokens.ts | 16 ++++++- src/SandboxClient.ts | 36 ++++++---------- src/bin/commands/build.ts | 8 +++- src/browser.ts | 17 ++++---- src/clients/WebSocketClient/ports.ts | 64 +++++----------------------- src/sandbox.ts | 5 +-- src/utils/api.ts | 34 +++++++++++++++ 8 files changed, 98 insertions(+), 93 deletions(-) diff --git a/TODO.md b/TODO.md index ae4ae6b..173d591 100644 --- a/TODO.md +++ b/TODO.md @@ -4,10 +4,18 @@ - If `gitAccessToken` should we create a temp session to clone and then still require a `createSession` to interact with the repo? - Should we allow `gitAccessToken` with global user on `vm/{id}/start`? +- Why do you need to connect to get the signed preview url? Could we rather return the preview origin on PitcherManagerResponse and rather make it a Sandbox management type of thing + +## USER QUESTIONS + +- Do you need to create/fork a sandbox without starting the VM +- Do you need to shutdown/restart the sandbox without resuming it first ## TODO -- Improve data passed to browser with a BrowserSession, also ensure reconnect token works +- Allow getting `sandbox` reference without resuming +- Start VM on create/fork +- Move preview tokens to sandbox # 1 New API @@ -20,7 +28,6 @@ const sandbox = await sdk.sandbox.create(SandboxOptions & StartOptions); sandbox.isUpToDate; sandbox.bootupType; sandbox.cluster; -sandbox.globalSession; sandbox.fork(Omit & StartOptions); sandbox.restart(StartOptions); sandbox.hibernate(); diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts index b1ab18e..1ec6815 100644 --- a/src/PreviewTokens.ts +++ b/src/PreviewTokens.ts @@ -1,5 +1,4 @@ import { Disposable } from "./utils/disposable"; -import { SandboxClient } from "./SandboxClient"; import { Client } from "@hey-api/client-fetch"; import { handleResponse } from "./utils/api"; import { @@ -33,6 +32,21 @@ export class PreviewTokens extends Disposable { super(); } + /** + * Get a signed preview URL for a port using a preview token. + * + * @param port - The port to get a signed preview URL for + * @param token - The preview token to sign the URL with + * @returns The signed preview URL, or undefined if the port is not open + */ + getSignedPreviewUrl( + port: number, + token: string, + protocol = "https://" + ): string { + return `${protocol}${this.sandboxId}-${port}.csb.app?preview_token=${token}`; + } + /** * Generate a new preview token that can be used to access private sandbox previews. * diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index cb2e3d5..c8a9fb3 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -3,7 +3,12 @@ import { createClient, createConfig } from "@hey-api/client-fetch"; import { sandboxFork, sandboxList, vmStart } from "./api-clients/client"; import { Sandbox } from "./Sandbox"; -import { getBaseUrl, handleResponse } from "./utils/api"; +import { + getBaseUrl, + getStartOptions, + getStartResponse, + handleResponse, +} from "./utils/api"; import { ClientOpts } from "."; import { CreateSandboxGitSourceOpts, @@ -70,17 +75,7 @@ export class SandboxClient { `Failed to start sandbox ${sandboxId}` ); - return { - bootupType: response.bootup_type as PitcherManagerResponse["bootupType"], - cluster: response.cluster, - pitcherURL: response.pitcher_url, - workspacePath: response.workspace_path, - userWorkspacePath: response.user_workspace_path, - pitcherManagerVersion: response.pitcher_manager_version, - pitcherVersion: response.pitcher_version, - latestPitcherVersion: response.latest_pitcher_version, - pitcherToken: response.pitcher_token, - }; + return getStartResponse(response); } /** @@ -128,9 +123,6 @@ export class SandboxClient { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - // We never want to start the Sandbox as part of this call. The reason is that we currently need to - // call an explicit START to start it in the correct cluster const result = await sandboxFork({ client: this.apiClient, body: { @@ -139,20 +131,20 @@ export class SandboxClient { description: opts?.description, tags: tagsWithSdk, path, + start_options: getStartOptions(opts), }, path: { id: templateId, }, }); - const sandbox = handleResponse( - result, - "Failed to create sandbox" - // We currently always pass "start_options" to create a session - ); - const startResponse = await this.start(sandbox.id, opts); + const sandbox = handleResponse(result, "Failed to create sandbox"); - return new Sandbox(sandbox.id, startResponse, this); + return new Sandbox( + sandbox.id, + getStartResponse(sandbox.start_response), + this + ); } /** * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 21c0658..015b9ce 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -7,7 +7,7 @@ import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { WebSocketSession, VMTier, CodeSandbox } from "../../"; +import { WebSocketSession, VMTier, CodeSandbox, Sandbox } from "../../"; import { sandboxCreate, @@ -139,7 +139,11 @@ export const buildCommand: yargs.CommandModule< spinner.start(`Starting sandbox...`); } - const sandbox = await sdk.sandbox.resume(sandboxId); + const startResponse = await sdk.sandbox["start"](sandboxId, { + ipcountry: argv.ipCountry, + vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, + }); + const sandbox = new Sandbox(sandboxId, startResponse, sdk.sandbox); const session = await sandbox.connect(); spinner.succeed("Sandbox opened"); diff --git a/src/browser.ts b/src/browser.ts index d444827..53e68fd 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -37,23 +37,22 @@ import { WebSocketClient } from "./clients/WebSocketClient"; * const sandbox = await connectToSandbox(startData); * ``` */ -export async function connectToSandbox( - session: SandboxBrowserSession, - options: { - onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; - initStatusCb?: (event: protocol.system.InitStatus) => void; - } = {} -): Promise { +export async function connectToSandbox(options: { + id: string; + getSession: (id: string) => Promise; + onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; + initStatusCb?: (event: protocol.system.InitStatus) => void; +}): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: session.id, + instanceId: options.id, onFocusChange: options.onFocusChange || (() => { return () => {}; }), - requestPitcherInstance: () => Promise.resolve(session), + requestPitcherInstance: options.getSession, subscriptions: DEFAULT_SUBSCRIPTIONS, }, options.initStatusCb || (() => {}) diff --git a/src/clients/WebSocketClient/ports.ts b/src/clients/WebSocketClient/ports.ts index 6237317..bdcba2f 100644 --- a/src/clients/WebSocketClient/ports.ts +++ b/src/clients/WebSocketClient/ports.ts @@ -1,31 +1,12 @@ -import type { IPitcherClient } from "@codesandbox/pitcher-client"; +import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; -export class PortInfo { - constructor(public port: number, public hostname: string) {} - - getPreviewUrl(protocol = "https://"): string { - return `${protocol}${this.hostname}`; - } - - /** - * Get a signed preview URL using a preview token. Private sandbox previews are inaccessible - * unless a preview token is provided in the URL (or as a header `csb-preview-token` or cookie - * `csb_preview_token`). - * - * @param token - The preview token to sign the URL with - * @param protocol - The protocol to use for the preview URL, defaults to `https://` - * @returns The signed preview URL - */ - getSignedPreviewUrl(token: string, protocol = "https://"): string { - return `${this.getPreviewUrl(protocol)}?preview_token=${token}`; - } -} - export class Ports extends Disposable { - private onDidPortOpenEmitter = this.addDisposable(new Emitter()); + private onDidPortOpenEmitter = this.addDisposable( + new Emitter() + ); get onDidPortOpen() { return this.onDidPortOpenEmitter.event; } @@ -46,9 +27,9 @@ export class Ports extends Disposable { this.addDisposable( pitcherClient.clients.port.onPortsUpdated((ports) => { - const openedPorts = ports - .filter((port) => !this.lastOpenedPorts.has(port.port)) - .map((port) => new PortInfo(port.port, port.url)); + const openedPorts = ports.filter( + (port) => !this.lastOpenedPorts.has(port.port) + ); const closedPorts = [...this.lastOpenedPorts].filter( (port) => !ports.some((p) => p.port === port) @@ -71,14 +52,12 @@ export class Ports extends Disposable { ); } - getOpenedPort(port: number): PortInfo | undefined { + getOpenedPort(port: number) { return this.getOpenedPorts().find((p) => p.port === port); } - getOpenedPorts(): PortInfo[] { - return this.pitcherClient.clients.port - .getPorts() - .map((port) => new PortInfo(port.port, port.url)); + getOpenedPorts() { + return this.pitcherClient.clients.port.getPorts(); } getPreviewUrl(port: number, protocol = "https://"): string | undefined { @@ -101,7 +80,7 @@ export class Ports extends Disposable { async waitForPort( port: number, options?: { timeoutMs?: number } - ): Promise { + ): Promise { await this.pitcherClient.clients.port.readyPromise; return new Promise((resolve, reject) => { @@ -138,25 +117,4 @@ export class Ports extends Disposable { ); }); } - - /** - * Get a signed preview URL for a port using a preview token. - * - * @param port - The port to get a signed preview URL for - * @param token - The preview token to sign the URL with - * @returns The signed preview URL, or undefined if the port is not open - * @throws {Error} If the port is not open - */ - getSignedPreviewUrl( - port: number, - token: string, - protocol = "https://" - ): string { - const portInfo = this.getOpenedPort(port); - if (!portInfo) { - throw new Error("Port is not open"); - } - - return portInfo.getSignedPreviewUrl(token, protocol); - } } diff --git a/src/sandbox.ts b/src/sandbox.ts index 6373769..7a4c34b 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -9,7 +9,6 @@ import type { SessionCreateOptions, StartSandboxOpts, CreateSandboxBaseOpts, - SandboxOpts, SandboxBrowserSession, } from "./types"; import { PreviewTokens } from "./PreviewTokens"; @@ -18,8 +17,6 @@ import { vmCreateSession, vmHibernate, vmShutdown, - vmStart, - VmStartResponse, vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./api-clients/client"; @@ -56,7 +53,7 @@ export class Sandbox extends Disposable { this.pitcherManagerResponse.pitcherVersion ); } - get globalSession() { + private get globalSession() { return { sandboxId: this.id, pitcherToken: this.pitcherManagerResponse.pitcherToken, diff --git a/src/utils/api.ts b/src/utils/api.ts index cc3fa1b..4e65b8d 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,3 +1,6 @@ +import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +import { VmStartResponse } from "../api-clients/client"; +import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; export type HandledResponse = { @@ -8,6 +11,37 @@ export type HandledResponse = { response: Response; }; +export function getStartOptions(opts: StartSandboxOpts | undefined) { + if (!opts) return {}; + + return { + ipcountry: opts.ipcountry, + tier: opts.vmTier?.name, + hibernation_timeout_seconds: opts.hibernationTimeoutSeconds, + automatic_wakeup_config: opts.automaticWakeupConfig, + }; +} + +export function getStartResponse( + response: VmStartResponse["data"] | null +): PitcherManagerResponse { + if (!response) { + throw new Error("No start response"); + } + + return { + bootupType: response.bootup_type as PitcherManagerResponse["bootupType"], + cluster: response.cluster, + pitcherURL: response.pitcher_url, + workspacePath: response.workspace_path, + userWorkspacePath: response.user_workspace_path, + pitcherManagerVersion: response.pitcher_manager_version, + pitcherVersion: response.pitcher_version, + latestPitcherVersion: response.latest_pitcher_version, + pitcherToken: response.pitcher_token, + }; +} + export function getBaseUrl(token: string) { if (token.startsWith("csb_")) { return "https://api.codesandbox.io"; From d356a4b6d1694a85f692b4b5c9a48bb338fe3dea Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 10:17:45 +0200 Subject: [PATCH 078/241] fix tasks --- src/clients/WebSocketClient/tasks.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/clients/WebSocketClient/tasks.ts b/src/clients/WebSocketClient/tasks.ts index 1ac6016..941a19c 100644 --- a/src/clients/WebSocketClient/tasks.ts +++ b/src/clients/WebSocketClient/tasks.ts @@ -1,6 +1,5 @@ import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { PortInfo } from "./ports"; import { Disposable } from "../../utils/disposable"; export type TaskDefinition = { @@ -17,7 +16,7 @@ export type Task = TaskDefinition & { id: string; unconfigured?: boolean; shellId: null | string; - ports: PortInfo[]; + ports: protocol.port.Port[]; }; export class Tasks extends Disposable { @@ -65,6 +64,6 @@ function taskFromDTO(value: protocol.task.TaskDTO): Task { runAtStart: value.runAtStart, preview: value.preview, shellId: value.shell?.shellId ?? null, - ports: value.ports.map((port) => new PortInfo(port.port, port.url)), + ports: value.ports, }; } From c1f96c1fad2be06b8e5cbaa7d1b956a92c4b1a8c Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 14:48:04 +0200 Subject: [PATCH 079/241] reworked api --- TODO.md | 22 ++-- package.json | 2 +- src/PreviewTokens.ts | 27 +++-- src/SandboxClient.ts | 155 ++++++++++++++++++++------- src/clients/WebSocketClient/index.ts | 40 +++++-- src/sandbox.ts | 130 +++------------------- 6 files changed, 182 insertions(+), 194 deletions(-) diff --git a/TODO.md b/TODO.md index 173d591..bba8262 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,6 @@ ## QUESTIONS -- Should we always require a `gitAccessToken` for git sandboxes? -- If `gitAccessToken` should we create a temp session to clone and then still require - a `createSession` to interact with the repo? -- Should we allow `gitAccessToken` with global user on `vm/{id}/start`? -- Why do you need to connect to get the signed preview url? Could we rather return the preview origin on PitcherManagerResponse and rather make it a Sandbox management type of thing +- Does updateTier and updateHibernation require VM start? ## USER QUESTIONS @@ -13,9 +9,7 @@ ## TODO -- Allow getting `sandbox` reference without resuming -- Start VM on create/fork -- Move preview tokens to sandbox +- Document state/onStateChange # 1 New API @@ -28,12 +22,12 @@ const sandbox = await sdk.sandbox.create(SandboxOptions & StartOptions); sandbox.isUpToDate; sandbox.bootupType; sandbox.cluster; -sandbox.fork(Omit & StartOptions); -sandbox.restart(StartOptions); -sandbox.hibernate(); -sandbox.shutdown(); -sandbox.resume(); -sandbox.updateTier(); +sandbox.connect(); +sandbox.createBrowserSession(); +sandbox.createRestClient(); + +sdk.sandbox.shutdown(id); +sdk.sandbox.previewTokens.create(id); const session = await sandbox.createBrowserSession(); const client = sandbox.connect(); diff --git a/package.json b/package.json index f72f78c..b9af9f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "0.12.0", + "version": "1.0.0-beta.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts index 1ec6815..ba4b8a2 100644 --- a/src/PreviewTokens.ts +++ b/src/PreviewTokens.ts @@ -28,7 +28,7 @@ export interface PreviewToken extends BasePreviewTokenInfo { * with an authenticated API client (like Node.js). */ export class PreviewTokens extends Disposable { - constructor(private sandboxId: string, private apiClient: Client) { + constructor(private apiClient: Client) { super(); } @@ -40,11 +40,12 @@ export class PreviewTokens extends Disposable { * @returns The signed preview URL, or undefined if the port is not open */ getSignedPreviewUrl( + sandboxId: string, port: number, token: string, protocol = "https://" ): string { - return `${protocol}${this.sandboxId}-${port}.csb.app?preview_token=${token}`; + return `${protocol}${sandboxId}-${port}.csb.app?preview_token=${token}`; } /** @@ -54,12 +55,15 @@ export class PreviewTokens extends Disposable { * @param opts.expiresAt - Optional expiration date for the preview token * @returns A preview token that can be used with Ports.getSignedPreviewUrl */ - async create(opts: { expiresAt?: Date } = {}): Promise { + async create( + sandboxId: string, + opts: { expiresAt?: Date } = {} + ): Promise { const response = handleResponse( await previewTokenCreate({ client: this.apiClient, path: { - id: this.sandboxId, + id: sandboxId, }, body: { expires_at: opts.expiresAt?.toISOString(), @@ -89,12 +93,12 @@ export class PreviewTokens extends Disposable { * * @returns A list of preview tokens */ - async list(): Promise { + async list(sandboxId: string): Promise { const response = handleResponse( await previewTokenList({ client: this.apiClient, path: { - id: this.sandboxId, + id: sandboxId, }, }), "Failed to list preview tokens" @@ -117,12 +121,12 @@ export class PreviewTokens extends Disposable { * * @param tokenId - The ID of the token to revoke */ - async revoke(tokenId: string): Promise { + async revoke(sandboxId: string, tokenId: string): Promise { handleResponse( await previewTokenUpdate({ client: this.apiClient, path: { - id: this.sandboxId, + id: sandboxId, token_id: tokenId, }, body: { @@ -138,12 +142,12 @@ export class PreviewTokens extends Disposable { * This will immediately invalidate all tokens, and they can no longer be used * to access the sandbox preview. */ - async revokeAll(): Promise { + async revokeAll(sandboxId: string): Promise { handleResponse( await previewTokenRevokeAll({ client: this.apiClient, path: { - id: this.sandboxId, + id: sandboxId, }, }), "Failed to revoke preview tokens" @@ -158,6 +162,7 @@ export class PreviewTokens extends Disposable { * @returns The updated preview token info */ async update( + sandboxId: string, tokenId: string, expiresAt: Date | null ): Promise { @@ -165,7 +170,7 @@ export class PreviewTokens extends Disposable { await previewTokenUpdate({ client: this.apiClient, path: { - id: this.sandboxId, + id: sandboxId, token_id: tokenId, }, body: { diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index c8a9fb3..7144473 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -1,7 +1,14 @@ import type { Client } from "@hey-api/client-fetch"; import { createClient, createConfig } from "@hey-api/client-fetch"; -import { sandboxFork, sandboxList, vmStart } from "./api-clients/client"; +import { + sandboxFork, + sandboxList, + vmHibernate, + vmShutdown, + vmStart, +} from "./api-clients/client"; +import { VMTier } from "./VMTier"; import { Sandbox } from "./Sandbox"; import { getBaseUrl, @@ -18,11 +25,11 @@ import { SandboxInfo, SandboxListOpts, SandboxListResponse, - SandboxOpts, SandboxPrivacy, StartSandboxOpts, } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +import { PreviewTokens } from "./PreviewTokens"; export class SandboxClient { private apiClient: Client; @@ -35,6 +42,19 @@ export class SandboxClient { return "pcz35m"; } + /** + * Provider for generating preview tokens. These tokens can be used to generate signed + * preview URLs for private sandboxes. + * + * @example + * ```ts + * const sandbox = await sdk.sandbox.create(); + * const previewToken = await sandbox.previewTokens.createToken(); + * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); + * ``` + */ + public readonly previewTokens: PreviewTokens; + constructor(apiToken: string, opts: ClientOpts) { const baseUrl = process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(apiToken); @@ -49,41 +69,7 @@ export class SandboxClient { fetch: opts.fetch ?? fetch, }) ); - } - - private async start( - sandboxId: string, - startOpts?: StartSandboxOpts - ): Promise { - const startResult = await vmStart({ - client: this.apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); - - return getStartResponse(response); - } - - /** - * - */ - async resume(sandboxId: string) { - const startResponse = await this.start(sandboxId); - return new Sandbox(sandboxId, startResponse, this); + this.previewTokens = new PreviewTokens(this.apiClient); } private async createGitSandbox( @@ -95,7 +81,16 @@ export class SandboxClient { id: this.defaultTemplateId, }); - const client = await sandbox.connect(); + const client = await sandbox.connect( + // We do not want users to pass gitAccessToken on global user, because it + // can be read by other users + opts.gitAccessToken + ? { + id: "clone-admin", + permission: "write", + } + : undefined + ); await client.shells.run( [ @@ -143,9 +138,91 @@ export class SandboxClient { return new Sandbox( sandbox.id, getStartResponse(sandbox.start_response), - this + this.apiClient ); } + + private async start( + sandboxId: string, + startOpts?: StartSandboxOpts + ): Promise { + const startResult = await vmStart({ + client: this.apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }); + + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); + + return getStartResponse(response); + } + + /** + * + */ + async resume(sandboxId: string) { + const startResponse = await this.start(sandboxId); + return new Sandbox(sandboxId, startResponse, this.apiClient); + } + + /** + * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. + * + * @param sandboxId The ID of the sandbox to shutdown + */ + async shutdown(sandboxId: string): Promise { + const response = await vmShutdown({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }); + + handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); + } + + /** + * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in + * the project directory (`/project/sandbox`) will be preserved. + * + * Will resolve once the sandbox is rebooted. + */ + public async restart(sandboxId: string, opts?: StartSandboxOpts) { + await this.shutdown(sandboxId); + const startResponse = await this.start(sandboxId, opts); + + return new Sandbox(sandboxId, startResponse, this.apiClient); + } + + /** + * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time + * you start the sandbox it will be resumed from the last state it was in. + * + * @param sandboxId The ID of the sandbox to hibernate + */ + async hibernate(sandboxId: string): Promise { + const response = await vmHibernate({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }); + + handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); + } + /** * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from * any sandbox/template created on codesandbox.io, even your own templates) or don't pass diff --git a/src/clients/WebSocketClient/index.ts b/src/clients/WebSocketClient/index.ts index ad99205..1c45a45 100644 --- a/src/clients/WebSocketClient/index.ts +++ b/src/clients/WebSocketClient/index.ts @@ -19,7 +19,9 @@ export * from "./setup"; export * from "./shells"; export * from "./tasks"; -export class WebSocketClient extends Disposable { +export class WebSocketClient { + private disposable = new Disposable(); + static async init(session: SandboxSession, apiClient: Client) { const pitcherClient = await initPitcherClient( { @@ -70,18 +72,24 @@ export class WebSocketClient extends Disposable { /** * Namespace for all filesystem operations on this sandbox. */ - public readonly fs = this.addDisposable(new FileSystem(this.pitcherClient)); + public readonly fs = this.disposable.addDisposable( + new FileSystem(this.pitcherClient) + ); /** * Namespace for running shell commands on this sandbox. */ - public readonly shells = this.addDisposable(new Shells(this.pitcherClient)); + public readonly shells = this.disposable.addDisposable( + new Shells(this.pitcherClient) + ); /** * Namespace for detecting open ports on this sandbox, and getting preview URLs for * them. */ - public readonly ports = this.addDisposable(new Ports(this.pitcherClient)); + public readonly ports = this.disposable.addDisposable( + new Ports(this.pitcherClient) + ); /** * Namespace for all setup operations on this sandbox (installing dependencies, etc). @@ -89,7 +97,9 @@ export class WebSocketClient extends Disposable { * This provider is *experimental*, it might get changes or completely be removed * if it is not used. */ - public readonly setup = this.addDisposable(new Setup(this.pitcherClient)); + public readonly setup = this.disposable.addDisposable( + new Setup(this.pitcherClient) + ); /** * Namespace for all task operations on a sandbox. This includes running tasks, @@ -104,11 +114,11 @@ export class WebSocketClient extends Disposable { * This provider is *experimental*, it might get changes or completely be removed * if it is not used. */ - public readonly tasks = this.addDisposable(new Tasks(this.pitcherClient)); + public readonly tasks = this.disposable.addDisposable( + new Tasks(this.pitcherClient) + ); constructor(protected pitcherClient: IPitcherClient) { - super(); - // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { // dispose: @@ -116,7 +126,15 @@ export class WebSocketClient extends Disposable { // }; // this.addDisposable(metricsDisposable); - this.addDisposable(this.pitcherClient); + this.disposable.addDisposable(this.pitcherClient); + } + + get state() { + return this.pitcherClient.state; + } + + get onStateChange() { + return this.pitcherClient.onStateChange.bind(this.pitcherClient); } /** @@ -196,7 +214,7 @@ export class WebSocketClient extends Disposable { */ public disconnect() { this.pitcherClient.disconnect(); - this.dispose(); + this.disposable.dispose(); } private keepAliveInterval: NodeJS.Timeout | null = null; @@ -209,7 +227,7 @@ export class WebSocketClient extends Disposable { this.pitcherClient.clients.system.update(); }, 1000 * 30); - this.onWillDispose(() => { + this.disposable.onWillDispose(() => { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; diff --git a/src/sandbox.ts b/src/sandbox.ts index 7a4c34b..a770bd4 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,46 +1,25 @@ import { Disposable, - initPitcherClient, PitcherManagerResponse, type protocol as _protocol, } from "@codesandbox/pitcher-client"; import type { SandboxSession, SessionCreateOptions, - StartSandboxOpts, - CreateSandboxBaseOpts, SandboxBrowserSession, } from "./types"; -import { PreviewTokens } from "./PreviewTokens"; import { Client } from "@hey-api/client-fetch"; import { vmCreateSession, - vmHibernate, - vmShutdown, vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./api-clients/client"; import { handleResponse } from "./utils/api"; -import { SandboxClient } from "./SandboxClient"; import { VMTier } from "./VMTier"; import { WebSocketClient } from "./clients/WebSocketClient"; import { RestClient } from "./clients/RestClient"; -export class Sandbox extends Disposable { - private apiClient: Client; - /** - * Provider for generating preview tokens. These tokens can be used to generate signed - * preview URLs for private sandboxes. - * - * @example - * ```ts - * const sandbox = await sdk.sandbox.create(); - * const previewToken = await sandbox.previewTokens.createToken(); - * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); - * ``` - */ - public readonly previewTokens: PreviewTokens; - +export class Sandbox { get bootupType() { return this.pitcherManagerResponse.bootupType; } @@ -64,79 +43,8 @@ export class Sandbox extends Disposable { constructor( public id: string, private pitcherManagerResponse: PitcherManagerResponse, - private sandboxClient: SandboxClient - ) { - super(); - - this.apiClient = sandboxClient["apiClient"]; - this.previewTokens = this.addDisposable( - new PreviewTokens(this.id, this.apiClient) - ); - } - - /** - * Creates a sandbox by forking an existing sandbox reference. - * - * This function will also start & connect to the VM of the created sandbox as a ROOT session, and return a {@link Sandbox} - * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. - * - * @param opts Additional options for creating the sandbox - * - * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM - */ - async fork(opts: CreateSandboxBaseOpts & StartSandboxOpts): Promise { - return this.sandboxClient.create({ - ...opts, - source: "template", - id: this.id, - }); - } - - /** - * Try to start a sandbox that already exists, it will return the data of the started - * VM, which you can pass to the browser. In the browser you can call `connectToSandbox` with this - * data to control the VM without sharing your CodeSandbox API token in the browser. - * - * @param id the ID of the sandbox - * @returns The start data, contains a single use token to connect to the VM - */ - public async resume(): Promise { - this.pitcherManagerResponse = await this.sandboxClient["start"](this.id); - } - - /** - * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. - * - * @param sandboxId The ID of the sandbox to shutdown - */ - async shutdown(): Promise { - this.dispose(); - const response = await vmShutdown({ - client: this.apiClient, - path: { - id: this.id, - }, - }); - - handleResponse(response, `Failed to shutdown sandbox ${this.id}`); - } - - /** - * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time - * you start the sandbox it will be resumed from the last state it was in. - * - * @param sandboxId The ID of the sandbox to hibernate - */ - async hibernate(): Promise { - const response = await vmHibernate({ - client: this.apiClient, - path: { - id: this.id, - }, - }); - - handleResponse(response, `Failed to hibernate sandbox ${this.id}`); - } + private apiClient: Client + ) {} /** * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the @@ -146,34 +54,31 @@ export class Sandbox extends Disposable { * @param id The ID of the sandbox to update * @param tier The new VM tier */ - async updateTier(tier: VMTier): Promise { + async updateTier(sandboxId: string, tier: VMTier): Promise { const response = await vmUpdateSpecs({ client: this.apiClient, - path: { id: this.id }, + path: { id: sandboxId }, body: { tier: tier.name, }, }); - handleResponse(response, `Failed to update sandbox tier ${this.id}`); + handleResponse(response, `Failed to update sandbox tier ${sandboxId}`); } - /** - * Updates the hibernation timeout of a sandbox. - * - * @param id The ID of the sandbox to update - * @param timeoutSeconds The new hibernation timeout in seconds - */ - async updateHibernationTimeout(timeoutSeconds: number): Promise { + async updateHibernationTimeout( + sandboxId: string, + timeoutSeconds: number + ): Promise { const response = await vmUpdateHibernationTimeout({ client: this.apiClient, - path: { id: this.id }, + path: { id: sandboxId }, body: { hibernation_timeout_seconds: timeoutSeconds }, }); handleResponse( response, - `Failed to update hibernation timeout for sandbox ${this.id}` + `Failed to update hibernation timeout for sandbox ${sandboxId}` ); } @@ -244,15 +149,4 @@ export class Sandbox extends Disposable { pitcherVersion: this.pitcherManagerResponse.pitcherVersion, }; } - - /** - * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in - * the project directory (`/project/sandbox`) will be preserved. - * - * Will resolve once the sandbox is rebooted. - */ - public async restart(): Promise { - await this.shutdown(); - await this.resume(); - } } From dea66d29a88926a8d0bbf1fe015e19a130f1dc08 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 16:25:47 +0200 Subject: [PATCH 080/241] okay, sessions --- src/browser.ts | 6 +++--- src/index.ts | 4 ++-- src/sandbox.ts | 12 ++++++------ .../RestClient => sessions/RestSession}/index.ts | 4 ++-- .../RestSession}/sandbox-rest-container.ts | 0 .../RestSession}/sandbox-rest-fs.ts | 0 .../RestSession}/sandbox-rest-git.ts | 0 .../RestSession}/sandbox-rest-shell.ts | 0 .../RestSession}/sandbox-rest-system.ts | 0 .../RestSession}/sandbox-rest-task.ts | 0 .../WebSocketSession}/filesystem.ts | 0 .../WebSocketSession}/git.ts | 0 .../WebSocketSession}/index.ts | 4 ++-- .../WebSocketSession}/ports.ts | 0 .../WebSocketSession}/setup.ts | 0 .../WebSocketSession}/shells.ts | 0 .../WebSocketSession}/tasks.ts | 0 17 files changed, 15 insertions(+), 15 deletions(-) rename src/{clients/RestClient => sessions/RestSession}/index.ts (97%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-container.ts (100%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-fs.ts (100%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-git.ts (100%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-shell.ts (100%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-system.ts (100%) rename src/{clients/RestClient => sessions/RestSession}/sandbox-rest-task.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/filesystem.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/git.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/index.ts (98%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/ports.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/setup.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/shells.ts (100%) rename src/{clients/WebSocketClient => sessions/WebSocketSession}/tasks.ts (100%) diff --git a/src/browser.ts b/src/browser.ts index 53e68fd..9869695 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,6 +1,6 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; -import { WebSocketClient } from "./clients/WebSocketClient"; +import { WebSocketSession } from "./sessions/WebSocketSession"; /** * With this function you can connect to a sandbox from the browser. @@ -42,7 +42,7 @@ export async function connectToSandbox(options: { getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; -}): Promise { +}): Promise { const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -58,5 +58,5 @@ export async function connectToSandbox(options: { options.initStatusCb || (() => {}) ); - return new WebSocketClient(pitcherClient); + return new WebSocketSession(pitcherClient); } diff --git a/src/index.ts b/src/index.ts index 22c4062..5b89c35 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; -import * as WebSocketSession from "./clients/WebSocketClient"; +import * as WebSocketSession from "./sessions/WebSocketSession"; export { WebSocketSession }; @@ -19,7 +19,7 @@ function ensure(value: T | undefined, message: string): T { return value; } -export { RestClient as RestClient } from "./clients/RestClient"; +export { RestSession as RestSession } from "./sessions/RestSession"; export class CodeSandbox { public readonly sandbox: SandboxClient; diff --git a/src/sandbox.ts b/src/sandbox.ts index a770bd4..fe34c63 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -16,8 +16,8 @@ import { } from "./api-clients/client"; import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; -import { WebSocketClient } from "./clients/WebSocketClient"; -import { RestClient } from "./clients/RestClient"; +import { WebSocketSession } from "./sessions/WebSocketSession"; +import { RestSession } from "./sessions/RestSession"; export class Sandbox { get bootupType() { @@ -113,20 +113,20 @@ export class Sandbox { async connect( customSession?: SessionCreateOptions - ): Promise { + ): Promise { const session = customSession ? await this.createSession(customSession) : this.globalSession; - return WebSocketClient.init(session, this.apiClient); + return WebSocketSession.init(session, this.apiClient); } - async createRestClient(customSession?: SessionCreateOptions) { + async createRestSession(customSession?: SessionCreateOptions) { const session = customSession ? await this.createSession(customSession) : this.globalSession; - return new RestClient(session); + return new RestSession(session); } async createBrowserSession( diff --git a/src/clients/RestClient/index.ts b/src/sessions/RestSession/index.ts similarity index 97% rename from src/clients/RestClient/index.ts rename to src/sessions/RestSession/index.ts index e9444db..81f999e 100644 --- a/src/clients/RestClient/index.ts +++ b/src/sessions/RestSession/index.ts @@ -13,7 +13,7 @@ export interface ClientOpts { headers?: Record; } -export class RestClient { +export class RestSession { static id = 0; fs: SandboxRestFS; container: SandboxRestContainer; @@ -56,7 +56,7 @@ export class RestClient { const method = opts.url.substring(1); const message = { - id: RestClient.id++, + id: RestSession.id++, method, params: opts.body, }; diff --git a/src/clients/RestClient/sandbox-rest-container.ts b/src/sessions/RestSession/sandbox-rest-container.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-container.ts rename to src/sessions/RestSession/sandbox-rest-container.ts diff --git a/src/clients/RestClient/sandbox-rest-fs.ts b/src/sessions/RestSession/sandbox-rest-fs.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-fs.ts rename to src/sessions/RestSession/sandbox-rest-fs.ts diff --git a/src/clients/RestClient/sandbox-rest-git.ts b/src/sessions/RestSession/sandbox-rest-git.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-git.ts rename to src/sessions/RestSession/sandbox-rest-git.ts diff --git a/src/clients/RestClient/sandbox-rest-shell.ts b/src/sessions/RestSession/sandbox-rest-shell.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-shell.ts rename to src/sessions/RestSession/sandbox-rest-shell.ts diff --git a/src/clients/RestClient/sandbox-rest-system.ts b/src/sessions/RestSession/sandbox-rest-system.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-system.ts rename to src/sessions/RestSession/sandbox-rest-system.ts diff --git a/src/clients/RestClient/sandbox-rest-task.ts b/src/sessions/RestSession/sandbox-rest-task.ts similarity index 100% rename from src/clients/RestClient/sandbox-rest-task.ts rename to src/sessions/RestSession/sandbox-rest-task.ts diff --git a/src/clients/WebSocketClient/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts similarity index 100% rename from src/clients/WebSocketClient/filesystem.ts rename to src/sessions/WebSocketSession/filesystem.ts diff --git a/src/clients/WebSocketClient/git.ts b/src/sessions/WebSocketSession/git.ts similarity index 100% rename from src/clients/WebSocketClient/git.ts rename to src/sessions/WebSocketSession/git.ts diff --git a/src/clients/WebSocketClient/index.ts b/src/sessions/WebSocketSession/index.ts similarity index 98% rename from src/clients/WebSocketClient/index.ts rename to src/sessions/WebSocketSession/index.ts index 1c45a45..550ac58 100644 --- a/src/clients/WebSocketClient/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -19,7 +19,7 @@ export * from "./setup"; export * from "./shells"; export * from "./tasks"; -export class WebSocketClient { +export class WebSocketSession { private disposable = new Disposable(); static async init(session: SandboxSession, apiClient: Client) { @@ -67,7 +67,7 @@ export class WebSocketClient { () => {} ); - return new WebSocketClient(pitcherClient); + return new WebSocketSession(pitcherClient); } /** * Namespace for all filesystem operations on this sandbox. diff --git a/src/clients/WebSocketClient/ports.ts b/src/sessions/WebSocketSession/ports.ts similarity index 100% rename from src/clients/WebSocketClient/ports.ts rename to src/sessions/WebSocketSession/ports.ts diff --git a/src/clients/WebSocketClient/setup.ts b/src/sessions/WebSocketSession/setup.ts similarity index 100% rename from src/clients/WebSocketClient/setup.ts rename to src/sessions/WebSocketSession/setup.ts diff --git a/src/clients/WebSocketClient/shells.ts b/src/sessions/WebSocketSession/shells.ts similarity index 100% rename from src/clients/WebSocketClient/shells.ts rename to src/sessions/WebSocketSession/shells.ts diff --git a/src/clients/WebSocketClient/tasks.ts b/src/sessions/WebSocketSession/tasks.ts similarity index 100% rename from src/clients/WebSocketClient/tasks.ts rename to src/sessions/WebSocketSession/tasks.ts From 286a73a8e6f1410dd341ae879ea16221e633d49b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 18:44:46 +0200 Subject: [PATCH 081/241] move preview tokens --- package.json | 2 +- src/SandboxClient.ts | 43 +++---------------------------------------- src/index.ts | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index b9af9f8..3ce19fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.0", + "version": "1.0.0-beta.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index 7144473..e656709 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -8,15 +8,9 @@ import { vmShutdown, vmStart, } from "./api-clients/client"; -import { VMTier } from "./VMTier"; import { Sandbox } from "./Sandbox"; -import { - getBaseUrl, - getStartOptions, - getStartResponse, - handleResponse, -} from "./utils/api"; -import { ClientOpts } from "."; +import { getStartOptions, getStartResponse, handleResponse } from "./utils/api"; + import { CreateSandboxGitSourceOpts, CreateSandboxOpts, @@ -29,11 +23,8 @@ import { StartSandboxOpts, } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; -import { PreviewTokens } from "./PreviewTokens"; export class SandboxClient { - private apiClient: Client; - get defaultTemplateId() { if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; @@ -42,35 +33,7 @@ export class SandboxClient { return "pcz35m"; } - /** - * Provider for generating preview tokens. These tokens can be used to generate signed - * preview URLs for private sandboxes. - * - * @example - * ```ts - * const sandbox = await sdk.sandbox.create(); - * const previewToken = await sandbox.previewTokens.createToken(); - * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); - * ``` - */ - public readonly previewTokens: PreviewTokens; - - constructor(apiToken: string, opts: ClientOpts) { - const baseUrl = - process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(apiToken); - - this.apiClient = this.apiClient = createClient( - createConfig({ - baseUrl, - headers: { - Authorization: `Bearer ${apiToken}`, - ...(opts.headers ?? {}), - }, - fetch: opts.fetch ?? fetch, - }) - ); - this.previewTokens = new PreviewTokens(this.apiClient); - } + constructor(private apiClient: Client) {} private async createGitSandbox( opts: CreateSandboxGitSourceOpts & StartSandboxOpts diff --git a/src/index.ts b/src/index.ts index 5b89c35..8ac6c80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,9 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; import * as WebSocketSession from "./sessions/WebSocketSession"; +import { PreviewTokens } from "./PreviewTokens"; +import { createClient, createConfig } from "@hey-api/client-fetch"; +import { getBaseUrl } from "./utils/api"; export { WebSocketSession }; @@ -24,6 +27,19 @@ export { RestSession as RestSession } from "./sessions/RestSession"; export class CodeSandbox { public readonly sandbox: SandboxClient; + /** + * Provider for generating preview tokens. These tokens can be used to generate signed + * preview URLs for private sandboxes. + * + * @example + * ```ts + * const sandbox = await sdk.sandbox.create(); + * const previewToken = await sandbox.previewTokens.createToken(); + * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); + * ``` + */ + public readonly previewTokens: PreviewTokens; + constructor(apiToken?: string, readonly opts: ClientOpts = {}) { const evaluatedApiToken = apiToken || @@ -34,6 +50,21 @@ export class CodeSandbox { "CSB_API_KEY or TOGETHER_API_KEY is not set" ); - this.sandbox = new SandboxClient(evaluatedApiToken, opts); + const baseUrl = + process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(evaluatedApiToken); + + const apiClient = createClient( + createConfig({ + baseUrl, + headers: { + Authorization: `Bearer ${apiToken}`, + ...(opts.headers ?? {}), + }, + fetch: opts.fetch ?? fetch, + }) + ); + + this.sandbox = new SandboxClient(apiClient); + this.previewTokens = new PreviewTokens(apiClient); } } From 5a02b2864059fa1ae2a0950345c88df2a6a8a61a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 2 May 2025 09:03:57 +0200 Subject: [PATCH 082/241] fix disposal of shell --- TODO.md | 9 ++------- src/sessions/WebSocketSession/shells.ts | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index bba8262..6939f5c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,9 @@ ## QUESTIONS -- Does updateTier and updateHibernation require VM start? - ## USER QUESTIONS -- Do you need to create/fork a sandbox without starting the VM -- Do you need to shutdown/restart the sandbox without resuming it first - ## TODO -- Document state/onStateChange - # 1 New API ```ts @@ -25,6 +18,8 @@ sandbox.cluster; sandbox.connect(); sandbox.createBrowserSession(); sandbox.createRestClient(); +sandbox.updateTier(); +sandbox.updateHibernationTimeout(); sdk.sandbox.shutdown(id); sdk.sandbox.previewTokens.create(id); diff --git a/src/sessions/WebSocketSession/shells.ts b/src/sessions/WebSocketSession/shells.ts index b66ebf1..d04c4db 100644 --- a/src/sessions/WebSocketSession/shells.ts +++ b/src/sessions/WebSocketSession/shells.ts @@ -232,7 +232,7 @@ export class ShellInstance extends Disposable { this.onWillDispose(async () => { try { - await this.pitcherClient.clients.shell.close(this.shell.shellId); + await this.pitcherClient.clients.shell.delete(this.shell.shellId); } catch (e) { // Ignore errors, we don't care if it's already closed or if we disconnected } From 01b6568f0e8dcb3da37085440e312dac854283f3 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 2 May 2025 09:42:45 +0200 Subject: [PATCH 083/241] updated todo --- TODO.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 6939f5c..8bc5fad 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,18 @@ ## QUESTIONS +- Have we removed the "This phishing overlay for SDK users?" https://github.com/codesandbox/codesandbox-sdk/issues/5" +- What you mean about "Dynamically resize terminal"? You can not really do that? +- Python SDK priority? +- Have we added Bun to default template? https://github.com/codesandbox/codesandbox-sdk/issues/62 +- Do we allow deleting sandboxes? https://github.com/codesandbox/codesandbox-sdk/issues/86 +- State of env var? https://github.com/codesandbox/codesandbox-sdk/issues/90 + ## USER QUESTIONS ## TODO +- Highlight snapshot building in docs + # 1 New API ```ts diff --git a/package.json b/package.json index 3ce19fe..a0dea4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 6e2705f12b492cde80b0cdad823ffa637f0b722a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 2 May 2025 13:22:51 +0200 Subject: [PATCH 084/241] export all types and test task listener --- TODO.md | 23 +++++-- src/browser.ts | 2 + src/index.ts | 7 +- src/sessions/RestSession/index.ts | 7 +- src/sessions/WebSocketSession/index.ts | 6 +- src/sessions/WebSocketSession/tasks.ts | 91 +++++++++++++++++++++++++- 6 files changed, 116 insertions(+), 20 deletions(-) diff --git a/TODO.md b/TODO.md index 8bc5fad..7272249 100644 --- a/TODO.md +++ b/TODO.md @@ -1,17 +1,28 @@ ## QUESTIONS -- Have we removed the "This phishing overlay for SDK users?" https://github.com/codesandbox/codesandbox-sdk/issues/5" -- What you mean about "Dynamically resize terminal"? You can not really do that? -- Python SDK priority? -- Have we added Bun to default template? https://github.com/codesandbox/codesandbox-sdk/issues/62 -- Do we allow deleting sandboxes? https://github.com/codesandbox/codesandbox-sdk/issues/86 -- State of env var? https://github.com/codesandbox/codesandbox-sdk/issues/90 +- Should Snapshot Tags work like NPM? + + - Create Sandbox with no wakeup config + - Write files + - Wait for condition + - Hibernate + - New endpoint to tag it + - Sandbox is tagged (Check if hibernated?) + - BIG QUESTION: Should we force prevent starting the Sandbox? What about TanStack + + - New endpoint to create an alias to any tag + + - Change endpoint for Sandbox creation to allow tags/aliases as id + + - What is Tag / Alias format? ## USER QUESTIONS ## TODO - Highlight snapshot building in docs +- https://github.com/codesandbox/codesandbox-applications/pull/4645 +- Publish browser-static-server # 1 New API diff --git a/src/browser.ts b/src/browser.ts index 9869695..676569d 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -2,6 +2,8 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; import { WebSocketSession } from "./sessions/WebSocketSession"; +export { WebSocketSession }; + /** * With this function you can connect to a sandbox from the browser. * diff --git a/src/index.ts b/src/index.ts index 8ac6c80..8b48017 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,12 +7,13 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; -import * as WebSocketSession from "./sessions/WebSocketSession"; + import { PreviewTokens } from "./PreviewTokens"; import { createClient, createConfig } from "@hey-api/client-fetch"; import { getBaseUrl } from "./utils/api"; -export { WebSocketSession }; +export * from "./sessions/WebSocketSession"; +export * from "./sessions/RestSession"; function ensure(value: T | undefined, message: string): T { if (!value) { @@ -22,8 +23,6 @@ function ensure(value: T | undefined, message: string): T { return value; } -export { RestSession as RestSession } from "./sessions/RestSession"; - export class CodeSandbox { public readonly sandbox: SandboxClient; diff --git a/src/sessions/RestSession/index.ts b/src/sessions/RestSession/index.ts index 81f999e..433f305 100644 --- a/src/sessions/RestSession/index.ts +++ b/src/sessions/RestSession/index.ts @@ -6,12 +6,7 @@ import { SandboxRestGit } from "./sandbox-rest-git"; import { SandboxRestShell } from "./sandbox-rest-shell"; import { SandboxRestSystem } from "./sandbox-rest-system"; import { SandboxRestTask } from "./sandbox-rest-task"; -import { SandboxSession } from "../../types"; - -export interface ClientOpts { - fetch?: typeof fetch; - headers?: Record; -} +import { ClientOpts, SandboxSession } from "../../types"; export class RestSession { static id = 0; diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 550ac58..df94400 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -129,7 +129,8 @@ export class WebSocketSession { this.disposable.addDisposable(this.pitcherClient); } - get state() { + // Not sure why we have to explicitly type this + get state(): typeof this.pitcherClient.state { return this.pitcherClient.state; } @@ -213,8 +214,7 @@ export class WebSocketSession { * automatically hibernate after an inactivity timer). */ public disconnect() { - this.pitcherClient.disconnect(); - this.disposable.dispose(); + return this.pitcherClient.disconnect(); } private keepAliveInterval: NodeJS.Timeout | null = null; diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index 941a19c..aa1f1ac 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -1,4 +1,9 @@ -import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; +import { + Emitter, + IDisposable, + type IPitcherClient, + type protocol, +} from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; @@ -19,9 +24,93 @@ export type Task = TaskDefinition & { ports: protocol.port.Port[]; }; +type TaskShellSubscription = { + shellId: string; + disposable: IDisposable; +}; + export class Tasks extends Disposable { + private shellOutputListeners: Record = {}; + private onTaskOutputEmitter = this.addDisposable( + new Emitter<{ + taskId: string; + output: string; + }>() + ); + public readonly onTaskOutput = this.onTaskOutputEmitter.event; + constructor(private pitcherClient: IPitcherClient) { super(); + this.pitcherClient.clients.shell.onShellCreated((shell) => { + console.log("CREATED", shell); + }); + this.addDisposable( + pitcherClient.clients.task.onTaskUpdate((task) => + this.listenToShellOutput(task) + ) + ); + this.onWillDispose(() => { + Object.values(this.shellOutputListeners).forEach((listener) => + listener.disposable.dispose() + ); + }); + } + + private async listenToShellOutput(task: protocol.task.TaskDTO) { + const existingListener = this.shellOutputListeners[task.id]; + + console.log("TASK UPDATED", JSON.stringify(task, null, 2)); + + // Already have shell registered + if (existingListener && task.shell?.shellId === existingListener.shellId) { + return; + } + + // Has removed shell + if ( + existingListener && + (!task.shell || task.shell.shellId !== existingListener.shellId) + ) { + existingListener.disposable.dispose(); + } + + // No new shell + if (!task.shell) { + return; + } + + console.log("ADDING NEW SHELL!!!!!"); + + // Has new shell + const taskShellId = task.shell.shellId; + let listener: TaskShellSubscription = { + shellId: taskShellId, + disposable: this.pitcherClient.clients.shell.onShellOut( + ({ shellId, out }) => { + console.log("WTF", shellId, out); + if (shellId === taskShellId) { + this.onTaskOutputEmitter.fire({ + taskId: task.id, + output: out, + }); + } + } + ), + }; + + this.shellOutputListeners[task.id] = listener; + + this.pitcherClient.clients.shell.open(task.shell.shellId, { + cols: 80, + rows: 24, + }); + + /* + this.onTaskOutputEmitter.fire({ + taskId: task.id, + output: shell.buffer.join("\n"), + }); + */ } /** From 1389d019e550e4d83450f01e2c0a7231264b457f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 5 May 2025 14:04:27 +0200 Subject: [PATCH 085/241] improve shells --- package.json | 2 +- src/sessions/WebSocketSession/ports.ts | 8 -------- src/sessions/WebSocketSession/shells.ts | 19 ++++++++++++++----- src/sessions/WebSocketSession/tasks.ts | 4 ++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index a0dea4b..d109577 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index bdcba2f..80964d8 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -60,14 +60,6 @@ export class Ports extends Disposable { return this.pitcherClient.clients.port.getPorts(); } - getPreviewUrl(port: number, protocol = "https://"): string | undefined { - const hostname = this.pitcherClient.clients.port - .getPorts() - .find((p) => p.port === port)?.url; - - return hostname ? `${protocol}${hostname}` : undefined; - } - /** * Wait for a port to be opened. * diff --git a/src/sessions/WebSocketSession/shells.ts b/src/sessions/WebSocketSession/shells.ts index d04c4db..9e433e9 100644 --- a/src/sessions/WebSocketSession/shells.ts +++ b/src/sessions/WebSocketSession/shells.ts @@ -246,6 +246,10 @@ export class ShellInstance extends Disposable { return this.shell.shellId as string; } + get type(): string { + return this.shell.shellType; + } + /** * Gets the name of the shell. */ @@ -264,11 +268,16 @@ export class ShellInstance extends Disposable { return this.shell.status; } - async write(input: string): Promise { - await this.pitcherClient.clients.shell.send(this.shell.shellId, input, { - cols: 80, - rows: 24, - }); + async write(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { + await this.pitcherClient.clients.shell.send( + this.shell.shellId, + input, + dimensions + ); + } + + async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { + return this.write(input + "\n"); } // TODO: allow for kill signals diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index aa1f1ac..f96be54 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -143,6 +143,10 @@ export class Tasks extends Disposable { return taskFromDTO(task); } + + async stopTask(taskId: string) { + await this.pitcherClient.clients.task.stopTask(taskId); + } } function taskFromDTO(value: protocol.task.TaskDTO): Task { From f12327ae4786d2f4ea1b9771625771710264fafd Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 6 May 2025 10:56:48 +0200 Subject: [PATCH 086/241] previews --- esbuild.cjs | 4 +- package.json | 2 +- src/PreviewTokens.ts | 11 +- src/bin/commands/build.ts | 28 ++-- src/bin/commands/sandbox/list.ts | 10 +- src/bin/commands/sandbox/preview-tokens.ts | 15 +- .../WebSocketSession}/browser.ts | 6 +- src/sessions/WebSocketSession/filesystem.ts | 14 +- src/sessions/WebSocketSession/index.ts | 32 ++-- src/sessions/WebSocketSession/ports.ts | 23 ++- .../WebSocketSession/previews/Preview.ts | 153 ++++++++++++++++++ .../WebSocketSession/previews/index.ts | 16 ++ .../previews/preview-script.ts | 148 +++++++++++++++++ .../WebSocketSession/previews/types.ts | 103 ++++++++++++ src/sessions/WebSocketSession/setup.ts | 17 +- src/sessions/WebSocketSession/shells.ts | 34 ++-- src/sessions/WebSocketSession/tasks.ts | 24 ++- 17 files changed, 544 insertions(+), 96 deletions(-) rename src/{ => sessions/WebSocketSession}/browser.ts (91%) create mode 100644 src/sessions/WebSocketSession/previews/Preview.ts create mode 100644 src/sessions/WebSocketSession/previews/index.ts create mode 100644 src/sessions/WebSocketSession/previews/preview-script.ts create mode 100644 src/sessions/WebSocketSession/previews/types.ts diff --git a/esbuild.cjs b/esbuild.cjs index ea75e46..a79791b 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -21,7 +21,7 @@ Promise.all([ // Browser builds: // CommonJS build esbuild.build({ - entryPoints: ["src/browser.ts"], + entryPoints: ["src/sessions/WebSocketSession/browser.ts"], bundle: true, format: "cjs", // .cjs extension is required because "type": "module" is set in package.json @@ -32,7 +32,7 @@ Promise.all([ // ESM build esbuild.build({ - entryPoints: ["src/browser.ts"], + entryPoints: ["src/sessions/WebSocketSession/browser.ts"], bundle: true, format: "esm", outdir: "dist/esm", diff --git a/package.json b/package.json index d109577..4c72dbf 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "default": "./dist/cjs/index.cjs" }, "./browser": { - "types": "./dist/esm/browser.d.ts", + "types": "./dist/esm/sessions/WebSocketSession/browser.d.ts", "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs", "default": "./dist/cjs/browser.cjs" diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts index ba4b8a2..559c3d2 100644 --- a/src/PreviewTokens.ts +++ b/src/PreviewTokens.ts @@ -20,6 +20,7 @@ export interface PreviewTokenInfo extends BasePreviewTokenInfo { export interface PreviewToken extends BasePreviewTokenInfo { token: string; + sandboxId: string; } /** @@ -39,13 +40,8 @@ export class PreviewTokens extends Disposable { * @param token - The preview token to sign the URL with * @returns The signed preview URL, or undefined if the port is not open */ - getSignedPreviewUrl( - sandboxId: string, - port: number, - token: string, - protocol = "https://" - ): string { - return `${protocol}${sandboxId}-${port}.csb.app?preview_token=${token}`; + getSignedPreviewUrl(token: PreviewToken, port: number): string { + return `https://${token.sandboxId}-${port}.csb.app?preview_token=${token.token}`; } /** @@ -77,6 +73,7 @@ export class PreviewTokens extends Disposable { } return { + sandboxId, token: response.token.token, expiresAt: response.token.expires_at ? new Date(response.token.expires_at) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 015b9ce..fc000d8 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -7,7 +7,13 @@ import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { WebSocketSession, VMTier, CodeSandbox, Sandbox } from "../../"; +import { + WebSocketSession, + VMTier, + CodeSandbox, + Sandbox, + SetupProgress, +} from "../../"; import { sandboxCreate, @@ -143,7 +149,7 @@ export const buildCommand: yargs.CommandModule< ipcountry: argv.ipCountry, vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); - const sandbox = new Sandbox(sandboxId, startResponse, sdk.sandbox); + const sandbox = new Sandbox(sandboxId, startResponse, apiClient); const session = await sandbox.connect(); spinner.succeed("Sandbox opened"); @@ -165,14 +171,12 @@ export const buildCommand: yargs.CommandModule< spinner.succeed("Files written to sandbox"); spinner.start("Rebooting sandbox..."); - await sandbox.restart(); + await sdk.sandbox.restart(sandbox.id); spinner.succeed("Sandbox restarted"); } const disposableStore = new DisposableStore(); - const handleProgress = async ( - progress: WebSocketSession.SetupProgress - ) => { + const handleProgress = async (progress: SetupProgress) => { if (progress.state === "IN_PROGRESS" && progress.steps.length > 0) { const step = progress.steps[progress.currentStepIndex]; if (!step) { @@ -258,11 +262,11 @@ export const buildCommand: yargs.CommandModule< let timeout; const portInfo = await Promise.race([ session.ports.waitForPort(port), - new Promise( - (_, reject) => + new Promise( + (resolve) => (timeout = setTimeout( () => - reject( + resolve( new Error( `Waiting for port ${port} timed out after 60s` ) @@ -273,13 +277,13 @@ export const buildCommand: yargs.CommandModule< ]); clearTimeout(timeout); - if (!(portInfo instanceof WebSocketSession.PortInfo)) { + if (portInfo instanceof Error) { throw portInfo; } // eslint-disable-next-line no-constant-condition while (true) { - const res = await fetch(portInfo.getPreviewUrl()); + const res = await fetch("https://" + portInfo.url); if (res.status !== 502 && res.status !== 503) { spinner.succeed(`Port ${port} is open (status ${res.status})`); break; @@ -305,7 +309,7 @@ export const buildCommand: yargs.CommandModule< } spinner.start("Creating memory snapshot..."); - await sandbox.hibernate(); + await sdk.sandbox.hibernate(sandbox.id); spinner.succeed( "Snapshot created, you can use this sandbox id as your template:" ); diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 9210043..5d4c52c 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -1,11 +1,11 @@ import ora from "ora"; import Table from "cli-table3"; -import { CodeSandbox } from "../../../"; -import type { - SandboxListOpts, - SandboxInfo, +import { + CodeSandbox, PaginationOpts, -} from "../../../sandbox-client"; + SandboxInfo, + SandboxListOpts, +} from "../../../"; type OutputFormat = { field: string; diff --git a/src/bin/commands/sandbox/preview-tokens.ts b/src/bin/commands/sandbox/preview-tokens.ts index d234455..8457af7 100644 --- a/src/bin/commands/sandbox/preview-tokens.ts +++ b/src/bin/commands/sandbox/preview-tokens.ts @@ -11,7 +11,7 @@ export async function listPreviewTokens(sandboxId: string) { const spinner = ora("Fetching preview tokens...").start(); try { - const tokens = await sdk.sandbox.previewTokens.list(sandboxId); + const tokens = await sdk.previewTokens.list(sandboxId); spinner.stop(); if (tokens.length === 0) { @@ -68,10 +68,9 @@ export async function createPreviewToken( const spinner = ora("Creating preview token...").start(); try { - const token = await sdk.sandbox.previewTokens.create( - sandboxId, - expiresAt ? new Date(expiresAt) : null - ); + const token = await sdk.previewTokens.create(sandboxId, { + expiresAt: expiresAt ? new Date(expiresAt) : undefined, + }); spinner.stop(); const table = new Table({ @@ -122,7 +121,7 @@ export async function revokePreviewToken( const spinner = ora("Revoking preview token...").start(); try { - await sdk.sandbox.previewTokens.revoke(sandboxId, previewTokenId); + await sdk.previewTokens.revoke(sandboxId, previewTokenId); spinner.stop(); console.log("Preview token revoked successfully"); } catch (error) { @@ -140,7 +139,7 @@ export async function updatePreviewToken( const spinner = ora("Updating preview token...").start(); try { - await sdk.sandbox.previewTokens.update( + await sdk.previewTokens.update( sandboxId, previewTokenId, expiresAt ? new Date(expiresAt) : null @@ -158,7 +157,7 @@ export async function revokeAllPreviewTokens(sandboxId: string) { const spinner = ora("Revoking all preview tokens...").start(); try { - await sdk.sandbox.previewTokens.revokeAll(sandboxId); + await sdk.previewTokens.revokeAll(sandboxId); spinner.stop(); console.log("All preview tokens have been revoked"); } catch (error) { diff --git a/src/browser.ts b/src/sessions/WebSocketSession/browser.ts similarity index 91% rename from src/browser.ts rename to src/sessions/WebSocketSession/browser.ts index 676569d..7ed3635 100644 --- a/src/browser.ts +++ b/src/sessions/WebSocketSession/browser.ts @@ -1,8 +1,8 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; -import { WebSocketSession } from "./sessions/WebSocketSession"; +import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "../../types"; +import { WebSocketSession } from "."; -export { WebSocketSession }; +export * from "."; /** * With this function you can connect to a sandbox from the browser. diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts index 69620c3..2a3b86f 100644 --- a/src/sessions/WebSocketSession/filesystem.ts +++ b/src/sessions/WebSocketSession/filesystem.ts @@ -38,9 +38,15 @@ export type Watcher = { onEvent: Event; }; -export class FileSystem extends Disposable { - constructor(private pitcherClient: IPitcherClient) { - super(); +export class FileSystem { + private disposable = new Disposable(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); } /** @@ -255,7 +261,7 @@ export class FileSystem extends Disposable { }, onEvent: emitter.event, }; - this.addDisposable(watcher); + this.disposable.addDisposable(watcher); return watcher; } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index df94400..9f2c85e 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -1,6 +1,6 @@ import { initPitcherClient } from "@codesandbox/pitcher-client"; +import { Disposable } from "../../utils/disposable"; import { - Disposable, protocol as _protocol, type IPitcherClient, } from "@codesandbox/pitcher-client"; @@ -12,12 +12,14 @@ import { Shells } from "./shells"; import { Tasks } from "./tasks"; import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../../types"; import { Client } from "@hey-api/client-fetch"; +import { Previews } from "./previews"; export * from "./filesystem"; export * from "./ports"; export * from "./setup"; export * from "./shells"; export * from "./tasks"; +export * from "./previews"; export class WebSocketSession { private disposable = new Disposable(); @@ -72,24 +74,23 @@ export class WebSocketSession { /** * Namespace for all filesystem operations on this sandbox. */ - public readonly fs = this.disposable.addDisposable( - new FileSystem(this.pitcherClient) - ); + public readonly fs = new FileSystem(this.disposable, this.pitcherClient); + + /** + * Namespace for previews + */ + public readonly previews = new Previews(this.disposable); /** * Namespace for running shell commands on this sandbox. */ - public readonly shells = this.disposable.addDisposable( - new Shells(this.pitcherClient) - ); + public readonly shells = new Shells(this.disposable, this.pitcherClient); /** * Namespace for detecting open ports on this sandbox, and getting preview URLs for * them. */ - public readonly ports = this.disposable.addDisposable( - new Ports(this.pitcherClient) - ); + public readonly ports = new Ports(this.disposable, this.pitcherClient); /** * Namespace for all setup operations on this sandbox (installing dependencies, etc). @@ -97,9 +98,7 @@ export class WebSocketSession { * This provider is *experimental*, it might get changes or completely be removed * if it is not used. */ - public readonly setup = this.disposable.addDisposable( - new Setup(this.pitcherClient) - ); + public readonly setup = new Setup(this.disposable, this.pitcherClient); /** * Namespace for all task operations on a sandbox. This includes running tasks, @@ -114,9 +113,7 @@ export class WebSocketSession { * This provider is *experimental*, it might get changes or completely be removed * if it is not used. */ - public readonly tasks = this.disposable.addDisposable( - new Tasks(this.pitcherClient) - ); + public readonly tasks = new Tasks(this.disposable, this.pitcherClient); constructor(protected pitcherClient: IPitcherClient) { // TODO: Bring this back once metrics polling does not reset inactivity @@ -240,4 +237,7 @@ export class WebSocketSession { } } } + dispose() { + this.disposable.dispose(); + } } diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index 80964d8..90a2d78 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -3,29 +3,37 @@ import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; -export class Ports extends Disposable { - private onDidPortOpenEmitter = this.addDisposable( +export class Ports { + private disposable = new Disposable(); + private onDidPortOpenEmitter = this.disposable.addDisposable( new Emitter() ); get onDidPortOpen() { return this.onDidPortOpenEmitter.event; } - private onDidPortCloseEmitter = this.addDisposable(new Emitter()); + private onDidPortCloseEmitter = this.disposable.addDisposable( + new Emitter() + ); get onDidPortClose() { return this.onDidPortCloseEmitter.event; } private lastOpenedPorts: Set = new Set(); - constructor(private pitcherClient: IPitcherClient) { - super(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); pitcherClient.clients.port.getPorts().forEach((port) => { this.lastOpenedPorts.add(port.port); }); - this.addDisposable( + this.disposable.addDisposable( pitcherClient.clients.port.onPortsUpdated((ports) => { const openedPorts = ports.filter( (port) => !this.lastOpenedPorts.has(port.port) @@ -78,6 +86,7 @@ export class Ports extends Disposable { return new Promise((resolve, reject) => { // Check if port is already open const portInfo = this.getOpenedPorts().find((p) => p.port === port); + if (portInfo) { resolve(portInfo); return; @@ -96,7 +105,7 @@ export class Ports extends Disposable { } // Listen for port open events - const disposable = this.addDisposable( + const disposable = this.disposable.addDisposable( this.onDidPortOpen((portInfo) => { if (portInfo.port === port) { if (timeoutId !== undefined) { diff --git a/src/sessions/WebSocketSession/previews/Preview.ts b/src/sessions/WebSocketSession/previews/Preview.ts new file mode 100644 index 0000000..f361593 --- /dev/null +++ b/src/sessions/WebSocketSession/previews/Preview.ts @@ -0,0 +1,153 @@ +import { Emitter } from "@codesandbox/pitcher-client"; +import { + BaseMessageToPreview, + BaseMessageFromPreview, + InjectFunction, + InjectMessage, + Message, +} from "./types"; +import { Disposable } from "../../../utils/disposable"; +import { injectAndInvokeInsidePreview } from "./preview-script"; + +type PreviewStatus = "DISCONNECTED" | "CONNECTED"; + +export class Preview< + MessageToPreview extends Message = BaseMessageToPreview, + MessageFromPreview extends Message = BaseMessageFromPreview +> { + private subscribers: Set< + (message: MessageFromPreview | BaseMessageFromPreview) => void + > = new Set(); + private _status: PreviewStatus = "DISCONNECTED"; + get status() { + return this._status; + } + private set status(status: PreviewStatus) { + this._status = status; + this.onStatusChangeEmitter.fire(status); + } + private origin: string; + private windowListener: (event: MessageEvent) => void; + private onPreviewLoad: () => void; + private onStatusChangeEmitter = new Emitter(); + + onStatusChange = this.onStatusChangeEmitter.event; + iframe: HTMLIFrameElement; + + constructor(sessionDisposable: Disposable, src: string) { + sessionDisposable.onWillDispose(() => { + this.dispose(); + }); + this.origin = new URL(src).origin; + this.iframe = this.createIframe(src); + this.windowListener = (event: MessageEvent) => { + if (event.origin !== this.origin) return; + if (event.source !== this.iframe.contentWindow) return; + + const message = event.data as MessageFromPreview; + + if (message.type === "PREVIEW_UNLOADING") { + this.status = "DISCONNECTED"; + } + + if ( + !message.type || + message.type.startsWith("INJECT_") || + message.type === "TO_DEVTOOL" + ) { + return; + } + + this.subscribers.forEach((onMessage) => { + onMessage(event.data); + }); + }; + + this.onPreviewLoad = () => { + this.status = "CONNECTED"; + console.log("INJECT SCRIPT"); + this._injectAndInvoke(injectAndInvokeInsidePreview, {}); + }; + + window.addEventListener("message", this.windowListener); + this.iframe.addEventListener("load", this.onPreviewLoad); + } + + private createIframe(src: string) { + const iframe = document.createElement("iframe"); + iframe.allow = + "clipboard-read; clipboard-write; accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; xr-spatial-tracking; cross-origin-isolated"; + // The 'sandbox' attribute is readonly after the iframe is added to the DOM. + // Set it before appending to the DOM or setting 'src'. + iframe.setAttribute( + "sandbox", + "allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" + ); + + iframe.src = src; + + return iframe; + } + + private sendMessage(message: MessageToPreview | BaseMessageToPreview) { + if (this.status === "CONNECTED") { + this.iframe.contentWindow?.postMessage(message, "*"); + } + } + + private _injectAndInvoke< + Incoming extends Message, + Outgoing extends Message, + Scope extends Record + >(func: InjectFunction, scope: Scope) { + if (this.status === "CONNECTED") { + const injectMessage: InjectMessage = { + code: `exports.activate = ${func.toString()}`, + type: "INJECT_AND_INVOKE", + uid: "TODO", // TODO: supply a unique id from the workspace feature. + scope, + }; + this.iframe.contentWindow?.postMessage(injectMessage, "*"); + } + } + + injectAndInvoke>( + func: InjectFunction, + scope: Scope + ) { + this._injectAndInvoke(func, scope); + } + + onMessage( + subscriber: (message: MessageFromPreview | BaseMessageFromPreview) => void + ) { + this.subscribers.add(subscriber); + return () => { + this.subscribers.delete(subscriber); + }; + } + refresh() { + this.sendMessage({ + type: "REFRESH", + }); + } + setUrl(url: string) { + this.iframe.src = url; + } + back() { + this.sendMessage({ + type: "GO_BACK", + }); + } + forward() { + this.sendMessage({ + type: "GO_FORWARD", + }); + } + dispose() { + this.subscribers.clear(); + this.status = "DISCONNECTED"; + window.removeEventListener("message", this.windowListener); + this.iframe.removeEventListener("load", this.onPreviewLoad); + } +} diff --git a/src/sessions/WebSocketSession/previews/index.ts b/src/sessions/WebSocketSession/previews/index.ts new file mode 100644 index 0000000..7c66920 --- /dev/null +++ b/src/sessions/WebSocketSession/previews/index.ts @@ -0,0 +1,16 @@ +import { Disposable } from "../../../utils/disposable"; +import { Preview } from "./Preview"; + +export { Preview } from "./Preview"; + +export class Previews { + private disposable = new Disposable(); + constructor(private sessionDisposable: Disposable) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); + } + create(src: string) { + return new Preview(this.sessionDisposable, src); + } +} diff --git a/src/sessions/WebSocketSession/previews/preview-script.ts b/src/sessions/WebSocketSession/previews/preview-script.ts new file mode 100644 index 0000000..7054275 --- /dev/null +++ b/src/sessions/WebSocketSession/previews/preview-script.ts @@ -0,0 +1,148 @@ +import type { + BaseMessageToPreview, + BaseMessageFromPreview, + InjectFunction, + PreviewProtocolType, +} from "./types"; + +/** + * Be very, very careful when editing this function. + * Before it is called, this function is stringified, then evaled inside the Preview iFrame. + * Referencing ANY variables outside of this function will not work. + */ +export const injectAndInvokeInsidePreview: InjectFunction< + BaseMessageToPreview, + BaseMessageFromPreview +> = ({ previewWindow, previewProtocol }) => { + // #region history logic + + // @ts-expect-error please ignore __proto__ :) + const origHistoryProto = window.history.__proto__; // eslint-disable-line no-proto + const historyList: Array<{ url: string; state: unknown }> = []; + let historyPosition = -1; + let disableNextHashChange = false; + + function pushHistory(url: string, state: unknown) { + // remove "future" locations + historyList.splice(historyPosition + 1); + historyList.push({ url, state }); + historyPosition = historyList.length - 1; + } + + function pathWithHash(location: Location) { + return `${location.pathname}${location.hash}`; + } + + function setupHistoryListeners( + previewProtocol: PreviewProtocolType< + BaseMessageToPreview, + BaseMessageFromPreview + > + ) { + const existingState = window.history.state; + + Object.assign(window.history, { + go(delta: number) { + const newPos = historyPosition + delta; + if (newPos >= 0 && newPos <= historyList.length - 1) { + historyPosition = newPos; + const { url, state } = historyList[historyPosition]!; + const oldURL = document.location.href; + origHistoryProto.replaceState.call(window.history, state, "", url); + const newURL = document.location.href; + window.dispatchEvent(new PopStateEvent("popstate", { state })); + if (newURL.indexOf("#") !== -1) { + disableNextHashChange = true; + window.dispatchEvent( + new HashChangeEvent("hashchange", { oldURL, newURL }) + ); + } + } + }, + + back() { + window.history.go(-1); + }, + + forward() { + window.history.go(1); + }, + + pushState(state: unknown, title: string, url: string) { + origHistoryProto.replaceState.call(window.history, state, title, url); + pushHistory(url, state); + }, + + replaceState(state: unknown, title: string, url: string) { + origHistoryProto.replaceState.call(window.history, state, title, url); + historyList[historyPosition] = { state, url }; + }, + }); + + Object.defineProperties(window.history, { + length: { + get() { + return historyList.length; + }, + }, + + state: { + get() { + return historyList[historyPosition]!.state; + }, + }, + }); + + window.addEventListener("hashchange", () => { + if (!disableNextHashChange) { + const url = pathWithHash(document.location); + pushHistory(url, null); + } else { + disableNextHashChange = false; + } + }); + + pushHistory(pathWithHash(document.location), existingState); + } + + setupHistoryListeners(previewProtocol); + let lastSentUrl: string | undefined = undefined; + + /** + * Send the URL to the parent frame whenever it changes. + * This approach might create a bit of a performance overhead with multiple previews. + * Prior art rewrote the history object, but didn't handle cases like client-side routing. + * */ + const syncHref = () => { + if (!lastSentUrl || previewWindow.location.href !== lastSentUrl) { + const url = previewWindow.location.href; + lastSentUrl = url; + previewProtocol.sendMessage({ + type: "SET_URL", + url, + back: historyPosition > 0, + forward: historyPosition < historyList.length - 1, + }); + } + previewWindow.requestAnimationFrame(syncHref); + }; + previewWindow.requestAnimationFrame(syncHref); + previewProtocol.addListener("GO_BACK", () => { + previewWindow.history.back(); + }); + previewProtocol.addListener("GO_FORWARD", () => { + previewWindow.history.forward(); + }); + + // #endregion history logic + + window.onbeforeunload = function () { + previewProtocol.sendMessage({ + type: "RELOAD", + }); + }; + + previewProtocol.addListener("REFRESH", () => { + previewWindow.location.reload(); + }); +}; diff --git a/src/sessions/WebSocketSession/previews/types.ts b/src/sessions/WebSocketSession/previews/types.ts new file mode 100644 index 0000000..47fcf8f --- /dev/null +++ b/src/sessions/WebSocketSession/previews/types.ts @@ -0,0 +1,103 @@ +/** + * Messages sent from this context, into the iFrame + */ +export type BaseMessageToPreview = + | { + type: "REFRESH"; + } + | { + type: "GO_BACK"; + } + | { + type: "GO_FORWARD"; + }; + +/** + * Messages sent from the iFrame, into this context + */ +export type BaseMessageFromPreview = + | { + type: "SET_URL"; + url: string; + back: boolean; + forward: boolean; + } + | { type: "RELOAD" } + | { type: "PREVIEW_UNLOADING" }; + +export const INJECT_MESSAGE_TYPE = "INJECT_AND_INVOKE"; +export const HAS_PREVIEW_LOADED_MESSAGE_TYPE = "HAS_PREVIEW_LOADED"; +export const PREVIEW_LOADED_MESSAGE_TYPE = "PREVIEW_LOADED"; +export const PREVIEW_UNLOADING_MESSAGE_TYPE = "PREVIEW_UNLOADING"; +export const INJECT_SUCCESS_TYPE = "INJECT_SUCCESS"; + +/** + * This interface is exposed globally on the window of the preview. This allows other libraries to create + * debugging experiences in the CodeSandbox previews. For example a library can allow you to open files + * directly in the editors from the preview. The implementation of this interface is different for the + * preview in the editor (post message to parent) and the inline preview where the message is passed to + * the embedded inline preview iframe + */ +export type GlobalPreviewApi = { + focusFile(): void; +}; + +type BaseScope = Record; + +export interface Message { + type: string; +} + +export interface UIDMessage { + type: string; + uid: string; +} + +export interface StatusMessage extends UIDMessage { + type: typeof INJECT_SUCCESS_TYPE; +} + +export interface ReadyMessage extends Message { + type: typeof PREVIEW_LOADED_MESSAGE_TYPE; +} + +export interface ReadyRequest extends Message { + type: typeof HAS_PREVIEW_LOADED_MESSAGE_TYPE; +} + +export interface UnloadingMessage extends Message { + type: typeof PREVIEW_UNLOADING_MESSAGE_TYPE; +} + +export interface InjectMessage extends UIDMessage { + type: typeof INJECT_MESSAGE_TYPE; + /* A stringified function that will be called inside the iFrame. */ + code: string; + /* The scope that will be passed to the injected code, as `options.scope`. */ + scope: Scope; +} + +export type InjectFunction< + IncomingMessage extends Message, + OutgoingMessage extends Message, + Scope = Record +> = (injectOptions: { + previewWindow: Window; + previewProtocol: PreviewProtocolType; + scope: Scope; +}) => void; + +export type Listener = (message: Message) => void; + +export interface PreviewProtocolType< + IncomingMessage extends Message, + OutgoingMessage extends Message +> { + addListener( + type: Type, + listener: ( + message: IncomingMessage extends { type: Type } ? IncomingMessage : never + ) => void + ): void; + sendMessage(message: OutgoingMessage): void; +} diff --git a/src/sessions/WebSocketSession/setup.ts b/src/sessions/WebSocketSession/setup.ts index 8eace3f..64cff66 100644 --- a/src/sessions/WebSocketSession/setup.ts +++ b/src/sessions/WebSocketSession/setup.ts @@ -4,8 +4,9 @@ import { listenOnce } from "@codesandbox/pitcher-common/dist/event"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; -export class Setup extends Disposable { - private readonly onSetupProgressUpdateEmitter = this.addDisposable( +export class Setup { + private disposable = new Disposable(); + private readonly onSetupProgressUpdateEmitter = this.disposable.addDisposable( new Emitter() ); /** @@ -14,10 +15,14 @@ export class Setup extends Disposable { public readonly onSetupProgressUpdate = this.onSetupProgressUpdateEmitter.event; - constructor(private pitcherClient: IPitcherClient) { - super(); - - this.addDisposable( + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); + this.disposable.addDisposable( pitcherClient.clients.setup.onSetupProgressUpdate((progress) => { this.onSetupProgressUpdateEmitter.fire(progress); }) diff --git a/src/sessions/WebSocketSession/shells.ts b/src/sessions/WebSocketSession/shells.ts index 9e433e9..0388042 100644 --- a/src/sessions/WebSocketSession/shells.ts +++ b/src/sessions/WebSocketSession/shells.ts @@ -33,9 +33,15 @@ export type ShellStatus = | "RESTARTING"; export const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; -export class Shells extends Disposable { - constructor(private pitcherClient: IPitcherClient) { - super(); +export class Shells { + private disposable = new Disposable(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); } public readonly js = new LanguageInterpreter(this.pitcherClient, { @@ -188,13 +194,18 @@ class LanguageInterpreter { } } -export class ShellInstance extends Disposable { +export class ShellInstance { + private disposable = new Disposable(); // TODO: differentiate between stdout and stderr, also send back bytes instead of // strings - private onShellOutputEmitter = this.addDisposable(new Emitter()); + private onShellOutputEmitter = this.disposable.addDisposable( + new Emitter() + ); public readonly onOutput = this.onShellOutputEmitter.event; - private onShellUpdatedEmitter = this.addDisposable(new Emitter()); + private onShellUpdatedEmitter = this.disposable.addDisposable( + new Emitter() + ); public readonly onShellUpdated = this.onShellUpdatedEmitter.event; private output = this.shell.buffer || []; @@ -203,9 +214,7 @@ export class ShellInstance extends Disposable { private shell: protocol.shell.ShellDTO & { buffer?: string[] }, private pitcherClient: IPitcherClient ) { - super(); - - this.addDisposable( + this.disposable.addDisposable( pitcherClient.clients.shell.onShellsUpdated((shells) => { const updatedShell = shells.find( (s) => s.shellId === this.shell.shellId @@ -217,7 +226,7 @@ export class ShellInstance extends Disposable { }) ); - this.addDisposable( + this.disposable.addDisposable( this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { if (shellId === this.shell.shellId) { this.onShellOutputEmitter.fire(out); @@ -230,7 +239,7 @@ export class ShellInstance extends Disposable { }) ); - this.onWillDispose(async () => { + this.disposable.onWillDispose(async () => { try { await this.pitcherClient.clients.shell.delete(this.shell.shellId); } catch (e) { @@ -277,11 +286,12 @@ export class ShellInstance extends Disposable { } async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - return this.write(input + "\n"); + return this.write(input + "\n", dimensions); } // TODO: allow for kill signals async kill(): Promise { + this.disposable.dispose(); await this.pitcherClient.clients.shell.delete(this.shell.shellId); } diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index f96be54..dd6eff9 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -29,9 +29,10 @@ type TaskShellSubscription = { disposable: IDisposable; }; -export class Tasks extends Disposable { +export class Tasks { + private disposable = new Disposable(); private shellOutputListeners: Record = {}; - private onTaskOutputEmitter = this.addDisposable( + private onTaskOutputEmitter = this.disposable.addDisposable( new Emitter<{ taskId: string; output: string; @@ -39,17 +40,19 @@ export class Tasks extends Disposable { ); public readonly onTaskOutput = this.onTaskOutputEmitter.event; - constructor(private pitcherClient: IPitcherClient) { - super(); - this.pitcherClient.clients.shell.onShellCreated((shell) => { - console.log("CREATED", shell); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); }); - this.addDisposable( + this.disposable.addDisposable( pitcherClient.clients.task.onTaskUpdate((task) => this.listenToShellOutput(task) ) ); - this.onWillDispose(() => { + this.disposable.onWillDispose(() => { Object.values(this.shellOutputListeners).forEach((listener) => listener.disposable.dispose() ); @@ -59,8 +62,6 @@ export class Tasks extends Disposable { private async listenToShellOutput(task: protocol.task.TaskDTO) { const existingListener = this.shellOutputListeners[task.id]; - console.log("TASK UPDATED", JSON.stringify(task, null, 2)); - // Already have shell registered if (existingListener && task.shell?.shellId === existingListener.shellId) { return; @@ -79,15 +80,12 @@ export class Tasks extends Disposable { return; } - console.log("ADDING NEW SHELL!!!!!"); - // Has new shell const taskShellId = task.shell.shellId; let listener: TaskShellSubscription = { shellId: taskShellId, disposable: this.pitcherClient.clients.shell.onShellOut( ({ shellId, out }) => { - console.log("WTF", shellId, out); if (shellId === taskShellId) { this.onTaskOutputEmitter.fire({ taskId: task.id, From d1473214330c6e39b2ebf03187dd5338ffb3b472 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 6 May 2025 11:11:34 +0200 Subject: [PATCH 087/241] rename --- src/sessions/WebSocketSession/previews/Preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sessions/WebSocketSession/previews/Preview.ts b/src/sessions/WebSocketSession/previews/Preview.ts index f361593..a5b043e 100644 --- a/src/sessions/WebSocketSession/previews/Preview.ts +++ b/src/sessions/WebSocketSession/previews/Preview.ts @@ -126,7 +126,7 @@ export class Preview< this.subscribers.delete(subscriber); }; } - refresh() { + reload() { this.sendMessage({ type: "REFRESH", }); From 81ee0f3a51f59cabdf22439f3484daee1d9b9e0a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 7 May 2025 17:20:43 +0200 Subject: [PATCH 088/241] expose terminals, commands and interpreters --- package.json | 2 +- src/sessions/WebSocketSession/commands.ts | 232 ++++++++++++++++++ src/sessions/WebSocketSession/index.ts | 18 ++ src/sessions/WebSocketSession/interpreters.ts | 82 +++++++ src/sessions/WebSocketSession/shells.ts | 23 +- src/sessions/WebSocketSession/terminals.ts | 133 ++++++++++ 6 files changed, 477 insertions(+), 13 deletions(-) create mode 100644 src/sessions/WebSocketSession/commands.ts create mode 100644 src/sessions/WebSocketSession/interpreters.ts create mode 100644 src/sessions/WebSocketSession/terminals.ts diff --git a/package.json b/package.json index 4c72dbf..743ef99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.4", + "version": "1.0.0-beta.5", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts new file mode 100644 index 0000000..6dadf8c --- /dev/null +++ b/src/sessions/WebSocketSession/commands.ts @@ -0,0 +1,232 @@ +import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; +import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; +import { Disposable } from "../../utils/disposable"; +import { Emitter, type Event } from "../../utils/event"; + +export interface RunningCommand + extends Promise<{ output: string; exitCode?: number }> { + onOutput: Event; + kill(): void; +} + +type ShellSize = { cols: number; rows: number }; + +export type ShellRunOpts = { + dimensions?: ShellSize; + name?: string; + env?: Record; + cwd?: string; +}; + +export type CommandStatus = + | "RUNNING" + | "FINISHED" + | "ERROR" + | "KILLED" + | "RESTARTING"; + +const DEFAULT_SHELL_SIZE = { cols: 128, rows: 24 }; + +export class Commands { + private disposable = new Disposable(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); + } + + async run(command: string, opts?: ShellRunOpts): Promise { + const disposableStore = new DisposableStore(); + const onOutput = new Emitter(); + disposableStore.add(onOutput); + + // TODO: use a new shell API that natively supports cwd & env + let commandWithEnv = Object.keys(opts?.env ?? {}).length + ? `env ${Object.entries(opts?.env ?? {}) + .map(([key, value]) => `${key}=${value}`) + .join(" ")} ${command}` + : command; + + if (opts?.cwd) { + commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; + } + + const shell = await this.pitcherClient.clients.shell.create( + this.pitcherClient.workspacePath, + opts?.dimensions ?? DEFAULT_SHELL_SIZE, + commandWithEnv, + "COMMAND", + true + ); + + if (opts?.name) { + this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); + } + + return new Command(this.pitcherClient, shell); + } + + async getAll(): Promise { + const shells = this.pitcherClient.clients.shell.getShells(); + + return shells + .filter((shell) => shell.shellType === "COMMAND") + .map((shell) => new Command(this.pitcherClient, shell)); + } +} + +export class Command { + private disposable = new Disposable(); + // TODO: differentiate between stdout and stderr, also send back bytes instead of + // strings + private onOutputEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onOutput = this.onOutputEmitter.event; + + private onStatusChangeEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onStatusChange = this.onStatusChangeEmitter.event; + private outputPromise: Promise<{ output: string; status: CommandStatus }>; + + private output = this.shell.buffer || []; + + get id(): string { + return this.shell.shellId as string; + } + + get name(): string { + return this.shell.name; + } + + get status(): CommandStatus { + return this.shell.status; + } + + constructor( + private pitcherClient: IPitcherClient, + private shell: protocol.shell.ShellDTO & { buffer?: string[] } + ) { + this.outputPromise = createCommandOutputPromise(pitcherClient, shell); + this.disposable.addDisposable( + pitcherClient.clients.shell.onShellsUpdated((shells) => { + const updatedShell = shells.find( + (s) => s.shellId === this.shell.shellId + ); + if (updatedShell) { + this.shell = { ...updatedShell, buffer: [] }; + this.onStatusChangeEmitter.fire(updatedShell.status); + } + }) + ); + + this.disposable.addDisposable( + this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + if (shellId === this.shell.shellId) { + this.onOutputEmitter.fire(out); + + this.output.push(out); + if (this.output.length > 1000) { + this.output.shift(); + } + } + }) + ); + + this.disposable.onWillDispose(async () => { + try { + await this.pitcherClient.clients.shell.delete(this.shell.shellId); + } catch (e) { + // Ignore errors, we don't care if it's already closed or if we disconnected + } + }); + } + + getOutput(): string { + return this.output.join("\n"); + } + + async getFinalOutput() { + return this.outputPromise; + } + + // TODO: allow for kill signals + async kill(): Promise { + this.disposable.dispose(); + await this.pitcherClient.clients.shell.delete(this.shell.shellId); + } + + async restart(): Promise { + await this.pitcherClient.clients.shell.restart(this.shell.shellId); + } +} + +async function createCommandOutputPromise( + pitcher: IPitcherClient, + shell: protocol.shell.ShellDTO & { buffer?: string[] } +): Promise<{ output: string; status: CommandStatus }> { + const disposableStore = new DisposableStore(); + const onOutput = new Emitter(); + + disposableStore.add(onOutput); + + if (shell.status === "FINISHED") { + return { + output: shell.buffer?.join("\n").trim() ?? "", + status: shell.status, + }; + } + + let combinedOut = shell.buffer?.join("\n") ?? ""; + if (combinedOut) { + onOutput.fire(combinedOut); + } + const barrier = new Barrier(); + + disposableStore.add( + pitcher.clients.shell.onShellOut(({ shellId, out }) => { + if (shellId !== shell.shellId) { + return; + } + + onOutput.fire(out); + combinedOut += out; + }) + ); + + disposableStore.add( + pitcher.clients.shell.onShellExited(({ shellId, exitCode }) => { + if (shellId !== shell.shellId) { + return; + } + + barrier.open(exitCode === 0 ? "FINISHED" : "ERROR"); + }) + ); + + disposableStore.add( + pitcher.clients.shell.onShellTerminated(({ shellId }) => { + if (shellId !== shell.shellId) { + return; + } + + barrier.open("KILLED"); + }) + ); + + const result = await barrier.wait(); + disposableStore.dispose(); + + if (result.status === "disposed") { + throw new Error("Shell was disposed"); + } + + return { + output: combinedOut.trim(), + status: result.value, + }; +} diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 9f2c85e..59494f1 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -13,6 +13,9 @@ import { Tasks } from "./tasks"; import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../../types"; import { Client } from "@hey-api/client-fetch"; import { Previews } from "./previews"; +import { Interpreters } from "./interpreters"; +import { Terminals } from "./terminals"; +import { Commands } from "./commands"; export * from "./filesystem"; export * from "./ports"; @@ -20,6 +23,9 @@ export * from "./setup"; export * from "./shells"; export * from "./tasks"; export * from "./previews"; +export * from "./terminals"; +export * from "./commands"; +export * from "./interpreters"; export class WebSocketSession { private disposable = new Disposable(); @@ -86,6 +92,18 @@ export class WebSocketSession { */ public readonly shells = new Shells(this.disposable, this.pitcherClient); + public readonly terminals = new Terminals( + this.disposable, + this.pitcherClient + ); + public readonly commands = new Commands(this.disposable, this.pitcherClient); + + public readonly interpreters = new Interpreters( + this.disposable, + this.pitcherClient, + this.commands + ); + /** * Namespace for detecting open ports on this sandbox, and getting preview URLs for * them. diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts new file mode 100644 index 0000000..05654cd --- /dev/null +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -0,0 +1,82 @@ +import type { IPitcherClient } from "@codesandbox/pitcher-client"; + +import { Disposable } from "../../utils/disposable"; +import { type Event } from "../../utils/event"; +import { Command, Commands } from "./commands"; + +export interface RunningInterpreter + extends Promise<{ output: string; exitCode?: number }> { + onOutput: Event; + kill(): void; +} + +export class Interpreters { + private disposable = new Disposable(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient, + private commands: Commands + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); + } + + public readonly js = new LanguageInterpreter( + this.pitcherClient, + this.commands, + { + runtime: "node", + extension: "js", + env: { NO_COLOR: "true" }, + } + ); + public readonly python = new LanguageInterpreter( + this.pitcherClient, + this.commands, + { + runtime: "python", + extension: "py", + env: {}, + } + ); +} + +interface ILanguageInterpreterOpts { + runtime: string; + extension: string; + env: Record; +} + +function getRandomString() { + return Math.random().toString(36).substring(7); +} + +class LanguageInterpreter { + constructor( + private pitcherClient: IPitcherClient, + private commands: Commands, + private opts: ILanguageInterpreterOpts + ) {} + + async run(code: string): Promise { + const randomString = getRandomString(); + const tmpFileName = `/tmp/tmp.${randomString}.${this.opts.extension}`; + const command = `${this.opts.runtime} ${tmpFileName}`; + + const tmpFile = await this.pitcherClient.clients.fs.writeFile( + tmpFileName, + new TextEncoder().encode(code), + true, + true + ); + + if (tmpFile.type === "error") { + throw new Error(`${tmpFile.errno}: ${tmpFile.error}`); + } + + return this.commands.run(command, { + env: this.opts.env, + }); + } +} diff --git a/src/sessions/WebSocketSession/shells.ts b/src/sessions/WebSocketSession/shells.ts index 0388042..3460067 100644 --- a/src/sessions/WebSocketSession/shells.ts +++ b/src/sessions/WebSocketSession/shells.ts @@ -6,33 +6,32 @@ import type { OpenShellDTO } from "@codesandbox/pitcher-protocol/dist/src/messag import { Disposable } from "../../utils/disposable"; import { Emitter, type Event } from "../../utils/event"; -export interface RunningCommand +interface RunningCommand extends Promise<{ output: string; exitCode?: number }> { onOutput: Event; kill(): void; } -export type ShellCreateOpts = { +type ShellCreateOpts = { ptySize?: ShellSize; }; -export type ShellRunOpts = { +type ShellRunOpts = { ptySize?: ShellSize; shellName?: string; env?: Record; cwd?: string; }; -export type ShellOpenOpts = { +type ShellOpenOpts = { ptySize?: ShellSize; }; -export type ShellSize = { cols: number; rows: number }; -export type ShellStatus = - | "RUNNING" - | "FINISHED" - | "ERROR" - | "KILLED" - | "RESTARTING"; -export const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; +type ShellSize = { cols: number; rows: number }; +type ShellStatus = "RUNNING" | "FINISHED" | "ERROR" | "KILLED" | "RESTARTING"; +const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; + +/** + * @deprecated + */ export class Shells { private disposable = new Disposable(); constructor( diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts new file mode 100644 index 0000000..0bb191a --- /dev/null +++ b/src/sessions/WebSocketSession/terminals.ts @@ -0,0 +1,133 @@ +import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; +import type { Id } from "@codesandbox/pitcher-common"; +import { Disposable } from "../../utils/disposable"; +import { Emitter } from "../../utils/event"; + +export type ShellSize = { cols: number; rows: number }; + +export const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; + +export class Terminals { + private disposable = new Disposable(); + constructor( + sessionDisposable: Disposable, + private pitcherClient: IPitcherClient + ) { + sessionDisposable.onWillDispose(() => { + this.disposable.dispose(); + }); + } + + async create( + shellType: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", + dimensions = DEFAULT_SHELL_SIZE + ): Promise { + const shell = await this.pitcherClient.clients.shell.create( + this.pitcherClient.workspacePath, + dimensions, + shellType, + "TERMINAL", + true + ); + + return new Terminal(shell, this.pitcherClient); + } + + /** + * Opens an existing terminal. + */ + async open( + shellId: string, + dimensions = DEFAULT_SHELL_SIZE + ): Promise { + const shell = await this.pitcherClient.clients.shell.open( + shellId as Id, + dimensions + ); + return new Terminal(shell, this.pitcherClient); + } + + /** + * Gets all terminals running in the current sandbox + */ + getAll() { + const shells = this.pitcherClient.clients.shell.getShells(); + + return shells + .filter((shell) => shell.shellType === "TERMINAL") + .map((shell) => new Terminal(shell, this.pitcherClient)); + } +} + +export class Terminal { + private disposable = new Disposable(); + // TODO: differentiate between stdout and stderr, also send back bytes instead of + // strings + private onOutputEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onOutput = this.onOutputEmitter.event; + private output = this.shell.buffer || []; + + /** + * Gets the ID of the terminal. Can be used to open it again. + */ + get id(): string { + return this.shell.shellId as string; + } + + /** + * Gets the name of the terminal. + */ + get name(): string { + return this.shell.name; + } + + constructor( + private shell: protocol.shell.ShellDTO & { buffer?: string[] }, + private pitcherClient: IPitcherClient + ) { + this.disposable.addDisposable( + this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + if (shellId === this.shell.shellId) { + this.onOutputEmitter.fire(out); + + this.output.push(out); + if (this.output.length > 1000) { + this.output.shift(); + } + } + }) + ); + + this.disposable.onWillDispose(async () => { + try { + await this.pitcherClient.clients.shell.delete(this.shell.shellId); + } catch (e) { + // Ignore errors, we don't care if it's already closed or if we disconnected + } + }); + } + + getOutput(): string { + return this.output.join("\n"); + } + + async write(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { + await this.pitcherClient.clients.shell.send( + this.shell.shellId, + input, + dimensions + ); + } + + async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { + return this.write(input + "\n", dimensions); + } + + // TODO: allow for kill signals + async kill(): Promise { + this.disposable.dispose(); + await this.pitcherClient.clients.shell.delete(this.shell.shellId); + } +} From 42fcc1546c2dea86b18887916fff90fdc8193a31 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 8 May 2025 12:58:31 +0200 Subject: [PATCH 089/241] wrap up interpreters and prevent process error on first try --- esbuild.cjs | 18 +++ package.json | 2 +- src/sessions/WebSocketSession/commands.ts | 148 ++++++------------ src/sessions/WebSocketSession/interpreters.ts | 99 +++++------- src/sessions/WebSocketSession/terminals.ts | 8 - 5 files changed, 102 insertions(+), 173 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index a79791b..5a6e030 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -27,6 +27,15 @@ Promise.all([ // .cjs extension is required because "type": "module" is set in package.json outfile: "dist/cjs/browser.cjs", platform: "browser", + // pitcher-common currently requires this, but breaks the first experience + banner: { + js: `if (!window.process) { + window.process = { + env: {}, + }; +} +`, + }, plugins: [browserifyPlugin], }), @@ -37,6 +46,15 @@ Promise.all([ format: "esm", outdir: "dist/esm", platform: "browser", + // pitcher-common currently requires this, but breaks the first experience + banner: { + js: `if (!window.process) { + window.process = { + env: {}, + }; +} +`, + }, plugins: [browserifyPlugin], }), diff --git a/package.json b/package.json index 743ef99..3914c49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 6dadf8c..8f7dd47 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -66,10 +66,13 @@ export class Commands { this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); } - return new Command(this.pitcherClient, shell); + return new Command( + this.pitcherClient, + shell as protocol.shell.CommandShellDTO + ); } - async getAll(): Promise { + getAll(): Command[] { const shells = this.pitcherClient.clients.shell.getShells(); return shells @@ -91,67 +94,68 @@ export class Command { new Emitter() ); public readonly onStatusChange = this.onStatusChangeEmitter.event; - private outputPromise: Promise<{ output: string; status: CommandStatus }>; + private barrier = new Barrier(); - private output = this.shell.buffer || []; + private output: string[] = []; get id(): string { return this.shell.shellId as string; } - get name(): string { - return this.shell.name; + get command(): string { + return this.shell.startCommand; } - get status(): CommandStatus { - return this.shell.status; - } + status: CommandStatus = "RUNNING"; constructor( private pitcherClient: IPitcherClient, - private shell: protocol.shell.ShellDTO & { buffer?: string[] } + private shell: protocol.shell.CommandShellDTO & { buffer?: string[] } ) { - this.outputPromise = createCommandOutputPromise(pitcherClient, shell); this.disposable.addDisposable( - pitcherClient.clients.shell.onShellsUpdated((shells) => { - const updatedShell = shells.find( - (s) => s.shellId === this.shell.shellId - ); - if (updatedShell) { - this.shell = { ...updatedShell, buffer: [] }; - this.onStatusChangeEmitter.fire(updatedShell.status); + pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => { + if (shellId === this.id) { + this.status = exitCode === 0 ? "FINISHED" : "ERROR"; + this.barrier.open(); + this.kill(); + } + }) + ); + + this.disposable.addDisposable( + pitcherClient.clients.shell.onShellTerminated(({ shellId }) => { + if (shellId === this.id) { + this.status = "KILLED"; + this.barrier.open(); + this.kill(); } }) ); this.disposable.addDisposable( this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { - if (shellId === this.shell.shellId) { - this.onOutputEmitter.fire(out); + if (shellId !== this.shell.shellId || out.startsWith("[CODESANDBOX]")) { + return; + } + + this.onOutputEmitter.fire(out); - this.output.push(out); - if (this.output.length > 1000) { - this.output.shift(); - } + this.output.push(out); + if (this.output.length > 1000) { + this.output.shift(); } }) ); - - this.disposable.onWillDispose(async () => { - try { - await this.pitcherClient.clients.shell.delete(this.shell.shellId); - } catch (e) { - // Ignore errors, we don't care if it's already closed or if we disconnected - } - }); } - getOutput(): string { - return this.output.join("\n"); - } + async getOutput(): Promise { + await this.barrier.wait(); + + if (this.status === "FINISHED") { + return this.output.join("\n"); + } - async getFinalOutput() { - return this.outputPromise; + throw new Error(`Command ERROR: ${this.output.join("\n")}`); } // TODO: allow for kill signals @@ -161,72 +165,10 @@ export class Command { } async restart(): Promise { - await this.pitcherClient.clients.shell.restart(this.shell.shellId); - } -} - -async function createCommandOutputPromise( - pitcher: IPitcherClient, - shell: protocol.shell.ShellDTO & { buffer?: string[] } -): Promise<{ output: string; status: CommandStatus }> { - const disposableStore = new DisposableStore(); - const onOutput = new Emitter(); - - disposableStore.add(onOutput); - - if (shell.status === "FINISHED") { - return { - output: shell.buffer?.join("\n").trim() ?? "", - status: shell.status, - }; - } - - let combinedOut = shell.buffer?.join("\n") ?? ""; - if (combinedOut) { - onOutput.fire(combinedOut); - } - const barrier = new Barrier(); - - disposableStore.add( - pitcher.clients.shell.onShellOut(({ shellId, out }) => { - if (shellId !== shell.shellId) { - return; - } - - onOutput.fire(out); - combinedOut += out; - }) - ); - - disposableStore.add( - pitcher.clients.shell.onShellExited(({ shellId, exitCode }) => { - if (shellId !== shell.shellId) { - return; - } - - barrier.open(exitCode === 0 ? "FINISHED" : "ERROR"); - }) - ); - - disposableStore.add( - pitcher.clients.shell.onShellTerminated(({ shellId }) => { - if (shellId !== shell.shellId) { - return; - } - - barrier.open("KILLED"); - }) - ); - - const result = await barrier.wait(); - disposableStore.dispose(); + if (this.status !== "RUNNING") { + throw new Error("Command is not running"); + } - if (result.status === "disposed") { - throw new Error("Shell was disposed"); + await this.pitcherClient.clients.shell.restart(this.shell.shellId); } - - return { - output: combinedOut.trim(), - status: result.value, - }; } diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts index 05654cd..633e260 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -1,14 +1,7 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; -import { type Event } from "../../utils/event"; -import { Command, Commands } from "./commands"; - -export interface RunningInterpreter - extends Promise<{ output: string; exitCode?: number }> { - onOutput: Event; - kill(): void; -} +import { Commands } from "./commands"; export class Interpreters { private disposable = new Disposable(); @@ -21,62 +14,46 @@ export class Interpreters { this.disposable.dispose(); }); } + private async run(command: string, env: Record = {}) { + return this.commands.run(command, { + env, + }); + } - public readonly js = new LanguageInterpreter( - this.pitcherClient, - this.commands, - { - runtime: "node", - extension: "js", - env: { NO_COLOR: "true" }, - } - ); - public readonly python = new LanguageInterpreter( - this.pitcherClient, - this.commands, - { - runtime: "python", - extension: "py", - env: {}, - } - ); -} - -interface ILanguageInterpreterOpts { - runtime: string; - extension: string; - env: Record; -} - -function getRandomString() { - return Math.random().toString(36).substring(7); -} - -class LanguageInterpreter { - constructor( - private pitcherClient: IPitcherClient, - private commands: Commands, - private opts: ILanguageInterpreterOpts - ) {} - - async run(code: string): Promise { - const randomString = getRandomString(); - const tmpFileName = `/tmp/tmp.${randomString}.${this.opts.extension}`; - const command = `${this.opts.runtime} ${tmpFileName}`; - - const tmpFile = await this.pitcherClient.clients.fs.writeFile( - tmpFileName, - new TextEncoder().encode(code), - true, - true + async javascript(code: string) { + const command = await this.run( + `node -p "$(cat <<'EOF' +(() => {${code + .split("\n") + .map((line, index, lines) => { + return index === lines.length - 1 && !line.startsWith("return") + ? `return ${line}` + : line; + }) + .join("\n")}})() +EOF +)"`, + { + NO_COLOR: "true", + } ); - if (tmpFile.type === "error") { - throw new Error(`${tmpFile.errno}: ${tmpFile.error}`); - } + console.log(command); - return this.commands.run(command, { - env: this.opts.env, - }); + return command.getOutput(); + } + async python(code: string) { + const command = await this.run(`python3 -c "exec('''\ +${code + .split("\n") + .map((line, index, lines) => { + return index === lines.length - 1 && !line.startsWith("print") + ? `print(${line})` + : line; + }) + .join("\n")} +''')"`); + + return command.getOutput(); } } diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index 0bb191a..099328c 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -99,14 +99,6 @@ export class Terminal { } }) ); - - this.disposable.onWillDispose(async () => { - try { - await this.pitcherClient.clients.shell.delete(this.shell.shellId); - } catch (e) { - // Ignore errors, we don't care if it's already closed or if we disconnected - } - }); } getOutput(): string { From e8933a9cb325a3ba16e2256edad41c5bbb7bc3dd Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 9 May 2025 12:51:49 +0200 Subject: [PATCH 090/241] working --- .gitignore | 1 + openapi.json | 145 +++++++- package-lock.json | 220 ++++++------ package.json | 8 +- src/SandboxClient.ts | 13 +- src/api-clients/client/sdk.gen.ts | 42 ++- src/api-clients/client/types.gen.ts | 70 ++++ src/bin/commands/build.ts | 520 +++++++++++++--------------- src/utils/api.ts | 9 + 9 files changed, 626 insertions(+), 402 deletions(-) diff --git a/.gitignore b/.gitignore index 60a8d15..308ba6d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ dist tests test.ts +test-template ### macOS ### *.DS_Store diff --git a/openapi.json b/openapi.json index 1e8441f..6c73b01 100644 --- a/openapi.json +++ b/openapi.json @@ -179,6 +179,39 @@ "title": "PreviewTokenCreateRequest", "type": "object" }, + "VMCreateTagResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { "tag_id": { "type": "string" } }, + "required": ["tag_id"], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMCreateTagResponse", + "type": "object" + }, "SandboxGetResponse": { "allOf": [ { @@ -243,7 +276,7 @@ "type": "boolean" }, "websocket": { - "default": true, + "default": false, "description": "Whether the VM should automatically wake up on WebSocket connections", "type": "boolean" } @@ -520,7 +553,7 @@ "type": "boolean" }, "websocket": { - "default": true, + "default": false, "description": "Whether the VM should automatically wake up on WebSocket connections", "type": "boolean" } @@ -602,6 +635,15 @@ "title": "WorkspaceCreateRequest", "type": "object" }, + "VMCreateTagRequest": { + "description": "Create a tag for a list of VM IDs", + "properties": { + "vm_ids": { "items": { "type": "string" }, "type": "array" } + }, + "required": ["vm_ids"], + "title": "VMCreateTagRequest", + "type": "object" + }, "VMStartResponse": { "allOf": [ { @@ -780,6 +822,48 @@ "required": ["files"], "title": "SandboxCreateRequest" }, + "VMListClustersResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "clusters": { + "items": { + "properties": { "host": { "type": "string" } }, + "required": ["host"], + "type": "object" + }, + "type": "array" + } + }, + "required": ["clusters"], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMListClustersResponse", + "type": "object" + }, "TokenUpdateResponse": { "allOf": [ { @@ -907,6 +991,10 @@ }, "VMCreateSessionRequest": { "properties": { + "git_access_token": { + "description": "GitHub token for the session", + "type": "string" + }, "permission": { "description": "Permission level for the session", "enum": ["read", "write"], @@ -1670,6 +1758,59 @@ "tags": [] } }, + "/vm/clusters": { + "get": { + "callbacks": {}, + "description": "List all available clusters.\n", + "operationId": "vm/list_clusters", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMListClustersResponse" + } + } + }, + "description": "VM List Clusters Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "List all available clusters", + "tags": ["vm"] + } + }, + "/vm/tag": { + "post": { + "callbacks": {}, + "description": "Creates a new tag for a VM.\n", + "operationId": "vm/create_tag", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VMCreateTagRequest" } + } + }, + "description": "VM Create Tag Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VMCreateTagResponse" } + } + }, + "description": "VM Create Tag Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Create a new tag for a VM", + "tags": ["vm"] + } + }, "/vm/{id}/hibernate": { "post": { "callbacks": {}, diff --git a/package-lock.json b/package-lock.json index ec7dc4c..b9de5cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,17 @@ { "name": "@codesandbox/sdk", - "version": "0.12.0", + "version": "1.0.0-beta.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "0.12.0", + "version": "1.0.0-beta.6", "license": "MIT", + "dependencies": { + "ora": "^8.2.0", + "readline": "^1.3.0" + }, "bin": { "csb": "dist/bin/codesandbox.cjs" }, @@ -28,7 +32,6 @@ "esbuild": "^0.25.0", "ignore": "^6.0.2", "isbinaryfile": "^5.0.4", - "ora": "7.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -1159,33 +1162,6 @@ ], "license": "MIT" }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", @@ -1563,16 +1539,15 @@ "license": "ISC" }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1582,7 +1557,6 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2188,19 +2162,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2247,6 +2208,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-graphql-schema": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", @@ -2688,7 +2661,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2724,13 +2696,12 @@ } }, "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2856,17 +2827,16 @@ "license": "MIT" }, "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", "license": "MIT", "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2876,7 +2846,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -2885,6 +2854,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru_map": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", @@ -3073,14 +3054,16 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimalistic-assert": { @@ -3294,16 +3277,15 @@ "license": "MIT" }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3330,24 +3312,23 @@ } }, "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dev": true, + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", "license": "MIT", "dependencies": { "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3357,7 +3338,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3370,7 +3350,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -3383,22 +3362,20 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, "license": "MIT" }, "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3408,7 +3385,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -3770,6 +3746,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "license": "BSD" + }, "node_modules/redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", @@ -3797,17 +3779,16 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3962,11 +3943,16 @@ "license": "ISC" }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/source-map": { "version": "0.6.1", @@ -4015,16 +4001,12 @@ } }, "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dev": true, + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 3914c49..4977dfc 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ "README.md" ], "devDependencies": { - "@msgpack/msgpack": "^3.1.0", "@codesandbox/pitcher-client": "1.1.5", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", + "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", @@ -100,7 +100,6 @@ "esbuild": "^0.25.0", "ignore": "^6.0.2", "isbinaryfile": "^5.0.4", - "ora": "7.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -112,5 +111,8 @@ "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" }, - "dependencies": {} + "dependencies": { + "ora": "^8.2.0", + "readline": "^1.3.0" + } } diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index e656709..80b50d8 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -9,7 +9,12 @@ import { vmStart, } from "./api-clients/client"; import { Sandbox } from "./Sandbox"; -import { getStartOptions, getStartResponse, handleResponse } from "./utils/api"; +import { + getDefaultTemplateId, + getStartOptions, + getStartResponse, + handleResponse, +} from "./utils/api"; import { CreateSandboxGitSourceOpts, @@ -26,11 +31,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; export class SandboxClient { get defaultTemplateId() { - if (this.apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { - return "7ngcrf"; - } - - return "pcz35m"; + return getDefaultTemplateId(this.apiClient); } constructor(private apiClient: Client) {} diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 75fe33c..38a9cec 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -250,6 +250,46 @@ export const previewTokenUpdate = (options }); }; +/** + * List all available clusters + * List all available clusters. + * + */ +export const vmListClusters = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/clusters', + ...options + }); +}; + +/** + * Create a new tag for a VM + * Creates a new tag for a VM. + * + */ +export const vmCreateTag = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * Hibernate a VM * Suspends a running VM, saving a snapshot of its memory and running processes diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index ce3c5b4..416c48f 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -84,6 +84,17 @@ export type PreviewTokenCreateRequest = { expires_at?: string | null; }; +export type VmCreateTagResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + tag_id: string; + }; +}; + export type SandboxGetResponse = { errors?: Array; +}; + export type VmStartResponse = { errors?: Array; + success?: boolean; +} & { + data?: { + clusters: Array<{ + host: string; + }>; + }; +}; + export type TokenUpdateResponse = { errors?: Array { + const spinner = ora({ stream: process.stdout }); + const spinnerIndex = currentSpinnerIndex++; + let lastMethod: string; + + function updateCursor(method: string) { + readline.moveCursor( + process.stdout, + 0, + spinnerIndex - currentLineIndex + (lastMethod !== "start" ? -1 : 0) + ); + currentLineIndex = spinnerIndex; + lastMethod = method; + } + + return { + start(message: string) { + updateCursor("start"); + spinner.start(`${prefix}: ${message}`); + }, + succeed(message: string) { + updateCursor("succeed"); + spinner.succeed(`${prefix}: ${message}`); + }, + fail(message: string) { + updateCursor("fail"); + spinner.fail(`${prefix}: ${message}`); + }, + info(message: string) { + updateCursor("info"); + spinner.info(`${prefix}: ${message}`); + }, + }; + }; +} + export const buildCommand: yargs.CommandModule< Record, BuildCommandArgs @@ -44,23 +81,10 @@ export const buildCommand: yargs.CommandModule< "Build an efficient memory snapshot from a directory. This snapshot can be used to create sandboxes quickly.", builder: (yargs: yargs.Argv) => yargs - .option("ip-country", { - describe: - "Cluster closest to this country to create the snapshot in, this ensures that sandboxes created of this snapshot will be created in the same cluster", - type: "string", - }) .option("from-sandbox", { describe: "Use and update an existing sandbox as a template", type: "string", }) - .option("skip-files", { - describe: "Skip writing files to the sandbox", - type: "boolean", - }) - .option("cluster", { - describe: "Cluster to create the sandbox in", - type: "string", - }) .option("name", { describe: "Name for the resulting sandbox that will serve as snapshot", type: "string", @@ -93,233 +117,223 @@ export const buildCommand: yargs.CommandModule< }) ); - const spinner = ora("Indexing folder...").start(); + const createSpinner = createSpinnerFactory(); try { - const getSdk = (cluster?: string) => { - const headers: Record = cluster - ? { - "x-pitcher-manager-url": `https://${cluster}.pitcher.csb.app/api/v1`, - } - : {}; - - return { - sdk: new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, - headers, - }), - cluster, - }; - }; - - const { sdk, cluster } = getSdk(argv.cluster); - const { hash, files: filePaths } = await hashDirectory(argv.directory); - spinner.succeed(`Indexed ${filePaths.length} files`); - const shortHash = hash.slice(0, 6); - const tag = `sha:${shortHash}-${cluster || ""}`; - - spinner.start(`Creating or updating sandbox...`); - const { sandboxId, filesIncluded } = await createSandbox({ - apiClient, - shaTag: tag, - filePaths, - rootPath: argv.directory, - fromSandbox: argv.fromSandbox, - collectionPath: argv.path, - name: argv.name, - ipcountry: argv.ipCountry, - vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, - }); - - if (argv.fromSandbox) { - spinner.succeed( - `Created sandbox from template (${argv.fromSandbox}): ${sandboxId}` - ); - } else { - spinner.succeed(`Sandbox created: ${sandboxId}`); - } + const clustersData = handleResponse( + await vmListClusters({ + client: apiClient, + }), + "Failed to list clusters" + ); - if (argv.cluster) { - spinner.start(`Starting sandbox in cluster ${argv.cluster}...`); - } else { - spinner.start(`Starting sandbox...`); - } + const clusters = clustersData.clusters; - const startResponse = await sdk.sandbox["start"](sandboxId, { - ipcountry: argv.ipCountry, - vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, - }); - const sandbox = new Sandbox(sandboxId, startResponse, apiClient); - const session = await sandbox.connect(); - spinner.succeed("Sandbox opened"); - - if (!argv.skipFiles && !filesIncluded) { - spinner.start("Writing files to sandbox..."); - let i = 0; - for (const filePath of filePaths) { - i++; - spinner.start(`Writing file ${i} of ${filePaths.length}...`); - const fullPath = path.join(argv.directory, filePath); - const content = await fs.readFile(fullPath); - const dirname = path.dirname(filePath); - await session.fs.mkdir(dirname, true); - await session.fs.writeFile(filePath, content, { - create: true, - overwrite: true, + const sandboxIds = await Promise.all( + clusters.map(async ({ host: cluster }) => { + const sdk = new CodeSandbox(API_KEY, { + baseUrl: BASE_URL, + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, }); - } - spinner.succeed("Files written to sandbox"); + const spinner = createSpinner(`${cluster}`); - spinner.start("Rebooting sandbox..."); - await sdk.sandbox.restart(sandbox.id); - spinner.succeed("Sandbox restarted"); - } - - const disposableStore = new DisposableStore(); - const handleProgress = async (progress: SetupProgress) => { - if (progress.state === "IN_PROGRESS" && progress.steps.length > 0) { - const step = progress.steps[progress.currentStepIndex]; - if (!step) { - return; - } - - const spinnerMessage = `Running setup: ${ - progress.currentStepIndex + 1 - } / ${progress.steps.length}: ${step.name}`; - spinner.info(spinnerMessage); + try { + const { hash, files: filePaths } = await hashDirectory( + argv.directory + ); + spinner.succeed(`Indexed ${filePaths.length} files`); + const shortHash = hash.slice(0, 6); + const tag = `sha:${shortHash}-${cluster || ""}`; + + spinner.start(`Creating sandbox...`); + const sandboxId = await createSandbox({ + apiClient, + shaTag: tag, + fromSandbox: argv.fromSandbox, + collectionPath: argv.path, + name: argv.name, + vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, + }); - const shellId = step.shellId; + spinner.start(`Starting sandbox... `); - if (shellId) { - const shell = await session.shells.open(shellId, { - ptySize: { - cols: process.stderr.columns, - rows: process.stderr.rows, - }, + const startResponse = await sdk.sandbox["start"](sandboxId, { + vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); + const sandbox = new Sandbox(sandboxId, startResponse, apiClient); + const session = await sandbox.connect(); + + spinner.start("Writing files to sandbox..."); + let i = 0; + for (const filePath of filePaths) { + i++; + const fullPath = path.join(argv.directory, filePath); + const content = await fs.readFile(fullPath); + const dirname = path.dirname(filePath); + await session.fs.mkdir(dirname, true); + await session.fs.writeFile(filePath, content, { + create: true, + overwrite: true, + }); + } + + spinner.start("Restarting sandbox..."); + await sdk.sandbox.restart(sandbox.id); + + const disposableStore = new DisposableStore(); + const handleProgress = async (progress: SetupProgress) => { + if ( + progress.state === "IN_PROGRESS" && + progress.steps.length > 0 + ) { + const step = progress.steps[progress.currentStepIndex]; + if (!step) { + return; + } + + const spinnerMessage = `Running setup: ${ + progress.currentStepIndex + 1 + } / ${progress.steps.length}: ${step.name}...`; + spinner.start(spinnerMessage); + + const shellId = step.shellId; + + if (shellId) { + const shell = await session.shells.open(shellId, { + ptySize: { + cols: process.stderr.columns, + rows: process.stderr.rows, + }, + }); + + disposableStore.add(Disposable.create(() => shell.kill())); + disposableStore.add( + shell.onOutput((data) => { + process.stderr.write(data); + }) + ); + } + } else if (progress.state === "STOPPED") { + const step = progress.steps[progress.currentStepIndex]; + if (!step) { + return; + } + + if (step.finishStatus === "FAILED") { + spinner.fail(`Setup step failed: ${step.name}`); + throw new Error(`Setup step failed: ${step.name}`); + } + } + }; - disposableStore.add(shell); + const progress = await session.setup.getProgress(); + await handleProgress(progress); disposableStore.add( - shell.onOutput((data) => { - process.stderr.write(data); - }) + session.setup.onSetupProgressUpdate(handleProgress) ); - } - } else if (progress.state === "FINISHED") { - spinner.succeed("Setup finished"); - } else if (progress.state === "STOPPED") { - const step = progress.steps[progress.currentStepIndex]; - if (!step) { - return; - } - - if (step.finishStatus === "FAILED") { - throw new Error(`Setup step failed: ${step.name}`); - } - } - }; - - const progress = await session.setup.getProgress(); - await handleProgress(progress); - disposableStore.add(session.setup.onSetupProgressUpdate(handleProgress)); - - await session.setup.waitForFinish(); - - disposableStore.dispose(); - spinner.succeed("Sandbox built"); - - const tasksWithStart = (await session.tasks.getTasks()).filter( - (t) => t.runAtStart === true - ); - let tasksWithPorts = tasksWithStart.filter((t) => t.preview?.port); - const isMultipleTasks = tasksWithStart.length > 1; - spinner.info( - `Started ${tasksWithStart.length} ${ - isMultipleTasks ? "tasks" : "task" - }: ${tasksWithStart.map((t) => t.name).join(", ")}` - ); - - const updatePortSpinner = () => { - const isMultiplePorts = tasksWithPorts.length > 1; - spinner.start( - `Waiting for ${isMultiplePorts ? "ports" : "port"} ${tasksWithPorts - .map((t) => t.preview?.port) - .join(", ")} to open...` - ); - }; - - if (tasksWithPorts.length > 0) { - updatePortSpinner(); - - await Promise.all( - tasksWithPorts.map(async (task) => { - const port = task.preview?.port; - if (!port) { - return; - } - - let timeout; - const portInfo = await Promise.race([ - session.ports.waitForPort(port), - new Promise( - (resolve) => - (timeout = setTimeout( - () => - resolve( - new Error( - `Waiting for port ${port} timed out after 60s` - ) - ), - 60000 - )) - ), - ]); - clearTimeout(timeout); - - if (portInfo instanceof Error) { - throw portInfo; - } + await session.setup.waitForFinish(); - // eslint-disable-next-line no-constant-condition - while (true) { - const res = await fetch("https://" + portInfo.url); - if (res.status !== 502 && res.status !== 503) { - spinner.succeed(`Port ${port} is open (status ${res.status})`); - break; - } - - spinner.fail( - `Port ${port} is not open yet (status ${res.status}), retrying in 1 second...` + disposableStore.dispose(); + const tasksWithStart = (await session.tasks.getTasks()).filter( + (t) => t.runAtStart === true + ); + let tasksWithPorts = tasksWithStart.filter((t) => t.preview?.port); + + const updatePortSpinner = () => { + const isMultiplePorts = tasksWithPorts.length > 1; + spinner.start( + `Waiting for ${ + isMultiplePorts ? "ports" : "port" + } ${tasksWithPorts + .map((t) => t.preview?.port) + .join(", ")} to open...` + ); + }; + + if (tasksWithPorts.length > 0) { + updatePortSpinner(); + + await Promise.all( + tasksWithPorts.map(async (task) => { + const port = task.preview?.port; + if (!port) { + return; + } + + let timeout; + const portInfo = await Promise.race([ + session.ports.waitForPort(port), + new Promise( + (resolve) => + (timeout = setTimeout( + () => + resolve( + new Error( + `Waiting for port ${port} timed out after 60s` + ) + ), + 60000 + )) + ), + ]); + clearTimeout(timeout); + + if (portInfo instanceof Error) { + throw portInfo; + } + + // eslint-disable-next-line no-constant-condition + while (true) { + const res = await fetch("https://" + portInfo.url); + if (res.status !== 502 && res.status !== 503) { + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + tasksWithPorts = tasksWithPorts.filter( + (t) => t.id !== task.id + ); + updatePortSpinner(); + }) ); - await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + spinner.start( + "No ports to open, waiting 5 seconds for tasks to run..." + ); + await new Promise((resolve) => setTimeout(resolve, 5000)); } - tasksWithPorts = tasksWithPorts.filter((t) => t.id !== task.id); - updatePortSpinner(); - }) - ); - - spinner.succeed("All ports are open"); - } else { - spinner.succeed( - "No ports to open, waiting 5 seconds for tasks to run..." - ); - await new Promise((resolve) => setTimeout(resolve, 5000)); - } + spinner.start("Creating memory snapshot..."); + await sdk.sandbox.hibernate(sandbox.id); + spinner.succeed("Snapshot created"); - spinner.start("Creating memory snapshot..."); - await sdk.sandbox.hibernate(sandbox.id); - spinner.succeed( - "Snapshot created, you can use this sandbox id as your template:" + return sandbox.id; + } catch (error) { + spinner.fail( + error instanceof Error ? error.message : "Unknown error occurred" + ); + throw error; + } + }) ); - // eslint-disable-next-line no-console - console.log(sandbox.id); - } catch (error) { - spinner.fail( - error instanceof Error ? error.message : "Unknown error occurred" + const data = handleResponse( + await vmCreateTag({ + client: apiClient, + body: { + vm_ids: sandboxIds, + }, + }), + "Failed to create tag" ); + console.log("Tag created: " + data.tag_id); + } catch (error) { + console.error(error); process.exit(1); } }, @@ -328,8 +342,6 @@ export const buildCommand: yargs.CommandModule< type CreateSandboxParams = { apiClient: Client; shaTag: string; - filePaths: string[]; - rootPath: string; fromSandbox?: string; collectionPath?: string; name?: string; @@ -339,68 +351,34 @@ type CreateSandboxParams = { async function createSandbox({ apiClient, - filePaths, - rootPath, shaTag, collectionPath, fromSandbox, name, -}: CreateSandboxParams): Promise<{ - sandboxId: string; - filesIncluded: boolean; -}> { - // Include the files in the sandbox if there are no binary files and there are 30 or less files - const files = await getFiles(filePaths, rootPath); - +}: CreateSandboxParams) { const sanitizedCollectionPath = collectionPath ? collectionPath.startsWith("/") ? collectionPath : `/${collectionPath}` : "/SDK-Templates"; - if (fromSandbox) { - const sandbox = handleResponse( - await sandboxFork({ - client: apiClient, - path: { - id: fromSandbox, - }, - body: { - title: name, - privacy: 1, - tags: ["sdk", shaTag], - path: sanitizedCollectionPath, - }, - }), - "Failed to fork sandbox" - ); - - return { - sandboxId: sandbox.id, - filesIncluded: false, - }; - } - const sandbox = handleResponse( - await sandboxCreate({ + await sandboxFork({ client: apiClient, + path: { + id: fromSandbox || getDefaultTemplateId(apiClient), + }, body: { title: name, - files, privacy: 1, tags: ["sdk", shaTag], path: sanitizedCollectionPath, - runtime: "vm", - is_frozen: true, }, }), - "Failed to create sandbox" + "Failed to fork sandbox" ); - return { - sandboxId: sandbox.id, - filesIncluded: Object.keys(files).length > 0, - }; + return sandbox.id; } async function getFiles( diff --git a/src/utils/api.ts b/src/utils/api.ts index 4e65b8d..a13c08c 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -2,6 +2,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VmStartResponse } from "../api-clients/client"; import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; +import { Client } from "@hey-api/client-fetch"; export type HandledResponse = { data?: { @@ -50,6 +51,14 @@ export function getBaseUrl(token: string) { return "https://api.together.ai/csb/sdk"; } +export function getDefaultTemplateId(apiClient: Client): string { + if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } + + return "pcz35m"; +} + export function handleResponse( result: Awaited<{ data?: { data?: D }; error?: E; response: Response }>, errorPrefix: string From b46ca5bc69cd21be9d1551ce69b98be7ed59064e Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 9 May 2025 14:27:02 +0200 Subject: [PATCH 091/241] awesome tasks --- src/sessions/WebSocketSession/tasks.ts | 235 +++++++++++++------------ 1 file changed, 120 insertions(+), 115 deletions(-) diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index dd6eff9..6c31800 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -1,11 +1,11 @@ import { Emitter, - IDisposable, type IPitcherClient, type protocol, } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; +import { DEFAULT_SHELL_SIZE } from "./terminals"; export type TaskDefinition = { name: string; @@ -17,144 +17,149 @@ export type TaskDefinition = { }; }; -export type Task = TaskDefinition & { - id: string; - unconfigured?: boolean; - shellId: null | string; - ports: protocol.port.Port[]; -}; - -type TaskShellSubscription = { - shellId: string; - disposable: IDisposable; -}; - export class Tasks { private disposable = new Disposable(); - private shellOutputListeners: Record = {}; - private onTaskOutputEmitter = this.disposable.addDisposable( - new Emitter<{ - taskId: string; - output: string; - }>() - ); - public readonly onTaskOutput = this.onTaskOutputEmitter.event; - + private tasks: Task[] = []; constructor( sessionDisposable: Disposable, private pitcherClient: IPitcherClient ) { + this.tasks = Object.values( + this.pitcherClient.clients.task.getTasks().tasks + ).map((task) => new Task(this.pitcherClient, task)); + sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); - this.disposable.addDisposable( - pitcherClient.clients.task.onTaskUpdate((task) => - this.listenToShellOutput(task) - ) - ); - this.disposable.onWillDispose(() => { - Object.values(this.shellOutputListeners).forEach((listener) => - listener.disposable.dispose() - ); - }); - } - - private async listenToShellOutput(task: protocol.task.TaskDTO) { - const existingListener = this.shellOutputListeners[task.id]; - - // Already have shell registered - if (existingListener && task.shell?.shellId === existingListener.shellId) { - return; - } - - // Has removed shell - if ( - existingListener && - (!task.shell || task.shell.shellId !== existingListener.shellId) - ) { - existingListener.disposable.dispose(); - } - - // No new shell - if (!task.shell) { - return; - } - - // Has new shell - const taskShellId = task.shell.shellId; - let listener: TaskShellSubscription = { - shellId: taskShellId, - disposable: this.pitcherClient.clients.shell.onShellOut( - ({ shellId, out }) => { - if (shellId === taskShellId) { - this.onTaskOutputEmitter.fire({ - taskId: task.id, - output: out, - }); - } - } - ), - }; - - this.shellOutputListeners[task.id] = listener; - - this.pitcherClient.clients.shell.open(task.shell.shellId, { - cols: 80, - rows: 24, - }); - - /* - this.onTaskOutputEmitter.fire({ - taskId: task.id, - output: shell.buffer.join("\n"), - }); - */ } /** * Gets all tasks that are available in the current sandbox. */ - async getTasks(): Promise { - const tasks = await this.pitcherClient.clients.task.getTasks(); - - return Object.values(tasks.tasks).map(taskFromDTO); + getTasks(): Task[] { + return this.tasks; } /** * Gets a task by its ID. */ - async getTask(taskId: string): Promise { - const task = await this.pitcherClient.clients.task.getTask(taskId); - - if (!task) { - return undefined; - } + getTask(taskId: string): Task | undefined { + return this.tasks.find((task) => task.id === taskId); + } +} - return taskFromDTO(task); +export class Task { + private disposable = new Disposable(); + private get shell() { + return this.data.shell; + } + private openedShell?: { + shellId: string; + output: string[]; + dimensions: typeof DEFAULT_SHELL_SIZE; + }; + private onOutputEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onOutput = this.onOutputEmitter.event; + private onStatusChangeEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onStatusChange = this.onStatusChangeEmitter.event; + get id() { + return this.data.id; + } + get name() { + return this.data.name; + } + get command() { + return this.data.command; } + get runAtStart() { + return this.data.runAtStart; + } + get ports() { + return this.data.ports; + } + get status() { + return this.shell?.status || "IDLE"; + } + constructor( + private pitcherClient: IPitcherClient, + private data: protocol.task.TaskDTO + ) { + pitcherClient.clients.task.onTaskUpdate(async (task) => { + if (task.id !== this.id) { + return; + } + + const lastStatus = this.status; + const lastShellId = this.shell?.shellId; + + this.data = task; + + if (lastStatus !== this.status) { + this.onStatusChangeEmitter.fire(this.status); + } + + if ( + this.openedShell && + task.shell && + task.shell.shellId !== lastShellId + ) { + const openedShell = await this.pitcherClient.clients.shell.open( + task.shell.shellId, + this.openedShell.dimensions + ); + + this.openedShell = { + shellId: openedShell.shellId, + output: openedShell.buffer, + dimensions: this.openedShell.dimensions, + }; + + this.onOutputEmitter.fire("\x1B[2J\x1B[3J\x1B[1;1H"); + openedShell.buffer.forEach((out) => this.onOutputEmitter.fire(out)); + } + }); - /** - * Runs a task by its ID. - */ - async runTask(taskId: string): Promise { - const task = await this.pitcherClient.clients.task.runTask(taskId); + pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + if (!this.shell || this.shell.shellId !== shellId || !this.openedShell) { + return; + } - return taskFromDTO(task); + // Update output for shell + this.openedShell.output.push(out); + this.onOutputEmitter.fire(out); + }); } + async open(dimensions = DEFAULT_SHELL_SIZE) { + if (!this.shell) { + throw new Error("Task is not running"); + } - async stopTask(taskId: string) { - await this.pitcherClient.clients.task.stopTask(taskId); - } -} + const openedShell = await this.pitcherClient.clients.shell.open( + this.shell.shellId, + dimensions + ); -function taskFromDTO(value: protocol.task.TaskDTO): Task { - return { - id: value.id, - name: value.name, - command: value.command, - runAtStart: value.runAtStart, - preview: value.preview, - shellId: value.shell?.shellId ?? null, - ports: value.ports, - }; + this.openedShell = { + shellId: openedShell.shellId, + output: openedShell.buffer, + dimensions, + }; + + return this.openedShell.output.join("\n"); + } + async run() { + await this.pitcherClient.clients.task.runTask(this.id); + } + async restart() { + await this.run(); + } + async stop() { + if (this.shell) { + await this.pitcherClient.clients.task.stopTask(this.id); + } + } } From f3f9c6087799b3c2ba43a9163e25837fb858af71 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 9 May 2025 14:59:11 +0200 Subject: [PATCH 092/241] some changes --- esbuild.cjs | 8 ++--- package.json | 2 +- .../WebSocketSession => }/browser.ts | 8 +++-- .../WebSocketSession => }/previews/Preview.ts | 20 ++++++++---- src/previews/index.ts | 8 +++++ .../previews/preview-script.ts | 0 .../WebSocketSession => }/previews/types.ts | 0 src/sessions/WebSocketSession/git.ts | 32 +++++++++++++------ src/sessions/WebSocketSession/index.ts | 7 ---- .../WebSocketSession/previews/index.ts | 16 ---------- 10 files changed, 54 insertions(+), 47 deletions(-) rename src/{sessions/WebSocketSession => }/browser.ts (89%) rename src/{sessions/WebSocketSession => }/previews/Preview.ts (91%) create mode 100644 src/previews/index.ts rename src/{sessions/WebSocketSession => }/previews/preview-script.ts (100%) rename src/{sessions/WebSocketSession => }/previews/types.ts (100%) delete mode 100644 src/sessions/WebSocketSession/previews/index.ts diff --git a/esbuild.cjs b/esbuild.cjs index 5a6e030..c528ae1 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -21,7 +21,7 @@ Promise.all([ // Browser builds: // CommonJS build esbuild.build({ - entryPoints: ["src/sessions/WebSocketSession/browser.ts"], + entryPoints: ["src/browser.ts"], bundle: true, format: "cjs", // .cjs extension is required because "type": "module" is set in package.json @@ -29,7 +29,7 @@ Promise.all([ platform: "browser", // pitcher-common currently requires this, but breaks the first experience banner: { - js: `if (!window.process) { + js: `if (typeof window !== "undefined" && !window.process) { window.process = { env: {}, }; @@ -41,14 +41,14 @@ Promise.all([ // ESM build esbuild.build({ - entryPoints: ["src/sessions/WebSocketSession/browser.ts"], + entryPoints: ["src/browser.ts"], bundle: true, format: "esm", outdir: "dist/esm", platform: "browser", // pitcher-common currently requires this, but breaks the first experience banner: { - js: `if (!window.process) { + js: `if (typeof window !== "undefined" && !window.process) { window.process = { env: {}, }; diff --git a/package.json b/package.json index 4977dfc..7753639 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "default": "./dist/cjs/index.cjs" }, "./browser": { - "types": "./dist/esm/sessions/WebSocketSession/browser.d.ts", + "types": "./dist/esm/browser.d.ts", "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs", "default": "./dist/cjs/browser.cjs" diff --git a/src/sessions/WebSocketSession/browser.ts b/src/browser.ts similarity index 89% rename from src/sessions/WebSocketSession/browser.ts rename to src/browser.ts index 7ed3635..b7e065e 100644 --- a/src/sessions/WebSocketSession/browser.ts +++ b/src/browser.ts @@ -1,8 +1,10 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "../../types"; -import { WebSocketSession } from "."; +import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; +import { WebSocketSession } from "./sessions/WebSocketSession"; -export * from "."; +export * from "./sessions/WebSocketSession"; + +export { createPreview } from "./previews"; /** * With this function you can connect to a sandbox from the browser. diff --git a/src/sessions/WebSocketSession/previews/Preview.ts b/src/previews/Preview.ts similarity index 91% rename from src/sessions/WebSocketSession/previews/Preview.ts rename to src/previews/Preview.ts index a5b043e..a7a8248 100644 --- a/src/sessions/WebSocketSession/previews/Preview.ts +++ b/src/previews/Preview.ts @@ -6,7 +6,6 @@ import { InjectMessage, Message, } from "./types"; -import { Disposable } from "../../../utils/disposable"; import { injectAndInvokeInsidePreview } from "./preview-script"; type PreviewStatus = "DISCONNECTED" | "CONNECTED"; @@ -34,10 +33,7 @@ export class Preview< onStatusChange = this.onStatusChangeEmitter.event; iframe: HTMLIFrameElement; - constructor(sessionDisposable: Disposable, src: string) { - sessionDisposable.onWillDispose(() => { - this.dispose(); - }); + constructor(src: string) { this.origin = new URL(src).origin; this.iframe = this.createIframe(src); this.windowListener = (event: MessageEvent) => { @@ -65,7 +61,6 @@ export class Preview< this.onPreviewLoad = () => { this.status = "CONNECTED"; - console.log("INJECT SCRIPT"); this._injectAndInvoke(injectAndInvokeInsidePreview, {}); }; @@ -111,10 +106,21 @@ export class Preview< } } - injectAndInvoke>( + async injectAndInvoke>( func: InjectFunction, scope: Scope ) { + if (this.status !== "CONNECTED") { + return new Promise(() => { + const listener = this.onStatusChange((status) => { + if (status === "CONNECTED") { + this._injectAndInvoke(func, scope); + listener.dispose(); + } + }); + }); + } + this._injectAndInvoke(func, scope); } diff --git a/src/previews/index.ts b/src/previews/index.ts new file mode 100644 index 0000000..3e27377 --- /dev/null +++ b/src/previews/index.ts @@ -0,0 +1,8 @@ +import { Preview } from "./Preview"; +import { InjectFunction } from "./types"; + +export { Preview, InjectFunction }; + +export function createPreview(src: string) { + return new Preview(src); +} diff --git a/src/sessions/WebSocketSession/previews/preview-script.ts b/src/previews/preview-script.ts similarity index 100% rename from src/sessions/WebSocketSession/previews/preview-script.ts rename to src/previews/preview-script.ts diff --git a/src/sessions/WebSocketSession/previews/types.ts b/src/previews/types.ts similarity index 100% rename from src/sessions/WebSocketSession/previews/types.ts rename to src/previews/types.ts diff --git a/src/sessions/WebSocketSession/git.ts b/src/sessions/WebSocketSession/git.ts index 3dd52b5..420dd14 100644 --- a/src/sessions/WebSocketSession/git.ts +++ b/src/sessions/WebSocketSession/git.ts @@ -1,14 +1,28 @@ import type { Id, IPitcherClient } from "@codesandbox/pitcher-client"; -import { listenOnce } from "@codesandbox/pitcher-common/dist/event"; -import { Disposable } from "../../utils/disposable"; -import { Emitter } from "../../utils/event"; - -export class Git extends Disposable { - constructor(private pitcherClient: IPitcherClient) { - super(); +export class Git { + onStatusChange = this.pitcherClient.clients.git.onStatusUpdated; + constructor(private pitcherClient: IPitcherClient) {} + pull(force?: boolean) { + return this.pitcherClient.clients.git.pull(undefined, force); + } + commit(message: string, paths?: string[]) { + return this.pitcherClient.clients.git.commit(message, paths); + } + push() { + return this.pitcherClient.clients.git.push(); + } + status() { + return this.pitcherClient.clients.git.getStatus(); + } + discard(path?: string[]) { + return this.pitcherClient.clients.git.discard(path); + } + resetToRemote() { + return this.pitcherClient.clients.git.resetLocalWithRemote(); } - pull() { - return this.pitcherClient.clients.git.pull(); + // TODO: Expose git checkout + checkout(branch: string) { + return this.pitcherClient.clients.task.runCommand(`git checkout ${branch}`); } } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 59494f1..e863556 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -12,7 +12,6 @@ import { Shells } from "./shells"; import { Tasks } from "./tasks"; import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../../types"; import { Client } from "@hey-api/client-fetch"; -import { Previews } from "./previews"; import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; @@ -22,7 +21,6 @@ export * from "./ports"; export * from "./setup"; export * from "./shells"; export * from "./tasks"; -export * from "./previews"; export * from "./terminals"; export * from "./commands"; export * from "./interpreters"; @@ -82,11 +80,6 @@ export class WebSocketSession { */ public readonly fs = new FileSystem(this.disposable, this.pitcherClient); - /** - * Namespace for previews - */ - public readonly previews = new Previews(this.disposable); - /** * Namespace for running shell commands on this sandbox. */ diff --git a/src/sessions/WebSocketSession/previews/index.ts b/src/sessions/WebSocketSession/previews/index.ts deleted file mode 100644 index 7c66920..0000000 --- a/src/sessions/WebSocketSession/previews/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Disposable } from "../../../utils/disposable"; -import { Preview } from "./Preview"; - -export { Preview } from "./Preview"; - -export class Previews { - private disposable = new Disposable(); - constructor(private sessionDisposable: Disposable) { - sessionDisposable.onWillDispose(() => { - this.disposable.dispose(); - }); - } - create(src: string) { - return new Preview(this.sessionDisposable, src); - } -} From c471b06a6aa3d451a95b851e6a3b7301df651f97 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 12 May 2025 10:26:31 +0200 Subject: [PATCH 093/241] Ready for beta... I think --- openapi.json | 7 +- package-lock.json | 934 ++++++++++++++----------- package.json | 2 +- src/api-clients/client/types.gen.ts | 1 + src/bin/commands/build.ts | 155 ++-- src/sessions/WebSocketSession/ports.ts | 1 - 6 files changed, 626 insertions(+), 474 deletions(-) diff --git a/openapi.json b/openapi.json index 6c73b01..2a2d5aa 100644 --- a/openapi.json +++ b/openapi.json @@ -847,8 +847,11 @@ "properties": { "clusters": { "items": { - "properties": { "host": { "type": "string" } }, - "required": ["host"], + "properties": { + "host": { "type": "string" }, + "slug": { "type": "string" } + }, + "required": ["host", "slug"], "type": "object" }, "type": "array" diff --git a/package-lock.json b/package-lock.json index b9de5cd..b4febb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "csb": "dist/bin/codesandbox.cjs" }, "devDependencies": { - "@codesandbox/pitcher-client": "1.1.5", + "@codesandbox/pitcher-client": "1.1.7", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", @@ -49,6 +49,7 @@ "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", "@jumpn/utils-array": "0.3.4", @@ -66,6 +67,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", "dev": true, + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.12.0" } @@ -75,6 +77,7 @@ "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-1.1.3.tgz", "integrity": "sha512-GHrxd/tL4CHy/+42Qx0RC65EHUH6jjzuRm+JcBgAlRcMCJPvSTANpoNgI6fQKofvEJIVc4ghqR7X87j6emEZyg==", "dev": true, + "license": "MIT", "dependencies": { "@absinthe/socket": "^0.2.1", "@codesandbox/create-gql-api": "^1.0.1", @@ -89,13 +92,15 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.15.tgz", "integrity": "sha512-ReR0LKl1C3tK+Wwe2zlAtXEjLavuR3+m+oFFNELdJmywEoh00iDYCWLxWJof83YSpqIBK/iegy0blUuktJ7+6A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@codesandbox/create-gql-api": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-1.0.1.tgz", "integrity": "sha512-k+C8OS2wYdakIoYEtHjW+pvwb7uq+emHQX5W1tqJQ9gDEYYy2dHItQNulY/8H0ymudhW3b6zcKTVNTMl9QgGWQ==", "dev": true, + "license": "ISC", "dependencies": { "get-graphql-schema": "^2.1.2", "graphql": "^16.8.1" @@ -116,9 +121,9 @@ } }, "node_modules/@codesandbox/pitcher-client": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-1.1.5.tgz", - "integrity": "sha512-YRDF7X1i2+P888N+P9DQkwatUDNEAPcaAHjojwvt8dYoshO8IL9iKeN6sdHp+dDi/NLnVzz1cDO6mcAvlXAC0Q==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-1.1.7.tgz", + "integrity": "sha512-D3KkU+nDUR6kXCsnsEFaYMjkWhsum7imD6aduG44cxYmZi1jlqnUb1FCoDW2Sj4+6LmhFOnpUdSE1nnkJ2eoFQ==", "dev": true, "license": "GPL-3.0", "dependencies": { @@ -139,9 +144,9 @@ } }, "node_modules/@codesandbox/pitcher-client/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "license": "ISC", "bin": { @@ -248,9 +253,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -265,9 +270,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -282,9 +287,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -299,9 +304,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -316,9 +321,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -333,9 +338,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -350,9 +355,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -367,9 +372,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -384,9 +389,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -401,9 +406,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -418,9 +423,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -435,9 +440,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -452,9 +457,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -469,9 +474,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -486,9 +491,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -503,9 +508,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -520,9 +525,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -537,9 +542,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -554,9 +559,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -571,9 +576,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -588,9 +593,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -605,9 +610,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -622,9 +627,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -639,9 +644,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -656,9 +661,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -756,19 +761,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -810,24 +802,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -840,6 +814,7 @@ "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", "dev": true, + "license": "MIT", "dependencies": { "babel-polyfill": "6.26.0", "babel-runtime": "6.26.0", @@ -851,6 +826,7 @@ "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", "dev": true, + "license": "MIT", "dependencies": { "@jumpn/utils-array": "0.3.4", "babel-polyfill": "6.26.0", @@ -863,13 +839,15 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jumpn/utils-graphql": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", "core-js": "2.6.0", @@ -880,7 +858,9 @@ "version": "14.0.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", "dev": true, + "license": "MIT", "dependencies": { "iterall": "^1.2.2" }, @@ -889,9 +869,9 @@ } }, "node_modules/@msgpack/msgpack": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.0.tgz", - "integrity": "sha512-igBxaq5JHWdJ0lDyKkCo00pDu+bNVuBAs/cHra6a3ndCw6vlZK9BGLuG7Fvmar/DXK2uJ25zvgcAZEl+AvLpjQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.1.tgz", + "integrity": "sha512-DnBpqkMOUGayNVKyTLlkM6ILmU/m/+VUxGkuQlPQVAcvreLz5jn1OlQnWd8uHKL/ZSiljpM12rjRhr51VtvJUQ==", "dev": true, "license": "ISC", "engines": { @@ -916,9 +896,9 @@ } }, "node_modules/@types/braces": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.4.tgz", - "integrity": "sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", + "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", "dev": true, "license": "MIT" }, @@ -926,7 +906,8 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -946,13 +927,13 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/retry": { @@ -997,9 +978,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -1079,9 +1060,9 @@ } }, "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, @@ -1106,6 +1087,7 @@ "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", "dev": true, + "license": "MIT", "dependencies": { "babel-runtime": "^6.26.0", "core-js": "^2.5.0", @@ -1116,13 +1098,15 @@ "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", "dev": true, + "license": "MIT", "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -1132,7 +1116,8 @@ "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -1199,9 +1184,9 @@ } }, "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", "dev": true, "license": "MIT" }, @@ -1430,9 +1415,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1443,6 +1428,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", @@ -1606,6 +1608,22 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -1619,20 +1637,43 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "13.0.0", @@ -1652,9 +1693,9 @@ "license": "MIT" }, "node_modules/consola": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", - "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "dev": true, "license": "MIT", "engines": { @@ -1666,6 +1707,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1675,7 +1717,8 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -1696,9 +1739,9 @@ } }, "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, @@ -1732,13 +1775,13 @@ } }, "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "dev": true, "license": "MIT", "dependencies": { - "node-fetch": "^2.6.12" + "node-fetch": "^2.7.0" } }, "node_modules/cross-spawn": { @@ -1848,9 +1891,9 @@ } }, "node_modules/destr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", - "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "dev": true, "license": "MIT" }, @@ -1867,16 +1910,16 @@ } }, "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -1908,13 +1951,13 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -1946,9 +1989,9 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, @@ -1980,9 +2023,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -1993,9 +2036,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2006,31 +2049,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -2092,6 +2135,16 @@ "node": "*" } }, + "node_modules/event-stream/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -2114,7 +2167,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", @@ -2133,26 +2187,33 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -2225,6 +2286,7 @@ "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", "integrity": "sha512-1z5Hw91VrE3GrpCZE6lE8Dy+jz4kXWesLS7rCSjwOxf5BOcIedAZeTUJRIeIzmmR+PA9CKOkPTYFRJbdgUtrxA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^2.4.1", "graphql": "^14.0.2", @@ -2240,6 +2302,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -2252,6 +2315,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2261,11 +2325,30 @@ "node": ">=4" } }, + "node_modules/get-graphql-schema/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/get-graphql-schema/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/get-graphql-schema/node_modules/graphql": { "version": "14.7.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", + "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", "dev": true, + "license": "MIT", "dependencies": { "iterall": "^1.2.2" }, @@ -2278,6 +2361,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2287,6 +2371,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -2295,22 +2380,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2319,10 +2404,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/giget": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.4.tgz", - "integrity": "sha512-Wv+daGyispVoA31TrWAVR+aAdP7roubTPEM/8JzRnqXhLbdJH0T9eQyXVFF8fjk3WKTsctII6QcyxILYgNp2DA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.5.tgz", + "integrity": "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -2330,9 +2429,8 @@ "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", - "nypm": "^0.5.1", - "ohash": "^1.1.4", - "pathe": "^2.0.2", + "nypm": "^0.5.4", + "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { @@ -2340,9 +2438,9 @@ } }, "node_modules/giget/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -2354,9 +2452,9 @@ "license": "MIT" }, "node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2391,10 +2489,11 @@ } }, "node_modules/graphql": { - "version": "16.10.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", - "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -2421,13 +2520,6 @@ "uglify-js": "^3.1.4" } }, - "node_modules/handlebars/node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2554,7 +2646,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ieee754": { "version": "1.2.1", @@ -2602,14 +2695,14 @@ "license": "ISC" }, "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2642,13 +2735,16 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2679,14 +2775,33 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -2748,12 +2863,13 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2874,9 +2990,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, "license": "ISC", "engines": { @@ -2929,9 +3045,9 @@ } }, "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", "engines": { @@ -2942,9 +3058,9 @@ } }, "node_modules/math-intrinsics": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -3038,16 +3154,16 @@ } }, "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, "node_modules/mime-db": { - "version": "1.53.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", - "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { @@ -3170,9 +3286,9 @@ } }, "node_modules/mlly/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -3242,15 +3358,15 @@ } }, "node_modules/nypm": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.5.2.tgz", - "integrity": "sha512-AHzvnyUJYSrrphPhRWWZNcoZfArGNp3Vrc4pm/ZurO74tYNTgAPrEyBQEKy+qioqmWlPXwvMZCG2wOaHlPG0Pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.5.4.tgz", + "integrity": "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==", "dev": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", - "pathe": "^2.0.2", + "pathe": "^2.0.3", "pkg-types": "^1.3.1", "tinyexec": "^0.3.2", "ufo": "^1.5.4" @@ -3263,16 +3379,16 @@ } }, "node_modules/nypm/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/ohash": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", - "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", + "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", "dev": true, "license": "MIT" }, @@ -3292,9 +3408,9 @@ } }, "node_modules/oo-ascii-tree": { - "version": "1.105.0", - "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.105.0.tgz", - "integrity": "sha512-fz4QixX/ImVEMbABqCJxxSwvJGfw9vfq2121RMq/qtCv7BiarY4ZPpheHheOTBvEnhqy81dyMpxiXAY8U3rPjA==", + "version": "1.112.0", + "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.112.0.tgz", + "integrity": "sha512-qQH4jZSdabcKpwcqvJTi7eQL86UucvMacbaHiiIrOynT8jhTLtKS2ixaXgGlNBMeN9UhFi1wS00Hnxhw9aYLsA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3311,6 +3427,16 @@ "wordwrap": "~0.0.2" } }, + "node_modules/optimist/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ora": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", @@ -3548,10 +3674,11 @@ "license": "MIT" }, "node_modules/phoenix": { - "version": "1.7.18", - "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.18.tgz", - "integrity": "sha512-Qo+V9+knfEd+R1pzCe+XJlj3GPSxWz4PNwzFl7GgssuTVYPoh/he3mbPQJ+NEDdqulxAbBtWCNYGPB3WplS5Mg==", - "dev": true + "version": "1.7.21", + "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.21.tgz", + "integrity": "sha512-8wOvJ8pQXRxNbyFlMI+wQhK3bvX4Ps3FtUX2+0cV6lkcebe9VTIl+xS60FLAeaPieFg80djor5z/AKofnwqAUw==", + "dev": true, + "license": "MIT" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3600,9 +3727,9 @@ } }, "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", - "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -3613,9 +3740,9 @@ "dev": true }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -3623,10 +3750,11 @@ } }, "node_modules/preact": { - "version": "10.25.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", - "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", + "version": "10.26.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", + "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -3671,9 +3799,9 @@ } }, "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", - "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "dev": true, "license": "MIT" }, @@ -3733,9 +3861,9 @@ "license": "MIT" }, "node_modules/readdirp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", - "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { @@ -3766,7 +3894,8 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", @@ -3856,6 +3985,24 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -4296,9 +4443,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4310,9 +4457,9 @@ } }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true, "license": "MIT" }, @@ -4331,9 +4478,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, @@ -4342,6 +4489,7 @@ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", "dev": true, + "license": "MIT", "dependencies": { "@types/cookie": "^0.3.3", "cookie": "^0.4.0" @@ -4369,9 +4517,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -4434,16 +4582,18 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", - "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { @@ -4471,28 +4621,25 @@ } }, "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -4543,26 +4690,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4577,68 +4704,76 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "dev": true, "license": "MIT", "peer": true, @@ -4742,7 +4877,8 @@ "version": "0.8.11", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", - "dev": true + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 7753639..29d66ef 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "README.md" ], "devDependencies": { - "@codesandbox/pitcher-client": "1.1.5", + "@codesandbox/pitcher-client": "1.1.7", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 416c48f..0af1efe 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -445,6 +445,7 @@ export type VmListClustersResponse = { data?: { clusters: Array<{ host: string; + slug: string; }>; }; }; diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index f1b0c76..1980bfb 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -11,7 +11,6 @@ import type * as yargs from "yargs"; import { VMTier, CodeSandbox, Sandbox, SetupProgress } from "../../"; import { - sandboxCreate, sandboxFork, vmCreateTag, vmListClusters, @@ -25,10 +24,9 @@ export type BuildCommandArgs = { directory: string; name?: string; path?: string; - ipCountry?: string; + ports?: number[]; fromSandbox?: string; skipFiles?: boolean; - cluster?: string; vmTier?: VmUpdateSpecsRequest["tier"]; }; @@ -36,7 +34,7 @@ function createSpinnerFactory() { let currentLineIndex = 0; let currentSpinnerIndex = 0; - return (prefix: string) => { + return () => { const spinner = ora({ stream: process.stdout }); const spinnerIndex = currentSpinnerIndex++; let lastMethod: string; @@ -54,19 +52,19 @@ function createSpinnerFactory() { return { start(message: string) { updateCursor("start"); - spinner.start(`${prefix}: ${message}`); + spinner.start(message); }, succeed(message: string) { updateCursor("succeed"); - spinner.succeed(`${prefix}: ${message}`); + spinner.succeed(message); }, fail(message: string) { updateCursor("fail"); - spinner.fail(`${prefix}: ${message}`); + spinner.fail(message); }, info(message: string) { updateCursor("info"); - spinner.info(`${prefix}: ${message}`); + spinner.info(message); }, }; }; @@ -89,6 +87,11 @@ export const buildCommand: yargs.CommandModule< describe: "Name for the resulting sandbox that will serve as snapshot", type: "string", }) + .option("ports", { + describe: "Ports to wait for to open before creating snapshot", + type: "number", + array: true, + }) .option("path", { describe: "Which folder (in the dashboard) the sandbox will be created in", @@ -130,14 +133,22 @@ export const buildCommand: yargs.CommandModule< const clusters = clustersData.clusters; const sandboxIds = await Promise.all( - clusters.map(async ({ host: cluster }) => { + clusters.map(async ({ host: cluster, slug }) => { const sdk = new CodeSandbox(API_KEY, { baseUrl: BASE_URL, headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, }, }); - const spinner = createSpinner(`${cluster}`); + const spinner = createSpinner(); + + function createSpinnerMessage(message: string, sandboxId?: string) { + return `[cluster: ${slug}, sandbox: ${ + sandboxId || "-" + }]: ${message}`; + } + + let sandboxId: string | undefined; try { const { hash, files: filePaths } = await hashDirectory( @@ -145,10 +156,10 @@ export const buildCommand: yargs.CommandModule< ); spinner.succeed(`Indexed ${filePaths.length} files`); const shortHash = hash.slice(0, 6); - const tag = `sha:${shortHash}-${cluster || ""}`; + const tag = `sha:${shortHash}-${slug}`; - spinner.start(`Creating sandbox...`); - const sandboxId = await createSandbox({ + spinner.start(createSpinnerMessage("Creating sandbox...")); + sandboxId = await createSandbox({ apiClient, shaTag: tag, fromSandbox: argv.fromSandbox, @@ -157,15 +168,19 @@ export const buildCommand: yargs.CommandModule< vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); - spinner.start(`Starting sandbox... `); + spinner.start( + createSpinnerMessage("Starting sandbox...", sandboxId) + ); const startResponse = await sdk.sandbox["start"](sandboxId, { vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); - const sandbox = new Sandbox(sandboxId, startResponse, apiClient); - const session = await sandbox.connect(); + let sandbox = new Sandbox(sandboxId, startResponse, apiClient); + let session = await sandbox.connect(); - spinner.start("Writing files to sandbox..."); + spinner.start( + createSpinnerMessage("Writing files to sandbox...", sandboxId) + ); let i = 0; for (const filePath of filePaths) { i++; @@ -179,11 +194,16 @@ export const buildCommand: yargs.CommandModule< }); } - spinner.start("Restarting sandbox..."); - await sdk.sandbox.restart(sandbox.id); + spinner.start( + createSpinnerMessage("Restarting sandbox...", sandboxId) + ); + sandbox = await sdk.sandbox.restart(sandbox.id); + session = await sandbox.connect(); const disposableStore = new DisposableStore(); const handleProgress = async (progress: SetupProgress) => { + let buffer: string[] = []; + if ( progress.state === "IN_PROGRESS" && progress.steps.length > 0 @@ -193,10 +213,14 @@ export const buildCommand: yargs.CommandModule< return; } - const spinnerMessage = `Running setup: ${ - progress.currentStepIndex + 1 - } / ${progress.steps.length}: ${step.name}...`; - spinner.start(spinnerMessage); + spinner.start( + createSpinnerMessage( + `Running setup ${progress.currentStepIndex + 1} / ${ + progress.steps.length + } - ${step.name}...`, + sandboxId + ) + ); const shellId = step.shellId; @@ -208,10 +232,9 @@ export const buildCommand: yargs.CommandModule< }, }); - disposableStore.add(Disposable.create(() => shell.kill())); disposableStore.add( shell.onOutput((data) => { - process.stderr.write(data); + buffer.push(data); }) ); } @@ -222,7 +245,13 @@ export const buildCommand: yargs.CommandModule< } if (step.finishStatus === "FAILED") { - spinner.fail(`Setup step failed: ${step.name}`); + spinner.fail( + createSpinnerMessage( + `Setup step failed: ${step.name}`, + sandboxId + ) + ); + console.log(buffer.join("\n")); throw new Error(`Setup step failed: ${step.name}`); } } @@ -237,53 +266,28 @@ export const buildCommand: yargs.CommandModule< await session.setup.waitForFinish(); disposableStore.dispose(); - const tasksWithStart = (await session.tasks.getTasks()).filter( - (t) => t.runAtStart === true - ); - let tasksWithPorts = tasksWithStart.filter((t) => t.preview?.port); + const ports = argv.ports || []; const updatePortSpinner = () => { - const isMultiplePorts = tasksWithPorts.length > 1; + const isMultiplePorts = ports.length > 1; spinner.start( - `Waiting for ${ - isMultiplePorts ? "ports" : "port" - } ${tasksWithPorts - .map((t) => t.preview?.port) - .join(", ")} to open...` + createSpinnerMessage( + `Waiting for ${ + isMultiplePorts ? "ports" : "port" + } ${ports.join(", ")} to open...`, + sandboxId + ) ); }; - if (tasksWithPorts.length > 0) { + if (ports.length > 0) { updatePortSpinner(); await Promise.all( - tasksWithPorts.map(async (task) => { - const port = task.preview?.port; - if (!port) { - return; - } - - let timeout; - const portInfo = await Promise.race([ - session.ports.waitForPort(port), - new Promise( - (resolve) => - (timeout = setTimeout( - () => - resolve( - new Error( - `Waiting for port ${port} timed out after 60s` - ) - ), - 60000 - )) - ), - ]); - clearTimeout(timeout); - - if (portInfo instanceof Error) { - throw portInfo; - } + ports.map(async (port) => { + const portInfo = await session.ports.waitForPort(port, { + timeoutMs: 60000, + }); // eslint-disable-next-line no-constant-condition while (true) { @@ -295,27 +299,36 @@ export const buildCommand: yargs.CommandModule< await new Promise((resolve) => setTimeout(resolve, 1000)); } - tasksWithPorts = tasksWithPorts.filter( - (t) => t.id !== task.id - ); updatePortSpinner(); }) ); } else { spinner.start( - "No ports to open, waiting 5 seconds for tasks to run..." + createSpinnerMessage( + "No ports to open, waiting 5 seconds for tasks to run...", + sandboxId + ) ); await new Promise((resolve) => setTimeout(resolve, 5000)); } - spinner.start("Creating memory snapshot..."); + spinner.start( + createSpinnerMessage("Creating memory snapshot...", sandboxId) + ); await sdk.sandbox.hibernate(sandbox.id); - spinner.succeed("Snapshot created"); + spinner.succeed( + createSpinnerMessage("Snapshot created", sandboxId) + ); return sandbox.id; } catch (error) { spinner.fail( - error instanceof Error ? error.message : "Unknown error occurred" + createSpinnerMessage( + error instanceof Error + ? error.message + : "Unknown error occurred", + sandboxId + ) ); throw error; } diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index 90a2d78..883af78 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -11,7 +11,6 @@ export class Ports { get onDidPortOpen() { return this.onDidPortOpenEmitter.event; } - private onDidPortCloseEmitter = this.disposable.addDisposable( new Emitter() ); From c446d019d0c60e5ebc932d41bf983cba5c158339 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 12 May 2025 13:30:21 +0200 Subject: [PATCH 094/241] ready... --- src/SandboxClient.ts | 11 ++++---- src/bin/commands/build.ts | 2 +- src/previews/index.ts | 14 ++++++++--- src/sessions/WebSocketSession/tasks.ts | 29 ++++++++++++++++++++++ src/sessions/WebSocketSession/terminals.ts | 29 +++++++++++++--------- src/types.ts | 10 +++----- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index 80b50d8..b14f0dc 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -45,7 +45,7 @@ export class SandboxClient { id: this.defaultTemplateId, }); - const client = await sandbox.connect( + const session = await sandbox.connect( // We do not want users to pass gitAccessToken on global user, because it // can be read by other users opts.gitAccessToken @@ -56,7 +56,7 @@ export class SandboxClient { : undefined ); - await client.shells.run( + await session.shells.run( [ "rm -rf .git", "git init", @@ -67,7 +67,9 @@ export class SandboxClient { ].join("&&") ); - client.disconnect(); + await opts.setup?.(session); + + session.disconnect(); return sandbox; } @@ -206,9 +208,6 @@ export class SandboxClient { case "git": { return this.createGitSandbox(opts); } - case "files": { - throw new Error("Not implemented"); - } case "template": { return this.createTemplateSandbox(opts); } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 1980bfb..6297780 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -143,7 +143,7 @@ export const buildCommand: yargs.CommandModule< const spinner = createSpinner(); function createSpinnerMessage(message: string, sandboxId?: string) { - return `[cluster: ${slug}, sandbox: ${ + return `[cluster: ${slug}, sandboxId: ${ sandboxId || "-" }]: ${message}`; } diff --git a/src/previews/index.ts b/src/previews/index.ts index 3e27377..63f329b 100644 --- a/src/previews/index.ts +++ b/src/previews/index.ts @@ -1,8 +1,16 @@ import { Preview } from "./Preview"; -import { InjectFunction } from "./types"; +import { + BaseMessageFromPreview, + BaseMessageToPreview, + InjectFunction, + Message, +} from "./types"; export { Preview, InjectFunction }; -export function createPreview(src: string) { - return new Preview(src); +export function createPreview< + MessageToPreview extends Message = BaseMessageToPreview, + MessageFromPreview extends Message = BaseMessageFromPreview +>(src: string) { + return new Preview(src); } diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index 6c31800..43ef80b 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -1,5 +1,6 @@ import { Emitter, + IDisposable, type IPitcherClient, type protocol, } from "@codesandbox/pitcher-client"; @@ -151,6 +152,34 @@ export class Task { return this.openedShell.output.join("\n"); } + async waitForPort(timeout: number = 30_000) { + if (this.ports.length) { + return this.ports[0]; + } + + let disposer: IDisposable | undefined; + + return Promise.all([ + new Promise((resolve) => { + disposer = this.pitcherClient.clients.task.onTaskUpdate((task) => { + if (task.id !== this.id) { + return; + } + + if (task.ports.length) { + disposer?.dispose(); + resolve(task.ports[0]); + } + }); + }), + new Promise((resolve, reject) => { + setTimeout(() => { + disposer?.dispose(); + reject(new Error("Timeout waiting for port")); + }, timeout); + }), + ]); + } async run() { await this.pitcherClient.clients.task.runTask(this.id); } diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index 099328c..6f0c4f9 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -33,17 +33,15 @@ export class Terminals { return new Terminal(shell, this.pitcherClient); } - /** - * Opens an existing terminal. - */ - async open( - shellId: string, - dimensions = DEFAULT_SHELL_SIZE - ): Promise { - const shell = await this.pitcherClient.clients.shell.open( - shellId as Id, - dimensions - ); + get(shellId: string) { + const shell = this.pitcherClient.clients.shell + .getShells() + .find((shell) => shell.shellId === shellId); + + if (!shell) { + return; + } + return new Terminal(shell, this.pitcherClient); } @@ -101,7 +99,14 @@ export class Terminal { ); } - getOutput(): string { + async open(dimensions = DEFAULT_SHELL_SIZE): Promise { + const shell = await this.pitcherClient.clients.shell.open( + this.shell.shellId, + dimensions + ); + + this.output = shell.buffer; + return this.output.join("\n"); } diff --git a/src/types.ts b/src/types.ts index 099accf..5b7d667 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; +import type { WebSocketSession } from "./sessions/WebSocketSession"; export interface SystemMetricsStatus { cpu: { @@ -206,17 +207,12 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { url: string; branch: string; gitAccessToken?: string; -}; - -export type CreateSandboxFilesSourceOpts = CreateSandboxBaseOpts & { - source: "files"; - files: Record; + setup?: (session: WebSocketSession) => Promise; }; export type CreateSandboxOpts = | CreateSandboxTemplateSourceOpts - | CreateSandboxGitSourceOpts - | CreateSandboxFilesSourceOpts; + | CreateSandboxGitSourceOpts; export type SandboxOpts = { id: string; From bfb5a1206be4f4d4894eeeda405cfaba1fee31ba Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 12 May 2025 15:01:47 +0200 Subject: [PATCH 095/241] new setup approach --- src/bin/commands/build.ts | 85 ++++------ src/sessions/WebSocketSession/setup.ts | 218 ++++++++++++++++++++----- 2 files changed, 207 insertions(+), 96 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 6297780..bbac03e 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -8,7 +8,7 @@ import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { VMTier, CodeSandbox, Sandbox, SetupProgress } from "../../"; +import { VMTier, CodeSandbox, Sandbox } from "../../"; import { sandboxFork, @@ -201,69 +201,44 @@ export const buildCommand: yargs.CommandModule< session = await sandbox.connect(); const disposableStore = new DisposableStore(); - const handleProgress = async (progress: SetupProgress) => { - let buffer: string[] = []; - - if ( - progress.state === "IN_PROGRESS" && - progress.steps.length > 0 - ) { - const step = progress.steps[progress.currentStepIndex]; - if (!step) { - return; - } + const steps = await session.setup.getSteps(); + + for (const step of steps) { + const buffer: string[] = []; + + try { spinner.start( createSpinnerMessage( - `Running setup ${progress.currentStepIndex + 1} / ${ - progress.steps.length + `Running setup ${steps.indexOf(step) + 1} / ${ + steps.length } - ${step.name}...`, sandboxId ) ); - const shellId = step.shellId; - - if (shellId) { - const shell = await session.shells.open(shellId, { - ptySize: { - cols: process.stderr.columns, - rows: process.stderr.rows, - }, - }); + disposableStore.add( + step.onOutput((output) => { + buffer.push(output); + }) + ); - disposableStore.add( - shell.onOutput((data) => { - buffer.push(data); - }) - ); - } - } else if (progress.state === "STOPPED") { - const step = progress.steps[progress.currentStepIndex]; - if (!step) { - return; - } - - if (step.finishStatus === "FAILED") { - spinner.fail( - createSpinnerMessage( - `Setup step failed: ${step.name}`, - sandboxId - ) - ); - console.log(buffer.join("\n")); - throw new Error(`Setup step failed: ${step.name}`); - } - } - }; + const output = await step.open(); - const progress = await session.setup.getProgress(); - await handleProgress(progress); - disposableStore.add( - session.setup.onSetupProgressUpdate(handleProgress) - ); + buffer.push(...output.split("\n")); - await session.setup.waitForFinish(); + await step.waitForFinish(); + } catch (error) { + spinner.fail( + createSpinnerMessage( + `Setup step failed: ${step.name}`, + sandboxId + ) + ); + console.log(buffer.join("\n")); + throw new Error(`Setup step failed: ${step.name}`); + } + } disposableStore.dispose(); @@ -342,9 +317,9 @@ export const buildCommand: yargs.CommandModule< vm_ids: sandboxIds, }, }), - "Failed to create tag" + "Failed to create template" ); - console.log("Tag created: " + data.tag_id); + console.log("Template created: " + data.tag_id); } catch (error) { console.error(error); process.exit(1); diff --git a/src/sessions/WebSocketSession/setup.ts b/src/sessions/WebSocketSession/setup.ts index 64cff66..08675e7 100644 --- a/src/sessions/WebSocketSession/setup.ts +++ b/src/sessions/WebSocketSession/setup.ts @@ -1,20 +1,28 @@ -import type { Id, IPitcherClient } from "@codesandbox/pitcher-client"; -import { listenOnce } from "@codesandbox/pitcher-common/dist/event"; +import { + Barrier, + type IPitcherClient, + type protocol, +} from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; +import { DEFAULT_SHELL_SIZE } from "./terminals"; export class Setup { private disposable = new Disposable(); - private readonly onSetupProgressUpdateEmitter = this.disposable.addDisposable( - new Emitter() + private steps: Promise; + private setupProgress: protocol.setup.SetupProgress; + private readonly onSetupProgressChangeEmitter = this.disposable.addDisposable( + new Emitter() ); - /** - * Emitted when the setup progress is updated. - */ - public readonly onSetupProgressUpdate = - this.onSetupProgressUpdateEmitter.event; - + public readonly onSetupProgressChange = + this.onSetupProgressChangeEmitter.event; + get status() { + return this.setupProgress.state; + } + get currentStepIndex() { + return this.setupProgress.currentStepIndex; + } constructor( sessionDisposable: Disposable, private pitcherClient: IPitcherClient @@ -22,52 +30,180 @@ export class Setup { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); + + // We have a race condition where we might not have the steps yet and need + // an event to tell us when they have started. But we might also have all the steps, + // where no new event will arrive. So we use a barrier to manage this + const initialStepsBarrier = new Barrier(); + + this.setupProgress = this.pitcherClient.clients.setup.getProgress(); + this.steps = initialStepsBarrier + .wait() + .then((result) => (result.status === "resolved" ? result.value : [])); + + let hasInitializedSteps = Boolean(this.setupProgress.steps.length); + + if (hasInitializedSteps) { + initialStepsBarrier.open( + this.setupProgress.steps.map( + (step, index) => new Step(index, step, pitcherClient) + ) + ); + } + this.disposable.addDisposable( pitcherClient.clients.setup.onSetupProgressUpdate((progress) => { - this.onSetupProgressUpdateEmitter.fire(progress); + if (!hasInitializedSteps) { + hasInitializedSteps = true; + initialStepsBarrier.open( + progress.steps.map( + (step, index) => new Step(index, step, pitcherClient) + ) + ); + } + + this.setupProgress = progress; + this.onSetupProgressChangeEmitter.fire(); }) ); } - /** - * Run the setup tasks, this will prepare the docker image, and run the user defined - * setup steps. This will automatically run when a sandbox is started. - */ - async run(): Promise { - return this.pitcherClient.clients.setup.init(); + getSteps() { + return this.steps; } - /** - * Returns the current progress of the setup tasks. - */ - async getProgress(): Promise { - await this.pitcherClient.clients.setup.readyPromise; - return this.pitcherClient.clients.setup.getProgress(); + async run(): Promise { + await this.pitcherClient.clients.setup.init(); } - async waitForFinish(): Promise { - const progress = await this.getProgress(); - if (progress.state === "FINISHED") { - return Promise.resolve(progress); + async waitForFinish(): Promise { + if (this.setupProgress.state === "STOPPED") { + throw new Error("Setup Failed"); } - return listenOnce(this.onSetupProgressUpdate, (progress) => { - return progress.state === "FINISHED"; + if (this.setupProgress.state === "FINISHED") { + return; + } + + return new Promise((resolve, reject) => { + const disposer = this.onSetupProgressChange(() => { + if (this.setupProgress.state === "FINISHED") { + disposer.dispose(); + resolve(); + } else if (this.setupProgress.state === "STOPPED") { + disposer.dispose(); + reject(new Error("Setup Failed")); + } + }); }); } } -export type SetupProgress = { - state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; - steps: Step[]; - currentStepIndex: number; -}; +export class Step { + private disposable = new Disposable(); + // TODO: differentiate between stdout and stderr, also send back bytes instead of + // strings + private onOutputEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onOutput = this.onOutputEmitter.event; + private onStatusChangeEmitter = this.disposable.addDisposable( + new Emitter() + ); + public readonly onStatusChange = this.onStatusChangeEmitter.event; + private output: string[] = []; + + get name(): string { + return this.step.name; + } + + get command() { + return this.step.command; + } -export type SetupShellStatus = "SUCCEEDED" | "FAILED" | "SKIPPED"; + get status() { + return this.step.finishStatus || "IDLE"; + } + + constructor( + stepIndex: number, + private step: protocol.setup.Step, + private pitcherClient: IPitcherClient + ) { + this.disposable.addDisposable( + this.pitcherClient.clients.setup.onSetupProgressUpdate((progress) => { + const oldStep = this.step; + const newStep = progress.steps[stepIndex]; -export type Step = { - name: string; - command: string; - shellId: Id | null; - finishStatus: SetupShellStatus | null; -}; + this.step = newStep; + + if (newStep.finishStatus !== oldStep.finishStatus) { + this.onStatusChangeEmitter.fire(newStep.finishStatus || "IDLE"); + } + }) + ); + this.disposable.addDisposable( + this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + if (shellId === this.step.shellId) { + this.onOutputEmitter.fire(out); + + this.output.push(out); + if (this.output.length > 1000) { + this.output.shift(); + } + } + }) + ); + } + + async open(dimensions = DEFAULT_SHELL_SIZE): Promise { + const open = async (shellId: protocol.shell.ShellId) => { + const shell = await this.pitcherClient.clients.shell.open( + shellId, + dimensions + ); + + this.output = shell.buffer; + + return this.output.join("\n"); + }; + + if (this.step.shellId) { + return open(this.step.shellId); + } + + return new Promise((resolve) => { + const disposable = this.onStatusChange(() => { + if (this.step.shellId) { + disposable.dispose(); + resolve(open(this.step.shellId)); + } + }); + }); + } + + async waitForFinish() { + if (this.step.finishStatus === "FAILED") { + throw new Error("Step Failed"); + } + + if ( + this.step.finishStatus === "SUCCEEDED" || + this.step.finishStatus === "SKIPPED" + ) { + return; + } + + return new Promise((resolve, reject) => { + const disposable = this.onStatusChange((status) => { + if (status === "SUCCEEDED" || status === "SKIPPED") { + disposable.dispose(); + resolve(); + } else if (status === "FAILED") { + disposable.dispose(); + reject(new Error("Step Failed")); + } + }); + }); + } +} From 26671e6901d09704b4d6732f5e56d6afb0c8e0d0 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 13 May 2025 10:59:15 +0200 Subject: [PATCH 096/241] preview hosts and command fix --- openapi.json | 146 +++++++++++++++ package.json | 2 +- src/SandboxClient.ts | 11 +- src/api-clients/client/sdk.gen.ts | 64 ++++++- src/api-clients/client/types.gen.ts | 74 +++++++- src/bin/commands/previewHosts.ts | 166 ++++++++++++++++++ src/bin/main.ts | 2 + src/sessions/WebSocketSession/commands.ts | 15 +- src/sessions/WebSocketSession/git.ts | 21 +-- src/sessions/WebSocketSession/index.ts | 4 + src/sessions/WebSocketSession/interpreters.ts | 16 +- 11 files changed, 479 insertions(+), 42 deletions(-) create mode 100644 src/bin/commands/previewHosts.ts diff --git a/openapi.json b/openapi.json index 2a2d5aa..ddab103 100644 --- a/openapi.json +++ b/openapi.json @@ -602,6 +602,54 @@ "title": "PreviewTokenUpdateRequest", "type": "object" }, + "PreviewHostListResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "preview_hosts": { + "items": { + "properties": { + "host": { "type": "string" }, + "inserted_at": { + "format": "date_time", + "type": "string" + } + }, + "required": ["host", "inserted_at"], + "type": "object" + }, + "type": "array" + } + }, + "required": ["preview_hosts"], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "PreviewHostListResponse", + "type": "object" + }, "VMShutdownRequest": { "properties": {}, "title": "VMShutdownRequest" }, "VMUpdateSpecsRequest": { "properties": { @@ -644,6 +692,21 @@ "title": "VMCreateTagRequest", "type": "object" }, + "PreviewHostRequest": { + "properties": { + "hosts": { + "items": { + "description": "Trusted domains that are allowed to access sandbox previews.", + "example": "https://example.com", + "type": "string" + }, + "type": "array" + } + }, + "required": ["hosts"], + "title": "PreviewHostRequest", + "type": "object" + }, "VMStartResponse": { "allOf": [ { @@ -2098,6 +2161,89 @@ "summary": "Update VM Specs", "tags": ["vm"] } + }, + "/workspace/preview_hosts": { + "get": { + "callbacks": {}, + "description": "List all trusted preview hosts for the current team\n", + "operationId": "preview_host/list", + "parameters": [], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewHostListResponse" + } + } + }, + "description": "Preview Host List Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "List Preview Hosts", + "tags": [] + }, + "post": { + "callbacks": {}, + "description": "Add one or more trusted domains that are allowed to access sandbox previews for this workspace.\n", + "operationId": "preview_host/create", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/PreviewHostRequest" } + } + }, + "description": "Preview Host Create Request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewHostListResponse" + } + } + }, + "description": "Preview Host List Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Create Preview Hosts", + "tags": [] + }, + "put": { + "callbacks": {}, + "description": "Replace the list of trusted domains that are allowed to access sandbox previews for this workspace.\n", + "operationId": "preview_host/update", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/PreviewHostRequest" } + } + }, + "description": "Preview Host Update Request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewHostListResponse" + } + } + }, + "description": "Preview Host List Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Update Preview Hosts", + "tags": [] + } } }, "security": [], diff --git a/package.json b/package.json index 29d66ef..3b2ca38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.0.0-beta.8", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index b14f0dc..fa328b3 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -48,12 +48,11 @@ export class SandboxClient { const session = await sandbox.connect( // We do not want users to pass gitAccessToken on global user, because it // can be read by other users - opts.gitAccessToken - ? { - id: "clone-admin", - permission: "write", - } - : undefined + { + id: "clone-admin", + permission: "write", + gitAccessToken: opts.gitAccessToken, + } ); await session.shells.run( diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 38a9cec..500467b 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -473,4 +473,66 @@ export const vmUpdateSpecs2 = (options: Op ...options?.headers } }); +}; + +/** + * List Preview Hosts + * List all trusted preview hosts for the current team + * + */ +export const previewHostList = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options + }); +}; + +/** + * Create Preview Hosts + * Add one or more trusted domains that are allowed to access sandbox previews for this workspace. + * + */ +export const previewHostCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Update Preview Hosts + * Replace the list of trusted domains that are allowed to access sandbox previews for this workspace. + * + */ +export const previewHostUpdate = (options?: Options) => { + return (options?.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; \ No newline at end of file diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 0af1efe..ef89c67 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -307,6 +307,20 @@ export type PreviewTokenUpdateRequest = { expires_at?: string | null; }; +export type PreviewHostListResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + preview_hosts: Array<{ + host: string; + inserted_at: string; + }>; + }; +}; + export type VmShutdownRequest = { [key: string]: unknown; }; @@ -335,6 +349,10 @@ export type VmCreateTagRequest = { vm_ids: Array; }; +export type PreviewHostRequest = { + hosts: Array; +}; + export type VmStartResponse = { errors?: Array { + const spinner = ora({ stream: process.stdout }); + const spinnerIndex = currentSpinnerIndex++; + let lastMethod: string; + + function updateCursor(method: string) { + readline.moveCursor( + process.stdout, + 0, + spinnerIndex - currentLineIndex + (lastMethod !== "start" ? -1 : 0) + ); + currentLineIndex = spinnerIndex; + lastMethod = method; + } + + return { + start(message: string) { + updateCursor("start"); + spinner.start(message); + }, + succeed(message: string) { + updateCursor("succeed"); + spinner.succeed(message); + }, + fail(message: string) { + updateCursor("fail"); + spinner.fail(message); + }, + info(message: string) { + updateCursor("info"); + spinner.info(message); + }, + }; + }; +} + +export const previewHostsCommand: yargs.CommandModule< + Record, + PreviewHostsCommandArgs +> = { + command: "preview-hosts", + describe: + "Manage preview hosts for your workspace. This allows you to access use the preview API from trusted hosts.", + builder: (yargs: yargs.Argv) => + yargs + .option("list", { + describe: "List current preview hosts", + type: "boolean", + }) + .option("add", { + describe: "Add a preview host, ex. ", + type: "string", + }) + .option("clear", { + describe: "Clear all preview hosts", + type: "boolean", + }) + .option("remove", { + describe: "Remove a preview host", + type: "string", + }), + handler: async (argv) => { + // Only allow one operation at a time + const ops = [argv.list, argv.add, argv.remove, argv.clear].filter(Boolean); + if (ops.length !== 1) { + throw new Error( + "Please specify exactly one operation: --list, --add, --remove, or --clear" + ); + } + + const API_KEY = getApiKey(); + const apiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + }, + }) + ); + + if (argv.list) { + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + const hosts = data.preview_hosts.map(({ host }) => host); + if (hosts.length) { + console.log(hosts.join("\n")); + } else { + console.log("No preview hosts found"); + } + return; + } + + // For add, remove, clear: always work with the full list + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + let hosts = data.preview_hosts.map(({ host }) => host); + + if (argv.add) { + const hostToAdd = argv.add.trim(); + if (hosts.includes(hostToAdd)) { + console.log(`Host already exists: ${hostToAdd}`); + return; + } + hosts.push(hostToAdd); + await previewHostUpdate({ + client: apiClient, + body: { hosts }, + }); + console.log(`Added preview host: ${hostToAdd}`); + return; + } + + if (argv.remove) { + const hostToRemove = argv.remove.trim(); + if (!hosts.includes(hostToRemove)) { + console.log(`Host not found: ${hostToRemove}`); + return; + } + hosts = hosts.filter((h) => h !== hostToRemove); + await previewHostUpdate({ + client: apiClient, + body: { hosts }, + }); + console.log(`Removed preview host: ${hostToRemove}`); + return; + } + + if (argv.clear) { + if (hosts.length === 0) { + console.log("Preview host list is already empty."); + return; + } + await previewHostUpdate({ + client: apiClient, + body: { hosts: [] }, + }); + console.log("Cleared all preview hosts."); + return; + } + }, +}; diff --git a/src/bin/main.ts b/src/bin/main.ts index dd6fe34..38e945c 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -3,6 +3,7 @@ import { hideBin } from "yargs/helpers"; import { buildCommand } from "./commands/build"; import { sandboxCommand } from "./commands/sandbox"; +import { previewHostsCommand } from "./commands/previewHosts"; yargs(hideBin(process.argv)) .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") @@ -12,4 +13,5 @@ yargs(hideBin(process.argv)) .recommendCommands() .command(buildCommand) .command(sandboxCommand) + .command(previewHostsCommand) .parse(); diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 8f7dd47..2dd6174 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -38,7 +38,7 @@ export class Commands { }); } - async run(command: string, opts?: ShellRunOpts): Promise { + async create(command: string, opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); @@ -66,10 +66,21 @@ export class Commands { this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); } - return new Command( + const cmd = new Command( this.pitcherClient, shell as protocol.shell.CommandShellDTO ); + + return cmd; + } + + async run(command: string | string[], opts?: ShellRunOpts): Promise { + const cmd = await this.create( + Array.isArray(command) ? command.join(" && ") : command, + opts + ); + + return cmd.getOutput(); } getAll(): Command[] { diff --git a/src/sessions/WebSocketSession/git.ts b/src/sessions/WebSocketSession/git.ts index 420dd14..c4be171 100644 --- a/src/sessions/WebSocketSession/git.ts +++ b/src/sessions/WebSocketSession/git.ts @@ -1,28 +1,9 @@ -import type { Id, IPitcherClient } from "@codesandbox/pitcher-client"; +import type { IPitcherClient } from "@codesandbox/pitcher-client"; export class Git { onStatusChange = this.pitcherClient.clients.git.onStatusUpdated; constructor(private pitcherClient: IPitcherClient) {} - pull(force?: boolean) { - return this.pitcherClient.clients.git.pull(undefined, force); - } - commit(message: string, paths?: string[]) { - return this.pitcherClient.clients.git.commit(message, paths); - } - push() { - return this.pitcherClient.clients.git.push(); - } status() { return this.pitcherClient.clients.git.getStatus(); } - discard(path?: string[]) { - return this.pitcherClient.clients.git.discard(path); - } - resetToRemote() { - return this.pitcherClient.clients.git.resetLocalWithRemote(); - } - // TODO: Expose git checkout - checkout(branch: string) { - return this.pitcherClient.clients.task.runCommand(`git checkout ${branch}`); - } } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index e863556..df843c9 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -15,6 +15,7 @@ import { Client } from "@hey-api/client-fetch"; import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; +import { Git } from "./git"; export * from "./filesystem"; export * from "./ports"; @@ -23,6 +24,7 @@ export * from "./shells"; export * from "./tasks"; export * from "./terminals"; export * from "./commands"; +export * from "./git"; export * from "./interpreters"; export class WebSocketSession { @@ -97,6 +99,8 @@ export class WebSocketSession { this.commands ); + public readonly git = new Git(this.pitcherClient); + /** * Namespace for detecting open ports on this sandbox, and getting preview URLs for * them. diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts index 633e260..64fb6e1 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -14,14 +14,14 @@ export class Interpreters { this.disposable.dispose(); }); } - private async run(command: string, env: Record = {}) { + private run(command: string, env: Record = {}) { return this.commands.run(command, { env, }); } - async javascript(code: string) { - const command = await this.run( + javascript(code: string) { + return this.run( `node -p "$(cat <<'EOF' (() => {${code .split("\n") @@ -37,13 +37,9 @@ EOF NO_COLOR: "true", } ); - - console.log(command); - - return command.getOutput(); } - async python(code: string) { - const command = await this.run(`python3 -c "exec('''\ + python(code: string) { + return this.run(`python3 -c "exec('''\ ${code .split("\n") .map((line, index, lines) => { @@ -53,7 +49,5 @@ ${code }) .join("\n")} ''')"`); - - return command.getOutput(); } } From 58558da536a76ea257ffa3be9ffadcc864afb9ae Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 14 May 2025 10:10:56 +0200 Subject: [PATCH 097/241] fix run commands --- src/SandboxClient.ts | 19 ++++++++++++------- src/sandbox.ts | 21 +++++++++++---------- src/sessions/WebSocketSession/commands.ts | 4 +--- src/types.ts | 6 +++++- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index fa328b3..e9f5232 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -1,5 +1,4 @@ import type { Client } from "@hey-api/client-fetch"; -import { createClient, createConfig } from "@hey-api/client-fetch"; import { sandboxFork, @@ -51,7 +50,13 @@ export class SandboxClient { { id: "clone-admin", permission: "write", - gitAccessToken: opts.gitAccessToken, + ...(opts.config + ? { + gitAccessToken: opts.config.accessToken, + email: opts.config.email, + name: opts.config.name, + } + : {}), } ); @@ -63,7 +68,7 @@ export class SandboxClient { "git fetch origin", `git checkout -b ${opts.branch}`, `git reset --hard origin/${opts.branch}`, - ].join("&&") + ].join(" && ") ); await opts.setup?.(session); @@ -102,8 +107,8 @@ export class SandboxClient { return new Sandbox( sandbox.id, - getStartResponse(sandbox.start_response), - this.apiClient + this.apiClient, + getStartResponse(sandbox.start_response) ); } @@ -139,7 +144,7 @@ export class SandboxClient { */ async resume(sandboxId: string) { const startResponse = await this.start(sandboxId); - return new Sandbox(sandboxId, startResponse, this.apiClient); + return new Sandbox(sandboxId, this.apiClient, startResponse); } /** @@ -168,7 +173,7 @@ export class SandboxClient { await this.shutdown(sandboxId); const startResponse = await this.start(sandboxId, opts); - return new Sandbox(sandboxId, startResponse, this.apiClient); + return new Sandbox(sandboxId, this.apiClient, startResponse); } /** diff --git a/src/sandbox.ts b/src/sandbox.ts index fe34c63..0727d8c 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -32,19 +32,20 @@ export class Sandbox { this.pitcherManagerResponse.pitcherVersion ); } - private get globalSession() { - return { + private defaultSession: SandboxSession; + constructor( + public id: string, + private apiClient: Client, + private pitcherManagerResponse: PitcherManagerResponse, + customSession?: SandboxSession + ) { + this.defaultSession = customSession || { sandboxId: this.id, pitcherToken: this.pitcherManagerResponse.pitcherToken, pitcherUrl: this.pitcherManagerResponse.pitcherURL, userWorkspacePath: this.pitcherManagerResponse.userWorkspacePath, }; } - constructor( - public id: string, - private pitcherManagerResponse: PitcherManagerResponse, - private apiClient: Client - ) {} /** * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the @@ -116,7 +117,7 @@ export class Sandbox { ): Promise { const session = customSession ? await this.createSession(customSession) - : this.globalSession; + : this.defaultSession; return WebSocketSession.init(session, this.apiClient); } @@ -124,7 +125,7 @@ export class Sandbox { async createRestSession(customSession?: SessionCreateOptions) { const session = customSession ? await this.createSession(customSession) - : this.globalSession; + : this.defaultSession; return new RestSession(session); } @@ -134,7 +135,7 @@ export class Sandbox { ): Promise { const session = customSession ? await this.createSession(customSession) - : this.globalSession; + : this.defaultSession; return { id: this.id, diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 2dd6174..7dcab83 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -58,7 +58,7 @@ export class Commands { this.pitcherClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, commandWithEnv, - "COMMAND", + "TERMINAL", true ); @@ -128,7 +128,6 @@ export class Command { if (shellId === this.id) { this.status = exitCode === 0 ? "FINISHED" : "ERROR"; this.barrier.open(); - this.kill(); } }) ); @@ -138,7 +137,6 @@ export class Command { if (shellId === this.id) { this.status = "KILLED"; this.barrier.open(); - this.kill(); } }) ); diff --git a/src/types.ts b/src/types.ts index 5b7d667..021e1ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -206,7 +206,11 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { source: "git"; url: string; branch: string; - gitAccessToken?: string; + config?: { + accessToken: string; + email: string; + name?: string; + }; setup?: (session: WebSocketSession) => Promise; }; From 211a07d6f2eb7f4b8c3719a7f58b16a3fb3e7d91 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 14 May 2025 14:49:00 +0200 Subject: [PATCH 098/241] things --- openapi.json | 10 +- src/SandboxClient.ts | 61 +++++----- src/api-clients/client/sdk.gen.ts | 2 + src/api-clients/client/types.gen.ts | 8 ++ src/bin/commands/build.ts | 7 +- src/browser.ts | 14 ++- src/sandbox.ts | 104 +++++++++++++++--- src/sessions/WebSocketSession/commands.ts | 13 ++- src/sessions/WebSocketSession/index.ts | 68 ++---------- src/sessions/WebSocketSession/interpreters.ts | 20 ++-- src/sessions/WebSocketSession/setup.ts | 8 +- src/sessions/WebSocketSession/terminals.ts | 29 ++++- src/types.ts | 9 +- 13 files changed, 215 insertions(+), 138 deletions(-) diff --git a/openapi.json b/openapi.json index ddab103..1307742 100644 --- a/openapi.json +++ b/openapi.json @@ -1061,6 +1061,14 @@ "description": "GitHub token for the session", "type": "string" }, + "git_user_email": { + "description": "Git user email to configure for this session", + "type": "string" + }, + "git_user_name": { + "description": "Git user name to configure for this session", + "type": "string" + }, "permission": { "description": "Permission level for the session", "enum": ["read", "write"], @@ -1962,7 +1970,7 @@ "/vm/{id}/sessions": { "post": { "callbacks": {}, - "description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container.\nA session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the Linux username is based on the session ID.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n", + "description": "Creates a new session on a running VM. A session represents an isolated Linux user, with their own container.\nA session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write).\nThe session is identified by a unique session ID, and the Linux username is based on the session ID.\n\nThe Git user name and email can be configured via parameters.\n\nThis endpoint requires the VM to be running. If the VM is not running, it will return a 404 error.\n", "operationId": "vm/create_session", "parameters": [ { diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index e9f5232..5975ef9 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -28,6 +28,34 @@ import { } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +export async function startVm( + apiClient: Client, + sandboxId: string, + startOpts?: StartSandboxOpts +): Promise { + const startResult = await vmStart({ + client: apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }); + + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); + + return getStartResponse(response); +} + export class SandboxClient { get defaultTemplateId() { return getDefaultTemplateId(this.apiClient); @@ -48,7 +76,7 @@ export class SandboxClient { // We do not want users to pass gitAccessToken on global user, because it // can be read by other users { - id: "clone-admin", + id: "clone-repo-user", permission: "write", ...(opts.config ? { @@ -112,38 +140,11 @@ export class SandboxClient { ); } - private async start( - sandboxId: string, - startOpts?: StartSandboxOpts - ): Promise { - const startResult = await vmStart({ - client: this.apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); - - return getStartResponse(response); - } - /** * */ async resume(sandboxId: string) { - const startResponse = await this.start(sandboxId); + const startResponse = await startVm(this.apiClient, sandboxId); return new Sandbox(sandboxId, this.apiClient, startResponse); } @@ -171,7 +172,7 @@ export class SandboxClient { */ public async restart(sandboxId: string, opts?: StartSandboxOpts) { await this.shutdown(sandboxId); - const startResponse = await this.start(sandboxId, opts); + const startResponse = await startVm(this.apiClient, sandboxId, opts); return new Sandbox(sandboxId, this.apiClient, startResponse); } diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 500467b..6119539 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -349,6 +349,8 @@ export const vmUpdateHibernationTimeout = * A session has a single use token that the user can use to connect to the VM. This token has specific permissions (currently, read or write). * The session is identified by a unique session ID, and the Linux username is based on the session ID. * + * The Git user name and email can be configured via parameters. + * * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. * */ diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index ef89c67..e553d64 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -523,6 +523,14 @@ export type VmCreateSessionRequest = { * GitHub token for the session */ git_access_token?: string; + /** + * Git user email to configure for this session + */ + git_user_email?: string; + /** + * Git user name to configure for this session + */ + git_user_name?: string; /** * Permission level for the session */ diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index bbac03e..762377a 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -19,6 +19,7 @@ import { import { getDefaultTemplateId, handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; +import { startVm } from "../../SandboxClient"; export type BuildCommandArgs = { directory: string; @@ -172,10 +173,10 @@ export const buildCommand: yargs.CommandModule< createSpinnerMessage("Starting sandbox...", sandboxId) ); - const startResponse = await sdk.sandbox["start"](sandboxId, { + const startResponse = await startVm(apiClient, sandboxId, { vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); - let sandbox = new Sandbox(sandboxId, startResponse, apiClient); + let sandbox = new Sandbox(sandboxId, apiClient, startResponse); let session = await sandbox.connect(); spinner.start( @@ -227,7 +228,7 @@ export const buildCommand: yargs.CommandModule< buffer.push(...output.split("\n")); - await step.waitForFinish(); + await step.waitUntilComplete(); } catch (error) { spinner.fail( createSpinnerMessage( diff --git a/src/browser.ts b/src/browser.ts index b7e065e..4ab4e4e 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -47,6 +47,8 @@ export async function connectToSandbox(options: { onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; }): Promise { + let env: Record = {}; + const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -56,11 +58,19 @@ export async function connectToSandbox(options: { (() => { return () => {}; }), - requestPitcherInstance: options.getSession, + requestPitcherInstance: async (id) => { + const session = await options.getSession(id); + + if (session.env) { + env = session.env; + } + + return session; + }, subscriptions: DEFAULT_SUBSCRIPTIONS, }, options.initStatusCb || (() => {}) ); - return new WebSocketSession(pitcherClient); + return new WebSocketSession(pitcherClient, () => env); } diff --git a/src/sandbox.ts b/src/sandbox.ts index 0727d8c..35b10bf 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,12 +1,14 @@ import { Disposable, + initPitcherClient, PitcherManagerResponse, type protocol as _protocol, } from "@codesandbox/pitcher-client"; -import type { - SandboxSession, - SessionCreateOptions, - SandboxBrowserSession, +import { + type SandboxSession, + type SessionCreateOptions, + type SandboxBrowserSession, + DEFAULT_SUBSCRIPTIONS, } from "./types"; import { Client } from "@hey-api/client-fetch"; import { @@ -18,6 +20,7 @@ import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; import { WebSocketSession } from "./sessions/WebSocketSession"; import { RestSession } from "./sessions/RestSession"; +import { SandboxClient, startVm } from "./SandboxClient"; export class Sandbox { get bootupType() { @@ -32,20 +35,19 @@ export class Sandbox { this.pitcherManagerResponse.pitcherVersion ); } - private defaultSession: SandboxSession; - constructor( - public id: string, - private apiClient: Client, - private pitcherManagerResponse: PitcherManagerResponse, - customSession?: SandboxSession - ) { - this.defaultSession = customSession || { + private get globalSession() { + return { sandboxId: this.id, pitcherToken: this.pitcherManagerResponse.pitcherToken, pitcherUrl: this.pitcherManagerResponse.pitcherURL, userWorkspacePath: this.pitcherManagerResponse.userWorkspacePath, }; } + constructor( + public id: string, + private apiClient: Client, + private pitcherManagerResponse: PitcherManagerResponse + ) {} /** * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the @@ -91,6 +93,13 @@ export class Sandbox { body: { session_id: opts.id, permission: opts.permission ?? "write", + ...(opts.git + ? { + git_access_token: opts.git.accessToken, + git_user_email: opts.git.email, + git_user_name: opts.git.name, + } + : {}), }, path: { id: this.id, @@ -107,6 +116,7 @@ export class Sandbox { pitcherToken: handledResponse.pitcher_token, pitcherUrl: handledResponse.pitcher_url, userWorkspacePath: handledResponse.user_workspace_path, + env: opts.env, }; return session; @@ -115,17 +125,74 @@ export class Sandbox { async connect( customSession?: SessionCreateOptions ): Promise { - const session = customSession - ? await this.createSession(customSession) - : this.defaultSession; + let hasConnected = false; + const pitcherClient = await initPitcherClient( + { + appId: "sdk", + instanceId: this.id, + onFocusChange() { + return () => {}; + }, + requestPitcherInstance: async () => { + // If we reconnect we have to resume the Sandbox and get new session details + if (hasConnected) { + this.pitcherManagerResponse = await startVm( + this.apiClient, + this.id + ); + } + + const session = customSession + ? await this.createSession(customSession) + : this.globalSession; + + const headers = this.apiClient.getConfig().headers as Headers; + + if (headers.get("x-pitcher-manager-url")) { + // This is a hack, we need to tell the global scheduler that the VM is running + // in a different cluster than the one it'd like to default to. + + const preferredManager = headers + .get("x-pitcher-manager-url") + ?.replace("/api/v1", "") + .replace("https://", ""); + const baseUrl = this.apiClient + .getConfig() + .baseUrl?.replace("api", "global-scheduler"); + + await fetch( + `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` + ).then((res) => res.json()); + } + + hasConnected = true; + + return { + bootupType: this.bootupType, + pitcherURL: session.pitcherUrl, + workspacePath: session.userWorkspacePath, + userWorkspacePath: session.userWorkspacePath, + pitcherManagerVersion: + this.pitcherManagerResponse.pitcherManagerVersion, + pitcherVersion: this.pitcherManagerResponse.pitcherVersion, + latestPitcherVersion: + this.pitcherManagerResponse.latestPitcherVersion, + pitcherToken: session.pitcherToken, + cluster: this.cluster, + }; + }, + subscriptions: DEFAULT_SUBSCRIPTIONS, + }, + () => {} + ); - return WebSocketSession.init(session, this.apiClient); + return new WebSocketSession(pitcherClient, () => customSession?.env ?? {}); } async createRestSession(customSession?: SessionCreateOptions) { const session = customSession ? await this.createSession(customSession) - : this.defaultSession; + : this.globalSession; return new RestSession(session); } @@ -135,10 +202,11 @@ export class Sandbox { ): Promise { const session = customSession ? await this.createSession(customSession) - : this.defaultSession; + : this.globalSession; return { id: this.id, + env: customSession?.env, bootupType: this.bootupType, cluster: this.cluster, latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion, diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 7dcab83..e3f41f9 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -31,7 +31,8 @@ export class Commands { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private pitcherClient: IPitcherClient, + private getEnv: () => Record ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -43,9 +44,11 @@ export class Commands { const onOutput = new Emitter(); disposableStore.add(onOutput); + const allEnv = Object.assign(this.getEnv(), opts?.env ?? {}); + // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(opts?.env ?? {}).length - ? `env ${Object.entries(opts?.env ?? {}) + let commandWithEnv = Object.keys(allEnv).length + ? `env ${Object.entries(allEnv) .map(([key, value]) => `${key}=${value}`) .join(" ")} ${command}` : command; @@ -80,7 +83,7 @@ export class Commands { opts ); - return cmd.getOutput(); + return cmd.waitUntilComplete(); } getAll(): Command[] { @@ -157,7 +160,7 @@ export class Command { ); } - async getOutput(): Promise { + async waitUntilComplete(): Promise { await this.barrier.wait(); if (this.status === "FINISHED") { diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index df843c9..5345210 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -30,53 +30,6 @@ export * from "./interpreters"; export class WebSocketSession { private disposable = new Disposable(); - static async init(session: SandboxSession, apiClient: Client) { - const pitcherClient = await initPitcherClient( - { - appId: "sdk", - instanceId: session.sandboxId, - onFocusChange() { - return () => {}; - }, - requestPitcherInstance: async () => { - const headers = apiClient.getConfig().headers as Headers; - - if (headers.get("x-pitcher-manager-url")) { - // This is a hack, we need to tell the global scheduler that the VM is running - // in a different cluster than the one it'd like to default to. - - const preferredManager = headers - .get("x-pitcher-manager-url") - ?.replace("/api/v1", "") - .replace("https://", ""); - const baseUrl = apiClient - .getConfig() - .baseUrl?.replace("api", "global-scheduler"); - - await fetch( - `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` - ).then((res) => res.json()); - } - - return { - bootupType: "RESUME", - pitcherURL: session.pitcherUrl, - workspacePath: session.userWorkspacePath, - userWorkspacePath: session.userWorkspacePath, - pitcherManagerVersion: "1.0.0-session", - pitcherVersion: "1.0.0-session", - latestPitcherVersion: "1.0.0-session", - pitcherToken: session.pitcherToken, - cluster: "session", - }; - }, - subscriptions: DEFAULT_SUBSCRIPTIONS, - }, - () => {} - ); - - return new WebSocketSession(pitcherClient); - } /** * Namespace for all filesystem operations on this sandbox. */ @@ -87,17 +40,10 @@ export class WebSocketSession { */ public readonly shells = new Shells(this.disposable, this.pitcherClient); - public readonly terminals = new Terminals( - this.disposable, - this.pitcherClient - ); - public readonly commands = new Commands(this.disposable, this.pitcherClient); + public readonly terminals: Terminals; + public readonly commands: Commands; - public readonly interpreters = new Interpreters( - this.disposable, - this.pitcherClient, - this.commands - ); + public readonly interpreters: Interpreters; public readonly git = new Git(this.pitcherClient); @@ -130,7 +76,10 @@ export class WebSocketSession { */ public readonly tasks = new Tasks(this.disposable, this.pitcherClient); - constructor(protected pitcherClient: IPitcherClient) { + constructor( + protected pitcherClient: IPitcherClient, + getEnv: () => Record + ) { // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { // dispose: @@ -138,6 +87,9 @@ export class WebSocketSession { // }; // this.addDisposable(metricsDisposable); + this.terminals = new Terminals(this.disposable, this.pitcherClient, getEnv); + this.commands = new Commands(this.disposable, this.pitcherClient, getEnv); + this.interpreters = new Interpreters(this.disposable, this.commands); this.disposable.addDisposable(this.pitcherClient); } diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts index 64fb6e1..f654865 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -1,23 +1,15 @@ -import type { IPitcherClient } from "@codesandbox/pitcher-client"; - import { Disposable } from "../../utils/disposable"; -import { Commands } from "./commands"; +import { Commands, ShellRunOpts } from "./commands"; export class Interpreters { private disposable = new Disposable(); - constructor( - sessionDisposable: Disposable, - private pitcherClient: IPitcherClient, - private commands: Commands - ) { + constructor(sessionDisposable: Disposable, private commands: Commands) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } - private run(command: string, env: Record = {}) { - return this.commands.run(command, { - env, - }); + private run(command: string, opts?: ShellRunOpts) { + return this.commands.run(command, opts); } javascript(code: string) { @@ -34,7 +26,9 @@ export class Interpreters { EOF )"`, { - NO_COLOR: "true", + env: { + NO_COLOR: "true", + }, } ); } diff --git a/src/sessions/WebSocketSession/setup.ts b/src/sessions/WebSocketSession/setup.ts index 08675e7..074ae5c 100644 --- a/src/sessions/WebSocketSession/setup.ts +++ b/src/sessions/WebSocketSession/setup.ts @@ -76,7 +76,7 @@ export class Setup { await this.pitcherClient.clients.setup.init(); } - async waitForFinish(): Promise { + async waitUntilComplete(): Promise { if (this.setupProgress.state === "STOPPED") { throw new Error("Setup Failed"); } @@ -135,6 +135,10 @@ export class Step { const oldStep = this.step; const newStep = progress.steps[stepIndex]; + if (!newStep) { + return; + } + this.step = newStep; if (newStep.finishStatus !== oldStep.finishStatus) { @@ -182,7 +186,7 @@ export class Step { }); } - async waitForFinish() { + async waitUntilComplete() { if (this.step.finishStatus === "FAILED") { throw new Error("Step Failed"); } diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index 6f0c4f9..cf47daf 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -2,6 +2,7 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; import type { Id } from "@codesandbox/pitcher-common"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; +import { ShellRunOpts } from "./commands"; export type ShellSize = { cols: number; rows: number }; @@ -11,7 +12,8 @@ export class Terminals { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private pitcherClient: IPitcherClient, + private getEnv: () => Record ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -19,17 +21,34 @@ export class Terminals { } async create( - shellType: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", - dimensions = DEFAULT_SHELL_SIZE + command: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", + opts?: ShellRunOpts ): Promise { + const allEnv = Object.assign(this.getEnv(), opts?.env ?? {}); + + // TODO: use a new shell API that natively supports cwd & env + let commandWithEnv = Object.keys(allEnv).length + ? `env ${Object.entries(allEnv) + .map(([key, value]) => `${key}=${value}`) + .join(" ")} ${command}` + : command; + + if (opts?.cwd) { + commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; + } + const shell = await this.pitcherClient.clients.shell.create( this.pitcherClient.workspacePath, - dimensions, - shellType, + opts?.dimensions ?? DEFAULT_SHELL_SIZE, + commandWithEnv, "TERMINAL", true ); + if (opts?.name) { + this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); + } + return new Terminal(shell, this.pitcherClient); } diff --git a/src/types.ts b/src/types.ts index 021e1ee..2d37c36 100644 --- a/src/types.ts +++ b/src/types.ts @@ -183,7 +183,12 @@ export type SandboxSessionData = { export interface SessionCreateOptions { id: string; permission?: "read" | "write"; - gitAccessToken?: string; + git?: { + accessToken?: string; + email: string; + name?: string; + }; + env?: Record; } export type SandboxSession = { @@ -191,6 +196,7 @@ export type SandboxSession = { pitcherToken: string; pitcherUrl: string; userWorkspacePath: string; + env?: Record; }; export type CreateSandboxTemplateSourceOpts = CreateSandboxBaseOpts & { @@ -228,4 +234,5 @@ export type SandboxOpts = { export type SandboxBrowserSession = PitcherManagerResponse & { id: string; + env?: Record; }; From 9d2e766506c25981f092f6af04505f0eb290f111 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 14 May 2025 15:05:52 +0200 Subject: [PATCH 099/241] Wop --- src/sessions/WebSocketSession/commands.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index e3f41f9..83220ca 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -39,11 +39,13 @@ export class Commands { }); } - async create(command: string, opts?: ShellRunOpts) { + async create(command: string | string[], opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); + command = Array.isArray(command) ? command.join(" && ") : command; + const allEnv = Object.assign(this.getEnv(), opts?.env ?? {}); // TODO: use a new shell API that natively supports cwd & env @@ -78,10 +80,7 @@ export class Commands { } async run(command: string | string[], opts?: ShellRunOpts): Promise { - const cmd = await this.create( - Array.isArray(command) ? command.join(" && ") : command, - opts - ); + const cmd = await this.create(command, opts); return cmd.waitUntilComplete(); } From 43780a7f060270534713fca0a0c0be2988ed67df Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 14 May 2025 15:40:19 +0200 Subject: [PATCH 100/241] expose preview --- package.json | 2 +- src/browser.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3b2ca38..d7b6311 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.8", + "version": "1.0.0-rc.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/browser.ts b/src/browser.ts index 4ab4e4e..23748c2 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -4,7 +4,7 @@ import { WebSocketSession } from "./sessions/WebSocketSession"; export * from "./sessions/WebSocketSession"; -export { createPreview } from "./previews"; +export { createPreview, Preview } from "./previews"; /** * With this function you can connect to a sandbox from the browser. From 89f57ac141366ba0b991322b68f6a68684d08012 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 15 May 2025 12:33:53 +0200 Subject: [PATCH 101/241] fix naming of stuff --- src/SandboxClient.ts | 18 +- src/index.ts | 9 +- src/previews/Preview.ts | 2 +- src/sessions/WebSocketSession/index.ts | 10 - src/sessions/WebSocketSession/ports.ts | 19 +- src/sessions/WebSocketSession/shells.ts | 415 ------------------------ 6 files changed, 29 insertions(+), 444 deletions(-) delete mode 100644 src/sessions/WebSocketSession/shells.ts diff --git a/src/SandboxClient.ts b/src/SandboxClient.ts index 5975ef9..0de7de4 100644 --- a/src/SandboxClient.ts +++ b/src/SandboxClient.ts @@ -88,16 +88,14 @@ export class SandboxClient { } ); - await session.shells.run( - [ - "rm -rf .git", - "git init", - `git remote add origin ${opts.url}`, - "git fetch origin", - `git checkout -b ${opts.branch}`, - `git reset --hard origin/${opts.branch}`, - ].join(" && ") - ); + await session.commands.run([ + "rm -rf .git", + "git init", + `git remote add origin ${opts.url}`, + "git fetch origin", + `git checkout -b ${opts.branch}`, + `git reset --hard origin/${opts.branch}`, + ]); await opts.setup?.(session); diff --git a/src/index.ts b/src/index.ts index 8b48017..9958236 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,10 @@ function ensure(value: T | undefined, message: string): T { } export class CodeSandbox { + public readonly sandboxes: SandboxClient; + /** + * @deprecated Use `sandboxes` instead + */ public readonly sandbox: SandboxClient; /** @@ -39,7 +43,7 @@ export class CodeSandbox { */ public readonly previewTokens: PreviewTokens; - constructor(apiToken?: string, readonly opts: ClientOpts = {}) { + constructor(apiToken?: string, opts: ClientOpts = {}) { const evaluatedApiToken = apiToken || ensure( @@ -63,7 +67,8 @@ export class CodeSandbox { }) ); - this.sandbox = new SandboxClient(apiClient); + this.sandboxes = new SandboxClient(apiClient); + this.sandbox = this.sandboxes; this.previewTokens = new PreviewTokens(apiClient); } } diff --git a/src/previews/Preview.ts b/src/previews/Preview.ts index a7a8248..476b897 100644 --- a/src/previews/Preview.ts +++ b/src/previews/Preview.ts @@ -84,7 +84,7 @@ export class Preview< return iframe; } - private sendMessage(message: MessageToPreview | BaseMessageToPreview) { + sendMessage(message: MessageToPreview | BaseMessageToPreview) { if (this.status === "CONNECTED") { this.iframe.contentWindow?.postMessage(message, "*"); } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 5345210..9165b97 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -1,4 +1,3 @@ -import { initPitcherClient } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { protocol as _protocol, @@ -8,10 +7,7 @@ import { import { FileSystem } from "./filesystem"; import { Ports } from "./ports"; import { Setup } from "./setup"; -import { Shells } from "./shells"; import { Tasks } from "./tasks"; -import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../../types"; -import { Client } from "@hey-api/client-fetch"; import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; @@ -20,7 +16,6 @@ import { Git } from "./git"; export * from "./filesystem"; export * from "./ports"; export * from "./setup"; -export * from "./shells"; export * from "./tasks"; export * from "./terminals"; export * from "./commands"; @@ -35,11 +30,6 @@ export class WebSocketSession { */ public readonly fs = new FileSystem(this.disposable, this.pitcherClient); - /** - * Namespace for running shell commands on this sandbox. - */ - public readonly shells = new Shells(this.disposable, this.pitcherClient); - public readonly terminals: Terminals; public readonly commands: Commands; diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index 883af78..cf82729 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -1,12 +1,17 @@ -import type { IPitcherClient, protocol } from "@codesandbox/pitcher-client"; +import type { IPitcherClient } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; +export type Port = { + port: number; + host: string; +}; + export class Ports { private disposable = new Disposable(); private onDidPortOpenEmitter = this.disposable.addDisposable( - new Emitter() + new Emitter() ); get onDidPortOpen() { return this.onDidPortOpenEmitter.event; @@ -44,7 +49,7 @@ export class Ports { if (openedPorts.length) { for (const port of openedPorts) { - this.onDidPortOpenEmitter.fire(port); + this.onDidPortOpenEmitter.fire({ port: port.port, host: port.url }); } } @@ -63,8 +68,10 @@ export class Ports { return this.getOpenedPorts().find((p) => p.port === port); } - getOpenedPorts() { - return this.pitcherClient.clients.port.getPorts(); + getOpenedPorts(): Port[] { + return this.pitcherClient.clients.port + .getPorts() + .map(({ port, url }) => ({ host: url, port })); } /** @@ -79,7 +86,7 @@ export class Ports { async waitForPort( port: number, options?: { timeoutMs?: number } - ): Promise { + ): Promise { await this.pitcherClient.clients.port.readyPromise; return new Promise((resolve, reject) => { diff --git a/src/sessions/WebSocketSession/shells.ts b/src/sessions/WebSocketSession/shells.ts deleted file mode 100644 index 3460067..0000000 --- a/src/sessions/WebSocketSession/shells.ts +++ /dev/null @@ -1,415 +0,0 @@ -import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; -import type { Id } from "@codesandbox/pitcher-common"; -import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; -import type { OpenShellDTO } from "@codesandbox/pitcher-protocol/dist/src/messages/shell"; - -import { Disposable } from "../../utils/disposable"; -import { Emitter, type Event } from "../../utils/event"; - -interface RunningCommand - extends Promise<{ output: string; exitCode?: number }> { - onOutput: Event; - kill(): void; -} - -type ShellCreateOpts = { - ptySize?: ShellSize; -}; -type ShellRunOpts = { - ptySize?: ShellSize; - shellName?: string; - env?: Record; - cwd?: string; -}; -type ShellOpenOpts = { - ptySize?: ShellSize; -}; -type ShellSize = { cols: number; rows: number }; - -type ShellStatus = "RUNNING" | "FINISHED" | "ERROR" | "KILLED" | "RESTARTING"; -const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; - -/** - * @deprecated - */ -export class Shells { - private disposable = new Disposable(); - constructor( - sessionDisposable: Disposable, - private pitcherClient: IPitcherClient - ) { - sessionDisposable.onWillDispose(() => { - this.disposable.dispose(); - }); - } - - public readonly js = new LanguageInterpreter(this.pitcherClient, { - runtime: "node", - extension: "js", - env: { NO_COLOR: "true" }, - }); - public readonly python = new LanguageInterpreter(this.pitcherClient, { - runtime: "python", - extension: "py", - env: {}, - }); - - /** - * Creates a shell that can run commands, will return output as data is sent to stdin. - * - * ## Example - * - * ```ts - * const shell = await sandbox.shell.create(); - * - * const disposable = shell.onShellOut((data) => { - * console.log(data); - * }); - * - * // Write to the shell - * shell.write("echo 'Hello, world!'"); - * - * // Stop listening to the shell - * disposable.dispose(); - * - * // Kill the shell - * await shell.kill(); - * ``` - * - * @param command - The command to run in the shell. - * @param shellSize - The size of the shell. - * @returns A disposable shell instance. - */ - async create( - command = "bash", - opts?: ShellCreateOpts - ): Promise { - const shell = await this.pitcherClient.clients.shell.create( - this.pitcherClient.workspacePath, - opts?.ptySize ?? DEFAULT_SHELL_SIZE, - command, - "TERMINAL", - true - ); - - return new ShellInstance(shell, this.pitcherClient); - } - - /** - * Opens an existing shell. - */ - async open(shellId: string, opts?: ShellOpenOpts): Promise { - const shell = await this.pitcherClient.clients.shell.open( - shellId as Id, - opts?.ptySize ?? DEFAULT_SHELL_SIZE - ); - return new ShellInstance(shell, this.pitcherClient); - } - - /** - * Runs the given command, and can be listened to for streaming output. To get all - * output, you can optionally await the returned promise. - * - * ## Example - * - * ```ts - * const shell = sandbox.shell.run("echo 'Hello, world!'"); - * - * shell.onOutput((data) => { - * console.log(data); - * }); - * - * const result = await shell; - * - * console.log(result.output, result.exitCode); - * ``` - */ - run(command: string, opts?: ShellRunOpts): RunningCommand { - const shell = runCommandAsUser( - this.pitcherClient, - command, - opts?.ptySize ?? DEFAULT_SHELL_SIZE, - undefined, - opts?.env, - opts?.shellName, - opts?.cwd - ); - - return shell; - } - - /** - * Gets all shells that are running or have ran before in the current sandbox. - */ - async getShells(): Promise { - const shells = this.pitcherClient.clients.shell.getShells(); - - return shells.map((shell) => new ShellInstance(shell, this.pitcherClient)); - } -} - -interface ILanguageInterpreterOpts { - runtime: string; - extension: string; - env: Record; -} - -function getRandomString() { - return Math.random().toString(36).substring(7); -} - -class LanguageInterpreter { - constructor( - private pitcherClient: IPitcherClient, - private opts: ILanguageInterpreterOpts - ) {} - - async run(code: string): Promise { - const randomString = getRandomString(); - const tmpFileName = `/tmp/tmp.${randomString}.${this.opts.extension}`; - - const command = `${this.opts.runtime} ${tmpFileName}`; - - const result = runCommandAsUser( - this.pitcherClient, - command, - DEFAULT_SHELL_SIZE, - async () => { - const tmpFile = await this.pitcherClient.clients.fs.writeFile( - tmpFileName, - new TextEncoder().encode(code), - true, - true - ); - - if (tmpFile.type === "error") { - throw new Error(`${tmpFile.errno}: ${tmpFile.error}`); - } - }, - this.opts.env - ); - - return result; - } -} - -export class ShellInstance { - private disposable = new Disposable(); - // TODO: differentiate between stdout and stderr, also send back bytes instead of - // strings - private onShellOutputEmitter = this.disposable.addDisposable( - new Emitter() - ); - public readonly onOutput = this.onShellOutputEmitter.event; - - private onShellUpdatedEmitter = this.disposable.addDisposable( - new Emitter() - ); - public readonly onShellUpdated = this.onShellUpdatedEmitter.event; - - private output = this.shell.buffer || []; - - constructor( - private shell: protocol.shell.ShellDTO & { buffer?: string[] }, - private pitcherClient: IPitcherClient - ) { - this.disposable.addDisposable( - pitcherClient.clients.shell.onShellsUpdated((shells) => { - const updatedShell = shells.find( - (s) => s.shellId === this.shell.shellId - ); - if (updatedShell) { - this.shell = { ...updatedShell, buffer: [] }; - this.onShellUpdatedEmitter.fire(); - } - }) - ); - - this.disposable.addDisposable( - this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { - if (shellId === this.shell.shellId) { - this.onShellOutputEmitter.fire(out); - - this.output.push(out); - if (this.output.length > 1000) { - this.output.shift(); - } - } - }) - ); - - this.disposable.onWillDispose(async () => { - try { - await this.pitcherClient.clients.shell.delete(this.shell.shellId); - } catch (e) { - // Ignore errors, we don't care if it's already closed or if we disconnected - } - }); - } - - /** - * Gets the ID of the shell. Can be used to open the shell again. - */ - get id(): string { - return this.shell.shellId as string; - } - - get type(): string { - return this.shell.shellType; - } - - /** - * Gets the name of the shell. - */ - get name(): string { - return this.shell.name; - } - - get exitCode(): number | undefined { - return this.shell.exitCode; - } - - /** - * Gets the status of the shell. - */ - get status(): ShellStatus { - return this.shell.status; - } - - async write(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - await this.pitcherClient.clients.shell.send( - this.shell.shellId, - input, - dimensions - ); - } - - async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - return this.write(input + "\n", dimensions); - } - - // TODO: allow for kill signals - async kill(): Promise { - this.disposable.dispose(); - await this.pitcherClient.clients.shell.delete(this.shell.shellId); - } - - /** - * @returns The total output of the shell - */ - getOutput(): string { - return this.output.join("\n"); - } -} - -function runCommandAsUser( - pitcher: IPitcherClient, - command: string, - shellSize: ShellSize = DEFAULT_SHELL_SIZE, - runPreCommand?: () => Promise, - env?: Record, - shellName?: string, - cwd?: string -): RunningCommand { - const disposableStore = new DisposableStore(); - const onOutput = new Emitter(); - disposableStore.add(onOutput); - - let shell: OpenShellDTO; - - const resultPromise = (async () => { - if (runPreCommand) { - await runPreCommand(); - } - - // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(env ?? {}).length - ? `env ${Object.entries(env ?? {}) - .map(([key, value]) => `${key}=${value}`) - .join(" ")} ${command}` - : command; - - if (cwd) { - commandWithEnv = `cd ${cwd} && ${commandWithEnv}`; - } - - shell = await pitcher.clients.shell.create( - pitcher.workspacePath, - shellSize, - commandWithEnv, - "TERMINAL", - true - ); - - if (shellName) { - pitcher.clients.shell.rename(shell.shellId, shellName); - } - - if (shell.status === "FINISHED") { - return { - output: shell.buffer.join("\n").trim(), - exitCode: shell.exitCode, - }; - } - - let combinedOut = shell.buffer.join("\n"); - if (combinedOut) { - onOutput.fire(combinedOut); - } - const barrier = new Barrier<{ exitCode?: number }>(); - - disposableStore.add( - pitcher.clients.shell.onShellOut(({ shellId, out }) => { - if (shellId !== shell.shellId) { - return; - } - - onOutput.fire(out); - combinedOut += out; - }) - ); - - disposableStore.add( - pitcher.clients.shell.onShellExited(({ shellId, exitCode }) => { - if (shellId !== shell.shellId) { - return; - } - - barrier.open({ exitCode }); - }) - ); - - disposableStore.add( - pitcher.clients.shell.onShellTerminated(({ shellId }) => { - if (shellId !== shell.shellId) { - return; - } - - barrier.open({ exitCode: undefined }); - }) - ); - - const result = await barrier.wait(); - disposableStore.dispose(); - - if (result.status === "disposed") { - throw new Error("Shell was disposed"); - } - - return { - output: combinedOut.trim(), - exitCode: result.value.exitCode, - }; - })() as RunningCommand; - - resultPromise.kill = () => { - disposableStore.dispose(); - - if (shell) { - pitcher.clients.shell.delete(shell.shellId); - } - }; - resultPromise.onOutput = onOutput.event; - - return resultPromise as RunningCommand; -} From f47281387d55b1d1e8e057a56bb11c1335aca266 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 15 May 2025 15:45:17 +0200 Subject: [PATCH 102/241] fix python inteprreter and template CLI visuals --- package.json | 2 +- src/bin/commands/build.ts | 117 +++++++----------- src/sessions/WebSocketSession/interpreters.ts | 5 +- 3 files changed, 49 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index d7b6311..9246d52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 762377a..e339fa5 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -31,46 +31,6 @@ export type BuildCommandArgs = { vmTier?: VmUpdateSpecsRequest["tier"]; }; -function createSpinnerFactory() { - let currentLineIndex = 0; - let currentSpinnerIndex = 0; - - return () => { - const spinner = ora({ stream: process.stdout }); - const spinnerIndex = currentSpinnerIndex++; - let lastMethod: string; - - function updateCursor(method: string) { - readline.moveCursor( - process.stdout, - 0, - spinnerIndex - currentLineIndex + (lastMethod !== "start" ? -1 : 0) - ); - currentLineIndex = spinnerIndex; - lastMethod = method; - } - - return { - start(message: string) { - updateCursor("start"); - spinner.start(message); - }, - succeed(message: string) { - updateCursor("succeed"); - spinner.succeed(message); - }, - fail(message: string) { - updateCursor("fail"); - spinner.fail(message); - }, - info(message: string) { - updateCursor("info"); - spinner.info(message); - }, - }; - }; -} - export const buildCommand: yargs.CommandModule< Record, BuildCommandArgs @@ -121,8 +81,6 @@ export const buildCommand: yargs.CommandModule< }) ); - const createSpinner = createSpinnerFactory(); - try { const clustersData = handleResponse( await vmListClusters({ @@ -133,21 +91,29 @@ export const buildCommand: yargs.CommandModule< const clusters = clustersData.clusters; + const spinner = ora({ stream: process.stdout }); + let spinnerMessages: string[] = clusters.map(() => ""); + + function updateSpinnerMessage( + index: number, + message: string, + sandboxId?: string + ) { + spinnerMessages[index] = `[cluster: ${ + clusters[index].slug + }, sandboxId: ${sandboxId || "-"}]: ${message}`; + + return `\n${spinnerMessages.join("\n")}`; + } + const sandboxIds = await Promise.all( - clusters.map(async ({ host: cluster, slug }) => { + clusters.map(async ({ host: cluster, slug }, index) => { const sdk = new CodeSandbox(API_KEY, { baseUrl: BASE_URL, headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, }, }); - const spinner = createSpinner(); - - function createSpinnerMessage(message: string, sandboxId?: string) { - return `[cluster: ${slug}, sandboxId: ${ - sandboxId || "-" - }]: ${message}`; - } let sandboxId: string | undefined; @@ -155,11 +121,10 @@ export const buildCommand: yargs.CommandModule< const { hash, files: filePaths } = await hashDirectory( argv.directory ); - spinner.succeed(`Indexed ${filePaths.length} files`); const shortHash = hash.slice(0, 6); const tag = `sha:${shortHash}-${slug}`; - spinner.start(createSpinnerMessage("Creating sandbox...")); + spinner.start(updateSpinnerMessage(index, "Creating sandbox...")); sandboxId = await createSandbox({ apiClient, shaTag: tag, @@ -170,7 +135,7 @@ export const buildCommand: yargs.CommandModule< }); spinner.start( - createSpinnerMessage("Starting sandbox...", sandboxId) + updateSpinnerMessage(index, "Starting sandbox...", sandboxId) ); const startResponse = await startVm(apiClient, sandboxId, { @@ -180,7 +145,11 @@ export const buildCommand: yargs.CommandModule< let session = await sandbox.connect(); spinner.start( - createSpinnerMessage("Writing files to sandbox...", sandboxId) + updateSpinnerMessage( + index, + "Writing files to sandbox...", + sandboxId + ) ); let i = 0; for (const filePath of filePaths) { @@ -196,9 +165,9 @@ export const buildCommand: yargs.CommandModule< } spinner.start( - createSpinnerMessage("Restarting sandbox...", sandboxId) + updateSpinnerMessage(index, "Restarting sandbox...", sandboxId) ); - sandbox = await sdk.sandbox.restart(sandbox.id); + sandbox = await sdk.sandboxes.restart(sandbox.id); session = await sandbox.connect(); const disposableStore = new DisposableStore(); @@ -210,7 +179,8 @@ export const buildCommand: yargs.CommandModule< try { spinner.start( - createSpinnerMessage( + updateSpinnerMessage( + index, `Running setup ${steps.indexOf(step) + 1} / ${ steps.length } - ${step.name}...`, @@ -230,13 +200,6 @@ export const buildCommand: yargs.CommandModule< await step.waitUntilComplete(); } catch (error) { - spinner.fail( - createSpinnerMessage( - `Setup step failed: ${step.name}`, - sandboxId - ) - ); - console.log(buffer.join("\n")); throw new Error(`Setup step failed: ${step.name}`); } } @@ -247,7 +210,8 @@ export const buildCommand: yargs.CommandModule< const updatePortSpinner = () => { const isMultiplePorts = ports.length > 1; spinner.start( - createSpinnerMessage( + updateSpinnerMessage( + index, `Waiting for ${ isMultiplePorts ? "ports" : "port" } ${ports.join(", ")} to open...`, @@ -267,7 +231,7 @@ export const buildCommand: yargs.CommandModule< // eslint-disable-next-line no-constant-condition while (true) { - const res = await fetch("https://" + portInfo.url); + const res = await fetch("https://" + portInfo.host); if (res.status !== 502 && res.status !== 503) { break; } @@ -280,7 +244,8 @@ export const buildCommand: yargs.CommandModule< ); } else { spinner.start( - createSpinnerMessage( + updateSpinnerMessage( + index, "No ports to open, waiting 5 seconds for tasks to run...", sandboxId ) @@ -289,17 +254,22 @@ export const buildCommand: yargs.CommandModule< } spinner.start( - createSpinnerMessage("Creating memory snapshot...", sandboxId) + updateSpinnerMessage( + index, + "Creating memory snapshot...", + sandboxId + ) ); - await sdk.sandbox.hibernate(sandbox.id); - spinner.succeed( - createSpinnerMessage("Snapshot created", sandboxId) + await sdk.sandboxes.hibernate(sandbox.id); + spinner.start( + updateSpinnerMessage(index, "Snapshot created", sandboxId) ); return sandbox.id; } catch (error) { spinner.fail( - createSpinnerMessage( + updateSpinnerMessage( + index, error instanceof Error ? error.message : "Unknown error occurred", @@ -311,6 +281,8 @@ export const buildCommand: yargs.CommandModule< }) ); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + const data = handleResponse( await vmCreateTag({ client: apiClient, @@ -321,6 +293,7 @@ export const buildCommand: yargs.CommandModule< "Failed to create template" ); console.log("Template created: " + data.tag_id); + process.exit(0); } catch (error) { console.error(error); process.exit(1); diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts index f654865..2983cde 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -33,7 +33,7 @@ EOF ); } python(code: string) { - return this.run(`python3 -c "exec('''\ + return this.run(`python3 <<'PYCODE' ${code .split("\n") .map((line, index, lines) => { @@ -42,6 +42,7 @@ ${code : line; }) .join("\n")} -''')"`); +PYCODE +`); } } From 43d378d822c83ba2537433ffcf665e296f4bd3b7 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 19 May 2025 10:00:20 +0200 Subject: [PATCH 103/241] documenting --- src/PreviewTokens.ts | 23 ++------ src/{SandboxClient.ts => Sandboxes.ts} | 30 ++++------ src/browser.ts | 34 +---------- src/index.ts | 10 ++-- src/previews/index.ts | 3 + src/sandbox.ts | 34 +++++++++-- src/sessions/WebSocketSession/commands.ts | 46 +++++++++++---- src/sessions/WebSocketSession/filesystem.ts | 40 +------------ src/sessions/WebSocketSession/git.ts | 8 +++ src/sessions/WebSocketSession/index.ts | 59 ++++++++++++------- src/sessions/WebSocketSession/interpreters.ts | 8 +++ 11 files changed, 144 insertions(+), 151 deletions(-) rename src/{SandboxClient.ts => Sandboxes.ts} (90%) diff --git a/src/PreviewTokens.ts b/src/PreviewTokens.ts index 559c3d2..7b77e7c 100644 --- a/src/PreviewTokens.ts +++ b/src/PreviewTokens.ts @@ -35,21 +35,16 @@ export class PreviewTokens extends Disposable { /** * Get a signed preview URL for a port using a preview token. - * - * @param port - The port to get a signed preview URL for - * @param token - The preview token to sign the URL with - * @returns The signed preview URL, or undefined if the port is not open */ - getSignedPreviewUrl(token: PreviewToken, port: number): string { + getSignedPreviewUrl( + token: { sandboxId: string; token: string }, + port: number + ): string { return `https://${token.sandboxId}-${port}.csb.app?preview_token=${token.token}`; } /** - * Generate a new preview token that can be used to access private sandbox previews. - * - * @param opts - Options - * @param opts.expiresAt - Optional expiration date for the preview token - * @returns A preview token that can be used with Ports.getSignedPreviewUrl + * Generate a new preview token that can be used to access private sandbox previews. By default the token never expires. */ async create( sandboxId: string, @@ -87,8 +82,6 @@ export class PreviewTokens extends Disposable { /** * List all active preview tokens for this sandbox. - * - * @returns A list of preview tokens */ async list(sandboxId: string): Promise { const response = handleResponse( @@ -115,8 +108,6 @@ export class PreviewTokens extends Disposable { /** * Revoke a single preview token for this sandbox. - * - * @param tokenId - The ID of the token to revoke */ async revoke(sandboxId: string, tokenId: string): Promise { handleResponse( @@ -153,10 +144,6 @@ export class PreviewTokens extends Disposable { /** * Update a preview token's expiration date. - * - * @param tokenId - The ID of the token to update - * @param expiresAt - The new expiration date for the token (null for no expiration) - * @returns The updated preview token info */ async update( sandboxId: string, diff --git a/src/SandboxClient.ts b/src/Sandboxes.ts similarity index 90% rename from src/SandboxClient.ts rename to src/Sandboxes.ts index 0de7de4..7fb052c 100644 --- a/src/SandboxClient.ts +++ b/src/Sandboxes.ts @@ -56,7 +56,10 @@ export async function startVm( return getStartResponse(response); } -export class SandboxClient { +/** + * This class provides methods for creating and managing sandboxes. + */ +export class Sandboxes { get defaultTemplateId() { return getDefaultTemplateId(this.apiClient); } @@ -139,7 +142,13 @@ export class SandboxClient { } /** + * Resume a sandbox. * + * - Hibernated with snapshot: It wakes up and continues within 2-3 seconds + * - Hibernated with expired snapshot: It will start from scratch (CLEAN bootup) + * - Shutdown: It will start from scratch (CLEAN bootup) + * + * Note! On CLEAN bootups the setup will run again. When hibernated a new snapshot will be created. */ async resume(sandboxId: string) { const startResponse = await startVm(this.apiClient, sandboxId); @@ -148,8 +157,6 @@ export class SandboxClient { /** * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. - * - * @param sandboxId The ID of the sandbox to shutdown */ async shutdown(sandboxId: string): Promise { const response = await vmShutdown({ @@ -166,7 +173,7 @@ export class SandboxClient { * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in * the project directory (`/project/sandbox`) will be preserved. * - * Will resolve once the sandbox is rebooted. + * Will resolve once the sandbox is restarted with its setup running. */ public async restart(sandboxId: string, opts?: StartSandboxOpts) { await this.shutdown(sandboxId); @@ -177,9 +184,7 @@ export class SandboxClient { /** * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time - * you start the sandbox it will be resumed from the last state it was in. - * - * @param sandboxId The ID of the sandbox to hibernate + * you resume the sandbox it will continue from the last state it was in. */ async hibernate(sandboxId: string): Promise { const response = await vmHibernate({ @@ -193,16 +198,7 @@ export class SandboxClient { } /** - * Creates a sandbox by forking a template. You can pass in any template or sandbox id (from - * any sandbox/template created on codesandbox.io, even your own templates) or don't pass - * in anything and we'll use the default universal template. - * - * This function will also start & connect to the VM of the created sandbox with a global session, and return a {@link Sandbox} - * that allows you to control the VM. Pass "autoConnect: false" to only return the session data. - * - * @param opts Additional options for creating the sandbox - * - * @returns A promise that resolves to a {@link Sandbox}, which you can use to control the VM + * Create a sandbox from a template or git repository. By default we will create a sandbox from the default template. */ async create( opts: CreateSandboxOpts & StartSandboxOpts = { source: "template" } diff --git a/src/browser.ts b/src/browser.ts index 23748c2..945af25 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -7,39 +7,7 @@ export * from "./sessions/WebSocketSession"; export { createPreview, Preview } from "./previews"; /** - * With this function you can connect to a sandbox from the browser. - * - * ## Why does this exist? - * - * The CodeSandbox API is a REST API that you can use to control sandboxes. However, it - * requires your CodeSandbox API token to be sent with every request. This makes it - * unsafe to use from the browser, where you don't want to expose your API token. - * - * With this helper function, you can generate a sandbox on the server, and then share a single-use - * token that can be used to create a connection to that sandbox from the browser. - * - * ## Example - * - * To use this function, you first need to start a sandbox on the server: - * - * ```ts - * import { CodeSandbox } from "@codesandbox/sdk"; - * - * const client = new CodeSandbox(apiToken); - * - * const startData = await client.sandbox.start("my-sandbox-id"); - * ``` - * - * Then you can start a sandbox using this start data in the browser: - * - * ```ts - * import { connectToSandbox } from "@codesandbox/sdk/browser"; - * - * // Get the start data from the server - * const startData = ...; - * - * const sandbox = await connectToSandbox(startData); - * ``` + * Connect to a Sandbox from the browser and automatically reconnect. `getSession` requires and endpoint that resumes the Sandbox. `onFocusChange` can be used to notify when a reconnect should happen. */ export async function connectToSandbox(options: { id: string; diff --git a/src/index.ts b/src/index.ts index 9958236..9ac7083 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import { SandboxClient } from "./SandboxClient"; +import { Sandboxes } from "./Sandboxes"; import { ClientOpts } from "./types"; -export { SandboxClient }; +export { Sandboxes as SandboxClient }; export { VMTier } from "./VMTier"; @@ -24,11 +24,11 @@ function ensure(value: T | undefined, message: string): T { } export class CodeSandbox { - public readonly sandboxes: SandboxClient; + public readonly sandboxes: Sandboxes; /** * @deprecated Use `sandboxes` instead */ - public readonly sandbox: SandboxClient; + public readonly sandbox: Sandboxes; /** * Provider for generating preview tokens. These tokens can be used to generate signed @@ -67,7 +67,7 @@ export class CodeSandbox { }) ); - this.sandboxes = new SandboxClient(apiClient); + this.sandboxes = new Sandboxes(apiClient); this.sandbox = this.sandboxes; this.previewTokens = new PreviewTokens(apiClient); } diff --git a/src/previews/index.ts b/src/previews/index.ts index 63f329b..a750284 100644 --- a/src/previews/index.ts +++ b/src/previews/index.ts @@ -8,6 +8,9 @@ import { export { Preview, InjectFunction }; +/** + * Create a preview that you can interact with. By default you can interact with navigation, but you can also inject your own code and do custom message passing. Append the `iframe` to your DOM to display the preview. + */ export function createPreview< MessageToPreview extends Message = BaseMessageToPreview, MessageFromPreview extends Message = BaseMessageFromPreview diff --git a/src/sandbox.ts b/src/sandbox.ts index 35b10bf..a7c88bb 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -20,15 +20,31 @@ import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; import { WebSocketSession } from "./sessions/WebSocketSession"; import { RestSession } from "./sessions/RestSession"; -import { SandboxClient, startVm } from "./SandboxClient"; +import { Sandboxes, startVm } from "./Sandboxes"; export class Sandbox { + /** + * How the Sandbox booted up: + * - RUNNING: Already running + * - RESUME: Resumes from hibernation + * - CLEAN: Clean bootup, no hibernation snapshot or shutdown + * - FORK: When the sandbox was created from a template + */ get bootupType() { return this.pitcherManagerResponse.bootupType; } + + /** + * The cluster the Sandbox is running on. + */ get cluster() { return this.pitcherManagerResponse.cluster; } + + /** + * Whether the Sandbox Agent version is up to date. Use "restart" to + * update the agent. + */ get isUpToDate() { return ( this.pitcherManagerResponse.latestPitcherVersion === @@ -53,9 +69,6 @@ export class Sandbox { * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory * than it can scale down to, it can become very slow. - * - * @param id The ID of the sandbox to update - * @param tier The new VM tier */ async updateTier(sandboxId: string, tier: VMTier): Promise { const response = await vmUpdateSpecs({ @@ -69,6 +82,10 @@ export class Sandbox { handleResponse(response, `Failed to update sandbox tier ${sandboxId}`); } + /** + * Updates the hibernation timeout for this sandbox. This is the amount of seconds the sandbox + * will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox. + */ async updateHibernationTimeout( sandboxId: string, timeoutSeconds: number @@ -122,6 +139,9 @@ export class Sandbox { return session; } + /** + * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. + */ async connect( customSession?: SessionCreateOptions ): Promise { @@ -189,6 +209,9 @@ export class Sandbox { return new WebSocketSession(pitcherClient, () => customSession?.env ?? {}); } + /** + * Returns a REST API client connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. + */ async createRestSession(customSession?: SessionCreateOptions) { const session = customSession ? await this.createSession(customSession) @@ -197,6 +220,9 @@ export class Sandbox { return new RestSession(session); } + /** + * Returns a browser session connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. + */ async createBrowserSession( customSession?: SessionCreateOptions ): Promise { diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 83220ca..1adcc82 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -1,13 +1,7 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; import { Disposable } from "../../utils/disposable"; -import { Emitter, type Event } from "../../utils/event"; - -export interface RunningCommand - extends Promise<{ output: string; exitCode?: number }> { - onOutput: Event; - kill(): void; -} +import { Emitter } from "../../utils/event"; type ShellSize = { cols: number; rows: number }; @@ -39,6 +33,9 @@ export class Commands { }); } + /** + * Create and run command in a new shell. Allows you to listen to the output and kill the command. + */ async create(command: string | string[], opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); @@ -79,12 +76,18 @@ export class Commands { return cmd; } + /** + * Run a command in a new shell and wait for it to finish, returning its output. + */ async run(command: string | string[], opts?: ShellRunOpts): Promise { const cmd = await this.create(command, opts); return cmd.waitUntilComplete(); } + /** + * Get all running commands. + */ getAll(): Command[] { const shells = this.pitcherClient.clients.shell.getShells(); @@ -101,24 +104,32 @@ export class Command { private onOutputEmitter = this.disposable.addDisposable( new Emitter() ); + /** + * An event that is emitted when the command outputs something. + */ public readonly onOutput = this.onOutputEmitter.event; private onStatusChangeEmitter = this.disposable.addDisposable( new Emitter() ); + /** + * An event that is emitted when the command status changes. + */ public readonly onStatusChange = this.onStatusChangeEmitter.event; private barrier = new Barrier(); private output: string[] = []; - get id(): string { - return this.shell.shellId as string; - } - + /** + * The command that was run. + */ get command(): string { return this.shell.startCommand; } + /** + * The status of the command. + */ status: CommandStatus = "RUNNING"; constructor( @@ -127,7 +138,7 @@ export class Command { ) { this.disposable.addDisposable( pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => { - if (shellId === this.id) { + if (shellId === this.shell.shellId) { this.status = exitCode === 0 ? "FINISHED" : "ERROR"; this.barrier.open(); } @@ -136,7 +147,7 @@ export class Command { this.disposable.addDisposable( pitcherClient.clients.shell.onShellTerminated(({ shellId }) => { - if (shellId === this.id) { + if (shellId === this.shell.shellId) { this.status = "KILLED"; this.barrier.open(); } @@ -159,6 +170,9 @@ export class Command { ); } + /** + * Wait for the command to finish with its returned output + */ async waitUntilComplete(): Promise { await this.barrier.wait(); @@ -170,11 +184,17 @@ export class Command { } // TODO: allow for kill signals + /** + * Kill the command and remove it from the session. + */ async kill(): Promise { this.disposable.dispose(); await this.pitcherClient.clients.shell.delete(this.shell.shellId); } + /** + * Restart the command. + */ async restart(): Promise { if (this.status !== "RUNNING") { throw new Error("Command is not running"); diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts index 2a3b86f..2c869f9 100644 --- a/src/sessions/WebSocketSession/filesystem.ts +++ b/src/sessions/WebSocketSession/filesystem.ts @@ -51,10 +51,6 @@ export class FileSystem { /** * Write a file. - * - * @param path - The path to write to. - * @param content - The content to write. - * @param opts - The options for the write. */ async writeFile( path: string, @@ -75,10 +71,6 @@ export class FileSystem { /** * Write a file as a string. - * - * @param path - The path to write to. - * @param content - The content to write. - * @param opts - The options for the write. */ async writeTextFile(path: string, content: string, opts: WriteFileOpts = {}) { return this.writeFile(path, new TextEncoder().encode(content), opts); @@ -86,9 +78,6 @@ export class FileSystem { /** * Create a directory. - * - * @param path - The path to create. - * @param recursive - Whether to create the directory recursively. */ async mkdir(path: string, recursive = false): Promise { const result = await this.pitcherClient.clients.fs.mkdir(path, recursive); @@ -100,9 +89,6 @@ export class FileSystem { /** * Read a directory. - * - * @param path - The path to read. - * @returns The entries in the directory. */ async readdir(path: string): Promise { const result = await this.pitcherClient.clients.fs.readdir(path); @@ -119,9 +105,6 @@ export class FileSystem { /** * Read a file - * - * @param path - The path to read. - * @returns The content of the file as a Uint8Array. */ async readFile(path: string): Promise { const result = await this.pitcherClient.clients.fs.readFile(path); @@ -135,9 +118,6 @@ export class FileSystem { /** * Read a file as a string. - * - * @param path - The path to read. - * @returns The content of the file as a string. */ async readTextFile(path: string): Promise { return await this.readFile(path).then((content) => @@ -147,9 +127,6 @@ export class FileSystem { /** * Get the stat of a file or directory. - * - * @param path - The path to get the stat of. - * @returns The stat of the file or directory. */ async stat(path: string): Promise { const result = await this.pitcherClient.clients.fs.stat(path); @@ -167,11 +144,6 @@ export class FileSystem { /** * Copy a file or directory. - * - * @param from - The path to copy from. - * @param to - The path to copy to. - * @param recursive - Whether to copy the directory recursively. - * @param overwrite - Whether to overwrite the destination if it exists. */ async copy( from: string, @@ -193,10 +165,6 @@ export class FileSystem { /** * Rename a file or directory. - * - * @param from - The path to rename from. - * @param to - The path to rename to. - * @param overwrite - Whether to overwrite the destination if it exists. */ async rename(from: string, to: string, overwrite = false): Promise { const result = await this.pitcherClient.clients.fs.rename( @@ -212,9 +180,6 @@ export class FileSystem { /** * Remove a file or directory. - * - * @param path - The path to remove. - * @param recursive - Whether to remove the directory recursively. */ async remove(path: string, recursive = false): Promise { const result = await this.pitcherClient.clients.fs.remove(path, recursive); @@ -268,10 +233,7 @@ export class FileSystem { /** * Download a file or folder from the filesystem, can only be used to download - * from within the workspace directory. - * - * @param path - The path to download. - * @returns A download URL that's valid for 5 minutes. + * from within the workspace directory. A download URL that's valid for 5 minutes. */ async download(path: string): Promise<{ downloadUrl: string }> { const result = await this.pitcherClient.clients.fs.download(path); diff --git a/src/sessions/WebSocketSession/git.ts b/src/sessions/WebSocketSession/git.ts index c4be171..7f3bac5 100644 --- a/src/sessions/WebSocketSession/git.ts +++ b/src/sessions/WebSocketSession/git.ts @@ -1,8 +1,16 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; export class Git { + /** + * An event that is emitted when the git status changes. + */ onStatusChange = this.pitcherClient.clients.git.onStatusUpdated; + constructor(private pitcherClient: IPitcherClient) {} + + /** + * Get the current git status. + */ status() { return this.pitcherClient.clients.git.getStatus(); } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 9165b97..0b199f4 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -26,43 +26,42 @@ export class WebSocketSession { private disposable = new Disposable(); /** - * Namespace for all filesystem operations on this sandbox. + * Namespace for all filesystem operations on this Sandbox */ public readonly fs = new FileSystem(this.disposable, this.pitcherClient); + /** + * Namespace for creating and managing terminals this Sandbox + */ public readonly terminals: Terminals; + + /** + * Namespace for running commands in the Sandbox + */ public readonly commands: Commands; + /** + * Namespace for running code interpreters in the Sandbox + */ public readonly interpreters: Interpreters; + /** + * Namespace for Git operations in the Sandbox + */ public readonly git = new Git(this.pitcherClient); /** - * Namespace for detecting open ports on this sandbox, and getting preview URLs for - * them. + * Namespace for managing ports on this Sandbox */ public readonly ports = new Ports(this.disposable, this.pitcherClient); /** - * Namespace for all setup operations on this sandbox (installing dependencies, etc). - * - * This provider is *experimental*, it might get changes or completely be removed - * if it is not used. + * Namespace for the setup that runs when the Sandbox starts from scratch. */ public readonly setup = new Setup(this.disposable, this.pitcherClient); /** - * Namespace for all task operations on a sandbox. This includes running tasks, - * getting tasks, and stopping tasks. - * - * In CodeSandbox, you can create tasks and manage them by creating a `.codesandbox/tasks.json` - * in the sandbox. These tasks become available under this namespace, this way you can manage - * tasks that you will need to run more often (like a dev server). - * - * More documentation: https://codesandbox.io/docs/learn/devboxes/task#adding-and-configuring-tasks - * - * This provider is *experimental*, it might get changes or completely be removed - * if it is not used. + * Namespace for tasks that are defined in the Sandbox. */ public readonly tasks = new Tasks(this.disposable, this.pitcherClient); @@ -83,17 +82,22 @@ export class WebSocketSession { this.disposable.addDisposable(this.pitcherClient); } - // Not sure why we have to explicitly type this + /** + * The current state of the Sandbox + */ get state(): typeof this.pitcherClient.state { return this.pitcherClient.state; } + /** + * An event that is emitted when the state of the Sandbox changes. + */ get onStateChange() { return this.pitcherClient.onStateChange.bind(this.pitcherClient); } /** - * Check if the VM agent process is up to date. To update a restart is required + * Check if the Sandbox Agent process is up to date. To update a restart is required */ get isUpToDate() { return this.pitcherClient.isUpToDate(); @@ -164,13 +168,21 @@ export class WebSocketSession { // } /** - * Disconnect from the sandbox, this does not hibernate the sandbox (but it will - * automatically hibernate after an inactivity timer). + * Disconnect from the sandbox, this does not hibernate the sandbox (it will + * automatically hibernate after hibernation timeout). Call "reconnect" to + * reconnect to the sandbox. */ public disconnect() { return this.pitcherClient.disconnect(); } + /** + * Explicitly reconnect to the sandbox. + */ + public reconnect() { + return this.pitcherClient.reconnect(); + } + private keepAliveInterval: NodeJS.Timeout | null = null; /** * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. @@ -194,6 +206,9 @@ export class WebSocketSession { } } } + /** + * Dispose the session, this will disconnect from the sandbox and dispose all resources. If you want to do a clean disconnect, await "disconnect" method first. + */ dispose() { this.disposable.dispose(); } diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/sessions/WebSocketSession/interpreters.ts index 2983cde..d478258 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/sessions/WebSocketSession/interpreters.ts @@ -8,10 +8,14 @@ export class Interpreters { this.disposable.dispose(); }); } + private run(command: string, opts?: ShellRunOpts) { return this.commands.run(command, opts); } + /** + * Run a JavaScript code snippet in a new shell. + */ javascript(code: string) { return this.run( `node -p "$(cat <<'EOF' @@ -32,6 +36,10 @@ EOF } ); } + + /** + * Run a Python code snippet in a new shell. + */ python(code: string) { return this.run(`python3 <<'PYCODE' ${code From edfad0bcb62779a3b9ded54a0c71b5c6a77bd547 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 19 May 2025 10:58:28 +0200 Subject: [PATCH 104/241] fix preview tokens --- src/bin/commands/build.ts | 2 +- src/browser.ts | 20 +++++++++------- src/sandbox.ts | 15 +++++++----- src/sessions/WebSocketSession/commands.ts | 4 ++-- src/sessions/WebSocketSession/index.ts | 13 ++++++---- src/sessions/WebSocketSession/ports.ts | 28 +++++++++++++++------- src/sessions/WebSocketSession/terminals.ts | 4 ++-- src/types.ts | 3 +++ 8 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index e339fa5..12f0b34 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -19,7 +19,7 @@ import { import { getDefaultTemplateId, handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; -import { startVm } from "../../SandboxClient"; +import { startVm } from "../../Sandboxes"; export type BuildCommandArgs = { directory: string; diff --git a/src/browser.ts b/src/browser.ts index 945af25..c100783 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -10,28 +10,27 @@ export { createPreview, Preview } from "./previews"; * Connect to a Sandbox from the browser and automatically reconnect. `getSession` requires and endpoint that resumes the Sandbox. `onFocusChange` can be used to notify when a reconnect should happen. */ export async function connectToSandbox(options: { - id: string; + session: SandboxBrowserSession; getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; }): Promise { - let env: Record = {}; - + let hasConnected = false; const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: options.id, + instanceId: options.session.id, onFocusChange: options.onFocusChange || (() => { return () => {}; }), requestPitcherInstance: async (id) => { - const session = await options.getSession(id); + const session = hasConnected + ? await options.getSession(id) + : options.session; - if (session.env) { - env = session.env; - } + hasConnected = true; return session; }, @@ -40,5 +39,8 @@ export async function connectToSandbox(options: { options.initStatusCb || (() => {}) ); - return new WebSocketSession(pitcherClient, () => env); + return new WebSocketSession(pitcherClient, { + env: options.session.env, + previewToken: options.session.previewToken, + }); } diff --git a/src/sandbox.ts b/src/sandbox.ts index a7c88bb..ab0eb2a 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -20,7 +20,7 @@ import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; import { WebSocketSession } from "./sessions/WebSocketSession"; import { RestSession } from "./sessions/RestSession"; -import { Sandboxes, startVm } from "./Sandboxes"; +import { startVm } from "./Sandboxes"; export class Sandbox { /** @@ -146,6 +146,10 @@ export class Sandbox { customSession?: SessionCreateOptions ): Promise { let hasConnected = false; + const session = customSession + ? await this.createSession(customSession) + : this.globalSession; + const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -162,10 +166,6 @@ export class Sandbox { ); } - const session = customSession - ? await this.createSession(customSession) - : this.globalSession; - const headers = this.apiClient.getConfig().headers as Headers; if (headers.get("x-pitcher-manager-url")) { @@ -206,7 +206,10 @@ export class Sandbox { () => {} ); - return new WebSocketSession(pitcherClient, () => customSession?.env ?? {}); + return new WebSocketSession(pitcherClient, { + env: customSession?.env, + previewToken: customSession?.previewToken, + }); } /** diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 1adcc82..097727d 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -26,7 +26,7 @@ export class Commands { constructor( sessionDisposable: Disposable, private pitcherClient: IPitcherClient, - private getEnv: () => Record + private env: Record = {} ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -43,7 +43,7 @@ export class Commands { command = Array.isArray(command) ? command.join(" && ") : command; - const allEnv = Object.assign(this.getEnv(), opts?.env ?? {}); + const allEnv = Object.assign(this.env, opts?.env ?? {}); // TODO: use a new shell API that natively supports cwd & env let commandWithEnv = Object.keys(allEnv).length diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 0b199f4..babbd24 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -12,6 +12,7 @@ import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; import { Git } from "./git"; +import { PreviewToken } from "../../PreviewTokens"; export * from "./filesystem"; export * from "./ports"; @@ -53,7 +54,7 @@ export class WebSocketSession { /** * Namespace for managing ports on this Sandbox */ - public readonly ports = new Ports(this.disposable, this.pitcherClient); + public readonly ports: Ports; /** * Namespace for the setup that runs when the Sandbox starts from scratch. @@ -67,7 +68,10 @@ export class WebSocketSession { constructor( protected pitcherClient: IPitcherClient, - getEnv: () => Record + { + env, + previewToken, + }: { env?: Record; previewToken?: PreviewToken } ) { // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { @@ -76,8 +80,9 @@ export class WebSocketSession { // }; // this.addDisposable(metricsDisposable); - this.terminals = new Terminals(this.disposable, this.pitcherClient, getEnv); - this.commands = new Commands(this.disposable, this.pitcherClient, getEnv); + this.terminals = new Terminals(this.disposable, this.pitcherClient, env); + this.commands = new Commands(this.disposable, this.pitcherClient, env); + this.ports = new Ports(this.disposable, this.pitcherClient, previewToken); this.interpreters = new Interpreters(this.disposable, this.commands); this.disposable.addDisposable(this.pitcherClient); } diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index cf82729..7599f38 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -2,11 +2,7 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; - -export type Port = { - port: number; - host: string; -}; +import { PreviewToken } from "../../PreviewTokens"; export class Ports { private disposable = new Disposable(); @@ -27,7 +23,8 @@ export class Ports { constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private pitcherClient: IPitcherClient, + private previewToken?: PreviewToken ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -49,7 +46,9 @@ export class Ports { if (openedPorts.length) { for (const port of openedPorts) { - this.onDidPortOpenEmitter.fire({ port: port.port, host: port.url }); + this.onDidPortOpenEmitter.fire( + new Port(port.port, port.url, this.previewToken) + ); } } @@ -71,7 +70,7 @@ export class Ports { getOpenedPorts(): Port[] { return this.pitcherClient.clients.port .getPorts() - .map(({ port, url }) => ({ host: url, port })); + .map(({ port, url }) => new Port(port, url, this.previewToken)); } /** @@ -125,3 +124,16 @@ export class Ports { }); } } + +class Port { + constructor( + public readonly port: number, + public readonly host: string, + private previewToken?: PreviewToken + ) {} + getPreviewUrl() { + return `https://${this.host}${ + this.previewToken ? `?preview_token=${this.previewToken.token}` : "" + }`; + } +} diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index cf47daf..7ac8ab5 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -13,7 +13,7 @@ export class Terminals { constructor( sessionDisposable: Disposable, private pitcherClient: IPitcherClient, - private getEnv: () => Record + private env: Record = {} ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -24,7 +24,7 @@ export class Terminals { command: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", opts?: ShellRunOpts ): Promise { - const allEnv = Object.assign(this.getEnv(), opts?.env ?? {}); + const allEnv = Object.assign(this.env, opts?.env ?? {}); // TODO: use a new shell API that natively supports cwd & env let commandWithEnv = Object.keys(allEnv).length diff --git a/src/types.ts b/src/types.ts index 2d37c36..628033e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; import type { WebSocketSession } from "./sessions/WebSocketSession"; +import { PreviewToken } from "./PreviewTokens"; export interface SystemMetricsStatus { cpu: { @@ -189,6 +190,7 @@ export interface SessionCreateOptions { name?: string; }; env?: Record; + previewToken?: PreviewToken; } export type SandboxSession = { @@ -235,4 +237,5 @@ export type SandboxOpts = { export type SandboxBrowserSession = PitcherManagerResponse & { id: string; env?: Record; + previewToken?: PreviewToken; }; From cef0ce0236e3c26645a2ed14f62f548bcb33e6ac Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 10:39:38 +0200 Subject: [PATCH 105/241] several updates --- src/{PreviewTokens.ts => Hosts.ts} | 75 +++++++---- src/bin/commands/host-tokens.ts | 122 ++++++++++++++++++ src/bin/commands/previewHosts.ts | 40 ------ .../{preview-tokens.ts => host-tokens.ts} | 0 src/bin/commands/sandbox/index.ts | 118 +---------------- src/bin/main.ts | 6 +- src/browser.ts | 2 +- src/index.ts | 22 +--- src/sandbox.ts | 2 +- src/sessions/WebSocketSession/commands.ts | 13 +- src/sessions/WebSocketSession/hosts.ts | 43 ++++++ src/sessions/WebSocketSession/index.ts | 18 ++- src/sessions/WebSocketSession/ports.ts | 46 ++++--- src/sessions/WebSocketSession/tasks.ts | 4 - src/types.ts | 10 +- 15 files changed, 266 insertions(+), 255 deletions(-) rename src/{PreviewTokens.ts => Hosts.ts} (64%) create mode 100644 src/bin/commands/host-tokens.ts rename src/bin/commands/sandbox/{preview-tokens.ts => host-tokens.ts} (100%) create mode 100644 src/sessions/WebSocketSession/hosts.ts diff --git a/src/PreviewTokens.ts b/src/Hosts.ts similarity index 64% rename from src/PreviewTokens.ts rename to src/Hosts.ts index 7b77e7c..73bf69b 100644 --- a/src/PreviewTokens.ts +++ b/src/Hosts.ts @@ -8,48 +8,69 @@ import { previewTokenUpdate, } from "./api-clients/client"; -interface BasePreviewTokenInfo { +interface BaseHostTokenInfo { expiresAt: Date | null; tokenId: string; lastUsedAt: Date | null; } -export interface PreviewTokenInfo extends BasePreviewTokenInfo { +export interface HostTokenInfo extends BaseHostTokenInfo { tokenPrefix: string; } -export interface PreviewToken extends BasePreviewTokenInfo { +export interface HostToken extends BaseHostTokenInfo { token: string; sandboxId: string; } /** - * Provider for generating preview tokens that can be used to access - * private sandbox previews. This provider is only available in environments + * Provider for generating host tokens that can be used to access + * private sandbox hosts. This provider is only available in environments * with an authenticated API client (like Node.js). */ -export class PreviewTokens extends Disposable { +export class HostTokens extends Disposable { constructor(private apiClient: Client) { super(); } /** - * Get a signed preview URL for a port using a preview token. + * Get url to access a private host using a host token. + * The PORT argument is needed as all hosts are exposed with + * a port. */ - getSignedPreviewUrl( + getUrl( token: { sandboxId: string; token: string }, - port: number + port: number, + protocol: string = "https" ): string { - return `https://${token.sandboxId}-${port}.csb.app?preview_token=${token.token}`; + return `${protocol}://${token.sandboxId}-${port}.csb.app?preview_token=${token.token}`; } /** - * Generate a new preview token that can be used to access private sandbox previews. By default the token never expires. + * Get headers to access a private host using a host token. */ - async create( + getHeaders(token: { sandboxId: string; token: string }) { + return { + "csb-preview-token": token.token, + }; + } + + /** + * Get cookies to access a private host using a host token. + */ + getCookies(token: { sandboxId: string; token: string }) { + return { + csb_preview_token: token.token, + }; + } + + /** + * Generate a new host token that can be used to access private sandbox hosts. By default the token never expires. + */ + async createToken( sandboxId: string, opts: { expiresAt?: Date } = {} - ): Promise { + ): Promise { const response = handleResponse( await previewTokenCreate({ client: this.apiClient, @@ -81,9 +102,9 @@ export class PreviewTokens extends Disposable { } /** - * List all active preview tokens for this sandbox. + * List all active host tokens for this sandbox. */ - async list(sandboxId: string): Promise { + async listTokens(sandboxId: string): Promise { const response = handleResponse( await previewTokenList({ client: this.apiClient, @@ -91,7 +112,7 @@ export class PreviewTokens extends Disposable { id: sandboxId, }, }), - "Failed to list preview tokens" + "Failed to list host tokens" ); if (!response.tokens) { @@ -107,9 +128,9 @@ export class PreviewTokens extends Disposable { } /** - * Revoke a single preview token for this sandbox. + * Revoke a single host token for this sandbox. */ - async revoke(sandboxId: string, tokenId: string): Promise { + async revokeToken(sandboxId: string, tokenId: string): Promise { handleResponse( await previewTokenUpdate({ client: this.apiClient, @@ -121,16 +142,16 @@ export class PreviewTokens extends Disposable { expires_at: new Date().toISOString(), }, }), - "Failed to revoke preview token" + "Failed to revoke host token" ); } /** - * Revoke all active preview tokens for this sandbox. + * Revoke all active host tokens for this sandbox. * This will immediately invalidate all tokens, and they can no longer be used - * to access the sandbox preview. + * to access the sandbox host. */ - async revokeAll(sandboxId: string): Promise { + async revokeAllTokens(sandboxId: string): Promise { handleResponse( await previewTokenRevokeAll({ client: this.apiClient, @@ -138,18 +159,18 @@ export class PreviewTokens extends Disposable { id: sandboxId, }, }), - "Failed to revoke preview tokens" + "Failed to revoke host tokens" ); } /** - * Update a preview token's expiration date. + * Update a host token's expiration date. */ - async update( + async updateToken( sandboxId: string, tokenId: string, expiresAt: Date | null - ): Promise { + ): Promise { const response = handleResponse( await previewTokenUpdate({ client: this.apiClient, @@ -161,7 +182,7 @@ export class PreviewTokens extends Disposable { expires_at: expiresAt?.toISOString(), }, }), - "Failed to update preview token" + "Failed to update host token" ); if (!response.token) { diff --git a/src/bin/commands/host-tokens.ts b/src/bin/commands/host-tokens.ts new file mode 100644 index 0000000..524096b --- /dev/null +++ b/src/bin/commands/host-tokens.ts @@ -0,0 +1,122 @@ +import type { CommandModule } from "yargs"; +import { + listPreviewTokens, + createPreviewToken, + revokeAllPreviewTokens, + revokePreviewToken, + updatePreviewToken, +} from "./sandbox/host-tokens"; + +export const hostTokensCommand: CommandModule = { + command: "host-tokens", + describe: "Manage host tokens", + builder: (yargs) => { + return yargs + .command({ + command: "list ", + describe: "List host tokens for a sandbox", + builder: (yargs) => { + return yargs.positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }); + }, + handler: async (argv) => { + await listPreviewTokens(argv.sandboxId as string); + }, + }) + .command({ + command: "create ", + describe: "Create a host token for a sandbox", + builder: (yargs) => { + return yargs + .positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .option("expires-at", { + alias: "e", + describe: + "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to create a token that never expires.", + type: "string", + }); + }, + handler: async (argv) => { + await createPreviewToken( + argv.sandboxId as string, + argv["expires-at"] as string | undefined + ); + }, + }) + .command({ + command: "update ", + describe: "Update the expiration date of a host token", + builder: (yargs) => { + return yargs + .positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .positional("host-token-id", { + describe: "ID of the host token", + type: "string", + demandOption: true, + }) + .option("expires-at", { + alias: "e", + describe: + "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to remove the expiration date.", + type: "string", + }); + }, + handler: async (argv) => { + await updatePreviewToken( + argv["sandbox-id"] as string, + argv["host-token-id"] as string, + argv["expires-at"] as string | undefined + ); + }, + }) + .command({ + command: "revoke [host-token-id]", + describe: "Revoke host token(s)", + builder: (yargs) => { + return yargs + .positional("sandbox-id", { + describe: "ID of the sandbox", + type: "string", + demandOption: true, + }) + .positional("host-token-id", { + describe: "ID of the host token", + type: "string", + }) + .option("all", { + alias: "a", + describe: "Revoke all preview tokens", + type: "boolean", + conflicts: "host-token-id", + }); + }, + handler: async (argv) => { + if (argv.all) { + await revokeAllPreviewTokens(argv["sandbox-id"] as string); + } else if (argv["host-token-id"]) { + await revokePreviewToken( + argv["sandbox-id"] as string, + argv["host-token-id"] as string + ); + } else { + console.error( + "Error: Either specify a host token ID or use --all to revoke all tokens" + ); + process.exit(1); + } + }, + }); + }, + handler: () => {}, +}; diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index ba908e4..4737558 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -19,46 +19,6 @@ export type PreviewHostsCommandArgs = { clear?: boolean; }; -function createSpinnerFactory() { - let currentLineIndex = 0; - let currentSpinnerIndex = 0; - - return () => { - const spinner = ora({ stream: process.stdout }); - const spinnerIndex = currentSpinnerIndex++; - let lastMethod: string; - - function updateCursor(method: string) { - readline.moveCursor( - process.stdout, - 0, - spinnerIndex - currentLineIndex + (lastMethod !== "start" ? -1 : 0) - ); - currentLineIndex = spinnerIndex; - lastMethod = method; - } - - return { - start(message: string) { - updateCursor("start"); - spinner.start(message); - }, - succeed(message: string) { - updateCursor("succeed"); - spinner.succeed(message); - }, - fail(message: string) { - updateCursor("fail"); - spinner.fail(message); - }, - info(message: string) { - updateCursor("info"); - spinner.info(message); - }, - }; - }; -} - export const previewHostsCommand: yargs.CommandModule< Record, PreviewHostsCommandArgs diff --git a/src/bin/commands/sandbox/preview-tokens.ts b/src/bin/commands/sandbox/host-tokens.ts similarity index 100% rename from src/bin/commands/sandbox/preview-tokens.ts rename to src/bin/commands/sandbox/host-tokens.ts diff --git a/src/bin/commands/sandbox/index.ts b/src/bin/commands/sandbox/index.ts index ad51de7..e6c843a 100644 --- a/src/bin/commands/sandbox/index.ts +++ b/src/bin/commands/sandbox/index.ts @@ -3,18 +3,11 @@ import { forkSandbox } from "./fork"; import { hibernateSandbox } from "./hibernate"; import { listSandboxes } from "./list"; import { shutdownSandbox } from "./shutdown"; -import { - listPreviewTokens, - createPreviewToken, - revokeAllPreviewTokens, - revokePreviewToken, - updatePreviewToken, -} from "./preview-tokens"; const DEFAULT_LIMIT = 100; -export const sandboxCommand: CommandModule = { - command: "sandbox", +export const sandboxesCommand: CommandModule = { + command: "sandboxes", describe: "Manage sandboxes", builder: (yargs) => { return yargs @@ -149,113 +142,6 @@ export const sandboxCommand: CommandModule = { await shutdownSandbox(argv.id); }, }) - .command({ - command: "preview-tokens", - describe: "Manage preview tokens", - builder: (yargs) => { - return yargs - .command({ - command: "list ", - describe: "List preview tokens", - builder: (yargs) => { - return yargs.positional("id", { - describe: "ID of the sandbox", - type: "string", - demandOption: true, - }); - }, - handler: async (argv) => { - await listPreviewTokens(argv.id); - }, - }) - .command({ - command: "create ", - describe: "Create a preview token", - builder: (yargs) => { - return yargs - .positional("id", { - describe: "ID of the sandbox", - type: "string", - demandOption: true, - }) - .option("expires-at", { - alias: "e", - describe: - "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to create a token that never expires.", - type: "string", - }); - }, - handler: async (argv) => { - await createPreviewToken(argv.id, argv["expires-at"]); - }, - }) - .command({ - command: "update ", - describe: - "Update the expiration date of a preview token, if no expiration", - builder: (yargs) => { - return yargs - .positional("sandbox-id", { - describe: "ID of the sandbox", - type: "string", - demandOption: true, - }) - .positional("preview-token-id", { - describe: "ID of the preview token", - type: "string", - demandOption: true, - }) - .option("expires-at", { - alias: "e", - describe: - "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to remove the expiration date.", - type: "string", - }); - }, - handler: async (argv) => { - await updatePreviewToken( - argv["sandbox-id"], - argv["preview-token-id"], - argv["expires-at"] - ); - }, - }) - .command({ - command: "revoke ", - describe: "Revoke preview token(s)", - builder: (yargs) => { - return yargs - .positional("sandbox-id", { - describe: "ID of the sandbox", - type: "string", - demandOption: true, - }) - .positional("preview-token-id", { - describe: "ID of the preview token", - type: "string", - demandOption: true, - }) - .option("all", { - alias: "a", - describe: "Revoke all preview tokens", - type: "boolean", - }); - }, - handler: async (argv) => { - if (argv.all) { - await revokeAllPreviewTokens(argv["sandbox-id"]); - } else { - await revokePreviewToken( - argv["sandbox-id"], - argv["preview-token-id"] - ); - } - }, - }) - .demandCommand(1, "Please specify a preview-tokens command"); - }, - handler: () => {}, - }) .demandCommand(1, "Please specify a sandbox command"); }, handler: () => {}, diff --git a/src/bin/main.ts b/src/bin/main.ts index 38e945c..5d4fb18 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -2,8 +2,9 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { buildCommand } from "./commands/build"; -import { sandboxCommand } from "./commands/sandbox"; +import { sandboxesCommand } from "./commands/sandbox"; import { previewHostsCommand } from "./commands/previewHosts"; +import { hostTokensCommand } from "./commands/host-tokens"; yargs(hideBin(process.argv)) .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") @@ -12,6 +13,7 @@ yargs(hideBin(process.argv)) .strict() .recommendCommands() .command(buildCommand) - .command(sandboxCommand) + .command(sandboxesCommand) + .command(hostTokensCommand) .command(previewHostsCommand) .parse(); diff --git a/src/browser.ts b/src/browser.ts index c100783..975b621 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -41,6 +41,6 @@ export async function connectToSandbox(options: { return new WebSocketSession(pitcherClient, { env: options.session.env, - previewToken: options.session.previewToken, + hostToken: options.session.hostToken, }); } diff --git a/src/index.ts b/src/index.ts index 9ac7083..5a5e4d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; -import { PreviewTokens } from "./PreviewTokens"; +import { HostTokens } from "./Hosts"; import { createClient, createConfig } from "@hey-api/client-fetch"; import { getBaseUrl } from "./utils/api"; @@ -25,23 +25,12 @@ function ensure(value: T | undefined, message: string): T { export class CodeSandbox { public readonly sandboxes: Sandboxes; - /** - * @deprecated Use `sandboxes` instead - */ - public readonly sandbox: Sandboxes; /** - * Provider for generating preview tokens. These tokens can be used to generate signed - * preview URLs for private sandboxes. - * - * @example - * ```ts - * const sandbox = await sdk.sandbox.create(); - * const previewToken = await sandbox.previewTokens.createToken(); - * const url = sandbox.ports.getSignedPreviewUrl(8080, previewToken.token); - * ``` + * Provider for generating host tokens. These tokens can be used to generate signed + * host URLs or headers for private sandboxes. */ - public readonly previewTokens: PreviewTokens; + public readonly hosts: HostTokens; constructor(apiToken?: string, opts: ClientOpts = {}) { const evaluatedApiToken = @@ -68,7 +57,6 @@ export class CodeSandbox { ); this.sandboxes = new Sandboxes(apiClient); - this.sandbox = this.sandboxes; - this.previewTokens = new PreviewTokens(apiClient); + this.hosts = new HostTokens(apiClient); } } diff --git a/src/sandbox.ts b/src/sandbox.ts index ab0eb2a..4e38010 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -208,7 +208,7 @@ export class Sandbox { return new WebSocketSession(pitcherClient, { env: customSession?.env, - previewToken: customSession?.previewToken, + hostToken: customSession?.hostToken, }); } diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 097727d..329a3ed 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -34,9 +34,9 @@ export class Commands { } /** - * Create and run command in a new shell. Allows you to listen to the output and kill the command. + * Create and run command in a new shell. */ - async create(command: string | string[], opts?: ShellRunOpts) { + async run(command: string | string[], opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); @@ -76,15 +76,6 @@ export class Commands { return cmd; } - /** - * Run a command in a new shell and wait for it to finish, returning its output. - */ - async run(command: string | string[], opts?: ShellRunOpts): Promise { - const cmd = await this.create(command, opts); - - return cmd.waitUntilComplete(); - } - /** * Get all running commands. */ diff --git a/src/sessions/WebSocketSession/hosts.ts b/src/sessions/WebSocketSession/hosts.ts new file mode 100644 index 0000000..7e7c2ba --- /dev/null +++ b/src/sessions/WebSocketSession/hosts.ts @@ -0,0 +1,43 @@ +import { IPitcherClient } from "@codesandbox/pitcher-client"; +import { HostToken } from "../../Hosts"; + +export class Hosts { + constructor( + private pitcherClient: IPitcherClient, + private hostToken?: HostToken + ) {} + /** + * If private Sandbox this will return a URL with a host token. + */ + getUrl(port: number, protocol: string = "https") { + return `${protocol}://${this.pitcherClient.instanceId}-${port}.csb.app${ + this.hostToken ? `?preview_token=${this.hostToken.token}` : "" + }`; + } + + /** + * If private Sandbox this will return headers with a host token. + */ + getHeaders() { + if (!this.hostToken) { + return {}; + } + + return { + "csb-preview-token": this.hostToken.token, + }; + } + + /** + * If private Sandbox this will return cookies with a host token. + */ + getCookies() { + if (!this.hostToken) { + return {}; + } + + return { + csb_preview_token: this.hostToken.token, + }; + } +} diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index babbd24..9dfe816 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -12,7 +12,8 @@ import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; import { Git } from "./git"; -import { PreviewToken } from "../../PreviewTokens"; +import { HostToken } from "../../Hosts"; +import { Hosts } from "./hosts"; export * from "./filesystem"; export * from "./ports"; @@ -31,6 +32,11 @@ export class WebSocketSession { */ public readonly fs = new FileSystem(this.disposable, this.pitcherClient); + /** + * Namespace for hosts + */ + public readonly hosts: Hosts; + /** * Namespace for creating and managing terminals this Sandbox */ @@ -54,7 +60,7 @@ export class WebSocketSession { /** * Namespace for managing ports on this Sandbox */ - public readonly ports: Ports; + public readonly ports = new Ports(this.disposable, this.pitcherClient); /** * Namespace for the setup that runs when the Sandbox starts from scratch. @@ -68,10 +74,7 @@ export class WebSocketSession { constructor( protected pitcherClient: IPitcherClient, - { - env, - previewToken, - }: { env?: Record; previewToken?: PreviewToken } + { env, hostToken }: { env?: Record; hostToken?: HostToken } ) { // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { @@ -82,7 +85,8 @@ export class WebSocketSession { // this.addDisposable(metricsDisposable); this.terminals = new Terminals(this.disposable, this.pitcherClient, env); this.commands = new Commands(this.disposable, this.pitcherClient, env); - this.ports = new Ports(this.disposable, this.pitcherClient, previewToken); + + this.hosts = new Hosts(this.pitcherClient, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); this.disposable.addDisposable(this.pitcherClient); } diff --git a/src/sessions/WebSocketSession/ports.ts b/src/sessions/WebSocketSession/ports.ts index 7599f38..4ed2c2a 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/sessions/WebSocketSession/ports.ts @@ -2,7 +2,12 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; -import { PreviewToken } from "../../PreviewTokens"; +import { HostToken } from "../../Hosts"; + +export type Port = { + host: string; + port: number; +}; export class Ports { private disposable = new Disposable(); @@ -23,8 +28,7 @@ export class Ports { constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient, - private previewToken?: PreviewToken + private pitcherClient: IPitcherClient ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -46,9 +50,10 @@ export class Ports { if (openedPorts.length) { for (const port of openedPorts) { - this.onDidPortOpenEmitter.fire( - new Port(port.port, port.url, this.previewToken) - ); + this.onDidPortOpenEmitter.fire({ + port: port.port, + host: port.url, + }); } } @@ -63,14 +68,20 @@ export class Ports { ); } - getOpenedPort(port: number) { - return this.getOpenedPorts().find((p) => p.port === port); + /** + * Get a port by number. + */ + get(port: number) { + return this.getAll().find((p) => p.port === port); } - getOpenedPorts(): Port[] { + /** + * Get all ports. + */ + getAll(): Port[] { return this.pitcherClient.clients.port .getPorts() - .map(({ port, url }) => new Port(port, url, this.previewToken)); + .map(({ port, url }) => ({ port, host: url })); } /** @@ -90,7 +101,7 @@ export class Ports { return new Promise((resolve, reject) => { // Check if port is already open - const portInfo = this.getOpenedPorts().find((p) => p.port === port); + const portInfo = this.getAll().find((p) => p.port === port); if (portInfo) { resolve(portInfo); @@ -124,16 +135,3 @@ export class Ports { }); } } - -class Port { - constructor( - public readonly port: number, - public readonly host: string, - private previewToken?: PreviewToken - ) {} - getPreviewUrl() { - return `https://${this.host}${ - this.previewToken ? `?preview_token=${this.previewToken.token}` : "" - }`; - } -} diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index 43ef80b..628e049 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -12,10 +12,6 @@ export type TaskDefinition = { name: string; command: string; runAtStart?: boolean; - preview?: { - port?: number; - "pr-link"?: "direct" | "redirect" | "devtool"; - }; }; export class Tasks { diff --git a/src/types.ts b/src/types.ts index 628033e..20c256f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; import type { WebSocketSession } from "./sessions/WebSocketSession"; -import { PreviewToken } from "./PreviewTokens"; +import { HostToken } from "./Hosts"; export interface SystemMetricsStatus { cpu: { @@ -132,14 +132,14 @@ export interface StartSandboxOpts { */ automaticWakeupConfig?: { /** - * Whether the VM should automatically wake up on HTTP requests to preview URLs (excludes WebSocket requests) + * Whether the VM should automatically wake up on HTTP requests to hosts exposed (excludes WebSocket requests) * * @default true */ http: boolean; /** - * Whether the VM should automatically wake up on WebSocket connections to preview URLs + * Whether the VM should automatically wake up on WebSocket connections to host exposed * * @default false */ @@ -190,7 +190,7 @@ export interface SessionCreateOptions { name?: string; }; env?: Record; - previewToken?: PreviewToken; + hostToken?: HostToken; } export type SandboxSession = { @@ -237,5 +237,5 @@ export type SandboxOpts = { export type SandboxBrowserSession = PitcherManagerResponse & { id: string; env?: Record; - previewToken?: PreviewToken; + hostToken?: HostToken; }; From ffc629d74c1df03151ff6db9a0dae58028671b24 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 12:17:22 +0200 Subject: [PATCH 106/241] latest fixes --- src/browser.ts | 10 ++++++++-- src/sessions/WebSocketSession/commands.ts | 13 +++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/browser.ts b/src/browser.ts index 975b621..72f2ebb 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -22,8 +22,14 @@ export async function connectToSandbox(options: { instanceId: options.session.id, onFocusChange: options.onFocusChange || - (() => { - return () => {}; + ((notify) => { + const listener = () => { + notify(document.hasFocus()); + }; + window.addEventListener("visibilitychange", listener); + return () => { + window.removeEventListener("visibilitychange", listener); + }; }), requestPitcherInstance: async (id) => { const session = hasConnected diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 329a3ed..c92d6e8 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -34,9 +34,9 @@ export class Commands { } /** - * Create and run command in a new shell. + * Create and run command in a new shell. Allows you to listen to the output and kill the command. */ - async run(command: string | string[], opts?: ShellRunOpts) { + async runBackground(command: string | string[], opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); @@ -76,6 +76,15 @@ export class Commands { return cmd; } + /** + * Run a command in a new shell and wait for it to finish, returning its output. + */ + async run(command: string | string[], opts?: ShellRunOpts): Promise { + const cmd = await this.runBackground(command, opts); + + return cmd.waitUntilComplete(); + } + /** * Get all running commands. */ From 74fe27fb16109fd17b3c099c10da3453a0d0eda0 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 12:40:19 +0200 Subject: [PATCH 107/241] fix terminals and commands list --- package.json | 2 +- src/sessions/WebSocketSession/commands.ts | 27 +++++++++++----------- src/sessions/WebSocketSession/terminals.ts | 5 +++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 9246d52..6342559 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index c92d6e8..46f28ac 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -36,7 +36,10 @@ export class Commands { /** * Create and run command in a new shell. Allows you to listen to the output and kill the command. */ - async runBackground(command: string | string[], opts?: ShellRunOpts) { + async runBackground( + command: string | string[], + opts?: Omit + ) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); @@ -64,9 +67,11 @@ export class Commands { true ); - if (opts?.name) { - this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); - } + // Only way for us to differentiate between a command and a terminal + this.pitcherClient.clients.shell.rename( + shell.shellId, + `COMMAND-${shell.shellId}` + ); const cmd = new Command( this.pitcherClient, @@ -92,7 +97,10 @@ export class Commands { const shells = this.pitcherClient.clients.shell.getShells(); return shells - .filter((shell) => shell.shellType === "COMMAND") + .filter( + (shell) => + shell.shellType === "TERMINAL" && shell.name.startsWith("COMMAND-") + ) .map((shell) => new Command(this.pitcherClient, shell)); } } @@ -120,13 +128,6 @@ export class Command { private output: string[] = []; - /** - * The command that was run. - */ - get command(): string { - return this.shell.startCommand; - } - /** * The status of the command. */ @@ -134,7 +135,7 @@ export class Command { constructor( private pitcherClient: IPitcherClient, - private shell: protocol.shell.CommandShellDTO & { buffer?: string[] } + private shell: protocol.shell.ShellDTO & { buffer?: string[] } ) { this.disposable.addDisposable( pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => { diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index 7ac8ab5..9f49b85 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -71,7 +71,10 @@ export class Terminals { const shells = this.pitcherClient.clients.shell.getShells(); return shells - .filter((shell) => shell.shellType === "TERMINAL") + .filter( + (shell) => + shell.shellType === "TERMINAL" && !shell.name.startsWith("COMMAND-") + ) .map((shell) => new Terminal(shell, this.pitcherClient)); } } From 068504d8bdfa92ee617e552cea4300176fd32463 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 13:16:17 +0200 Subject: [PATCH 108/241] fix cli --- .../{host-tokens.ts => hostTokens.ts} | 0 src/bin/commands/previewHosts.ts | 219 ++++++++++-------- src/bin/commands/sandbox/fork.ts | 6 +- src/bin/commands/sandbox/hibernate.ts | 2 +- src/bin/commands/sandbox/host-tokens.ts | 10 +- src/bin/commands/sandbox/list.ts | 2 +- src/bin/commands/sandbox/shutdown.ts | 2 +- src/bin/main.ts | 2 +- src/index.ts | 2 +- 9 files changed, 132 insertions(+), 113 deletions(-) rename src/bin/commands/{host-tokens.ts => hostTokens.ts} (100%) diff --git a/src/bin/commands/host-tokens.ts b/src/bin/commands/hostTokens.ts similarity index 100% rename from src/bin/commands/host-tokens.ts rename to src/bin/commands/hostTokens.ts diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 4737558..79eea11 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -12,115 +12,132 @@ import { import { handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; -export type PreviewHostsCommandArgs = { - list?: boolean; - add?: string; - remove?: string; - clear?: boolean; -}; - -export const previewHostsCommand: yargs.CommandModule< - Record, - PreviewHostsCommandArgs -> = { +export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", describe: - "Manage preview hosts for your workspace. This allows you to access use the preview API from trusted hosts.", - builder: (yargs: yargs.Argv) => - yargs - .option("list", { + "Manage preview hosts that should be able to access the Preview API", + builder: (yargs) => { + return yargs + .command({ + command: "list", describe: "List current preview hosts", - type: "boolean", - }) - .option("add", { - describe: "Add a preview host, ex. ", - type: "string", + handler: async () => { + const API_KEY = getApiKey(); + const apiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + }, + }) + ); + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + const hosts = data.preview_hosts.map(({ host }) => host); + if (hosts.length) { + console.log(hosts.join("\n")); + } else { + console.log("No preview hosts found"); + } + }, }) - .option("clear", { - describe: "Clear all preview hosts", - type: "boolean", + .command({ + command: "add ", + describe: "Add a preview host", + builder: (yargs) => + yargs.positional("host", { + describe: "Host to add", + type: "string", + demandOption: true, + }), + handler: async (argv) => { + const API_KEY = getApiKey(); + const apiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + }, + }) + ); + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + let hosts = data.preview_hosts.map(({ host }) => host); + const hostToAdd = (argv.host as string).trim(); + if (hosts.includes(hostToAdd)) { + console.log(`Host already exists: ${hostToAdd}`); + return; + } + hosts.push(hostToAdd); + await previewHostUpdate({ + client: apiClient, + body: { hosts }, + }); + console.log(`Added preview host: ${hostToAdd}`); + }, }) - .option("remove", { + .command({ + command: "remove ", describe: "Remove a preview host", - type: "string", - }), - handler: async (argv) => { - // Only allow one operation at a time - const ops = [argv.list, argv.add, argv.remove, argv.clear].filter(Boolean); - if (ops.length !== 1) { - throw new Error( - "Please specify exactly one operation: --list, --add, --remove, or --clear" - ); - } - - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, + builder: (yargs) => + yargs.positional("host", { + describe: "Host to remove", + type: "string", + demandOption: true, + }), + handler: async (argv) => { + const API_KEY = getApiKey(); + const apiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + }, + }) + ); + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + let hosts = data.preview_hosts.map(({ host }) => host); + const hostToRemove = (argv.host as string).trim(); + if (!hosts.includes(hostToRemove)) { + console.log(`Host not found: ${hostToRemove}`); + return; + } + hosts = hosts.filter((h) => h !== hostToRemove); + await previewHostUpdate({ + client: apiClient, + body: { hosts }, + }); + console.log(`Removed preview host: ${hostToRemove}`); }, }) - ); - - if (argv.list) { - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - const hosts = data.preview_hosts.map(({ host }) => host); - if (hosts.length) { - console.log(hosts.join("\n")); - } else { - console.log("No preview hosts found"); - } - return; - } - - // For add, remove, clear: always work with the full list - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - let hosts = data.preview_hosts.map(({ host }) => host); - - if (argv.add) { - const hostToAdd = argv.add.trim(); - if (hosts.includes(hostToAdd)) { - console.log(`Host already exists: ${hostToAdd}`); - return; - } - hosts.push(hostToAdd); - await previewHostUpdate({ - client: apiClient, - body: { hosts }, - }); - console.log(`Added preview host: ${hostToAdd}`); - return; - } - - if (argv.remove) { - const hostToRemove = argv.remove.trim(); - if (!hosts.includes(hostToRemove)) { - console.log(`Host not found: ${hostToRemove}`); - return; - } - hosts = hosts.filter((h) => h !== hostToRemove); - await previewHostUpdate({ - client: apiClient, - body: { hosts }, - }); - console.log(`Removed preview host: ${hostToRemove}`); - return; - } - - if (argv.clear) { - if (hosts.length === 0) { - console.log("Preview host list is already empty."); - return; - } - await previewHostUpdate({ - client: apiClient, - body: { hosts: [] }, + .command({ + command: "clear", + describe: "Clear all preview hosts", + handler: async () => { + const API_KEY = getApiKey(); + const apiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + }, + }) + ); + const resp = await previewHostList({ client: apiClient }); + const data = handleResponse(resp, "Failed to list preview hosts"); + const hosts = data.preview_hosts.map(({ host }) => host); + if (hosts.length === 0) { + console.log("Preview host list is already empty."); + return; + } + await previewHostUpdate({ + client: apiClient, + body: { hosts: [] }, + }); + console.log("Cleared all preview hosts."); + }, }); - console.log("Cleared all preview hosts."); - return; - } }, + handler: () => {}, }; diff --git a/src/bin/commands/sandbox/fork.ts b/src/bin/commands/sandbox/fork.ts index 6623e6d..f7a9d42 100644 --- a/src/bin/commands/sandbox/fork.ts +++ b/src/bin/commands/sandbox/fork.ts @@ -6,10 +6,12 @@ export async function forkSandbox(sandboxId: string) { const sdk = new CodeSandbox(); const spinner = ora("Forking sandbox...").start(); - const sandbox2 = await sdk.sandbox.fork(sandboxId); + const sandbox2 = await sdk.sandboxes.create({ + source: "template", + id: sandboxId, + }); spinner.succeed("Sandbox forked successfully"); - sandbox2.disconnect(); // eslint-disable-next-line no-console console.log(sandbox2.id); } diff --git a/src/bin/commands/sandbox/hibernate.ts b/src/bin/commands/sandbox/hibernate.ts index 316807c..fe8b596 100644 --- a/src/bin/commands/sandbox/hibernate.ts +++ b/src/bin/commands/sandbox/hibernate.ts @@ -11,7 +11,7 @@ async function hibernateSingleSandbox( spinner: ReturnType ): Promise { try { - await new CodeSandbox().sandbox.hibernate(id); + await new CodeSandbox().sandboxes.hibernate(id); const message = `✔ Sandbox ${id} hibernated successfully`; // eslint-disable-next-line no-console console.log(message); diff --git a/src/bin/commands/sandbox/host-tokens.ts b/src/bin/commands/sandbox/host-tokens.ts index 8457af7..275c3d4 100644 --- a/src/bin/commands/sandbox/host-tokens.ts +++ b/src/bin/commands/sandbox/host-tokens.ts @@ -11,7 +11,7 @@ export async function listPreviewTokens(sandboxId: string) { const spinner = ora("Fetching preview tokens...").start(); try { - const tokens = await sdk.previewTokens.list(sandboxId); + const tokens = await sdk.hosts.listTokens(sandboxId); spinner.stop(); if (tokens.length === 0) { @@ -68,7 +68,7 @@ export async function createPreviewToken( const spinner = ora("Creating preview token...").start(); try { - const token = await sdk.previewTokens.create(sandboxId, { + const token = await sdk.hosts.createToken(sandboxId, { expiresAt: expiresAt ? new Date(expiresAt) : undefined, }); spinner.stop(); @@ -121,7 +121,7 @@ export async function revokePreviewToken( const spinner = ora("Revoking preview token...").start(); try { - await sdk.previewTokens.revoke(sandboxId, previewTokenId); + await sdk.hosts.revokeToken(sandboxId, previewTokenId); spinner.stop(); console.log("Preview token revoked successfully"); } catch (error) { @@ -139,7 +139,7 @@ export async function updatePreviewToken( const spinner = ora("Updating preview token...").start(); try { - await sdk.previewTokens.update( + await sdk.hosts.updateToken( sandboxId, previewTokenId, expiresAt ? new Date(expiresAt) : null @@ -157,7 +157,7 @@ export async function revokeAllPreviewTokens(sandboxId: string) { const spinner = ora("Revoking all preview tokens...").start(); try { - await sdk.previewTokens.revokeAll(sandboxId); + await sdk.hosts.revokeAllTokens(sandboxId); spinner.stop(); console.log("All preview tokens have been revoked"); } catch (error) { diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index 5d4c52c..da2c339 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -83,7 +83,7 @@ export async function listSandboxes( sandboxes, totalCount: total, pagination, - } = await sdk.sandbox.list({ + } = await sdk.sandboxes.list({ ...listOpts, pagination: { page: currentPage, diff --git a/src/bin/commands/sandbox/shutdown.ts b/src/bin/commands/sandbox/shutdown.ts index fb36189..c974c74 100644 --- a/src/bin/commands/sandbox/shutdown.ts +++ b/src/bin/commands/sandbox/shutdown.ts @@ -11,7 +11,7 @@ async function shutdownSingleSandbox( spinner: ReturnType ): Promise { try { - await new CodeSandbox().sandbox.shutdown(id); + await new CodeSandbox().sandboxes.shutdown(id); const message = `✔ Sandbox ${id} shutdown successfully`; // eslint-disable-next-line no-console console.log(message); diff --git a/src/bin/main.ts b/src/bin/main.ts index 5d4fb18..6acda6e 100644 --- a/src/bin/main.ts +++ b/src/bin/main.ts @@ -4,7 +4,7 @@ import { hideBin } from "yargs/helpers"; import { buildCommand } from "./commands/build"; import { sandboxesCommand } from "./commands/sandbox"; import { previewHostsCommand } from "./commands/previewHosts"; -import { hostTokensCommand } from "./commands/host-tokens"; +import { hostTokensCommand } from "./commands/hostTokens"; yargs(hideBin(process.argv)) .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") diff --git a/src/index.ts b/src/index.ts index 5a5e4d4..a8c768d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,7 @@ export class CodeSandbox { createConfig({ baseUrl, headers: { - Authorization: `Bearer ${apiToken}`, + Authorization: `Bearer ${evaluatedApiToken}`, ...(opts.headers ?? {}), }, fetch: opts.fetch ?? fetch, From a14aa231cc048a8645c1f357f5d0e11267bc87ad Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 14:19:53 +0200 Subject: [PATCH 109/241] Use micro for template building --- package.json | 2 +- src/bin/commands/build.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6342559..4a817f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 12f0b34..6762d97 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -131,7 +131,6 @@ export const buildCommand: yargs.CommandModule< fromSandbox: argv.fromSandbox, collectionPath: argv.path, name: argv.name, - vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, }); spinner.start( @@ -139,7 +138,7 @@ export const buildCommand: yargs.CommandModule< ); const startResponse = await startVm(apiClient, sandboxId, { - vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : undefined, + vmTier: VMTier.fromName("Micro"), }); let sandbox = new Sandbox(sandboxId, apiClient, startResponse); let session = await sandbox.connect(); @@ -167,7 +166,11 @@ export const buildCommand: yargs.CommandModule< spinner.start( updateSpinnerMessage(index, "Restarting sandbox...", sandboxId) ); - sandbox = await sdk.sandboxes.restart(sandbox.id); + sandbox = await sdk.sandboxes.restart(sandbox.id, { + vmTier: argv.vmTier + ? VMTier.fromName(argv.vmTier) + : VMTier.fromName("Micro"), + }); session = await sandbox.connect(); const disposableStore = new DisposableStore(); From bc0ac5864aec797083e03ba2cb247c06f4c39ca4 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 14:23:19 +0200 Subject: [PATCH 110/241] default to micro for create and start --- src/bin/commands/build.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 6762d97..53a2bb5 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -131,6 +131,9 @@ export const buildCommand: yargs.CommandModule< fromSandbox: argv.fromSandbox, collectionPath: argv.path, name: argv.name, + vmTier: argv.vmTier + ? VMTier.fromName(argv.vmTier) + : VMTier.fromName("Micro"), }); spinner.start( From c655a667211c62c9b20776220cf65efe4540690f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 15:53:15 +0200 Subject: [PATCH 111/241] ready for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a817f8..69974ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-rc.5", + "version": "1.0.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 94caf9ec70ef3d020ff5280293bb3f5d83d8f791 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 16:02:43 +0200 Subject: [PATCH 112/241] blah --- src/{sandbox.ts => Sandbo.ts} | 0 src/Sandboxes.ts | 2 +- src/index.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{sandbox.ts => Sandbo.ts} (100%) diff --git a/src/sandbox.ts b/src/Sandbo.ts similarity index 100% rename from src/sandbox.ts rename to src/Sandbo.ts diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 7fb052c..1eecddc 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -7,7 +7,7 @@ import { vmShutdown, vmStart, } from "./api-clients/client"; -import { Sandbox } from "./Sandbox"; +import { Sandbox } from "./Sandbo"; import { getDefaultTemplateId, getStartOptions, diff --git a/src/index.ts b/src/index.ts index a8c768d..930f227 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ export { Sandboxes as SandboxClient }; export { VMTier } from "./VMTier"; -export * from "./Sandbox"; +export * from "./Sandbo"; export * from "./types"; import { HostTokens } from "./Hosts"; From 069b7c5d260f1a2259d394d7e585d5ff77b2fba5 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 20 May 2025 16:03:00 +0200 Subject: [PATCH 113/241] fixed --- src/{Sandbo.ts => Sandbox.ts} | 0 src/Sandboxes.ts | 2 +- src/index.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{Sandbo.ts => Sandbox.ts} (100%) diff --git a/src/Sandbo.ts b/src/Sandbox.ts similarity index 100% rename from src/Sandbo.ts rename to src/Sandbox.ts diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 1eecddc..7fb052c 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -7,7 +7,7 @@ import { vmShutdown, vmStart, } from "./api-clients/client"; -import { Sandbox } from "./Sandbo"; +import { Sandbox } from "./Sandbox"; import { getDefaultTemplateId, getStartOptions, diff --git a/src/index.ts b/src/index.ts index 930f227..a8c768d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ export { Sandboxes as SandboxClient }; export { VMTier } from "./VMTier"; -export * from "./Sandbo"; +export * from "./Sandbox"; export * from "./types"; import { HostTokens } from "./Hosts"; From 02b1b160c1d7c3a0a9c0b6d3a32351f8195b2555 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:02:13 +0200 Subject: [PATCH 114/241] several fixes --- src/Sandbox.ts | 19 +++--- src/Sandboxes.ts | 16 ++++- src/sessions/WebSocketSession/commands.ts | 68 ++++++++++++++++++---- src/sessions/WebSocketSession/git.ts | 40 ++++++++++++- src/sessions/WebSocketSession/index.ts | 3 +- src/sessions/WebSocketSession/terminals.ts | 8 ++- src/types.ts | 1 + 7 files changed, 130 insertions(+), 25 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 4e38010..da5da0b 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -70,41 +70,42 @@ export class Sandbox { * new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory * than it can scale down to, it can become very slow. */ - async updateTier(sandboxId: string, tier: VMTier): Promise { + async updateTier(tier: VMTier): Promise { const response = await vmUpdateSpecs({ client: this.apiClient, - path: { id: sandboxId }, + path: { id: this.id }, body: { tier: tier.name, }, }); - handleResponse(response, `Failed to update sandbox tier ${sandboxId}`); + handleResponse(response, `Failed to update sandbox tier ${this.id}`); } /** * Updates the hibernation timeout for this sandbox. This is the amount of seconds the sandbox * will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox. */ - async updateHibernationTimeout( - sandboxId: string, - timeoutSeconds: number - ): Promise { + async updateHibernationTimeout(timeoutSeconds: number): Promise { const response = await vmUpdateHibernationTimeout({ client: this.apiClient, - path: { id: sandboxId }, + path: { id: this.id }, body: { hibernation_timeout_seconds: timeoutSeconds }, }); handleResponse( response, - `Failed to update hibernation timeout for sandbox ${sandboxId}` + `Failed to update hibernation timeout for sandbox ${this.id}` ); } private async createSession( opts: SessionCreateOptions ): Promise { + if (opts.id.length > 20) { + throw new Error("Session ID must be 32 characters or less"); + } + const response = await vmCreateSession({ client: this.apiClient, body: { diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 7fb052c..3f54cb3 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -72,7 +72,7 @@ export class Sandboxes { const sandbox = await this.createTemplateSandbox({ ...opts, source: "template", - id: this.defaultTemplateId, + id: opts.templateId || this.defaultTemplateId, }); const session = await sandbox.connect( @@ -169,6 +169,17 @@ export class Sandboxes { handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); } + /** + * Forks a sandbox. This will create a new sandbox from the given sandbox. + */ + public async fork(sandboxId: string, opts?: StartSandboxOpts) { + return this.create({ + source: "template", + id: sandboxId, + ...opts, + }); + } + /** * Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in * the project directory (`/project/sandbox`) will be preserved. @@ -210,6 +221,9 @@ export class Sandboxes { case "template": { return this.createTemplateSandbox(opts); } + default: { + throw new Error("Invalid source"); + } } } diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 46f28ac..1bc175f 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -36,10 +36,7 @@ export class Commands { /** * Create and run command in a new shell. Allows you to listen to the output and kill the command. */ - async runBackground( - command: string | string[], - opts?: Omit - ) { + async runBackground(command: string | string[], opts?: ShellRunOpts) { const disposableStore = new DisposableStore(); const onOutput = new Emitter(); disposableStore.add(onOutput); @@ -67,15 +64,24 @@ export class Commands { true ); + const details = { + type: "command", + command, + name: opts?.name, + }; + // Only way for us to differentiate between a command and a terminal this.pitcherClient.clients.shell.rename( shell.shellId, - `COMMAND-${shell.shellId}` + // We embed some details in the name to properly show the command that was run + // , the name and that it is an actual command + JSON.stringify(details) ); const cmd = new Command( this.pitcherClient, - shell as protocol.shell.CommandShellDTO + shell as protocol.shell.CommandShellDTO, + details ); return cmd; @@ -98,10 +104,24 @@ export class Commands { return shells .filter( - (shell) => - shell.shellType === "TERMINAL" && shell.name.startsWith("COMMAND-") + (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) ) - .map((shell) => new Command(this.pitcherClient, shell)); + .map( + (shell) => + new Command(this.pitcherClient, shell, JSON.parse(shell.name)) + ); + } +} + +export function isCommandShell( + shell: protocol.shell.ShellDTO +): shell is protocol.shell.CommandShellDTO { + try { + const parsed = JSON.parse(shell.name); + + return parsed.type === "command"; + } catch { + return false; } } @@ -133,10 +153,24 @@ export class Command { */ status: CommandStatus = "RUNNING"; + /** + * The command that was run + */ + command: string; + + /** + * The name of the command + */ + name?: string; + constructor( private pitcherClient: IPitcherClient, - private shell: protocol.shell.ShellDTO & { buffer?: string[] } + private shell: protocol.shell.ShellDTO & { buffer?: string[] }, + details: { command: string; name?: string } ) { + this.command = details.command; + this.name = details.name; + this.disposable.addDisposable( pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => { if (shellId === this.shell.shellId) { @@ -171,6 +205,20 @@ export class Command { ); } + /** + * Open the command and get its current output, subscribes to future output + */ + async open(dimensions = DEFAULT_SHELL_SIZE): Promise { + const shell = await this.pitcherClient.clients.shell.open( + this.shell.shellId, + dimensions + ); + + this.output = shell.buffer; + + return this.output.join("\n"); + } + /** * Wait for the command to finish with its returned output */ diff --git a/src/sessions/WebSocketSession/git.ts b/src/sessions/WebSocketSession/git.ts index 7f3bac5..61b6425 100644 --- a/src/sessions/WebSocketSession/git.ts +++ b/src/sessions/WebSocketSession/git.ts @@ -1,4 +1,5 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; +import { Commands } from "./commands"; export class Git { /** @@ -6,7 +7,10 @@ export class Git { */ onStatusChange = this.pitcherClient.clients.git.onStatusUpdated; - constructor(private pitcherClient: IPitcherClient) {} + constructor( + private pitcherClient: IPitcherClient, + private commands: Commands + ) {} /** * Get the current git status. @@ -14,4 +18,38 @@ export class Git { status() { return this.pitcherClient.clients.git.getStatus(); } + + /** + * Commit all changes to git + */ + async commit(message: string) { + const messageWithQuotesEscaped = message.replace(/"/g, '\\"'); + + await this.commands.run([ + "git add .", + `git commit -m "${messageWithQuotesEscaped}"`, + ]); + } + + /** + * Checkout a branch + */ + async checkout(branch: string, isNewBranch = false) { + if (isNewBranch) { + await this.commands.run([ + "git checkout -b " + branch, + "git push --set-upstream origin " + branch, + ]); + return; + } + + await this.commands.run(["git checkout " + branch]); + } + + /** + * Push all changes to git + */ + async push() { + await this.commands.run(["git push"]); + } } diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 9dfe816..d4745c2 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -55,7 +55,7 @@ export class WebSocketSession { /** * Namespace for Git operations in the Sandbox */ - public readonly git = new Git(this.pitcherClient); + public readonly git: Git; /** * Namespace for managing ports on this Sandbox @@ -88,6 +88,7 @@ export class WebSocketSession { this.hosts = new Hosts(this.pitcherClient, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); + this.git = new Git(this.pitcherClient, this.commands); this.disposable.addDisposable(this.pitcherClient); } diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/sessions/WebSocketSession/terminals.ts index 9f49b85..b92d905 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/sessions/WebSocketSession/terminals.ts @@ -2,7 +2,7 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; import type { Id } from "@codesandbox/pitcher-common"; import { Disposable } from "../../utils/disposable"; import { Emitter } from "../../utils/event"; -import { ShellRunOpts } from "./commands"; +import { isCommandShell, ShellRunOpts } from "./commands"; export type ShellSize = { cols: number; rows: number }; @@ -72,8 +72,7 @@ export class Terminals { return shells .filter( - (shell) => - shell.shellType === "TERMINAL" && !shell.name.startsWith("COMMAND-") + (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) ) .map((shell) => new Terminal(shell, this.pitcherClient)); } @@ -121,6 +120,9 @@ export class Terminal { ); } + /** + * Open the terminal and get its current output, subscribes to future output + */ async open(dimensions = DEFAULT_SHELL_SIZE): Promise { const shell = await this.pitcherClient.clients.shell.open( this.shell.shellId, diff --git a/src/types.ts b/src/types.ts index 20c256f..ac24747 100644 --- a/src/types.ts +++ b/src/types.ts @@ -214,6 +214,7 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { source: "git"; url: string; branch: string; + templateId?: string; config?: { accessToken: string; email: string; From a53293a1fef2a10ae109888b8d1fcad8199a3fd7 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:06:43 +0200 Subject: [PATCH 115/241] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69974ea..75e89f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0", + "version": "1.1.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 07c6963c4be15f0f6c209b769a9eff9e82c8049d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:28:25 +0200 Subject: [PATCH 116/241] export hosts types --- src/sessions/WebSocketSession/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index d4745c2..36fd722 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -23,6 +23,7 @@ export * from "./terminals"; export * from "./commands"; export * from "./git"; export * from "./interpreters"; +export * from "./hosts"; export class WebSocketSession { private disposable = new Disposable(); From c7ac36f5485375a720c649318a01b4173dbf2f00 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:29:49 +0200 Subject: [PATCH 117/241] actually export it --- src/sessions/WebSocketSession/hosts.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sessions/WebSocketSession/hosts.ts b/src/sessions/WebSocketSession/hosts.ts index 7e7c2ba..e61c63c 100644 --- a/src/sessions/WebSocketSession/hosts.ts +++ b/src/sessions/WebSocketSession/hosts.ts @@ -1,6 +1,8 @@ import { IPitcherClient } from "@codesandbox/pitcher-client"; import { HostToken } from "../../Hosts"; +export { HostToken } from "../../Hosts"; + export class Hosts { constructor( private pitcherClient: IPitcherClient, From 73cc0a33d1adebb6e46a94bde49d25ee618ede98 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:30:32 +0200 Subject: [PATCH 118/241] unlisted by default --- src/Sandboxes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 3f54cb3..04ae793 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -111,7 +111,7 @@ export class Sandboxes { opts: CreateSandboxTemplateSourceOpts & StartSandboxOpts ) { const templateId = opts.id || this.defaultTemplateId; - const privacy = opts.privacy || "public"; + const privacy = opts.privacy || "unlisted"; const tags = opts.tags || ["sdk"]; const path = opts.path || "/SDK"; From 49ecec8924c098a4c7456af7c9307c48ac1ab375 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 21 May 2025 12:53:24 +0200 Subject: [PATCH 119/241] actually pass the host token on browser sessions --- src/Sandbox.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index da5da0b..12b3c21 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -237,6 +237,7 @@ export class Sandbox { return { id: this.id, env: customSession?.env, + hostToken: customSession?.hostToken, bootupType: this.bootupType, cluster: this.cluster, latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion, From fdd335dab8408b5debcdca51af3baa75204c355b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 22 May 2025 12:43:03 +0200 Subject: [PATCH 120/241] super duper --- esbuild.cjs | 12 +- package-lock.json | 741 +++++++++++++++++++++++- package.json | 8 +- src/Sandbox.ts | 3 +- src/Sandboxes.ts | 30 +- src/bin/commands/build.ts | 5 +- src/bin/commands/previewHosts.ts | 10 +- src/bin/commands/sandbox/fork.ts | 2 +- src/bin/commands/sandbox/hibernate.ts | 2 +- src/bin/commands/sandbox/host-tokens.ts | 2 +- src/bin/commands/sandbox/list.ts | 2 +- src/bin/commands/sandbox/shutdown.ts | 2 +- src/bin/main.ts | 19 - src/bin/main.tsx | 40 ++ src/bin/ui/Dashboard.tsx | 109 ++++ src/bin/ui/sdkContext.ts | 16 + src/utils/api.ts | 30 +- tsconfig.json | 7 +- 18 files changed, 961 insertions(+), 79 deletions(-) delete mode 100644 src/bin/main.ts create mode 100644 src/bin/main.tsx create mode 100644 src/bin/ui/Dashboard.tsx create mode 100644 src/bin/ui/sdkContext.ts diff --git a/esbuild.cjs b/esbuild.cjs index c528ae1..e010010 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -112,14 +112,20 @@ const require = topLevelCreateRequire(import.meta.url); // Bin builds: esbuild.build({ - entryPoints: ["src/bin/main.ts"], - outfile: "dist/bin/codesandbox.cjs", + entryPoints: ["src/bin/main.tsx"], + outfile: "dist/bin/codesandbox.mjs", bundle: true, - format: "cjs", + format: "esm", platform: "node", banner: { js: `#!/usr/bin/env node\n\n`, }, + external: [ + ...Object.keys(require("./package.json").dependencies || {}), + ...Object.keys(require("./package.json").devDependencies || {}), + ...require("module").builtinModules, + "@codesandbox/sdk", + ], }), ]).catch(() => { process.exit(1); diff --git a/package-lock.json b/package-lock.json index b4febb5..a4250f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,23 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "license": "MIT", "dependencies": { + "@tanstack/react-query": "^5.76.1", + "ink": "^5.2.1", + "ink-table": "^3.1.0", "ora": "^8.2.0", + "react": "^18.3.1", "readline": "^1.3.0" }, "bin": { - "csb": "dist/bin/codesandbox.cjs" + "csb": "dist/bin/codesandbox.mjs" }, "devDependencies": { "@codesandbox/pitcher-client": "1.1.7", @@ -23,6 +27,7 @@ "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", + "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", @@ -62,6 +67,43 @@ "phoenix": "^1.4.0" } }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", + "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/runtime": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", @@ -885,6 +927,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.76.0.tgz", + "integrity": "sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.76.1.tgz", + "integrity": "sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.76.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/blessed": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.25.tgz", @@ -936,6 +1004,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/react": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", + "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@types/retry": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", @@ -1066,6 +1144,18 @@ "dev": true, "license": "MIT" }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1540,6 +1630,18 @@ "dev": true, "license": "ISC" }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -1583,6 +1685,112 @@ "@colors/colors": "1.5.0" } }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1655,6 +1863,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1702,6 +1922,15 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -1826,6 +2055,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2002,6 +2238,18 @@ "dev": true, "license": "MIT" }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2035,6 +2283,16 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.38.0.tgz", + "integrity": "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -2687,6 +2945,18 @@ "dev": true, "license": "MIT" }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2694,6 +2964,237 @@ "dev": true, "license": "ISC" }, + "node_modules/ink": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", + "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.1.3", + "ansi-escapes": "^7.0.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^4.0.0", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.22.0", + "indent-string": "^5.0.0", + "is-in-ci": "^1.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "string-width": "^7.2.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-table": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ink-table/-/ink-table-3.1.0.tgz", + "integrity": "sha512-qxVb4DIaEaJryvF9uZGydnmP9Hkmas3DCKVpEcBYC0E4eJd3qNgNe+PZKuzgCERFe9LfAS1TNWxCr9+AU4v3YA==", + "license": "MIT", + "dependencies": { + "object-hash": "^2.0.3" + }, + "peerDependencies": { + "ink": ">=3.0.0", + "react": ">=16.8.0" + } + }, + "node_modules/ink/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ink/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ink/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ink/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/ink/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/ink/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -2753,6 +3254,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -2892,6 +3408,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/js-untar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", @@ -2982,6 +3504,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru_map": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", @@ -3170,6 +3704,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -3385,6 +3928,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/ohash": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", @@ -3608,6 +4160,15 @@ "node": ">= 0.10" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -3837,6 +4398,34 @@ "destr": "^2.0.3" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -4010,6 +4599,15 @@ "dev": true, "license": "ISC" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4101,6 +4699,49 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4127,6 +4768,27 @@ "node": ">= 0.8.0" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -4620,6 +5282,71 @@ "node": ">=8" } }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -4774,9 +5501,7 @@ "version": "8.18.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -4873,6 +5598,12 @@ "node": ">=12" } }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, "node_modules/zen-observable": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", diff --git a/package.json b/package.json index 75e89f3..b030879 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "repository": "git+https://github.com/codesandbox/codesandbox-sdk.git", "type": "module", "bin": { - "csb": "dist/bin/codesandbox.cjs" + "csb": "dist/bin/codesandbox.mjs" }, "module": "./dist/esm/index.js", "main": "./dist/esm/index.js", @@ -51,7 +51,7 @@ }, "types": "./dist/esm/index.d.ts", "scripts": { - "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.cjs", + "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.mjs", "build:cjs": "tsc -p ./tsconfig.build-cjs.json", "build:esm": "tsc -p ./tsconfig.build-esm.json", "build:esbuild": "node esbuild.cjs", @@ -91,6 +91,7 @@ "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", + "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", @@ -112,7 +113,10 @@ "yargs": "^17.7.2" }, "dependencies": { + "@tanstack/react-query": "^5.76.1", + "ink": "^5.2.1", "ora": "^8.2.0", + "react": "^18.3.1", "readline": "^1.3.0" } } diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 12b3c21..62b77fe 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -16,11 +16,10 @@ import { vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./api-clients/client"; -import { handleResponse } from "./utils/api"; +import { handleResponse, startVm } from "./utils/api"; import { VMTier } from "./VMTier"; import { WebSocketSession } from "./sessions/WebSocketSession"; import { RestSession } from "./sessions/RestSession"; -import { startVm } from "./Sandboxes"; export class Sandbox { /** diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 04ae793..c838682 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -13,6 +13,7 @@ import { getStartOptions, getStartResponse, handleResponse, + startVm, } from "./utils/api"; import { @@ -26,35 +27,6 @@ import { SandboxPrivacy, StartSandboxOpts, } from "./types"; -import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; - -export async function startVm( - apiClient: Client, - sandboxId: string, - startOpts?: StartSandboxOpts -): Promise { - const startResult = await vmStart({ - client: apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); - - return getStartResponse(response); -} /** * This class provides methods for creating and managing sandboxes. diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 53a2bb5..83ed130 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,14 +1,13 @@ import { promises as fs } from "fs"; import path from "path"; import { isBinaryFile } from "isbinaryfile"; -import readline from "readline"; import { Disposable, DisposableStore } from "@codesandbox/pitcher-common"; import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { VMTier, CodeSandbox, Sandbox } from "../../"; +import { VMTier, CodeSandbox, Sandbox } from "@codesandbox/sdk"; import { sandboxFork, @@ -19,7 +18,7 @@ import { import { getDefaultTemplateId, handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; -import { startVm } from "../../Sandboxes"; +import { startVm } from "../../utils/api"; export type BuildCommandArgs = { directory: string; diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 79eea11..259ff23 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -1,14 +1,8 @@ -import readline from "readline"; - import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; -import ora from "ora"; + import type * as yargs from "yargs"; -import { - previewHostList, - previewHostCreate, - previewHostUpdate, -} from "../../api-clients/client"; +import { previewHostList, previewHostUpdate } from "../../api-clients/client"; import { handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; diff --git a/src/bin/commands/sandbox/fork.ts b/src/bin/commands/sandbox/fork.ts index f7a9d42..0d72b85 100644 --- a/src/bin/commands/sandbox/fork.ts +++ b/src/bin/commands/sandbox/fork.ts @@ -1,6 +1,6 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; export async function forkSandbox(sandboxId: string) { const sdk = new CodeSandbox(); diff --git a/src/bin/commands/sandbox/hibernate.ts b/src/bin/commands/sandbox/hibernate.ts index fe8b596..43b16fc 100644 --- a/src/bin/commands/sandbox/hibernate.ts +++ b/src/bin/commands/sandbox/hibernate.ts @@ -1,5 +1,5 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; type CommandResult = { success: boolean; diff --git a/src/bin/commands/sandbox/host-tokens.ts b/src/bin/commands/sandbox/host-tokens.ts index 275c3d4..029ec08 100644 --- a/src/bin/commands/sandbox/host-tokens.ts +++ b/src/bin/commands/sandbox/host-tokens.ts @@ -1,6 +1,6 @@ import ora from "ora"; import Table from "cli-table3"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; function formatDate(date: Date): string { return date.toLocaleString(); diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index da2c339..c95d9f3 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -5,7 +5,7 @@ import { PaginationOpts, SandboxInfo, SandboxListOpts, -} from "../../../"; +} from "@codesandbox/sdk"; type OutputFormat = { field: string; diff --git a/src/bin/commands/sandbox/shutdown.ts b/src/bin/commands/sandbox/shutdown.ts index c974c74..b1eea40 100644 --- a/src/bin/commands/sandbox/shutdown.ts +++ b/src/bin/commands/sandbox/shutdown.ts @@ -1,5 +1,5 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; type CommandResult = { success: boolean; diff --git a/src/bin/main.ts b/src/bin/main.ts deleted file mode 100644 index 6acda6e..0000000 --- a/src/bin/main.ts +++ /dev/null @@ -1,19 +0,0 @@ -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; - -import { buildCommand } from "./commands/build"; -import { sandboxesCommand } from "./commands/sandbox"; -import { previewHostsCommand } from "./commands/previewHosts"; -import { hostTokensCommand } from "./commands/hostTokens"; - -yargs(hideBin(process.argv)) - .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") - .demandCommand(1, "Usage: csb [options]") - .scriptName("csb") - .strict() - .recommendCommands() - .command(buildCommand) - .command(sandboxesCommand) - .command(hostTokensCommand) - .command(previewHostsCommand) - .parse(); diff --git a/src/bin/main.tsx b/src/bin/main.tsx new file mode 100644 index 0000000..4c168c1 --- /dev/null +++ b/src/bin/main.tsx @@ -0,0 +1,40 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { render, Text } from "ink"; + +import { buildCommand } from "./commands/build"; +import { sandboxesCommand } from "./commands/sandbox"; +import { previewHostsCommand } from "./commands/previewHosts"; +import { hostTokensCommand } from "./commands/hostTokens"; +import { CodeSandbox } from "@codesandbox/sdk"; +import { Dashboard } from "./ui/Dashboard"; +import React from "react"; +import { SDKProvider } from "./ui/sdkContext"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +if (process.argv.length === 2) { + const sdk = new CodeSandbox(); + const queryClient = new QueryClient(); + render( + + + + + , + { + exitOnCtrlC: true, + } + ); +} else { + yargs(hideBin(process.argv)) + .usage("CodeSandbox SDK CLI - Manage your CodeSandbox projects") + .demandCommand(1, "Usage: csb [options]") + .scriptName("csb") + .strict() + .recommendCommands() + .command(buildCommand) + .command(sandboxesCommand) + .command(hostTokensCommand) + .command(previewHostsCommand) + .parse(); +} diff --git a/src/bin/ui/Dashboard.tsx b/src/bin/ui/Dashboard.tsx new file mode 100644 index 0000000..9ab7eeb --- /dev/null +++ b/src/bin/ui/Dashboard.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from "react"; +import { Box, Text, useInput, useStdout } from "ink"; // useStdout for terminal size +import { useQuery } from "@tanstack/react-query"; +import { CodeSandbox } from "@codesandbox/sdk"; + +function formatAge(date: Date): string { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + if (days > 0) return `${days}d`; + if (hours > 0) return `${hours}h`; + if (minutes > 0) return `${minutes}m`; + return `${seconds}s`; +} + +async function fetchSandboxes() { + const sdk = new CodeSandbox(); + // Fetch first 10 sandboxes (can be paginated later) + const { sandboxes } = await sdk.sandboxes.list({ + pagination: { page: 1, pageSize: 10 }, + }); + return sandboxes; +} + +// Custom hook to get terminal size +function useTerminalSize() { + const { stdout } = useStdout(); + const [size, setSize] = useState([stdout?.columns || 80, stdout?.rows || 24]); + useEffect(() => { + if (!stdout) return undefined; + const handler = () => setSize([stdout.columns, stdout.rows]); + stdout.on("resize", handler); + return () => { + stdout.off("resize", handler); + }; + }, [stdout]); + return size; +} + +export function Dashboard() { + const { data, isLoading, isError, error } = useQuery({ + queryKey: ["sandboxes"], + queryFn: fetchSandboxes, + }); + + useInput((input, key) => {}); + + // Get terminal dimensions + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + + if (isLoading) return Loading sandboxes...; + if (isError) return Error: {String(error)}; + if (!data || data.length === 0) return No sandboxes found.; + + // Define column widths + const COLS = [ + { key: "id", label: "ID", width: 10 }, + { key: "title", label: "TITLE", width: 24 }, + { key: "privacy", label: "PRIVACY", width: 10 }, + { key: "tags", label: "TAGS", width: 20 }, + { key: "age", label: "AGE", width: 6 }, + ]; + + // Helper to pad and trim cell content + const pad = (str: string, width: number) => { + if (str.length > width) return str.slice(0, width - 1) + "…"; + return str.padEnd(width, " "); + }; + + // Render header + const header = COLS.map((col) => pad(col.label, col.width)).join(" "); + + // Render rows + const rows = data.map((sandbox: any) => { + const tags = Array.isArray(sandbox.tags) ? sandbox.tags.join(",") : ""; + const age = sandbox.updatedAt + ? formatAge(new Date(sandbox.updatedAt)) + : "-"; + const cells = [ + pad(String(sandbox.id), COLS[0].width), + pad(String(sandbox.title), COLS[1].width), + pad(String(sandbox.privacy), COLS[2].width), + pad(tags, COLS[3].width), + pad(age, COLS[4].width), + ]; + return cells.join(" "); + }); + + // Calculate how many rows fit (1 for header) + const maxRows = Math.max(stdoutHeight - 1, 0); + const visibleRows = rows.slice(0, maxRows); + + // Pad with empty rows if needed to fill the window + while (visibleRows.length < maxRows) { + visibleRows.push(""); + } + + return ( + + {header} + {visibleRows.map((row, i) => ( + {row} + ))} + + ); +} diff --git a/src/bin/ui/sdkContext.ts b/src/bin/ui/sdkContext.ts new file mode 100644 index 0000000..e9a1715 --- /dev/null +++ b/src/bin/ui/sdkContext.ts @@ -0,0 +1,16 @@ +import { createContext, useContext } from "react"; +import { CodeSandbox } from "@codesandbox/sdk"; + +export const SDKContext = createContext(null); + +export const SDKProvider = SDKContext.Provider; + +export function useSDK() { + const sdk = useContext(SDKContext); + + if (!sdk) { + throw new Error("No SDK provided"); + } + + return sdk; +} diff --git a/src/utils/api.ts b/src/utils/api.ts index a13c08c..091cc59 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,9 +1,37 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; -import { VmStartResponse } from "../api-clients/client"; +import { vmStart, VmStartResponse } from "../api-clients/client"; import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; import { Client } from "@hey-api/client-fetch"; +export async function startVm( + apiClient: Client, + sandboxId: string, + startOpts?: StartSandboxOpts +): Promise { + const startResult = await vmStart({ + client: apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }); + + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); + + return getStartResponse(response); +} + export type HandledResponse = { data?: { data?: D; diff --git a/tsconfig.json b/tsconfig.json index 01567c8..a05be19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "dist", "lib": ["dom", "es2020"], "jsx": "react-jsx", - "jsxImportSource": "preact", + "jsxImportSource": "react", "moduleResolution": "node", "skipLibCheck": true, "importHelpers": true, @@ -15,7 +15,10 @@ "inlineSources": true, "esModuleInterop": true, "resolveJsonModule": true, - "strict": true + "strict": true, + "paths": { + "@codesandbox/sdk": ["./src/sdk"] + } }, "include": ["src"], "exclude": [ From d60257be1d8a26498a37e46b0443f66f4386d7a0 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 22 May 2025 13:11:50 +0200 Subject: [PATCH 121/241] fix typo and paths with dedicated sessions --- src/Sandbox.ts | 6 +++++- src/sessions/WebSocketSession/filesystem.ts | 19 +++++++++++++++++-- src/sessions/WebSocketSession/index.ts | 13 +++++++++++-- src/types.ts | 1 + 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 12b3c21..6dd0652 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -103,7 +103,7 @@ export class Sandbox { opts: SessionCreateOptions ): Promise { if (opts.id.length > 20) { - throw new Error("Session ID must be 32 characters or less"); + throw new Error("Session ID must be 20 characters or less"); } const response = await vmCreateSession({ @@ -151,6 +151,8 @@ export class Sandbox { ? await this.createSession(customSession) : this.globalSession; + console.log("SESSION", session); + const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -208,6 +210,7 @@ export class Sandbox { ); return new WebSocketSession(pitcherClient, { + sessionId: customSession?.id, env: customSession?.env, hostToken: customSession?.hostToken, }); @@ -237,6 +240,7 @@ export class Sandbox { return { id: this.id, env: customSession?.env, + sessionId: customSession?.id, hostToken: customSession?.hostToken, bootupType: this.bootupType, cluster: this.cluster, diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts index 2c869f9..f3ef500 100644 --- a/src/sessions/WebSocketSession/filesystem.ts +++ b/src/sessions/WebSocketSession/filesystem.ts @@ -42,7 +42,8 @@ export class FileSystem { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private pitcherClient: IPitcherClient, + private sessionId?: string ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -212,7 +213,21 @@ export class FileSystem { const result = await this.pitcherClient.clients.fs.watch( path, options, - (event) => emitter.fire(event) + (event) => { + if (this.sessionId) { + emitter.fire({ + ...event, + paths: event.paths.map((path) => + path.replace( + `home/csb-session-${this.sessionId}/workspace/`, + "sandbox/" + ) + ), + }); + } else { + emitter.fire(event); + } + } ); if (result.type === "error") { diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index 36fd722..bfb0fc7 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -31,7 +31,7 @@ export class WebSocketSession { /** * Namespace for all filesystem operations on this Sandbox */ - public readonly fs = new FileSystem(this.disposable, this.pitcherClient); + public readonly fs: FileSystem; /** * Namespace for hosts @@ -75,7 +75,15 @@ export class WebSocketSession { constructor( protected pitcherClient: IPitcherClient, - { env, hostToken }: { env?: Record; hostToken?: HostToken } + { + env, + hostToken, + sessionId, + }: { + env?: Record; + hostToken?: HostToken; + sessionId?: string; + } ) { // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { @@ -84,6 +92,7 @@ export class WebSocketSession { // }; // this.addDisposable(metricsDisposable); + this.fs = new FileSystem(this.disposable, this.pitcherClient, sessionId); this.terminals = new Terminals(this.disposable, this.pitcherClient, env); this.commands = new Commands(this.disposable, this.pitcherClient, env); diff --git a/src/types.ts b/src/types.ts index ac24747..2c53e19 100644 --- a/src/types.ts +++ b/src/types.ts @@ -237,6 +237,7 @@ export type SandboxOpts = { export type SandboxBrowserSession = PitcherManagerResponse & { id: string; + sessionId?: string; env?: Record; hostToken?: HostToken; }; From 437a31d8106f820c6e71757081edc9e14c5bf7a3 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 22 May 2025 18:34:45 +0200 Subject: [PATCH 122/241] Use join result username --- src/Sandbox.ts | 7 ++++--- src/browser.ts | 4 ++++ src/sessions/WebSocketSession/filesystem.ts | 9 +++------ src/sessions/WebSocketSession/index.ts | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 6dd0652..65d82ae 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -151,8 +151,6 @@ export class Sandbox { ? await this.createSession(customSession) : this.globalSession; - console.log("SESSION", session); - const pitcherClient = await initPitcherClient( { appId: "sdk", @@ -210,7 +208,10 @@ export class Sandbox { ); return new WebSocketSession(pitcherClient, { - sessionId: customSession?.id, + username: customSession + ? // @ts-ignore + pitcherClient["joinResult"].client.username + : undefined, env: customSession?.env, hostToken: customSession?.hostToken, }); diff --git a/src/browser.ts b/src/browser.ts index 72f2ebb..56356dd 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -46,6 +46,10 @@ export async function connectToSandbox(options: { ); return new WebSocketSession(pitcherClient, { + username: options.session.sessionId + ? // @ts-ignore + pitcherClient["joinResult"].client.username + : undefined, env: options.session.env, hostToken: options.session.hostToken, }); diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/sessions/WebSocketSession/filesystem.ts index f3ef500..0eb5a90 100644 --- a/src/sessions/WebSocketSession/filesystem.ts +++ b/src/sessions/WebSocketSession/filesystem.ts @@ -43,7 +43,7 @@ export class FileSystem { constructor( sessionDisposable: Disposable, private pitcherClient: IPitcherClient, - private sessionId?: string + private username?: string ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -214,14 +214,11 @@ export class FileSystem { path, options, (event) => { - if (this.sessionId) { + if (this.username) { emitter.fire({ ...event, paths: event.paths.map((path) => - path.replace( - `home/csb-session-${this.sessionId}/workspace/`, - "sandbox/" - ) + path.replace(`home/${this.username}/workspace/`, "sandbox/") ), }); } else { diff --git a/src/sessions/WebSocketSession/index.ts b/src/sessions/WebSocketSession/index.ts index bfb0fc7..53abdbb 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/sessions/WebSocketSession/index.ts @@ -78,11 +78,11 @@ export class WebSocketSession { { env, hostToken, - sessionId, + username, }: { env?: Record; hostToken?: HostToken; - sessionId?: string; + username?: string; } ) { // TODO: Bring this back once metrics polling does not reset inactivity @@ -92,7 +92,7 @@ export class WebSocketSession { // }; // this.addDisposable(metricsDisposable); - this.fs = new FileSystem(this.disposable, this.pitcherClient, sessionId); + this.fs = new FileSystem(this.disposable, this.pitcherClient, username); this.terminals = new Terminals(this.disposable, this.pitcherClient, env); this.commands = new Commands(this.disposable, this.pitcherClient, env); From 2636d476804ca1e974283f80d145f21de0fd970e Mon Sep 17 00:00:00 2001 From: Michael Yong Date: Thu, 22 May 2025 12:15:54 -0700 Subject: [PATCH 123/241] Fix session create bug when passing in git credentials --- src/Sandboxes.ts | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 04ae793..3f41b1d 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -25,6 +25,7 @@ import { SandboxListResponse, SandboxPrivacy, StartSandboxOpts, + SessionCreateOptions, } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; @@ -75,21 +76,21 @@ export class Sandboxes { id: opts.templateId || this.defaultTemplateId, }); - const session = await sandbox.connect( - // We do not want users to pass gitAccessToken on global user, because it - // can be read by other users - { - id: "clone-repo-user", - permission: "write", - ...(opts.config - ? { - gitAccessToken: opts.config.accessToken, - email: opts.config.email, - name: opts.config.name, - } - : {}), - } - ); + // We do not want users to pass gitAccessToken on global user, because it + // can be read by other users + const sessionCreateOptions: SessionCreateOptions = { + id: "clone-repo-user", + permission: "write", + }; + if (opts.config) { + sessionCreateOptions.git = { + accessToken: opts.config.accessToken, + email: opts.config.email, + name: opts.config.name, + }; + } + + const session = await sandbox.connect(sessionCreateOptions); await session.commands.run([ "rm -rf .git", From 2284e393c230e7bc95e1e286ce6043c7a579de7e Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 23 May 2025 11:15:28 +0200 Subject: [PATCH 124/241] fix restart of clusters --- package.json | 2 +- src/bin/commands/build.ts | 19 ++++++++++++++++--- src/utils/api.ts | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 75e89f3..c5a1860 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.1.0", + "version": "1.1.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 53a2bb5..cde3aee 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -108,6 +108,15 @@ export const buildCommand: yargs.CommandModule< const sandboxIds = await Promise.all( clusters.map(async ({ host: cluster, slug }, index) => { + const clusterApiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }) + ); const sdk = new CodeSandbox(API_KEY, { baseUrl: BASE_URL, headers: { @@ -126,7 +135,7 @@ export const buildCommand: yargs.CommandModule< spinner.start(updateSpinnerMessage(index, "Creating sandbox...")); sandboxId = await createSandbox({ - apiClient, + apiClient: clusterApiClient, shaTag: tag, fromSandbox: argv.fromSandbox, collectionPath: argv.path, @@ -140,10 +149,14 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage(index, "Starting sandbox...", sandboxId) ); - const startResponse = await startVm(apiClient, sandboxId, { + const startResponse = await startVm(clusterApiClient, sandboxId, { vmTier: VMTier.fromName("Micro"), }); - let sandbox = new Sandbox(sandboxId, apiClient, startResponse); + let sandbox = new Sandbox( + sandboxId, + clusterApiClient, + startResponse + ); let session = await sandbox.connect(); spinner.start( diff --git a/src/utils/api.ts b/src/utils/api.ts index a13c08c..d3f7a79 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -56,7 +56,7 @@ export function getDefaultTemplateId(apiClient: Client): string { return "7ngcrf"; } - return "pcz35m"; + return "pt_UAYyadeQTA9jw8bXqzgy6v"; } export function handleResponse( From da518c9635eb343e19beccc42916b0e76003240f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 23 May 2025 13:50:43 +0200 Subject: [PATCH 125/241] fix failing commands --- src/sessions/WebSocketSession/commands.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 1bc175f..1d25d4c 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -64,19 +64,25 @@ export class Commands { true ); + if (shell.status === "ERROR" || shell.status === "KILLED") { + throw new Error(`Failed to create shell: ${shell.buffer.join("\n")}`); + } + const details = { type: "command", command, name: opts?.name, }; - // Only way for us to differentiate between a command and a terminal - this.pitcherClient.clients.shell.rename( - shell.shellId, - // We embed some details in the name to properly show the command that was run - // , the name and that it is an actual command - JSON.stringify(details) - ); + if (shell.status !== "FINISHED") { + // Only way for us to differentiate between a command and a terminal + this.pitcherClient.clients.shell.rename( + shell.shellId, + // We embed some details in the name to properly show the command that was run + // , the name and that it is an actual command + JSON.stringify(details) + ); + } const cmd = new Command( this.pitcherClient, From dd624bd9d2553d3cf0c2bff3ff5ae7b87892ff56 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 26 May 2025 08:24:28 +0200 Subject: [PATCH 126/241] POC --- openapi.json | 71 ++++++++ package-lock.json | 90 ++++++++--- package.json | 1 + src/api-clients/client/sdk.gen.ts | 20 ++- src/api-clients/client/types.gen.ts | 31 ++++ src/bin/main.tsx | 3 + src/bin/ui/Dashboard.tsx | 242 +++++++++++++++++++++++++++- 7 files changed, 429 insertions(+), 29 deletions(-) diff --git a/openapi.json b/openapi.json index 1307742..737bb9e 100644 --- a/openapi.json +++ b/openapi.json @@ -212,6 +212,54 @@ "title": "VMCreateTagResponse", "type": "object" }, + "VMListRunningVMsResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "concurrent_vm_count": { "type": "integer" }, + "concurrent_vm_limit": { "type": "integer" }, + "vms": { + "items": { + "properties": { "id": { "type": "string" } }, + "type": "object" + }, + "required": ["id"], + "type": "array" + } + }, + "required": [ + "vms", + "concurrent_vm_count", + "concurrent_vm_limit" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMListRunningVMsResponse", + "type": "object" + }, "SandboxGetResponse": { "allOf": [ { @@ -1855,6 +1903,29 @@ "tags": ["vm"] } }, + "/vm/running": { + "get": { + "callbacks": {}, + "description": "List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date.\n", + "operationId": "vm/list_running_vms", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMListRunningVMsResponse" + } + } + }, + "description": "VM List Running VMs Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "List information about currently running VMs", + "tags": ["vm"] + } + }, "/vm/tag": { "post": { "callbacks": {}, diff --git a/package-lock.json b/package-lock.json index a4250f0..26ea341 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "1.1.0", "license": "MIT", "dependencies": { + "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", - "ink-table": "^3.1.0", "ora": "^8.2.0", "react": "^18.3.1", "readline": "^1.3.0" @@ -772,6 +772,48 @@ "typescript": "^5.5.3" } }, + "node_modules/@inkjs/ui": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inkjs/ui/-/ui-2.0.0.tgz", + "integrity": "sha512-5+8fJmwtF9UvikzLfph9sA+LS+l37Ij/szQltkuXLOAXwNkBX9innfzh4pLGXIB59vKEQUtc6D4qGvhD7h3pAg==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-spinners": "^3.0.0", + "deepmerge": "^4.3.1", + "figures": "^6.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5" + } + }, + "node_modules/@inkjs/ui/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@inkjs/ui/node_modules/cli-spinners": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.2.0.tgz", + "integrity": "sha512-pXftdQloMZzjCr3pCTIRniDcys6dDzgpgVhAHHk6TKBDbRuP1MkuetTF5KSv4YUutbOPa7+7ZrAJ2kVtbMqyXA==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2080,6 +2122,15 @@ } } }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2428,6 +2479,21 @@ "dev": true, "license": "MIT" }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3012,19 +3078,6 @@ } } }, - "node_modules/ink-table": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ink-table/-/ink-table-3.1.0.tgz", - "integrity": "sha512-qxVb4DIaEaJryvF9uZGydnmP9Hkmas3DCKVpEcBYC0E4eJd3qNgNe+PZKuzgCERFe9LfAS1TNWxCr9+AU4v3YA==", - "license": "MIT", - "dependencies": { - "object-hash": "^2.0.3" - }, - "peerDependencies": { - "ink": ">=3.0.0", - "react": ">=16.8.0" - } - }, "node_modules/ink/node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -3928,15 +3981,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/ohash": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", diff --git a/package.json b/package.json index b030879..9ef6462 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "yargs": "^17.7.2" }, "dependencies": { + "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "ora": "^8.2.0", diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 6119539..492199a 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -268,6 +268,24 @@ export const vmListClusters = (options?: O }); }; +/** + * List information about currently running VMs + * List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date. + * + */ +export const vmListRunningVms = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/running', + ...options + }); +}; + /** * Create a new tag for a VM * Creates a new tag for a VM. diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index e553d64..c92d927 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -95,6 +95,21 @@ export type VmCreateTagResponse = { }; }; +export type VmListRunningVmsResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + concurrent_vm_count: number; + concurrent_vm_limit: number; + vms: Array<{ + id?: string; + }>; + }; +}; + export type SandboxGetResponse = { errors?: Array(null); + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + + const menuOptions = ["Open Sandbox", "List Sandboxes"]; + + // Handle keyboard input for menu navigation + useInput((input, key) => { + // Navigation is only active when no view is selected + if (currentView === null) { + if (key.upArrow) { + setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); + } else if (key.downArrow) { + setSelectedOption((prev) => + prev < menuOptions.length - 1 ? prev + 1 : prev + ); + } else if (key.return) { + setCurrentView(menuOptions[selectedOption]); + } + } else if (key.escape) { + // Allow going back to main menu with ESC key + setCurrentView(null); + } + }); + + // Render the current view or menu + if (currentView === "List Sandboxes") { + return setCurrentView(null)} />; + } else if (currentView === "Open Sandbox") { + return setCurrentView(null)} />; + } + + // Render main menu + return ( + + + CodeSandbox Menu + + {menuOptions.map((option, index) => ( + + + {selectedOption === index ? "> " : " "} + {option} + + + ))} + + Use arrow keys to navigate, Enter to select + + + ); +} + +// Component to list sandboxes +function ListSandboxes({ onBack }: { onBack: () => void }) { const { data, isLoading, isError, error } = useQuery({ queryKey: ["sandboxes"], queryFn: fetchSandboxes, }); - useInput((input, key) => {}); - // Get terminal dimensions const [stdoutWidth, stdoutHeight] = useTerminalSize(); + useInput((input, key) => { + if (key.escape) { + onBack(); + } + }); + if (isLoading) return Loading sandboxes...; if (isError) return Error: {String(error)}; if (!data || data.length === 0) return No sandboxes found.; @@ -90,7 +151,7 @@ export function Dashboard() { }); // Calculate how many rows fit (1 for header) - const maxRows = Math.max(stdoutHeight - 1, 0); + const maxRows = Math.max(stdoutHeight - 3, 0); // Leave space for instructions const visibleRows = rows.slice(0, maxRows); // Pad with empty rows if needed to fill the window @@ -99,11 +160,182 @@ export function Dashboard() { } return ( - + + + Sandboxes List + {header} {visibleRows.map((row, i) => ( {row} ))} + + Press ESC to return to menu + + + ); +} + +// Component to open a sandbox by ID +function OpenSandbox({ onBack }: { onBack: () => void }) { + const [sandboxId, setSandboxId] = useState(""); + const [showSandbox, setShowSandbox] = useState(false); + const [isFocused, setIsFocused] = useState(true); + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + + useInput((input, key) => { + if (key.escape && !showSandbox) { + onBack(); + } else if (isFocused && !showSandbox) { + if (key.return) { + if (sandboxId.trim()) { + setShowSandbox(true); + } + } else if (key.backspace || key.delete) { + setSandboxId((prev) => prev.slice(0, -1)); + } else if (input && !key.ctrl && !key.meta && !key.shift) { + // Only add printable characters + setSandboxId((prev) => prev + input); + } + } + }); + + if (showSandbox) { + return setShowSandbox(false)} />; + } + + return ( + + + Open Sandbox + + + Enter Sandbox ID: + {sandboxId || "_"} + + + + Type to input ID, press ENTER to open, ESC to return to menu + + + + ); +} + +// Component to display a sandbox +function Sandbox({ id, onBack }: { id: string; onBack: () => void }) { + const sdk = useSDK(); + + // Only two states: RUNNING or IDLE + const [sandboxState, setSandboxState] = useState< + "RUNNING" | "IDLE" | "STARTING" + >("IDLE"); + const [selectedOption, setSelectedOption] = useState(0); + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + + // Define menu options based on state + const getMenuOptions = () => { + switch (sandboxState) { + case "RUNNING": + return ["Hibernate", "Shutdown", "Restart"]; + case "IDLE": + return ["Start"]; + case "STARTING": + return []; + default: + return []; + } + }; + + const menuOptions = getMenuOptions(); + + // Handle menu options + const handleAction = async (action: string) => { + switch (action) { + case "Hibernate": + case "Shutdown": + setSandboxState("IDLE"); + setSelectedOption(0); + break; + case "Restart": + setSandboxState("STARTING"); + setTimeout(() => { + setSandboxState("RUNNING"); + setSelectedOption(0); + }, 2000); + break; + case "Start": + setSandboxState("STARTING"); + await sdk.sandboxes.resume(id); + setSandboxState("RUNNING"); + setSelectedOption(0); + break; + } + }; + + // Handle keyboard navigation + useInput((input, key) => { + if (key.escape) { + onBack(); + } else if (menuOptions.length > 0) { + if (key.upArrow) { + setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); + } else if (key.downArrow) { + setSelectedOption((prev) => + prev < menuOptions.length - 1 ? prev + 1 : prev + ); + } else if (key.return) { + handleAction(menuOptions[selectedOption]); + } + } + }); + + return ( + + + Sandbox: {id} + + + Status: + + {sandboxState} + + + + {menuOptions.length > 0 && ( + + Actions: + {menuOptions.map((option, index) => ( + + + {selectedOption === index ? "> " : " "} + {option} + + + ))} + + )} + + {sandboxState === "STARTING" && ( + + Sandbox is starting... + + )} + + + + {menuOptions.length > 0 + ? "Use arrow keys to navigate, Enter to select, ESC to go back" + : "Press ESC to go back"} + + ); } From c9cfbf9e83025d8553ed6e3b2bfb5d4b3dce07f5 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 26 May 2025 10:57:09 +0200 Subject: [PATCH 127/241] moving on --- package-lock.json | 7 +- package.json | 5 +- src/AgentProtocol/PendingPitcherMessage.ts | 134 +++++++++ src/AgentProtocol/WebSocketClient.ts | 280 ++++++++++++++++++ src/AgentProtocol/index.ts | 185 ++++++++++++ src/Sandbox.ts | 100 ++----- .../WebSocketSession => Session}/commands.ts | 36 +-- .../filesystem.ts | 4 +- .../WebSocketSession => Session}/git.ts | 0 .../WebSocketSession => Session}/hosts.ts | 8 +- .../WebSocketSession => Session}/index.ts | 6 +- .../interpreters.ts | 2 +- .../WebSocketSession => Session}/ports.ts | 6 +- .../WebSocketSession => Session}/setup.ts | 4 +- .../WebSocketSession => Session}/tasks.ts | 12 +- .../WebSocketSession => Session}/terminals.ts | 5 +- src/agent-client-interface.ts | 34 +++ src/browser/BrowserAgentClient.ts | 48 +++ src/{browser.ts => browser/index.ts} | 10 +- src/{ => browser}/previews/Preview.ts | 0 src/{ => browser}/previews/index.ts | 0 src/{ => browser}/previews/preview-script.ts | 0 src/{ => browser}/previews/types.ts | 0 src/sessions/RestSession/index.ts | 87 ------ .../RestSession/sandbox-rest-container.ts | 12 - src/sessions/RestSession/sandbox-rest-fs.ts | 111 ------- src/sessions/RestSession/sandbox-rest-git.ts | 53 ---- .../RestSession/sandbox-rest-shell.ts | 42 --- .../RestSession/sandbox-rest-system.ts | 18 -- src/sessions/RestSession/sandbox-rest-task.ts | 42 --- src/types.ts | 4 +- 31 files changed, 760 insertions(+), 495 deletions(-) create mode 100644 src/AgentProtocol/PendingPitcherMessage.ts create mode 100644 src/AgentProtocol/WebSocketClient.ts create mode 100644 src/AgentProtocol/index.ts rename src/{sessions/WebSocketSession => Session}/commands.ts (85%) rename src/{sessions/WebSocketSession => Session}/filesystem.ts (98%) rename src/{sessions/WebSocketSession => Session}/git.ts (100%) rename src/{sessions/WebSocketSession => Session}/hosts.ts (86%) rename src/{sessions/WebSocketSession => Session}/index.ts (98%) rename src/{sessions/WebSocketSession => Session}/interpreters.ts (95%) rename src/{sessions/WebSocketSession => Session}/ports.ts (96%) rename src/{sessions/WebSocketSession => Session}/setup.ts (98%) rename src/{sessions/WebSocketSession => Session}/tasks.ts (95%) rename src/{sessions/WebSocketSession => Session}/terminals.ts (96%) create mode 100644 src/agent-client-interface.ts create mode 100644 src/browser/BrowserAgentClient.ts rename src/{browser.ts => browser/index.ts} (86%) rename src/{ => browser}/previews/Preview.ts (100%) rename src/{ => browser}/previews/index.ts (100%) rename src/{ => browser}/previews/preview-script.ts (100%) rename src/{ => browser}/previews/types.ts (100%) delete mode 100644 src/sessions/RestSession/index.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-container.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-fs.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-git.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-shell.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-system.ts delete mode 100644 src/sessions/RestSession/sandbox-rest-task.ts diff --git a/package-lock.json b/package-lock.json index b4febb5..1bd3c93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "license": "MIT", "dependencies": { + "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "readline": "^1.3.0" }, @@ -2853,7 +2854,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", - "dev": true, "license": "MIT", "peerDependencies": { "ws": "*" @@ -4774,7 +4774,6 @@ "version": "8.18.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, "license": "MIT", "peer": true, "engines": { diff --git a/package.json b/package.json index 75e89f3..4abf527 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,6 @@ ], "devDependencies": { "@codesandbox/pitcher-client": "1.1.7", - "@codesandbox/pitcher-common": "0.360.2", - "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", @@ -112,6 +110,9 @@ "yargs": "^17.7.2" }, "dependencies": { + "isomorphic-ws": "^5.0.0", + "@codesandbox/pitcher-common": "0.360.2", + "@codesandbox/pitcher-protocol": "0.360.4", "ora": "^8.2.0", "readline": "^1.3.0" } diff --git a/src/AgentProtocol/PendingPitcherMessage.ts b/src/AgentProtocol/PendingPitcherMessage.ts new file mode 100644 index 0000000..2123144 --- /dev/null +++ b/src/AgentProtocol/PendingPitcherMessage.ts @@ -0,0 +1,134 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Disposable } from "@codesandbox/pitcher-common"; +import { + PitcherResponseStatus, + createRequestPayload, +} from "@codesandbox/pitcher-protocol"; +import type { + PitcherRequest, + PitcherResponse, + PitcherRequestPayload, + PitcherErrorCode, +} from "@codesandbox/pitcher-protocol"; + +const PITCHER_MESSAGE_TIMEOUT_MS = 90_000; + +export class PitcherMessageError extends Error { + code: number; + + constructor(message: string, code: number) { + super(message); + this.code = code; + } + + static match(error: any): error is PitcherMessageError { + return error instanceof PitcherMessageError; + } + + static matchCode( + error: any, + code: PitcherErrorCode + ): error is PitcherMessageError { + return error instanceof PitcherMessageError && error.code === code; + } +} + +export class PendingPitcherMessage< + T extends PitcherRequest, + R extends PitcherResponse = PitcherResponse +> extends Disposable { + id: number; + message: Uint8Array; + promise: Promise; + method: PitcherRequest["method"]; + + private timeoutRef?: NodeJS.Timeout; + private _hasResolved = false; + private _resolve?: (res: any) => void; + private _reject?: (err: Error) => void; + + constructor( + id: number, + request: T, + timeoutMs: number = PITCHER_MESSAGE_TIMEOUT_MS + ) { + super(); + + this.id = id; + const data: unknown = { + ...request, + id, + }; + this.method = request.method; + this.message = createRequestPayload(data as PitcherRequestPayload); + this.timeoutRef = setTimeout( + () => this.dispose(`Pitcher message ${this.method} timed out`), + timeoutMs + ); + this.onWillDispose(() => { + if (this.timeoutRef) { + clearTimeout(this.timeoutRef); + this.timeoutRef = undefined; + } + }); + this.promise = new Promise( + (_resolve, _reject) => { + this._resolve = _resolve; + this._reject = _reject; + } + ).then((response) => { + if (response.status === PitcherResponseStatus.RESOLVED) { + return response.result; + } + + const err = new PitcherMessageError( + response.error.message, + response.error.code + ); + // @ts-expect-error - data is optional + err.data = response.error.data; + + throw err; + }) as any; + } + + resolve(response: R extends { method: T["method"] } ? R : never): void { + if (!this.isDisposed && this._resolve) { + this._resolve(response); + this._hasResolved = true; + } + this.dispose(); + } + + reject(error: Error): void { + if (!this.isDisposed && this._reject) { + this._reject(error); + this._hasResolved = true; + } + this.dispose(); + } + + unwrap(): R extends { + method: T["method"]; + status: PitcherResponseStatus.RESOLVED; + } + ? Promise + : never { + return this.promise as any; + } + + dispose(message?: string) { + if (!this._hasResolved && this._reject) { + this._reject( + new Error(message ?? `Pitcher message ${this.method} has been disposed`) + ); + } + + super.dispose(); + + if (this.timeoutRef) { + clearTimeout(this.timeoutRef); + this.timeoutRef = undefined; + } + } +} diff --git a/src/AgentProtocol/WebSocketClient.ts b/src/AgentProtocol/WebSocketClient.ts new file mode 100644 index 0000000..474ba38 --- /dev/null +++ b/src/AgentProtocol/WebSocketClient.ts @@ -0,0 +1,280 @@ +import { Disposable, Emitter } from "@codesandbox/pitcher-common"; +import WebSocket from "isomorphic-ws"; + +export type WebsocketData = string | Uint8Array; + +export type DisconnectedEvent = { + code: number; + reason: string; + wasClean: boolean; +}; + +// With our PONG detection we rather use an offset of that to determine how often we ping. If not we result +// in never detecting PONG timeouts as we run a new ping, clearing the PONG timeout, before we get a chance +// to detect any offline condition +let WEBSOCKET_PING_OFFSET = 2000; +// During init of PitcherClient we have a much longer timeout for detection of the "pong" response. The +// reason is that during initialization Pitcher might do a lot of heavy lifting and we want to avoid unnecessary +// reconnects during this, but still have some fallback for detecting an actual disconnect +let INIT_DETECT_PONG_TIMEOUT = 20_000; + +const readyStateToString = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"]; + +if (typeof process !== "undefined" && process.env.NODE_ENV === "test") { + WEBSOCKET_PING_OFFSET = 20; + INIT_DETECT_PONG_TIMEOUT = 200; +} + +/* + This WebsocketClient is responsible for a single connection. That means when a disconnect happens a new WebsocketClient will be created. The + responsibility of this client is to keep the connection alive, detect disconnects and of course send messages. The following scenarios will cause + a disconnect event to trigger: + + - Close listener + - Error listener + - Sending message during closing/closed + - Late pong detection + + It is PitcherClient itself that is responsible for creating and disposing of WebsocketClients. It will do this when: + + - Disconnected event received + - Hibernation + - Seamless forking + - Explicit disconnect + - Explicit reconnect + + This creates a one way flow where a disposal from PitcherClient will not suddenly create a disconnected event, and when a disconnect + is detected it will not suddenly dispose of the instance. +*/ +export class WebSocketClient extends Disposable { + private ws: WebSocket; + private pongDetectionTimeout = INIT_DETECT_PONG_TIMEOUT; + private onMessageEmitter: Emitter = new Emitter(); + /** + * Whenever we are disconnected we will create a new WebSocketClient + */ + private onDisconnectedEmitter: Emitter<{ + code: number; + wasClean: boolean; + reason: string; + }> = new Emitter(); + /** + * Whenever the heartbeat is missing. This normally detects a disconnect, but + * there can be other reasons like a slow network or a big pending message causing it + */ + private onMissingHeartbeatEmitter: Emitter = new Emitter(); + + // This timeout is triggered when we send a ping, if we do not get a pong back within + // the timeout, we dispose of the websocket and go into disconnected state + private detectDisconnectByPongTimeout: number | undefined; + + onMessage = this.onMessageEmitter.event; + onDisconnected = this.onDisconnectedEmitter.event; + onMissingHeartbeat = this.onMissingHeartbeatEmitter.event; + + lastActivity = Date.now(); + + // It receives a connected websocket connection + constructor(ws: WebSocket) { + super(); + + if (ws.readyState !== ws.OPEN) { + throw new Error("Requires an OPEN websocket connection"); + } + + this.ws = ws; + this.lastActivity = Date.now(); + + /** + * Our heartbeat has two purposes: + * 1. Keep the connection alive by pinging Pitcher when there is no other activity + * 2. Detect the lack of a PONG response to identify the lack of a connection + */ + const onHeartbeatInterval = () => { + const timeSinceActivity = Date.now() - this.lastActivity; + + if (timeSinceActivity > this.pingTimeout) { + this.ping(); + } + }; + + const heartbeatInterval = setInterval( + onHeartbeatInterval, + this.pingTimeout + ); + + const onMessageListener = (event: { data: WebsocketData }) => { + this.lastActivity = Date.now(); + + const data = event.data; + + // We clear the PONG detection regardless of what message we got + clearTimeout(this.detectDisconnectByPongTimeout); + + // Node environment + if (typeof data !== "string") { + this.emitMessage(data); + return; + } + }; + + const onErrorListener = () => { + // We only want to dispose if we are actually moving towards a closing state, + // as we might send a message in CONNECTING state as well, which should not + // dispose of the WS + if (this.isClosingOrClosed()) { + this.onDisconnectedEmitter.fire({ + code: -1, + reason: "Error listener - " + readyStateToString[ws.readyState], + wasClean: false, + }); + } + }; + + const onCloseListener = ({ + wasClean, + code, + reason, + }: { + wasClean: boolean; + code: number; + reason: string; + }) => + this.onDisconnectedEmitter.fire({ + wasClean, + code, + reason: "Close listener - " + reason, + }); + + ws.addEventListener("message", onMessageListener); + ws.addEventListener("close", onCloseListener); + // This happens when we try to send a message in an invalid state + ws.addEventListener("error", onErrorListener); + + this.onWillDispose(() => { + clearInterval(heartbeatInterval); + clearTimeout(this.pongDetectionTimeout); + clearTimeout(this.detectDisconnectByPongTimeout); + + ws.removeEventListener("close", onCloseListener); + ws.removeEventListener("message", onMessageListener); + ws.removeEventListener("error", onErrorListener); + + this.onMessageEmitter.dispose(); + this.onDisconnectedEmitter.dispose(); + this.ws.close(); + }); + } + + private isClosingOrClosed() { + return ( + this.ws.readyState === this.ws.CLOSING || + this.ws.readyState === this.ws.CLOSED + ); + } + + private emitMessage(message: Uint8Array) { + this.onMessageEmitter.fire(message); + } + + private get pingTimeout() { + return this.pongDetectionTimeout + WEBSOCKET_PING_OFFSET; + } + + setPongDetectionTimeout(ms: number) { + this.pongDetectionTimeout = ms; + } + + /** + We use "PING" to both keep the session alive, but also detect disconnects. Certain interactions, like + focusing the application should trigger an immediate "ping", which is why this is a public method. An optional + pong timeout can be set. This is useful to detect disconnects faster for example when focusing the application + */ + ping(pongTimeout?: number) { + clearTimeout(this.detectDisconnectByPongTimeout); + this.detectDisconnectByPongTimeout = setTimeout(() => { + this.onMissingHeartbeatEmitter.fire(); + }, pongTimeout || this.pongDetectionTimeout) as unknown as number; + // Empty string is the payload of a heartbeat. We do not use + // ws.ping() here because it does not produce a pong response that is detectable and its + // callback does not give an error when internet is down + try { + this.send(""); + } catch { + // We do not care about errors here + } + } + + send(data: WebsocketData) { + // To avoid showing a bunch of errors when we already know the connection is down, we return early. A closing + // handshake can take up to 30 seconds, so this will help to more quickly close the connection related to + // interaction where we do want to detect it as fast as possible + if (this.isClosingOrClosed()) { + this.onDisconnectedEmitter.fire({ + code: -1, + reason: "WebSocket not in an open state", + wasClean: false, + }); + throw new Error( + "Could not send message in " + + readyStateToString[this.ws.readyState] + + " state" + ); + } + + // This is an async operation in Node, but to avoid wrapping every send in a promise, we + // rely on the error listener to deal with any errors. Any unsent messages will be timed out + // by our PendingMessage logic + this.ws.send(data); + } + + /** + * Closes the connection, triggering a disconnected event + */ + close() { + this.ws.close(); + } + + dispose(reason?: string) { + if (this.isDisposed) { + return; + } + + // When we dispose without an event it means we just want to get rid of the instance and + // not fire any events related to it + if (!reason) { + reason = "DISPOSED"; + } + + // Triggers disposal of any internal event listeners, intervals and any external listeners + super.dispose(); + } +} + +export const createWebSocketClient = (url: string) => + new Promise((resolve, reject) => { + const ws = new WebSocket(url); + + const openListener = () => { + cleanInitialListeners(); + resolve(new WebSocketClient(ws)); + }; + const errorListener = ({ message }: { message: string }) => { + cleanInitialListeners(); + reject(new Error(message)); + }; + const closeListener = () => { + cleanInitialListeners(); + reject(new Error("Connection closed before it was opened")); + }; + + const cleanInitialListeners = () => { + ws.removeEventListener("open", openListener); + ws.removeEventListener("error", errorListener); + ws.removeEventListener("close", closeListener); + }; + + ws.addEventListener("open", openListener); + ws.addEventListener("error", errorListener); + ws.addEventListener("close", closeListener); + }); diff --git a/src/AgentProtocol/index.ts b/src/AgentProtocol/index.ts new file mode 100644 index 0000000..9a750e2 --- /dev/null +++ b/src/AgentProtocol/index.ts @@ -0,0 +1,185 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Emitter, SliceList } from "@codesandbox/pitcher-common"; +import { + PitcherResponseStatus, + isNotificationPayload, + isErrorPayload, + isResultPayload, + decodeMessage, +} from "@codesandbox/pitcher-protocol"; +import type { + PitcherNotification, + PitcherRequest, + PitcherResponse, +} from "@codesandbox/pitcher-protocol"; + +import { PendingPitcherMessage } from "./PendingPitcherMessage"; +import { createWebSocketClient, WebSocketClient } from "./WebSocketClient"; + +export interface IRequestOptions { + /** + * The timeout for when to dispose the message when no response received + */ + timeoutMs?: number; +} + +/** + * This class is completely decoupled from the connection itself. Pitcher class is responsible for funneling the messages + * through the current connection. The connection can change when seamless branching or reconnecting. + */ +export class PitcherProtocol< + Request extends PitcherRequest = PitcherRequest, + Response extends PitcherResponse = PitcherResponse, + Notification extends PitcherNotification = PitcherNotification +> { + static async create(url: string) { + const ws = await createWebSocketClient(url); + + return new PitcherProtocol(ws); + } + + private nextMessageId = 0; + private pendingMessages = new Map>(); + private notificationListeners: Record< + string, + SliceList<(params: any) => void> + > = {}; + + private messageEmitter: Emitter = new Emitter(); + onMessage = this.messageEmitter.event; + + private errorEmitter: Emitter<{ + message: string; + extras: { + source: string; + type: string; + request: PitcherRequest; + }; + }> = new Emitter(); + onError = this.errorEmitter.event; + + constructor(private connection: WebSocketClient) { + connection.onMessage((message) => { + this.receiveMessage(message); + }); + } + + onNotification( + method: T, + cb: ( + params: Notification extends { method: T } + ? Notification["params"] + : never + ) => void + ): () => void { + let listeners = this.notificationListeners[method]; + if (!listeners) { + listeners = this.notificationListeners[method] = new SliceList(); + } + + const idx = listeners.add(cb); + return () => { + this.notificationListeners[method]?.remove(idx); + }; + } + + private receiveMessage(blob: Uint8Array): void { + const payload = decodeMessage(blob); + this.messageEmitter.fire(payload); + + const method = payload.method as + | Response["method"] + | Notification["method"]; + + if (isNotificationPayload(payload)) { + const listeners = this.notificationListeners[method]; + if (listeners) { + for (const cb of listeners.values()) { + cb(payload.params); + } + } + return; + } + + let response: Response; + if (isErrorPayload(payload)) { + response = { + status: PitcherResponseStatus.REJECTED, + error: { + code: payload.error.code, + data: payload.error.data, + message: payload.error.message, + }, + method, + } as Response; + } else if (isResultPayload(payload)) { + response = { + status: PitcherResponseStatus.RESOLVED, + result: payload.result, + method, + } as Response; + } else { + throw new Error("Unable to identify message type"); + } + + const messageToResolve = this.pendingMessages.get(payload.id); + + if (messageToResolve) { + messageToResolve.resolve(response); + } + + // We do not care if we do not have a matching message, this is related to changing connection + } + request(pitcherRequest: T, options: IRequestOptions = {}) { + const { timeoutMs } = options; + const request = this.createRequest(pitcherRequest, timeoutMs); + + try { + // This will throw if we are not in the right connection state + this.connection.send(request.message); + + return request.unwrap(); + } catch (error) { + this.errorEmitter.fire({ + message: (error as Error).message, + extras: { + source: "pitcher-message-handler", + type: "send-request", + request: pitcherRequest, + }, + }); + + // We always want to return a promise from the method so it does not matter if the error is related to disconnect + // or Pitcher giving an error. It all ends up in the `catch` of the unwrapped promise + return Promise.reject(error); + } + } + + private createRequest( + request: T, + timeoutMs?: number + ): PendingPitcherMessage { + const id = this.nextMessageId++; + const pitcherMessage = new PendingPitcherMessage(id, request, timeoutMs); + + this.pendingMessages.set(id, pitcherMessage); + pitcherMessage.onDidDispose(() => this.pendingMessages.delete(id)); + + return pitcherMessage; + } + + private disposePendingMessages() { + this.pendingMessages.forEach((pendingMessage) => { + pendingMessage.dispose(); + }); + } + + dispose(): void { + this.connection.dispose(); + this.disposePendingMessages(); + this.pendingMessages.clear(); + this.notificationListeners = {}; + this.errorEmitter.dispose(); + this.messageEmitter.dispose(); + } +} diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 65d82ae..b0c8b4b 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -18,9 +18,13 @@ import { } from "./api-clients/client"; import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; -import { WebSocketSession } from "./sessions/WebSocketSession"; -import { RestSession } from "./sessions/RestSession"; -import { startVm } from "./Sandboxes"; +import { NodeSession } from "./Session"; +import { PitcherProtocol } from "./PitcherProtocol"; +import { createWebSocketClient } from "./PitcherProtocol/WebSocketClient"; +import { version } from "@codesandbox/pitcher-protocol"; + +// Timeout for detecting a pong response, leading to a forced disconnect +let PONG_DETECTION_TIMEOUT = 15_000; export class Sandbox { /** @@ -143,91 +147,35 @@ export class Sandbox { /** * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. */ - async connect( - customSession?: SessionCreateOptions - ): Promise { - let hasConnected = false; + async connect(customSession?: SessionCreateOptions): Promise { const session = customSession ? await this.createSession(customSession) : this.globalSession; - - const pitcherClient = await initPitcherClient( - { - appId: "sdk", - instanceId: this.id, - onFocusChange() { - return () => {}; - }, - requestPitcherInstance: async () => { - // If we reconnect we have to resume the Sandbox and get new session details - if (hasConnected) { - this.pitcherManagerResponse = await startVm( - this.apiClient, - this.id - ); - } - - const headers = this.apiClient.getConfig().headers as Headers; - - if (headers.get("x-pitcher-manager-url")) { - // This is a hack, we need to tell the global scheduler that the VM is running - // in a different cluster than the one it'd like to default to. - - const preferredManager = headers - .get("x-pitcher-manager-url") - ?.replace("/api/v1", "") - .replace("https://", ""); - const baseUrl = this.apiClient - .getConfig() - .baseUrl?.replace("api", "global-scheduler"); - - await fetch( - `${baseUrl}/api/v1/cluster/${session.sandboxId}?preferredManager=${preferredManager}` - ).then((res) => res.json()); - } - - hasConnected = true; - - return { - bootupType: this.bootupType, - pitcherURL: session.pitcherUrl, - workspacePath: session.userWorkspacePath, - userWorkspacePath: session.userWorkspacePath, - pitcherManagerVersion: - this.pitcherManagerResponse.pitcherManagerVersion, - pitcherVersion: this.pitcherManagerResponse.pitcherVersion, - latestPitcherVersion: - this.pitcherManagerResponse.latestPitcherVersion, - pitcherToken: session.pitcherToken, - cluster: this.cluster, - }; + const url = `${session.pitcherUrl}/?token=${session.pitcherToken}`; + const connection = await createWebSocketClient(url); + const pitcherProtocol = new PitcherProtocol(connection); + const joinResult = await pitcherProtocol.request({ + method: "client/join", + params: { + clientInfo: { + protocolVersion: version, + appId: "sdk", }, + asyncProgress: true, subscriptions: DEFAULT_SUBSCRIPTIONS, }, - () => {} - ); + }); + + // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects + connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); - return new WebSocketSession(pitcherClient, { - username: customSession - ? // @ts-ignore - pitcherClient["joinResult"].client.username - : undefined, + return new NodeSession(pitcherProtocol, { + username: customSession ? joinResult.client.username : undefined, env: customSession?.env, hostToken: customSession?.hostToken, }); } - /** - * Returns a REST API client connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. - */ - async createRestSession(customSession?: SessionCreateOptions) { - const session = customSession - ? await this.createSession(customSession) - : this.globalSession; - - return new RestSession(session); - } - /** * Returns a browser session connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. */ diff --git a/src/sessions/WebSocketSession/commands.ts b/src/Session/commands.ts similarity index 85% rename from src/sessions/WebSocketSession/commands.ts rename to src/Session/commands.ts index 1bc175f..79d8958 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/Session/commands.ts @@ -1,7 +1,8 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; -import { Disposable } from "../../utils/disposable"; -import { Emitter } from "../../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; +import { IAgentClient } from "../agent-client-interface"; type ShellSize = { cols: number; rows: number }; @@ -25,7 +26,7 @@ export class Commands { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient, + private agentClient: IAgentClient, private env: Record = {} ) { sessionDisposable.onWillDispose(() => { @@ -56,8 +57,8 @@ export class Commands { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; } - const shell = await this.pitcherClient.clients.shell.create( - this.pitcherClient.workspacePath, + const shell = await this.agentClient.shells.create( + this.agentClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, commandWithEnv, "TERMINAL", @@ -71,7 +72,7 @@ export class Commands { }; // Only way for us to differentiate between a command and a terminal - this.pitcherClient.clients.shell.rename( + this.agentClient.shells.rename( shell.shellId, // We embed some details in the name to properly show the command that was run // , the name and that it is an actual command @@ -79,7 +80,7 @@ export class Commands { ); const cmd = new Command( - this.pitcherClient, + this.agentClient, shell as protocol.shell.CommandShellDTO, details ); @@ -99,16 +100,15 @@ export class Commands { /** * Get all running commands. */ - getAll(): Command[] { - const shells = this.pitcherClient.clients.shell.getShells(); + async getAll(): Promise { + const shells = await this.agentClient.shells.getShells(); return shells .filter( (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) ) .map( - (shell) => - new Command(this.pitcherClient, shell, JSON.parse(shell.name)) + (shell) => new Command(this.agentClient, shell, JSON.parse(shell.name)) ); } } @@ -164,7 +164,7 @@ export class Command { name?: string; constructor( - private pitcherClient: IPitcherClient, + private agentClient: IAgentClient, private shell: protocol.shell.ShellDTO & { buffer?: string[] }, details: { command: string; name?: string } ) { @@ -172,7 +172,7 @@ export class Command { this.name = details.name; this.disposable.addDisposable( - pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => { + agentClient.shells.onShellExited(({ shellId, exitCode }) => { if (shellId === this.shell.shellId) { this.status = exitCode === 0 ? "FINISHED" : "ERROR"; this.barrier.open(); @@ -181,7 +181,7 @@ export class Command { ); this.disposable.addDisposable( - pitcherClient.clients.shell.onShellTerminated(({ shellId }) => { + agentClient.shells.onShellTerminated(({ shellId }) => { if (shellId === this.shell.shellId) { this.status = "KILLED"; this.barrier.open(); @@ -190,7 +190,7 @@ export class Command { ); this.disposable.addDisposable( - this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + this.agentClient.shells.onShellOut(({ shellId, out }) => { if (shellId !== this.shell.shellId || out.startsWith("[CODESANDBOX]")) { return; } @@ -209,7 +209,7 @@ export class Command { * Open the command and get its current output, subscribes to future output */ async open(dimensions = DEFAULT_SHELL_SIZE): Promise { - const shell = await this.pitcherClient.clients.shell.open( + const shell = await this.agentClient.shells.open( this.shell.shellId, dimensions ); @@ -238,7 +238,7 @@ export class Command { */ async kill(): Promise { this.disposable.dispose(); - await this.pitcherClient.clients.shell.delete(this.shell.shellId); + await this.agentClient.shells.delete(this.shell.shellId); } /** @@ -249,6 +249,6 @@ export class Command { throw new Error("Command is not running"); } - await this.pitcherClient.clients.shell.restart(this.shell.shellId); + await this.agentClient.shells.restart(this.shell.shellId); } } diff --git a/src/sessions/WebSocketSession/filesystem.ts b/src/Session/filesystem.ts similarity index 98% rename from src/sessions/WebSocketSession/filesystem.ts rename to src/Session/filesystem.ts index 0eb5a90..72ae5f9 100644 --- a/src/sessions/WebSocketSession/filesystem.ts +++ b/src/Session/filesystem.ts @@ -1,7 +1,7 @@ import { type IPitcherClient } from "@codesandbox/pitcher-client"; -import { Disposable } from "../../utils/disposable"; -import { Emitter, type Event } from "../../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Emitter, type Event } from "../utils/event"; export type FSStatResult = { type: "file" | "directory"; diff --git a/src/sessions/WebSocketSession/git.ts b/src/Session/git.ts similarity index 100% rename from src/sessions/WebSocketSession/git.ts rename to src/Session/git.ts diff --git a/src/sessions/WebSocketSession/hosts.ts b/src/Session/hosts.ts similarity index 86% rename from src/sessions/WebSocketSession/hosts.ts rename to src/Session/hosts.ts index e61c63c..00fbc9f 100644 --- a/src/sessions/WebSocketSession/hosts.ts +++ b/src/Session/hosts.ts @@ -1,7 +1,7 @@ import { IPitcherClient } from "@codesandbox/pitcher-client"; -import { HostToken } from "../../Hosts"; +import { HostToken } from "../Hosts"; -export { HostToken } from "../../Hosts"; +export { HostToken }; export class Hosts { constructor( @@ -20,7 +20,7 @@ export class Hosts { /** * If private Sandbox this will return headers with a host token. */ - getHeaders() { + getHeaders(): Record { if (!this.hostToken) { return {}; } @@ -33,7 +33,7 @@ export class Hosts { /** * If private Sandbox this will return cookies with a host token. */ - getCookies() { + getCookies(): Record { if (!this.hostToken) { return {}; } diff --git a/src/sessions/WebSocketSession/index.ts b/src/Session/index.ts similarity index 98% rename from src/sessions/WebSocketSession/index.ts rename to src/Session/index.ts index 53abdbb..95ce600 100644 --- a/src/sessions/WebSocketSession/index.ts +++ b/src/Session/index.ts @@ -1,4 +1,4 @@ -import { Disposable } from "../../utils/disposable"; +import { Disposable } from "../utils/disposable"; import { protocol as _protocol, type IPitcherClient, @@ -12,7 +12,7 @@ import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; import { Git } from "./git"; -import { HostToken } from "../../Hosts"; +import { HostToken } from "../Hosts"; import { Hosts } from "./hosts"; export * from "./filesystem"; @@ -25,7 +25,7 @@ export * from "./git"; export * from "./interpreters"; export * from "./hosts"; -export class WebSocketSession { +export class Session { private disposable = new Disposable(); /** diff --git a/src/sessions/WebSocketSession/interpreters.ts b/src/Session/interpreters.ts similarity index 95% rename from src/sessions/WebSocketSession/interpreters.ts rename to src/Session/interpreters.ts index d478258..8953bad 100644 --- a/src/sessions/WebSocketSession/interpreters.ts +++ b/src/Session/interpreters.ts @@ -1,4 +1,4 @@ -import { Disposable } from "../../utils/disposable"; +import { Disposable } from "../utils/disposable"; import { Commands, ShellRunOpts } from "./commands"; export class Interpreters { diff --git a/src/sessions/WebSocketSession/ports.ts b/src/Session/ports.ts similarity index 96% rename from src/sessions/WebSocketSession/ports.ts rename to src/Session/ports.ts index 4ed2c2a..1cc672d 100644 --- a/src/sessions/WebSocketSession/ports.ts +++ b/src/Session/ports.ts @@ -1,8 +1,8 @@ import type { IPitcherClient } from "@codesandbox/pitcher-client"; -import { Disposable } from "../../utils/disposable"; -import { Emitter } from "../../utils/event"; -import { HostToken } from "../../Hosts"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; +import { HostToken } from "../Hosts"; export type Port = { host: string; diff --git a/src/sessions/WebSocketSession/setup.ts b/src/Session/setup.ts similarity index 98% rename from src/sessions/WebSocketSession/setup.ts rename to src/Session/setup.ts index 074ae5c..4579787 100644 --- a/src/sessions/WebSocketSession/setup.ts +++ b/src/Session/setup.ts @@ -4,8 +4,8 @@ import { type protocol, } from "@codesandbox/pitcher-client"; -import { Disposable } from "../../utils/disposable"; -import { Emitter } from "../../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; export class Setup { diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/Session/tasks.ts similarity index 95% rename from src/sessions/WebSocketSession/tasks.ts rename to src/Session/tasks.ts index 628e049..d8026bb 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/Session/tasks.ts @@ -5,7 +5,7 @@ import { type protocol, } from "@codesandbox/pitcher-client"; -import { Disposable } from "../../utils/disposable"; +import { Disposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; export type TaskDefinition = { @@ -33,14 +33,14 @@ export class Tasks { /** * Gets all tasks that are available in the current sandbox. */ - getTasks(): Task[] { + getAll(): Task[] { return this.tasks; } /** * Gets a task by its ID. */ - getTask(taskId: string): Task | undefined { + get(taskId: string): Task | undefined { return this.tasks.find((task) => task.id === taskId); } } @@ -73,7 +73,7 @@ export class Task { return this.data.command; } get runAtStart() { - return this.data.runAtStart; + return Boolean(this.data.runAtStart); } get ports() { return this.data.ports; @@ -155,7 +155,7 @@ export class Task { let disposer: IDisposable | undefined; - return Promise.all([ + const [port] = await Promise.all([ new Promise((resolve) => { disposer = this.pitcherClient.clients.task.onTaskUpdate((task) => { if (task.id !== this.id) { @@ -175,6 +175,8 @@ export class Task { }, timeout); }), ]); + + return port; } async run() { await this.pitcherClient.clients.task.runTask(this.id); diff --git a/src/sessions/WebSocketSession/terminals.ts b/src/Session/terminals.ts similarity index 96% rename from src/sessions/WebSocketSession/terminals.ts rename to src/Session/terminals.ts index b92d905..bba2a2c 100644 --- a/src/sessions/WebSocketSession/terminals.ts +++ b/src/Session/terminals.ts @@ -1,7 +1,6 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; -import type { Id } from "@codesandbox/pitcher-common"; -import { Disposable } from "../../utils/disposable"; -import { Emitter } from "../../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; export type ShellSize = { cols: number; rows: number }; diff --git a/src/agent-client-interface.ts b/src/agent-client-interface.ts new file mode 100644 index 0000000..e46ee4c --- /dev/null +++ b/src/agent-client-interface.ts @@ -0,0 +1,34 @@ +import { Id, Event } from "@codesandbox/pitcher-common"; +import { shell } from "@codesandbox/pitcher-protocol"; + +export interface IAgentClientShells { + onShellExited: Event<{ + shellId: Id; + exitCode: number; + }>; + onShellTerminated: Event<{ + shellId: Id; + author: string; + }>; + onShellOut: Event; + create( + projectPath: string, + size: shell.ShellSize, + command?: string, + type?: shell.ShellProcessType, + isSystemShell?: boolean + ): Promise; + rename(shellId: Id, name: string): Promise; + getShells(): Promise; + open(shellId: Id, size: shell.ShellSize): Promise; + delete( + shellId: Id + ): Promise; + restart(shellId: Id): Promise; +} + +export interface IAgentClient { + // this.pitcherClient.workspacePath + workspacePath: string; + shells: IAgentClientShells; +} diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts new file mode 100644 index 0000000..c642cc2 --- /dev/null +++ b/src/browser/BrowserAgentClient.ts @@ -0,0 +1,48 @@ +import { Id, IPitcherClient } from "@codesandbox/pitcher-client"; +import { IAgentClient, IAgentClientShells } from "../agent-client-interface"; +import { shell } from "@codesandbox/pitcher-protocol"; + +class Shells implements IAgentClientShells { + onShellExited = this.pitcherClient.clients.shell.onShellExited; + onShellTerminated = this.pitcherClient.clients.shell.onShellTerminated; + onShellOut = this.pitcherClient.clients.shell.onShellOut; + constructor(private pitcherClient: IPitcherClient) {} + create( + projectPath: string, + size: shell.ShellSize, + command?: string, + type?: shell.ShellProcessType, + isSystemShell?: boolean + ) { + return this.pitcherClient.clients.shell.create( + projectPath, + size, + command, + type, + isSystemShell + ); + } + rename(shellId: Id, name: string): Promise { + return this.pitcherClient.clients.shell.rename(shellId, name); + } + async getShells(): Promise { + return this.pitcherClient.clients.shell.getShells(); + } + async open(shellId: Id, size: shell.ShellSize): Promise { + return this.pitcherClient.clients.shell.open(shellId, size); + } + async delete( + shellId: Id + ): Promise { + return this.pitcherClient.clients.shell.delete(shellId); + } + async restart(shellId: Id): Promise { + return this.pitcherClient.clients.shell.restart(shellId); + } +} + +export class BrowserAgent implements IAgentClient { + shells = new Shells(this.pitcherClient); + + constructor(private pitcherClient: IPitcherClient) {} +} diff --git a/src/browser.ts b/src/browser/index.ts similarity index 86% rename from src/browser.ts rename to src/browser/index.ts index 56356dd..01937f5 100644 --- a/src/browser.ts +++ b/src/browser/index.ts @@ -1,8 +1,8 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "./types"; -import { WebSocketSession } from "./sessions/WebSocketSession"; +import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "../types"; +import { Session } from "../Session"; -export * from "./sessions/WebSocketSession"; +export * from "../Session"; export { createPreview, Preview } from "./previews"; @@ -14,7 +14,7 @@ export async function connectToSandbox(options: { getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; -}): Promise { +}): Promise { let hasConnected = false; const pitcherClient = await initPitcherClient( { @@ -45,7 +45,7 @@ export async function connectToSandbox(options: { options.initStatusCb || (() => {}) ); - return new WebSocketSession(pitcherClient, { + return new Session(pitcherClient, { username: options.session.sessionId ? // @ts-ignore pitcherClient["joinResult"].client.username diff --git a/src/previews/Preview.ts b/src/browser/previews/Preview.ts similarity index 100% rename from src/previews/Preview.ts rename to src/browser/previews/Preview.ts diff --git a/src/previews/index.ts b/src/browser/previews/index.ts similarity index 100% rename from src/previews/index.ts rename to src/browser/previews/index.ts diff --git a/src/previews/preview-script.ts b/src/browser/previews/preview-script.ts similarity index 100% rename from src/previews/preview-script.ts rename to src/browser/previews/preview-script.ts diff --git a/src/previews/types.ts b/src/browser/previews/types.ts similarity index 100% rename from src/previews/types.ts rename to src/browser/previews/types.ts diff --git a/src/sessions/RestSession/index.ts b/src/sessions/RestSession/index.ts deleted file mode 100644 index 433f305..0000000 --- a/src/sessions/RestSession/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Client, createClient, createConfig } from "@hey-api/client-fetch"; -import { decode, encode } from "@msgpack/msgpack"; -import { SandboxRestFS } from "./sandbox-rest-fs"; -import { SandboxRestContainer } from "./sandbox-rest-container"; -import { SandboxRestGit } from "./sandbox-rest-git"; -import { SandboxRestShell } from "./sandbox-rest-shell"; -import { SandboxRestSystem } from "./sandbox-rest-system"; -import { SandboxRestTask } from "./sandbox-rest-task"; -import { ClientOpts, SandboxSession } from "../../types"; - -export class RestSession { - static id = 0; - fs: SandboxRestFS; - container: SandboxRestContainer; - git: SandboxRestGit; - shell: SandboxRestShell; - system: SandboxRestSystem; - task: SandboxRestTask; - constructor(private session: SandboxSession, opts: ClientOpts = {}) { - const client = this.createRestClient(opts); - this.fs = new SandboxRestFS(session, client); - this.container = new SandboxRestContainer(client); - this.git = new SandboxRestGit(client); - this.shell = new SandboxRestShell(client); - this.system = new SandboxRestSystem(client); - this.task = new SandboxRestTask(client); - } - private createRestClient(opts: ClientOpts): Client { - const client = createClient( - createConfig({ - bodySerializer: null, - parseAs: "stream", - headers: { - ...(opts.headers ?? {}), - "content-type": "application/x-msgpack", - }, - throwOnError: true, - fetch: - opts.fetch ?? - // @ts-ignore - ((url, params) => { - return fetch(url, params); - }), - }) - ); - - const session = this.session; - - return { - post(opts) { - const method = opts.url.substring(1); - - const message = { - id: RestSession.id++, - method, - params: opts.body, - }; - - const encodedMessage = encode(message); - - // We have to create a baseUrl, because openapi fetcher always prefixes the url - // with "/" - const baseUrl = new URL(session.pitcherUrl); - - baseUrl.protocol = "https"; - baseUrl.pathname = ""; - - return client - .post({ - baseUrl: baseUrl.origin, - url: `${session.sandboxId}?token=${session.pitcherToken}`, - headers: { - "content-length": encodedMessage.byteLength.toString(), - }, - body: encodedMessage, - }) - .then(async ({ response, error }) => { - if (error) { - throw error; - } - - return decode(await response.arrayBuffer()); - }) as any; - }, - } as Client; - } -} diff --git a/src/sessions/RestSession/sandbox-rest-container.ts b/src/sessions/RestSession/sandbox-rest-container.ts deleted file mode 100644 index 32833a8..0000000 --- a/src/sessions/RestSession/sandbox-rest-container.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as container from "../../api-clients/client-rest-container"; - -export class SandboxRestContainer { - constructor(private client: Client) {} - setup(body: container.ContainerSetupData["body"]) { - return container.containerSetup({ - body, - client: this.client, - }); - } -} diff --git a/src/sessions/RestSession/sandbox-rest-fs.ts b/src/sessions/RestSession/sandbox-rest-fs.ts deleted file mode 100644 index 42256fb..0000000 --- a/src/sessions/RestSession/sandbox-rest-fs.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as fs from "../../api-clients/client-rest-fs"; -import { join } from "path"; - -import { SandboxSession } from "../../types"; - -export class SandboxRestFS { - constructor(private session: SandboxSession, private client: Client) {} - writeTextFile( - body: Omit & { content: string } - ) { - return fs.writeFile({ - client: this.client, - body: { - ...body, - // OpenAPI does not have UINT8Array, so we need to cast it to a Blob - content: new TextEncoder().encode(body.content) as unknown as Blob, - path: join(this.session.userWorkspacePath, body.path), - create: body.create === false ? false : true, - overwrite: body.overwrite === false ? false : true, - }, - }); - } - writeFile(body: fs.WriteFileData["body"]) { - return fs.writeFile({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - create: body.create === false ? false : true, - }, - }); - } - readFile(body: fs.FsReadFileData["body"]) { - return fs.fsReadFile({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - }, - }); - } - search(body: fs.FsSearchData["body"]) { - return fs.fsSearch({ client: this.client, body }); - } - pathSearch(body: fs.FsPathSearchData["body"]) { - return fs.fsPathSearch({ client: this.client, body }); - } - upload(body: fs.FsUploadData["body"]) { - return fs.fsUpload({ client: this.client, body }); - } - download(body: fs.FsDownloadData["body"]) { - return fs.fsDownload({ client: this.client, body }); - } - readDir(body: fs.FsReadDirData["body"]) { - return fs.fsReadDir({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - }, - }); - } - stat(body: fs.FsStatData["body"]) { - return fs.fsStat({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - }, - }); - } - copy(body: fs.FsCopyData["body"]) { - return fs.fsCopy({ - client: this.client, - body: { - ...body, - from: join(this.session.userWorkspacePath, body.from), - to: join(this.session.userWorkspacePath, body.to), - }, - }); - } - rename(body: fs.FsRenameData["body"]) { - return fs.fsRename({ - client: this.client, - body: { - ...body, - from: join(this.session.userWorkspacePath, body.from), - to: join(this.session.userWorkspacePath, body.to), - }, - }); - } - remove(body: fs.FsRemoveData["body"]) { - return fs.fsRemove({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - }, - }); - } - mkdir(body: fs.FsMkdirData["body"]) { - return fs.fsMkdir({ - client: this.client, - body: { - ...body, - path: join(this.session.userWorkspacePath, body.path), - }, - }); - } -} diff --git a/src/sessions/RestSession/sandbox-rest-git.ts b/src/sessions/RestSession/sandbox-rest-git.ts deleted file mode 100644 index 7a5e709..0000000 --- a/src/sessions/RestSession/sandbox-rest-git.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as git from "../../api-clients/client-rest-git"; - -export class SandboxRestGit { - constructor(private client: Client) {} - status(body: git.GitStatusData["body"]) { - return git.gitStatus({ client: this.client, body }); - } - - remotes(body: git.GitRemotesData["body"]) { - return git.gitRemotes({ client: this.client, body }); - } - - targetDiff(body: git.GitTargetDiffData["body"]) { - return git.gitTargetDiff({ client: this.client, body }); - } - - pull(body: git.GitPullData["body"]) { - return git.gitPull({ client: this.client, body }); - } - - discard(body: git.GitDiscardData["body"]) { - return git.gitDiscard({ client: this.client, body }); - } - - commit(body: git.GitCommitData["body"]) { - return git.gitCommit({ client: this.client, body }); - } - - push(body: git.GitPushData["body"]) { - return git.gitPush({ client: this.client, body }); - } - - pushToRemote(body: git.GitPushToRemoteData["body"]) { - return git.gitPushToRemote({ client: this.client, body }); - } - - renameBranch(body: git.GitRenameBranchData["body"]) { - return git.gitRenameBranch({ client: this.client, body }); - } - - remoteContent(body: git.GitRemoteContentData["body"]) { - return git.gitRemoteContent({ client: this.client, body }); - } - - diffStatus(body: git.GitDiffStatusData["body"]) { - return git.gitDiffStatus({ client: this.client, body }); - } - - resetLocalWithRemote(body: git.GitResetLocalWithRemoteData["body"]) { - return git.gitResetLocalWithRemote({ client: this.client, body }); - } -} diff --git a/src/sessions/RestSession/sandbox-rest-shell.ts b/src/sessions/RestSession/sandbox-rest-shell.ts deleted file mode 100644 index ddd24c1..0000000 --- a/src/sessions/RestSession/sandbox-rest-shell.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as shell from "../../api-clients/client-rest-shell"; - -export class SandboxRestShell { - constructor(private client: Client) {} - - create(body: shell.ShellCreateData["body"]) { - return shell.shellCreate({ client: this.client, body }); - } - - in(body: shell.ShellInData["body"]) { - return shell.shellIn({ client: this.client, body }); - } - - list(body: shell.ShellListData["body"]) { - return shell.shellList({ client: this.client, body }); - } - - open(body: shell.ShellOpenData["body"]) { - return shell.shellOpen({ client: this.client, body }); - } - - close(body: shell.ShellCloseData["body"]) { - return shell.shellClose({ client: this.client, body }); - } - - restart(body: shell.ShellRestartData["body"]) { - return shell.shellRestart({ client: this.client, body }); - } - - terminate(body: shell.ShellTerminateData["body"]) { - return shell.shellTerminate({ client: this.client, body }); - } - - resize(body: shell.ShellResizeData["body"]) { - return shell.shellResize({ client: this.client, body }); - } - - rename(body: shell.ShellRenameData["body"]) { - return shell.shellRename({ client: this.client, body }); - } -} diff --git a/src/sessions/RestSession/sandbox-rest-system.ts b/src/sessions/RestSession/sandbox-rest-system.ts deleted file mode 100644 index 6ebce84..0000000 --- a/src/sessions/RestSession/sandbox-rest-system.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as system from "../../api-clients/client-rest-system"; - -export class SandboxRestSystem { - constructor(private client: Client) {} - - update(body: system.SystemUpdateData["body"]) { - return system.systemUpdate({ client: this.client, body }); - } - - hibernate(body: system.SystemHibernateData["body"]) { - return system.systemHibernate({ client: this.client, body }); - } - - metrics(body: system.SystemMetricsData["body"]) { - return system.systemMetrics({ client: this.client, body }); - } -} diff --git a/src/sessions/RestSession/sandbox-rest-task.ts b/src/sessions/RestSession/sandbox-rest-task.ts deleted file mode 100644 index 4ed4b00..0000000 --- a/src/sessions/RestSession/sandbox-rest-task.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import * as task from "../../api-clients/client-rest-task"; - -export class SandboxRestTask { - constructor(private client: Client) {} - - list(body: task.TaskListData["body"] = {}) { - return task.taskList({ client: this.client, body }); - } - - run(body: task.TaskRunData["body"]) { - return task.taskRun({ client: this.client, body }); - } - - runCommand(body: task.TaskRunCommandData["body"]) { - return task.taskRunCommand({ client: this.client, body }); - } - - stop(body: task.TaskStopData["body"]) { - return task.taskStop({ client: this.client, body }); - } - - create(body: task.TaskCreateData["body"]) { - return task.taskCreate({ client: this.client, body }); - } - - update(body: task.TaskUpdateData["body"]) { - return task.taskUpdate({ client: this.client, body }); - } - - saveToConfig(body: task.TaskSaveToConfigData["body"]) { - return task.taskSaveToConfig({ client: this.client, body }); - } - - generateConfig(body: task.TaskGenerateConfigData["body"] = {}) { - return task.taskGenerateConfig({ client: this.client, body }); - } - - createSetupTasks(body: task.TaskCreateSetupTasksData["body"]) { - return task.taskCreateSetupTasks({ client: this.client, body }); - } -} diff --git a/src/types.ts b/src/types.ts index 2c53e19..95ac3c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; -import type { WebSocketSession } from "./sessions/WebSocketSession"; import { HostToken } from "./Hosts"; +import { ISession } from "./session-interface"; export interface SystemMetricsStatus { cpu: { @@ -220,7 +220,7 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { email: string; name?: string; }; - setup?: (session: WebSocketSession) => Promise; + setup?: (session: ISession) => Promise; }; export type CreateSandboxOpts = From c478800d97b6d4ccb6efd9377deb01baf1a0071a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 26 May 2025 10:59:13 +0200 Subject: [PATCH 128/241] fix them --- package-lock.json | 4 ++-- package.json | 2 +- src/sessions/WebSocketSession/tasks.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b4febb5..481fcf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "1.0.0-beta.6", + "version": "1.1.0", "license": "MIT", "dependencies": { "ora": "^8.2.0", diff --git a/package.json b/package.json index 75e89f3..0443061 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.1.0", + "version": "1.1.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/tasks.ts b/src/sessions/WebSocketSession/tasks.ts index 628e049..44c0ca1 100644 --- a/src/sessions/WebSocketSession/tasks.ts +++ b/src/sessions/WebSocketSession/tasks.ts @@ -33,14 +33,14 @@ export class Tasks { /** * Gets all tasks that are available in the current sandbox. */ - getTasks(): Task[] { + getAll(): Task[] { return this.tasks; } /** * Gets a task by its ID. */ - getTask(taskId: string): Task | undefined { + get(taskId: string): Task | undefined { return this.tasks.find((task) => task.id === taskId); } } From 443f5a2c5ee90e5e790c34d2c42810045e384a08 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 26 May 2025 12:12:07 +0200 Subject: [PATCH 129/241] allow running commands as global session --- package.json | 2 +- src/sessions/WebSocketSession/commands.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0557de9..9b4d8f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.1.4", + "version": "1.1.5", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 1d25d4c..2301005 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -10,6 +10,11 @@ export type ShellRunOpts = { name?: string; env?: Record; cwd?: string; + /** + * Run the command in the global session instead of the current session. This makes + * any environment variables available to all users of the Sandbox. + */ + asGlobalSession?: boolean; }; export type CommandStatus = @@ -60,7 +65,7 @@ export class Commands { this.pitcherClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, commandWithEnv, - "TERMINAL", + opts?.asGlobalSession ? "COMMAND" : "TERMINAL", true ); From 0167af07b8fba771e931f1a9a6e3b076743374c4 Mon Sep 17 00:00:00 2001 From: Nick Zhelezkov Date: Tue, 27 May 2025 10:49:44 +0300 Subject: [PATCH 130/241] call `onStatusChangeEmitter` on `status` change --- src/sessions/WebSocketSession/commands.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/sessions/WebSocketSession/commands.ts b/src/sessions/WebSocketSession/commands.ts index 2301005..9453721 100644 --- a/src/sessions/WebSocketSession/commands.ts +++ b/src/sessions/WebSocketSession/commands.ts @@ -162,7 +162,18 @@ export class Command { /** * The status of the command. */ - status: CommandStatus = "RUNNING"; + #status: CommandStatus = "RUNNING"; + + get status(): CommandStatus { + return this.#status; + } + + set status(value: CommandStatus) { + if (this.#status !== value) { + this.#status = value; + this.onStatusChangeEmitter.fire(this.#status); + } + } /** * The command that was run From af8fad9252112631fd3348dbe28d7f911ab50645 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 27 May 2025 13:58:04 +0200 Subject: [PATCH 131/241] decoupled and tested --- esbuild.cjs | 41 +- package-lock.json | 35 +- package.json | 22 +- src/{Hosts.ts => HostTokens.ts} | 6 +- .../AgentConnection.ts} | 128 +++++- .../PendingPitcherMessage.ts | 0 .../WebSocketClient.ts | 1 + src/NodeAgentClient/index.ts | 435 ++++++++++++++++++ src/Sandbox.ts | 32 +- src/Session/commands.ts | 2 +- src/Session/filesystem.ts | 52 +-- src/Session/git.ts | 24 +- src/Session/hosts.ts | 10 +- src/Session/index.ts | 95 ++-- src/Session/ports.ts | 34 +- src/Session/setup.ts | 64 +-- src/Session/tasks.ts | 52 ++- src/Session/terminals.ts | 43 +- src/agent-client-interface.ts | 123 ++++- src/bin/commands/build.ts | 5 +- src/bin/commands/sandbox/fork.ts | 2 +- src/bin/commands/sandbox/hibernate.ts | 2 +- src/bin/commands/sandbox/host-tokens.ts | 2 +- src/bin/commands/sandbox/list.ts | 2 +- src/bin/commands/sandbox/shutdown.ts | 2 +- src/browser/BrowserAgentClient.ts | 177 +++++-- src/browser/index.ts | 6 +- src/index.ts | 5 +- src/types.ts | 6 +- 29 files changed, 1044 insertions(+), 364 deletions(-) rename src/{Hosts.ts => HostTokens.ts} (95%) rename src/{AgentProtocol/index.ts => NodeAgentClient/AgentConnection.ts} (60%) rename src/{AgentProtocol => NodeAgentClient}/PendingPitcherMessage.ts (100%) rename src/{AgentProtocol => NodeAgentClient}/WebSocketClient.ts (99%) create mode 100644 src/NodeAgentClient/index.ts diff --git a/esbuild.cjs b/esbuild.cjs index c528ae1..7ee01a6 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -16,12 +16,17 @@ const browserifyPlugin = { }, }; +const nodeExternals = [ + ...Object.keys(require("./package.json").dependencies), + ...require("module").builtinModules, +]; + // Build both CJS and ESM versions Promise.all([ // Browser builds: // CommonJS build esbuild.build({ - entryPoints: ["src/browser.ts"], + entryPoints: ["src/browser/index.ts"], bundle: true, format: "cjs", // .cjs extension is required because "type": "module" is set in package.json @@ -41,10 +46,10 @@ Promise.all([ // ESM build esbuild.build({ - entryPoints: ["src/browser.ts"], + entryPoints: ["src/browser/index.ts"], bundle: true, format: "esm", - outdir: "dist/esm", + outfile: "dist/esm/browser.js", platform: "browser", // pitcher-common currently requires this, but breaks the first experience banner: { @@ -68,7 +73,7 @@ Promise.all([ platform: "node", // .cjs extension is required because "type": "module" is set in package.json outfile: "dist/cjs/index.cjs", - plugins: [browserifyPlugin], + external: nodeExternals, }), // ESM build @@ -85,29 +90,7 @@ const require = topLevelCreateRequire(import.meta.url); `.trim(), }, outfile: "dist/esm/index.js", - plugins: [browserifyPlugin], - }), - - // Edge: - // CommonJS build - esbuild.build({ - entryPoints: ["src/index.ts"], - // .cjs extension is required because "type": "module" is set in package.json - outfile: "dist/cjs/index.edge.cjs", - bundle: true, - format: "cjs", - platform: "browser", - plugins: [browserifyPlugin], - }), - - // ESM build - esbuild.build({ - entryPoints: ["src/index.ts"], - outfile: "dist/esm/index.edge.js", - bundle: true, - format: "esm", - platform: "browser", - plugins: [browserifyPlugin], + external: nodeExternals, }), // Bin builds: @@ -120,6 +103,10 @@ const require = topLevelCreateRequire(import.meta.url); banner: { js: `#!/usr/bin/env node\n\n`, }, + // ORA is an ESM module so we have to include it in the build + external: [...nodeExternals, "@codesandbox/sdk"].filter( + (mod) => mod !== "ora" + ), }), ]).catch(() => { process.exit(1); diff --git a/package-lock.json b/package-lock.json index 1bd3c93..99eb55a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.1.0", "license": "MIT", "dependencies": { + "@codesandbox/pitcher-common": "0.360.2", + "@codesandbox/pitcher-protocol": "0.360.4", "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "readline": "^1.3.0" @@ -18,8 +20,6 @@ }, "devDependencies": { "@codesandbox/pitcher-client": "1.1.7", - "@codesandbox/pitcher-common": "0.360.2", - "@codesandbox/pitcher-protocol": "0.360.4", "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", @@ -161,7 +161,6 @@ "version": "0.360.2", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-common/-/pitcher-common-0.360.2.tgz", "integrity": "sha512-FWy4YgDh0LFZRo9N8j7mTSoKyIftWJVs/9LR5rwzAta0lFUAV2X8hsGMzt0u16Ng5Wpbi92svMZQy6WygC93gg==", - "dev": true, "license": "GPL-3.0", "dependencies": { "@emotion/hash": "^0.8.0", @@ -185,7 +184,6 @@ "version": "0.360.4", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.4.tgz", "integrity": "sha512-oPxA2/F/ywyR73elJwdFOsw3D+rOId2UTNAXnRrTGjh66Ujyx/IFGVqfTlibGaQUg2HENkRoiRsvRJa5qphITA==", - "dev": true, "license": "GPL-3.0", "dependencies": { "@codesandbox/pitcher-common": "0.360.2", @@ -196,7 +194,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "dev": true, "license": "ISC", "engines": { "node": ">= 10" @@ -250,7 +247,6 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true, "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { @@ -900,7 +896,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", - "dev": true, "license": "MIT" }, "node_modules/@types/cookie": { @@ -921,7 +916,6 @@ "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", - "dev": true, "license": "MIT", "dependencies": { "@types/braces": "*" @@ -941,7 +935,6 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", - "dev": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -1205,7 +1198,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1779,7 +1771,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "dev": true, "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -2150,7 +2141,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, "license": "MIT" }, "node_modules/evp_bytestokey": { @@ -2175,7 +2165,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2770,7 +2759,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -2986,7 +2974,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -3129,7 +3116,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3320,7 +3306,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -3411,7 +3396,6 @@ "version": "1.112.0", "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.112.0.tgz", "integrity": "sha512-qQH4jZSdabcKpwcqvJTi7eQL86UucvMacbaHiiIrOynT8jhTLtKS2ixaXgGlNBMeN9UhFi1wS00Hnxhw9aYLsA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 14.17.0" @@ -3540,7 +3524,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3550,7 +3533,6 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", @@ -3567,7 +3549,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, "license": "MIT", "dependencies": { "p-finally": "^1.0.0" @@ -3684,7 +3665,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -3927,7 +3907,6 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4301,7 +4280,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4385,7 +4363,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, "license": "MIT" }, "node_modules/tinyexec": { @@ -4399,7 +4376,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -4412,14 +4388,12 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "dev": true, "license": "MIT" }, "node_modules/tslib": { @@ -4433,7 +4407,6 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" @@ -4520,7 +4493,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -4534,7 +4506,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/vscode-diff/-/vscode-diff-2.1.1.tgz", "integrity": "sha512-S2BwZbrQCk4N6FqgYQQPlQ44OZKZNcS2VwhMj4xU8QvixXN9GeNQnN7/7XHFbwrs6h5BiLADDcERTrKvfWeG9g==", - "dev": true, "license": "MIT" }, "node_modules/vscode-jsonrpc": { @@ -4551,14 +4522,12 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", diff --git a/package.json b/package.json index 4abf527..7b7fe60 100644 --- a/package.json +++ b/package.json @@ -18,32 +18,12 @@ "exports": { ".": { "types": "./dist/esm/index.d.ts", - "edge-light": { - "import": "./dist/esm/index.edge.js", - "require": "./dist/cjs/index.edge.cjs", - "default": "./dist/cjs/index.edge.cjs" - }, - "worker": { - "import": "./dist/esm/index.edge.js", - "require": "./dist/cjs/index.edge.js", - "default": "./dist/cjs/index.edge.js" - }, - "workerd": { - "import": "./dist/esm/index.edge.js", - "require": "./dist/cjs/index.edge.js", - "default": "./dist/cjs/index.edge.js" - }, - "browser": { - "import": "./dist/esm/index.edge.js", - "require": "./dist/cjs/index.edge.js", - "default": "./dist/cjs/index.edge.js" - }, "import": "./dist/esm/index.js", "require": "./dist/cjs/index.cjs", "default": "./dist/cjs/index.cjs" }, "./browser": { - "types": "./dist/esm/browser.d.ts", + "types": "./dist/esm/browser/index.d.ts", "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs", "default": "./dist/cjs/browser.cjs" diff --git a/src/Hosts.ts b/src/HostTokens.ts similarity index 95% rename from src/Hosts.ts rename to src/HostTokens.ts index 73bf69b..1c1ad17 100644 --- a/src/Hosts.ts +++ b/src/HostTokens.ts @@ -43,7 +43,11 @@ export class HostTokens extends Disposable { port: number, protocol: string = "https" ): string { - return `${protocol}://${token.sandboxId}-${port}.csb.app?preview_token=${token.token}`; + const domain = this.apiClient.getConfig().baseUrl?.includes(".stream") + ? "csb.dev" + : "csb.app"; + + return `${protocol}://${token.sandboxId}-${port}.${domain}?preview_token=${token.token}`; } /** diff --git a/src/AgentProtocol/index.ts b/src/NodeAgentClient/AgentConnection.ts similarity index 60% rename from src/AgentProtocol/index.ts rename to src/NodeAgentClient/AgentConnection.ts index 9a750e2..f1218f9 100644 --- a/src/AgentProtocol/index.ts +++ b/src/NodeAgentClient/AgentConnection.ts @@ -6,6 +6,7 @@ import { isErrorPayload, isResultPayload, decodeMessage, + version, } from "@codesandbox/pitcher-protocol"; import type { PitcherNotification, @@ -15,6 +16,9 @@ import type { import { PendingPitcherMessage } from "./PendingPitcherMessage"; import { createWebSocketClient, WebSocketClient } from "./WebSocketClient"; +import { IAgentClientState } from "../agent-client-interface"; +import { DEFAULT_SUBSCRIPTIONS } from "../types"; +import { startVm } from "../Sandboxes"; export interface IRequestOptions { /** @@ -27,17 +31,30 @@ export interface IRequestOptions { * This class is completely decoupled from the connection itself. Pitcher class is responsible for funneling the messages * through the current connection. The connection can change when seamless branching or reconnecting. */ -export class PitcherProtocol< - Request extends PitcherRequest = PitcherRequest, - Response extends PitcherResponse = PitcherResponse, - Notification extends PitcherNotification = PitcherNotification -> { +export class AgentConnection { static async create(url: string) { const ws = await createWebSocketClient(url); - return new PitcherProtocol(ws); + return new AgentConnection(ws, url); } + private _state: IAgentClientState = "CONNECTED"; + + get state() { + return this._state; + } + set state(state: IAgentClientState) { + this._state = state; + this.onStateChangeEmitter.fire(state); + + if (state === "DISCONNECTED" || state === "HIBERNATED") { + this.connection.dispose(); + } + } + + private onStateChangeEmitter = new Emitter(); + onStateChange = this.onStateChangeEmitter.event; + private nextMessageId = 0; private pendingMessages = new Map>(); private notificationListeners: Record< @@ -45,7 +62,8 @@ export class PitcherProtocol< SliceList<(params: any) => void> > = {}; - private messageEmitter: Emitter = new Emitter(); + private messageEmitter: Emitter = + new Emitter(); onMessage = this.messageEmitter.event; private errorEmitter: Emitter<{ @@ -58,26 +76,40 @@ export class PitcherProtocol< }> = new Emitter(); onError = this.errorEmitter.event; - constructor(private connection: WebSocketClient) { + constructor(public connection: WebSocketClient, private url: string) { + this.subscribeConnection(connection); + + this.onNotification("system/hibernate", () => { + this.state = "HIBERNATED"; + }); + } + + private subscribeConnection(connection: WebSocketClient) { connection.onMessage((message) => { this.receiveMessage(message); }); + + connection.onDisconnected(() => { + this.state = "DISCONNECTED"; + }); + + connection.onMissingHeartbeat(() => { + if (this.pendingMessages.size === 0) { + this.state = "DISCONNECTED"; + } + }); } - onNotification( + onNotification( method: T, - cb: ( - params: Notification extends { method: T } - ? Notification["params"] - : never - ) => void + cb: (params: (PitcherNotification & { method: T })["params"]) => void ): () => void { let listeners = this.notificationListeners[method]; if (!listeners) { listeners = this.notificationListeners[method] = new SliceList(); } - const idx = listeners.add(cb); + const idx = listeners.add(cb as any); return () => { this.notificationListeners[method]?.remove(idx); }; @@ -88,8 +120,8 @@ export class PitcherProtocol< this.messageEmitter.fire(payload); const method = payload.method as - | Response["method"] - | Notification["method"]; + | PitcherResponse["method"] + | PitcherNotification["method"]; if (isNotificationPayload(payload)) { const listeners = this.notificationListeners[method]; @@ -101,7 +133,7 @@ export class PitcherProtocol< return; } - let response: Response; + let response: PitcherResponse; if (isErrorPayload(payload)) { response = { status: PitcherResponseStatus.REJECTED, @@ -111,13 +143,13 @@ export class PitcherProtocol< message: payload.error.message, }, method, - } as Response; + } as PitcherResponse; } else if (isResultPayload(payload)) { response = { status: PitcherResponseStatus.RESOLVED, result: payload.result, method, - } as Response; + } as PitcherResponse; } else { throw new Error("Unable to identify message type"); } @@ -130,7 +162,10 @@ export class PitcherProtocol< // We do not care if we do not have a matching message, this is related to changing connection } - request(pitcherRequest: T, options: IRequestOptions = {}) { + request( + pitcherRequest: T, + options: IRequestOptions = {} + ) { const { timeoutMs } = options; const request = this.createRequest(pitcherRequest, timeoutMs); @@ -155,7 +190,7 @@ export class PitcherProtocol< } } - private createRequest( + private createRequest( request: T, timeoutMs?: number ): PendingPitcherMessage { @@ -174,6 +209,55 @@ export class PitcherProtocol< }); } + async disconnect() { + if (this.pendingMessages.size) { + await new Promise((resolve) => { + const interval = setInterval(() => { + if (this.pendingMessages.size) { + return; + } + + clearInterval(interval); + resolve(); + }, 50); + }); + } + + this.state = "DISCONNECTED"; + } + + async reconnect(reconnectToken: string, startVm: () => Promise) { + if (!(this.state === "DISCONNECTED" || this.state === "HIBERNATED")) { + return; + } + + this.state = "CONNECTING"; + this.connection.dispose(); + const url = new URL(this.url); + + const token = await startVm(); + + url.searchParams.set("token", token); + url.searchParams.set("reconnectToken", reconnectToken); + + this.connection = await createWebSocketClient(url.toString()); + this.subscribeConnection(this.connection); + + await this.request({ + method: "client/join", + params: { + clientInfo: { + protocolVersion: version, + appId: "sdk", + }, + asyncProgress: false, + subscriptions: DEFAULT_SUBSCRIPTIONS, + }, + }); + + this.state = "CONNECTED"; + } + dispose(): void { this.connection.dispose(); this.disposePendingMessages(); diff --git a/src/AgentProtocol/PendingPitcherMessage.ts b/src/NodeAgentClient/PendingPitcherMessage.ts similarity index 100% rename from src/AgentProtocol/PendingPitcherMessage.ts rename to src/NodeAgentClient/PendingPitcherMessage.ts diff --git a/src/AgentProtocol/WebSocketClient.ts b/src/NodeAgentClient/WebSocketClient.ts similarity index 99% rename from src/AgentProtocol/WebSocketClient.ts rename to src/NodeAgentClient/WebSocketClient.ts index 474ba38..fb36efd 100644 --- a/src/AgentProtocol/WebSocketClient.ts +++ b/src/NodeAgentClient/WebSocketClient.ts @@ -225,6 +225,7 @@ export class WebSocketClient extends Disposable { // This is an async operation in Node, but to avoid wrapping every send in a promise, we // rely on the error listener to deal with any errors. Any unsent messages will be timed out // by our PendingMessage logic + this.ws.send(data); } diff --git a/src/NodeAgentClient/index.ts b/src/NodeAgentClient/index.ts new file mode 100644 index 0000000..b5e5d35 --- /dev/null +++ b/src/NodeAgentClient/index.ts @@ -0,0 +1,435 @@ +import { + fs, + git, + PitcherErrorCode, + PitcherRequest, + port, + setup, + shell, + task, +} from "@codesandbox/pitcher-protocol"; +import { + IAgentClient, + IAgentClientFS, + IAgentClientGit, + IAgentClientPorts, + IAgentClientSetup, + IAgentClientShells, + IAgentClientSystem, + IAgentClientTasks, + PickRawFsResult, +} from "../agent-client-interface"; +import { AgentConnection } from "./AgentConnection"; +import { Emitter, Id } from "@codesandbox/pitcher-common"; +import { Client } from "@hey-api/client-fetch"; +import { startVm } from "../Sandboxes"; + +class NodeAgentClientShells implements IAgentClientShells { + private onShellExitedEmitter = new Emitter<{ + shellId: Id; + exitCode: number; + }>(); + onShellExited = this.onShellExitedEmitter.event; + + private onShellTerminatedEmitter = new Emitter< + shell.ShellTerminateNotification["params"] + >(); + onShellTerminated = this.onShellTerminatedEmitter.event; + + private onShellOutEmitter = new Emitter< + shell.ShellOutNotification["params"] + >(); + onShellOut = this.onShellOutEmitter.event; + + constructor(private agentConnection: AgentConnection) { + agentConnection.onNotification("shell/exit", (params) => { + this.onShellExitedEmitter.fire(params); + }); + + agentConnection.onNotification("shell/terminate", (params) => { + this.onShellTerminatedEmitter.fire(params); + }); + + agentConnection.onNotification("shell/out", (params) => { + this.onShellOutEmitter.fire(params); + }); + } + create( + projectPath: string, + size: shell.ShellSize, + command?: string, + type?: shell.ShellProcessType, + isSystemShell?: boolean + ): Promise { + return this.agentConnection.request({ + method: "shell/create", + params: { + command, + size, + type, + isSystemShell, + cwd: projectPath, + }, + }); + } + delete( + shellId: Id + ): Promise { + return this.agentConnection.request({ + method: "shell/terminate", + params: { + shellId, + }, + }); + } + async getShells(): Promise { + const result = await this.agentConnection.request({ + method: "shell/list", + params: {}, + }); + + return result.shells; + } + open(shellId: Id, size: shell.ShellSize): Promise { + return this.agentConnection.request({ + method: "shell/open", + params: { + shellId, + size, + }, + }); + } + rename(shellId: Id, name: string): Promise { + return this.agentConnection.request({ + method: "shell/rename", + params: { + shellId, + name, + }, + }); + } + restart(shellId: Id): Promise { + return this.agentConnection.request({ + method: "shell/restart", + params: { + shellId, + }, + }); + } + send(shellId: Id, input: string, size: shell.ShellSize): Promise { + return this.agentConnection.request({ + method: "shell/in", + params: { shellId, input, size }, + }); + } +} + +class NodeAgentClientFS implements IAgentClientFS { + constructor( + private agentConnection: AgentConnection, + private workspacePath: string + ) {} + private async handleRawFsResponse< + T extends PitcherRequest["method"], + B extends Extract + >(method: T, params: B["params"]): Promise> { + try { + // The params are right, the result is right too + // eslint-disable-next-line + const result = await this.agentConnection.request({ + method, + params, + }); + + return { type: "ok", result } as unknown as PickRawFsResult & { + type: "ok"; + }; + } catch (e) { + // There's some weirdness with our error typing + // eslint-disable-next-line + const err = e as any; + if ("code" in err) { + if (err.code === PitcherErrorCode.RAWFS_ERROR) { + return { + type: "error", + error: err.message, + errno: err.data.errno, + }; + } + + return { type: "error", error: err.message, errno: null }; + } + + if (err instanceof Error) { + return { type: "error", error: err.message, errno: null }; + } + + return { type: "error", error: "unknown error", errno: null }; + } + } + copy( + from: string, + to: string, + recursive?: boolean, + overwrite?: boolean + ): Promise> { + return this.handleRawFsResponse("fs/copy", { + from, + to, + recursive, + overwrite, + }); + } + async download(path?: string): Promise<{ downloadUrl: string }> { + return this.agentConnection.request({ + method: "fs/download", + params: { + path: path || this.workspacePath, + }, + }); + } + mkdir( + path: string, + recursive?: boolean + ): Promise> { + return this.handleRawFsResponse("fs/mkdir", { + path, + recursive, + }); + } + readdir(path: string): Promise> { + return this.handleRawFsResponse("fs/readdir", { + path, + }); + } + readFile(path: string): Promise> { + return this.handleRawFsResponse("fs/readFile", { + path, + }); + } + remove( + path: string, + recursive?: boolean + ): Promise> { + return this.handleRawFsResponse("fs/remove", { + path, + recursive, + }); + } + rename(path: string, newPath: string): Promise> { + return this.handleRawFsResponse("fs/rename", { + from: path, + to: newPath, + }); + } + stat(path: string): Promise> { + return this.handleRawFsResponse("fs/stat", { + path, + }); + } + async watch( + path: string, + options: { + readonly recursive?: boolean; + readonly excludes?: readonly string[]; + }, + onEvent: (watchEvent: fs.FSWatchEvent) => void + ): Promise< + | (PickRawFsResult<"fs/watch"> & { type: "error" }) + | { type: "success"; dispose(): void } + > { + const response = await this.handleRawFsResponse("fs/watch", { + path, + recursive: options.recursive, + // @ts-expect-error angry about using readonly here + excludes: options.excludes, + }); + + if (response.type === "error") { + return response; + } + + const watchId = response.result.watchId; + this.agentConnection.onNotification("fs/watchEvent", (params) => { + if (params.watchId === watchId) { + params.events.forEach(onEvent); + } + }); + + return { + type: "success" as const, + dispose: () => { + this.handleRawFsResponse("fs/unwatch", { watchId }); + }, + }; + } + writeFile( + path: string, + content: Uint8Array, + create?: boolean, + overwrite?: boolean + ): Promise> { + return this.handleRawFsResponse("fs/writeFile", { + path, + content, + create, + overwrite, + }); + } +} + +class NodeAgentClientGit implements IAgentClientGit { + private onStatusUpdatedEmitter = new Emitter(); + onStatusUpdated = this.onStatusUpdatedEmitter.event; + + constructor(private agentConnection: AgentConnection) { + agentConnection.onNotification("git/status", (params) => { + this.onStatusUpdatedEmitter.fire(params); + }); + } + + getStatus() { + return this.agentConnection.request({ + method: "git/status", + params: {}, + }); + } +} + +class NodeAgentClientPorts implements IAgentClientPorts { + private onPortsUpdatedEmitter = new Emitter(); + onPortsUpdated = this.onPortsUpdatedEmitter.event; + + constructor(private agentConnection: AgentConnection) { + this.agentConnection.onNotification("port/changed", (params) => { + this.onPortsUpdatedEmitter.fire(params.list); + }); + } + async getPorts() { + const result = await this.agentConnection.request({ + method: "port/list", + params: {}, + }); + + return result.list; + } +} + +class NodeAgentClientSetup implements IAgentClientSetup { + private onSetupProgressUpdateEmitter = new Emitter(); + onSetupProgressUpdate = this.onSetupProgressUpdateEmitter.event; + constructor(private agentConnection: AgentConnection) { + this.agentConnection.onNotification("setup/progress", (params) => { + this.onSetupProgressUpdateEmitter.fire(params); + }); + } + async getProgress() { + return this.agentConnection.request({ + method: "setup/get", + params: {}, + }); + } + init() { + return this.agentConnection.request({ + method: "setup/init", + params: null, + }); + } +} + +class NodeAgentClientTasks implements IAgentClientTasks { + private onTaskUpdateEmitter = new Emitter(); + onTaskUpdate = this.onTaskUpdateEmitter.event; + constructor(private agentConnection: AgentConnection) { + this.agentConnection.onNotification("task/update", (params) => { + this.onTaskUpdateEmitter.fire(params); + }); + } + getTasks() { + return this.agentConnection.request({ + method: "task/list", + params: {}, + }); + } + async getTask(taskId: string): Promise { + const tasks = await this.agentConnection.request({ + method: "task/list", + params: {}, + }); + + return tasks.tasks[taskId]; + } + runTask(taskId: string): Promise { + return this.agentConnection.request({ + method: "task/run", + params: { + taskId, + }, + }); + } + stopTask(taskId: string): Promise { + return this.agentConnection.request({ + method: "task/stop", + params: { + taskId, + }, + }); + } +} + +class NodeAgentClientSystem implements IAgentClientSystem { + constructor(private agentConnection: AgentConnection) {} + update() { + return this.agentConnection.request({ + method: "system/update", + params: {}, + }); + } +} + +export class NodeAgentClient implements IAgentClient { + sandboxId: string; + workspacePath: string; + isUpToDate: boolean; + private reconnectToken: string; + get state() { + return this.agentConnection.state; + } + onStateChange = this.agentConnection.onStateChange; + shells = new NodeAgentClientShells(this.agentConnection); + fs = new NodeAgentClientFS(this.agentConnection, this.params.workspacePath); + git = new NodeAgentClientGit(this.agentConnection); + setup = new NodeAgentClientSetup(this.agentConnection); + tasks = new NodeAgentClientTasks(this.agentConnection); + system = new NodeAgentClientSystem(this.agentConnection); + ports = new NodeAgentClientPorts(this.agentConnection); + + constructor( + private apiClient: Client, + private agentConnection: AgentConnection, + private params: { + sandboxId: string; + workspacePath: string; + isUpToDate: boolean; + reconnectToken: string; + } + ) { + this.sandboxId = params.sandboxId; + this.workspacePath = params.workspacePath; + this.isUpToDate = params.isUpToDate; + this.reconnectToken = params.reconnectToken; + } + async disconnect(): Promise { + await this.agentConnection.disconnect(); + } + async reconnect(): Promise { + await this.agentConnection.reconnect(this.reconnectToken, async () => { + const response = await startVm(this.apiClient, this.sandboxId); + + return response.pitcherToken; + }); + } + dispose() { + this.agentConnection.dispose(); + } +} diff --git a/src/Sandbox.ts b/src/Sandbox.ts index b0c8b4b..6715c20 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -18,10 +18,11 @@ import { } from "./api-clients/client"; import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; -import { NodeSession } from "./Session"; -import { PitcherProtocol } from "./PitcherProtocol"; -import { createWebSocketClient } from "./PitcherProtocol/WebSocketClient"; import { version } from "@codesandbox/pitcher-protocol"; +import { createWebSocketClient } from "./NodeAgentClient/WebSocketClient"; +import { AgentConnection } from "./NodeAgentClient/AgentConnection"; +import { Session } from "./Session"; +import { NodeAgentClient } from "./NodeAgentClient"; // Timeout for detecting a pong response, leading to a forced disconnect let PONG_DETECTION_TIMEOUT = 15_000; @@ -147,14 +148,14 @@ export class Sandbox { /** * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. */ - async connect(customSession?: SessionCreateOptions): Promise { - const session = customSession + async connect(customSession?: SessionCreateOptions): Promise { + const sessionDetails = customSession ? await this.createSession(customSession) : this.globalSession; - const url = `${session.pitcherUrl}/?token=${session.pitcherToken}`; - const connection = await createWebSocketClient(url); - const pitcherProtocol = new PitcherProtocol(connection); - const joinResult = await pitcherProtocol.request({ + const url = `${sessionDetails.pitcherUrl}/?token=${sessionDetails.pitcherToken}`; + + const agentConnection = await AgentConnection.create(url); + const joinResult = await agentConnection.request({ method: "client/join", params: { clientInfo: { @@ -167,13 +168,22 @@ export class Sandbox { }); // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects - connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); + agentConnection.connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); - return new NodeSession(pitcherProtocol, { + const agentClient = new NodeAgentClient(this.apiClient, agentConnection, { + sandboxId: this.id, + workspacePath: sessionDetails.userWorkspacePath, + reconnectToken: joinResult.reconnectToken, + isUpToDate: this.isUpToDate, + }); + + const session = await Session.create(agentClient, { username: customSession ? joinResult.client.username : undefined, env: customSession?.env, hostToken: customSession?.hostToken, }); + + return session; } /** diff --git a/src/Session/commands.ts b/src/Session/commands.ts index 79d8958..537e782 100644 --- a/src/Session/commands.ts +++ b/src/Session/commands.ts @@ -1,8 +1,8 @@ -import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { IAgentClient } from "../agent-client-interface"; +import * as protocol from "@codesandbox/pitcher-protocol"; type ShellSize = { cols: number; rows: number }; diff --git a/src/Session/filesystem.ts b/src/Session/filesystem.ts index 72ae5f9..5818e8e 100644 --- a/src/Session/filesystem.ts +++ b/src/Session/filesystem.ts @@ -1,4 +1,4 @@ -import { type IPitcherClient } from "@codesandbox/pitcher-client"; +import { type IAgentClient } from "../agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; @@ -42,7 +42,7 @@ export class FileSystem { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient, + private agentClient: IAgentClient, private username?: string ) { sessionDisposable.onWillDispose(() => { @@ -58,7 +58,7 @@ export class FileSystem { content: Uint8Array, opts: WriteFileOpts = {} ): Promise { - const result = await this.pitcherClient.clients.fs.writeFile( + const result = await this.agentClient.fs.writeFile( path, content, opts.create ?? true, @@ -81,7 +81,7 @@ export class FileSystem { * Create a directory. */ async mkdir(path: string, recursive = false): Promise { - const result = await this.pitcherClient.clients.fs.mkdir(path, recursive); + const result = await this.agentClient.fs.mkdir(path, recursive); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -92,7 +92,7 @@ export class FileSystem { * Read a directory. */ async readdir(path: string): Promise { - const result = await this.pitcherClient.clients.fs.readdir(path); + const result = await this.agentClient.fs.readdir(path); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -108,7 +108,7 @@ export class FileSystem { * Read a file */ async readFile(path: string): Promise { - const result = await this.pitcherClient.clients.fs.readFile(path); + const result = await this.agentClient.fs.readFile(path); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -130,7 +130,7 @@ export class FileSystem { * Get the stat of a file or directory. */ async stat(path: string): Promise { - const result = await this.pitcherClient.clients.fs.stat(path); + const result = await this.agentClient.fs.stat(path); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -152,7 +152,7 @@ export class FileSystem { recursive = false, overwrite = false ): Promise { - const result = await this.pitcherClient.clients.fs.copy( + const result = await this.agentClient.fs.copy( from, to, recursive, @@ -168,11 +168,7 @@ export class FileSystem { * Rename a file or directory. */ async rename(from: string, to: string, overwrite = false): Promise { - const result = await this.pitcherClient.clients.fs.rename( - from, - to, - overwrite - ); + const result = await this.agentClient.fs.rename(from, to, overwrite); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -183,7 +179,7 @@ export class FileSystem { * Remove a file or directory. */ async remove(path: string, recursive = false): Promise { - const result = await this.pitcherClient.clients.fs.remove(path, recursive); + const result = await this.agentClient.fs.remove(path, recursive); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -210,22 +206,18 @@ export class FileSystem { async watch(path: string, options: WatchOpts = {}): Promise { const emitter = new Emitter(); - const result = await this.pitcherClient.clients.fs.watch( - path, - options, - (event) => { - if (this.username) { - emitter.fire({ - ...event, - paths: event.paths.map((path) => - path.replace(`home/${this.username}/workspace/`, "sandbox/") - ), - }); - } else { - emitter.fire(event); - } + const result = await this.agentClient.fs.watch(path, options, (event) => { + if (this.username) { + emitter.fire({ + ...event, + paths: event.paths.map((path) => + path.replace(`home/${this.username}/workspace/`, "sandbox/") + ), + }); + } else { + emitter.fire(event); } - ); + }); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -248,7 +240,7 @@ export class FileSystem { * from within the workspace directory. A download URL that's valid for 5 minutes. */ async download(path: string): Promise<{ downloadUrl: string }> { - const result = await this.pitcherClient.clients.fs.download(path); + const result = await this.agentClient.fs.download(path); return result; } diff --git a/src/Session/git.ts b/src/Session/git.ts index 61b6425..9995e55 100644 --- a/src/Session/git.ts +++ b/src/Session/git.ts @@ -1,22 +1,32 @@ -import type { IPitcherClient } from "@codesandbox/pitcher-client"; import { Commands } from "./commands"; +import { IAgentClient } from "../agent-client-interface"; +/** + * An interface to interact with the git repository. + * + * The class is initialized with a `pitcherClient` and a `commands` object. + * The `pitcherClient` object is used to communicate with the Pitcher server. + * The `commands` object is used to run shell commands. + * + * The interface provides methods to commit changes to git, checkout a branch, + * and push changes to git. + * + * The interface also provides an event `onStatusChange` that is emitted when + * the git status changes. + */ export class Git { /** * An event that is emitted when the git status changes. */ - onStatusChange = this.pitcherClient.clients.git.onStatusUpdated; + onStatusChange = this.agentClient.git.onStatusUpdated; - constructor( - private pitcherClient: IPitcherClient, - private commands: Commands - ) {} + constructor(private agentClient: IAgentClient, private commands: Commands) {} /** * Get the current git status. */ status() { - return this.pitcherClient.clients.git.getStatus(); + return this.agentClient.git.getStatus(); } /** diff --git a/src/Session/hosts.ts b/src/Session/hosts.ts index 00fbc9f..35bb878 100644 --- a/src/Session/hosts.ts +++ b/src/Session/hosts.ts @@ -1,18 +1,14 @@ -import { IPitcherClient } from "@codesandbox/pitcher-client"; -import { HostToken } from "../Hosts"; +import { HostToken } from "../HostTokens"; export { HostToken }; export class Hosts { - constructor( - private pitcherClient: IPitcherClient, - private hostToken?: HostToken - ) {} + constructor(private sandboxId: string, private hostToken?: HostToken) {} /** * If private Sandbox this will return a URL with a host token. */ getUrl(port: number, protocol: string = "https") { - return `${protocol}://${this.pitcherClient.instanceId}-${port}.csb.app${ + return `${protocol}://${this.sandboxId}-${port}.csb.app${ this.hostToken ? `?preview_token=${this.hostToken.token}` : "" }`; } diff --git a/src/Session/index.ts b/src/Session/index.ts index 95ce600..4d278ec 100644 --- a/src/Session/index.ts +++ b/src/Session/index.ts @@ -1,8 +1,4 @@ import { Disposable } from "../utils/disposable"; -import { - protocol as _protocol, - type IPitcherClient, -} from "@codesandbox/pitcher-client"; import { FileSystem } from "./filesystem"; import { Ports } from "./ports"; @@ -12,8 +8,11 @@ import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; import { Git } from "./git"; -import { HostToken } from "../Hosts"; +import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; +import { IAgentClient } from "../agent-client-interface"; +import { setup } from "@codesandbox/pitcher-protocol"; +import { Barrier } from "@codesandbox/pitcher-common"; export * from "./filesystem"; export * from "./ports"; @@ -25,7 +24,41 @@ export * from "./git"; export * from "./interpreters"; export * from "./hosts"; +type SessionParams = { + env?: Record; + hostToken?: HostToken; + username?: string; +}; + export class Session { + static async create(agentClient: IAgentClient, params: SessionParams) { + let setupProgress = await agentClient.setup.getProgress(); + + let hasInitializedSteps = Boolean(setupProgress.steps.length); + + if (hasInitializedSteps) { + return new Session(agentClient, params, setupProgress); + } + + // We have a race condition where we might not have the steps yet and need + // an event to tell us when they have started. But we might also have all the steps, + // where no new event will arrive. So we use a barrier to manage this + const initialStepsBarrier = new Barrier(); + + const setupProgressUpdateDisposable = + agentClient.setup.onSetupProgressUpdate((progress) => { + setupProgressUpdateDisposable.dispose(); + initialStepsBarrier.open(progress); + }); + + const response = await initialStepsBarrier.wait(); + + if (response.status === "disposed") { + throw new Error("Failed to get setup progress"); + } + + return new Session(agentClient, params, response.value); + } private disposable = new Disposable(); /** @@ -61,29 +94,22 @@ export class Session { /** * Namespace for managing ports on this Sandbox */ - public readonly ports = new Ports(this.disposable, this.pitcherClient); + public readonly ports = new Ports(this.disposable, this.agentClient); /** * Namespace for the setup that runs when the Sandbox starts from scratch. */ - public readonly setup = new Setup(this.disposable, this.pitcherClient); + public readonly setup: Setup; /** * Namespace for tasks that are defined in the Sandbox. */ - public readonly tasks = new Tasks(this.disposable, this.pitcherClient); + public readonly tasks = new Tasks(this.disposable, this.agentClient); constructor( - protected pitcherClient: IPitcherClient, - { - env, - hostToken, - username, - }: { - env?: Record; - hostToken?: HostToken; - username?: string; - } + protected agentClient: IAgentClient, + { env, hostToken, username }: SessionParams, + initialSetupProgress: setup.SetupProgress ) { // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { @@ -92,42 +118,47 @@ export class Session { // }; // this.addDisposable(metricsDisposable); - this.fs = new FileSystem(this.disposable, this.pitcherClient, username); - this.terminals = new Terminals(this.disposable, this.pitcherClient, env); - this.commands = new Commands(this.disposable, this.pitcherClient, env); + this.setup = new Setup( + this.disposable, + this.agentClient, + initialSetupProgress + ); + this.fs = new FileSystem(this.disposable, this.agentClient, username); + this.terminals = new Terminals(this.disposable, this.agentClient, env); + this.commands = new Commands(this.disposable, this.agentClient, env); - this.hosts = new Hosts(this.pitcherClient, hostToken); + this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); - this.git = new Git(this.pitcherClient, this.commands); - this.disposable.addDisposable(this.pitcherClient); + this.git = new Git(this.agentClient, this.commands); + this.disposable.onWillDispose(() => this.agentClient.dispose()); } /** * The current state of the Sandbox */ - get state(): typeof this.pitcherClient.state { - return this.pitcherClient.state; + get state(): typeof this.agentClient.state { + return this.agentClient.state; } /** * An event that is emitted when the state of the Sandbox changes. */ get onStateChange() { - return this.pitcherClient.onStateChange.bind(this.pitcherClient); + return this.agentClient.onStateChange.bind(this.agentClient); } /** * Check if the Sandbox Agent process is up to date. To update a restart is required */ get isUpToDate() { - return this.pitcherClient.isUpToDate(); + return this.agentClient.isUpToDate; } /** * The ID of the sandbox. */ get id(): string { - return this.pitcherClient.instanceId; + return this.agentClient.sandboxId; } /** @@ -193,14 +224,14 @@ export class Session { * reconnect to the sandbox. */ public disconnect() { - return this.pitcherClient.disconnect(); + return this.agentClient.disconnect(); } /** * Explicitly reconnect to the sandbox. */ public reconnect() { - return this.pitcherClient.reconnect(); + return this.agentClient.reconnect(); } private keepAliveInterval: NodeJS.Timeout | null = null; @@ -210,7 +241,7 @@ export class Session { public keepActiveWhileConnected(enabled: boolean) { if (enabled && !this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { - this.pitcherClient.clients.system.update(); + this.agentClient.system.update(); }, 1000 * 30); this.disposable.onWillDispose(() => { diff --git a/src/Session/ports.ts b/src/Session/ports.ts index 1cc672d..e41d56b 100644 --- a/src/Session/ports.ts +++ b/src/Session/ports.ts @@ -1,8 +1,6 @@ -import type { IPitcherClient } from "@codesandbox/pitcher-client"; - import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { HostToken } from "../Hosts"; +import { IAgentClient } from "../agent-client-interface"; export type Port = { host: string; @@ -28,18 +26,20 @@ export class Ports { constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private agentClient: IAgentClient ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); - pitcherClient.clients.port.getPorts().forEach((port) => { - this.lastOpenedPorts.add(port.port); + agentClient.ports.getPorts().then((ports) => { + ports.forEach((port) => { + this.lastOpenedPorts.add(port.port); + }); }); this.disposable.addDisposable( - pitcherClient.clients.port.onPortsUpdated((ports) => { + agentClient.ports.onPortsUpdated((ports) => { const openedPorts = ports.filter( (port) => !this.lastOpenedPorts.has(port.port) ); @@ -71,17 +71,19 @@ export class Ports { /** * Get a port by number. */ - get(port: number) { - return this.getAll().find((p) => p.port === port); + async get(port: number) { + const ports = await this.getAll(); + + return ports.find((p) => p.port === port); } /** * Get all ports. */ - getAll(): Port[] { - return this.pitcherClient.clients.port - .getPorts() - .map(({ port, url }) => ({ port, host: url })); + async getAll(): Promise { + const ports = await this.agentClient.ports.getPorts(); + + return ports.map(({ port, url }) => ({ port, host: url })); } /** @@ -97,11 +99,9 @@ export class Ports { port: number, options?: { timeoutMs?: number } ): Promise { - await this.pitcherClient.clients.port.readyPromise; - - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // Check if port is already open - const portInfo = this.getAll().find((p) => p.port === port); + const portInfo = (await this.getAll()).find((p) => p.port === port); if (portInfo) { resolve(portInfo); diff --git a/src/Session/setup.ts b/src/Session/setup.ts index 4579787..a83c59d 100644 --- a/src/Session/setup.ts +++ b/src/Session/setup.ts @@ -1,17 +1,12 @@ -import { - Barrier, - type IPitcherClient, - type protocol, -} from "@codesandbox/pitcher-client"; - +import * as protocol from "@codesandbox/pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; +import { IAgentClient } from "../agent-client-interface"; export class Setup { private disposable = new Disposable(); - private steps: Promise; - private setupProgress: protocol.setup.SetupProgress; + private steps: Step[]; private readonly onSetupProgressChangeEmitter = this.disposable.addDisposable( new Emitter() ); @@ -25,46 +20,14 @@ export class Setup { } constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private agentClient: IAgentClient, + private setupProgress: protocol.setup.SetupProgress ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); - - // We have a race condition where we might not have the steps yet and need - // an event to tell us when they have started. But we might also have all the steps, - // where no new event will arrive. So we use a barrier to manage this - const initialStepsBarrier = new Barrier(); - - this.setupProgress = this.pitcherClient.clients.setup.getProgress(); - this.steps = initialStepsBarrier - .wait() - .then((result) => (result.status === "resolved" ? result.value : [])); - - let hasInitializedSteps = Boolean(this.setupProgress.steps.length); - - if (hasInitializedSteps) { - initialStepsBarrier.open( - this.setupProgress.steps.map( - (step, index) => new Step(index, step, pitcherClient) - ) - ); - } - - this.disposable.addDisposable( - pitcherClient.clients.setup.onSetupProgressUpdate((progress) => { - if (!hasInitializedSteps) { - hasInitializedSteps = true; - initialStepsBarrier.open( - progress.steps.map( - (step, index) => new Step(index, step, pitcherClient) - ) - ); - } - - this.setupProgress = progress; - this.onSetupProgressChangeEmitter.fire(); - }) + this.steps = this.setupProgress.steps.map( + (step, index) => new Step(index, step, agentClient) ); } @@ -73,7 +36,7 @@ export class Setup { } async run(): Promise { - await this.pitcherClient.clients.setup.init(); + await this.agentClient.setup.init(); } async waitUntilComplete(): Promise { @@ -128,10 +91,10 @@ export class Step { constructor( stepIndex: number, private step: protocol.setup.Step, - private pitcherClient: IPitcherClient + private agentClient: IAgentClient ) { this.disposable.addDisposable( - this.pitcherClient.clients.setup.onSetupProgressUpdate((progress) => { + this.agentClient.setup.onSetupProgressUpdate((progress) => { const oldStep = this.step; const newStep = progress.steps[stepIndex]; @@ -147,7 +110,7 @@ export class Step { }) ); this.disposable.addDisposable( - this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + this.agentClient.shells.onShellOut(({ shellId, out }) => { if (shellId === this.step.shellId) { this.onOutputEmitter.fire(out); @@ -162,10 +125,7 @@ export class Step { async open(dimensions = DEFAULT_SHELL_SIZE): Promise { const open = async (shellId: protocol.shell.ShellId) => { - const shell = await this.pitcherClient.clients.shell.open( - shellId, - dimensions - ); + const shell = await this.agentClient.shells.open(shellId, dimensions); this.output = shell.buffer; diff --git a/src/Session/tasks.ts b/src/Session/tasks.ts index d8026bb..185b3cb 100644 --- a/src/Session/tasks.ts +++ b/src/Session/tasks.ts @@ -1,12 +1,8 @@ -import { - Emitter, - IDisposable, - type IPitcherClient, - type protocol, -} from "@codesandbox/pitcher-client"; - +import * as protocol from "@codesandbox/pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; +import { IAgentClient } from "../agent-client-interface"; +import { Emitter, IDisposable } from "@codesandbox/pitcher-common"; export type TaskDefinition = { name: string; @@ -16,15 +12,11 @@ export type TaskDefinition = { export class Tasks { private disposable = new Disposable(); - private tasks: Task[] = []; + private cachedTasks?: Task[]; constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient + private agentClient: IAgentClient ) { - this.tasks = Object.values( - this.pitcherClient.clients.task.getTasks().tasks - ).map((task) => new Task(this.pitcherClient, task)); - sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); @@ -33,15 +25,25 @@ export class Tasks { /** * Gets all tasks that are available in the current sandbox. */ - getAll(): Task[] { - return this.tasks; + async getAll(): Promise { + if (!this.cachedTasks) { + const tasks = await this.agentClient.tasks.getTasks(); + + this.cachedTasks = Object.values(tasks.tasks).map( + (task) => new Task(this.agentClient, task) + ); + } + + return this.cachedTasks; } /** * Gets a task by its ID. */ - get(taskId: string): Task | undefined { - return this.tasks.find((task) => task.id === taskId); + async get(taskId: string): Promise { + const tasks = await this.getAll(); + + return tasks.find((task) => task.id === taskId); } } @@ -82,10 +84,10 @@ export class Task { return this.shell?.status || "IDLE"; } constructor( - private pitcherClient: IPitcherClient, + private agentClient: IAgentClient, private data: protocol.task.TaskDTO ) { - pitcherClient.clients.task.onTaskUpdate(async (task) => { + agentClient.tasks.onTaskUpdate(async (task) => { if (task.id !== this.id) { return; } @@ -104,7 +106,7 @@ export class Task { task.shell && task.shell.shellId !== lastShellId ) { - const openedShell = await this.pitcherClient.clients.shell.open( + const openedShell = await this.agentClient.shells.open( task.shell.shellId, this.openedShell.dimensions ); @@ -120,7 +122,7 @@ export class Task { } }); - pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + this.agentClient.shells.onShellOut(({ shellId, out }) => { if (!this.shell || this.shell.shellId !== shellId || !this.openedShell) { return; } @@ -135,7 +137,7 @@ export class Task { throw new Error("Task is not running"); } - const openedShell = await this.pitcherClient.clients.shell.open( + const openedShell = await this.agentClient.shells.open( this.shell.shellId, dimensions ); @@ -157,7 +159,7 @@ export class Task { const [port] = await Promise.all([ new Promise((resolve) => { - disposer = this.pitcherClient.clients.task.onTaskUpdate((task) => { + disposer = this.agentClient.tasks.onTaskUpdate((task) => { if (task.id !== this.id) { return; } @@ -179,14 +181,14 @@ export class Task { return port; } async run() { - await this.pitcherClient.clients.task.runTask(this.id); + await this.agentClient.tasks.runTask(this.id); } async restart() { await this.run(); } async stop() { if (this.shell) { - await this.pitcherClient.clients.task.stopTask(this.id); + await this.agentClient.tasks.stopTask(this.id); } } } diff --git a/src/Session/terminals.ts b/src/Session/terminals.ts index bba2a2c..f21009b 100644 --- a/src/Session/terminals.ts +++ b/src/Session/terminals.ts @@ -1,7 +1,8 @@ -import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client"; +import * as protocol from "@codesandbox/pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; +import { IAgentClient } from "../agent-client-interface"; export type ShellSize = { cols: number; rows: number }; @@ -11,7 +12,7 @@ export class Terminals { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private pitcherClient: IPitcherClient, + private agentClient: IAgentClient, private env: Record = {} ) { sessionDisposable.onWillDispose(() => { @@ -36,8 +37,8 @@ export class Terminals { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; } - const shell = await this.pitcherClient.clients.shell.create( - this.pitcherClient.workspacePath, + const shell = await this.agentClient.shells.create( + this.agentClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, commandWithEnv, "TERMINAL", @@ -45,35 +46,35 @@ export class Terminals { ); if (opts?.name) { - this.pitcherClient.clients.shell.rename(shell.shellId, opts.name); + this.agentClient.shells.rename(shell.shellId, opts.name); } - return new Terminal(shell, this.pitcherClient); + return new Terminal(shell, this.agentClient); } - get(shellId: string) { - const shell = this.pitcherClient.clients.shell - .getShells() - .find((shell) => shell.shellId === shellId); + async get(shellId: string) { + const shells = await this.agentClient.shells.getShells(); + + const shell = shells.find((shell) => shell.shellId === shellId); if (!shell) { return; } - return new Terminal(shell, this.pitcherClient); + return new Terminal(shell, this.agentClient); } /** * Gets all terminals running in the current sandbox */ - getAll() { - const shells = this.pitcherClient.clients.shell.getShells(); + async getAll() { + const shells = await this.agentClient.shells.getShells(); return shells .filter( (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) ) - .map((shell) => new Terminal(shell, this.pitcherClient)); + .map((shell) => new Terminal(shell, this.agentClient)); } } @@ -103,10 +104,10 @@ export class Terminal { constructor( private shell: protocol.shell.ShellDTO & { buffer?: string[] }, - private pitcherClient: IPitcherClient + private agentClient: IAgentClient ) { this.disposable.addDisposable( - this.pitcherClient.clients.shell.onShellOut(({ shellId, out }) => { + this.agentClient.shells.onShellOut(({ shellId, out }) => { if (shellId === this.shell.shellId) { this.onOutputEmitter.fire(out); @@ -123,7 +124,7 @@ export class Terminal { * Open the terminal and get its current output, subscribes to future output */ async open(dimensions = DEFAULT_SHELL_SIZE): Promise { - const shell = await this.pitcherClient.clients.shell.open( + const shell = await this.agentClient.shells.open( this.shell.shellId, dimensions ); @@ -134,11 +135,7 @@ export class Terminal { } async write(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - await this.pitcherClient.clients.shell.send( - this.shell.shellId, - input, - dimensions - ); + await this.agentClient.shells.send(this.shell.shellId, input, dimensions); } async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { @@ -148,6 +145,6 @@ export class Terminal { // TODO: allow for kill signals async kill(): Promise { this.disposable.dispose(); - await this.pitcherClient.clients.shell.delete(this.shell.shellId); + await this.agentClient.shells.delete(this.shell.shellId); } } diff --git a/src/agent-client-interface.ts b/src/agent-client-interface.ts index e46ee4c..ffd0d77 100644 --- a/src/agent-client-interface.ts +++ b/src/agent-client-interface.ts @@ -1,15 +1,22 @@ -import { Id, Event } from "@codesandbox/pitcher-common"; -import { shell } from "@codesandbox/pitcher-protocol"; +import { Id, Event, Emitter } from "@codesandbox/pitcher-common"; +import { + fs, + git, + port, + PitcherRequest, + PitcherResponse, + PitcherResponseStatus, + shell, + setup, + task, +} from "@codesandbox/pitcher-protocol"; export interface IAgentClientShells { onShellExited: Event<{ shellId: Id; exitCode: number; }>; - onShellTerminated: Event<{ - shellId: Id; - author: string; - }>; + onShellTerminated: Event; onShellOut: Event; create( projectPath: string, @@ -25,10 +32,112 @@ export interface IAgentClientShells { shellId: Id ): Promise; restart(shellId: Id): Promise; + send(shellId: Id, input: string, size: shell.ShellSize): Promise; } +export type RawFsResult = + | { type: "ok"; result: T } + | { type: "error"; error: string; errno: number | null }; + +export type PickRawFsResult = RawFsResult< + Extract< + PitcherResponse, + { method: T; status: PitcherResponseStatus.RESOLVED } + >["result"] +>; + +export interface IAgentClientFS { + readFile(path: string): Promise>; + readdir(path: string): Promise>; + writeFile( + path: string, + content: Uint8Array, + create?: boolean, + overwrite?: boolean + ): Promise>; + stat(path: string): Promise>; + copy( + from: string, + to: string, + recursive?: boolean, + overwrite?: boolean + ): Promise>; + rename( + from: string, + to: string, + overwrite?: boolean + ): Promise>; + remove( + path: string, + recursive?: boolean + ): Promise>; + mkdir( + path: string, + recursive?: boolean + ): Promise>; + watch( + path: string, + options: { + readonly recursive?: boolean; + readonly excludes?: readonly string[]; + }, + onEvent: (watchEvent: fs.FSWatchEvent) => void + ): Promise< + | (PickRawFsResult<"fs/watch"> & { type: "error" }) + | { type: "success"; dispose(): void } + >; + download(path?: string): Promise<{ downloadUrl: string }>; +} + +export interface IAgentClientGit { + onStatusUpdated: Event; + getStatus(): Promise; +} + +export interface IAgentClientPorts { + onPortsUpdated: Event; + getPorts(): Promise; +} + +export interface IAgentClientSetup { + onSetupProgressUpdate: Event; + getProgress(): Promise; + init(): Promise; +} + +export interface IAgentClientTasks { + onTaskUpdate: Event; + getTasks(): Promise; + getTask(taskId: string): Promise; + runTask(taskId: string): Promise; + stopTask(taskId: string): Promise; +} + +export interface IAgentClientSystem { + update(): Promise>; +} + +export type IAgentClientState = + | "CONNECTED" + | "CONNECTING" + | "RECONNECTING" + | "DISCONNECTED" + | "HIBERNATED"; + export interface IAgentClient { - // this.pitcherClient.workspacePath + sandboxId: string; workspacePath: string; + isUpToDate: boolean; + state: IAgentClientState; + onStateChange: Event; shells: IAgentClientShells; + fs: IAgentClientFS; + git: IAgentClientGit; + ports: IAgentClientPorts; + setup: IAgentClientSetup; + tasks: IAgentClientTasks; + system: IAgentClientSystem; + disconnect(): Promise; + reconnect(): Promise; + dispose(): void; } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 53a2bb5..ce821f0 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,14 +1,12 @@ import { promises as fs } from "fs"; import path from "path"; import { isBinaryFile } from "isbinaryfile"; -import readline from "readline"; -import { Disposable, DisposableStore } from "@codesandbox/pitcher-common"; import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { VMTier, CodeSandbox, Sandbox } from "../../"; +import { VMTier, CodeSandbox, Sandbox } from "@codesandbox/sdk"; import { sandboxFork, @@ -20,6 +18,7 @@ import { getDefaultTemplateId, handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; import { startVm } from "../../Sandboxes"; +import { DisposableStore } from "@codesandbox/pitcher-common"; export type BuildCommandArgs = { directory: string; diff --git a/src/bin/commands/sandbox/fork.ts b/src/bin/commands/sandbox/fork.ts index f7a9d42..0d72b85 100644 --- a/src/bin/commands/sandbox/fork.ts +++ b/src/bin/commands/sandbox/fork.ts @@ -1,6 +1,6 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; export async function forkSandbox(sandboxId: string) { const sdk = new CodeSandbox(); diff --git a/src/bin/commands/sandbox/hibernate.ts b/src/bin/commands/sandbox/hibernate.ts index fe8b596..43b16fc 100644 --- a/src/bin/commands/sandbox/hibernate.ts +++ b/src/bin/commands/sandbox/hibernate.ts @@ -1,5 +1,5 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; type CommandResult = { success: boolean; diff --git a/src/bin/commands/sandbox/host-tokens.ts b/src/bin/commands/sandbox/host-tokens.ts index 275c3d4..029ec08 100644 --- a/src/bin/commands/sandbox/host-tokens.ts +++ b/src/bin/commands/sandbox/host-tokens.ts @@ -1,6 +1,6 @@ import ora from "ora"; import Table from "cli-table3"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; function formatDate(date: Date): string { return date.toLocaleString(); diff --git a/src/bin/commands/sandbox/list.ts b/src/bin/commands/sandbox/list.ts index da2c339..c95d9f3 100644 --- a/src/bin/commands/sandbox/list.ts +++ b/src/bin/commands/sandbox/list.ts @@ -5,7 +5,7 @@ import { PaginationOpts, SandboxInfo, SandboxListOpts, -} from "../../../"; +} from "@codesandbox/sdk"; type OutputFormat = { field: string; diff --git a/src/bin/commands/sandbox/shutdown.ts b/src/bin/commands/sandbox/shutdown.ts index c974c74..b1eea40 100644 --- a/src/bin/commands/sandbox/shutdown.ts +++ b/src/bin/commands/sandbox/shutdown.ts @@ -1,5 +1,5 @@ import ora from "ora"; -import { CodeSandbox } from "../../../"; +import { CodeSandbox } from "@codesandbox/sdk"; type CommandResult = { success: boolean; diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts index c642cc2..ed19a29 100644 --- a/src/browser/BrowserAgentClient.ts +++ b/src/browser/BrowserAgentClient.ts @@ -1,48 +1,159 @@ -import { Id, IPitcherClient } from "@codesandbox/pitcher-client"; -import { IAgentClient, IAgentClientShells } from "../agent-client-interface"; -import { shell } from "@codesandbox/pitcher-protocol"; +import { IPitcherClient } from "@codesandbox/pitcher-client"; +import { + IAgentClient, + IAgentClientFS, + IAgentClientGit, + IAgentClientPorts, + IAgentClientSetup, + IAgentClientShells, + IAgentClientState, + IAgentClientSystem, + IAgentClientTasks, +} from "../agent-client-interface"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; -class Shells implements IAgentClientShells { +class BrowserAgentClientShells implements IAgentClientShells { onShellExited = this.pitcherClient.clients.shell.onShellExited; onShellTerminated = this.pitcherClient.clients.shell.onShellTerminated; onShellOut = this.pitcherClient.clients.shell.onShellOut; constructor(private pitcherClient: IPitcherClient) {} - create( - projectPath: string, - size: shell.ShellSize, - command?: string, - type?: shell.ShellProcessType, - isSystemShell?: boolean - ) { - return this.pitcherClient.clients.shell.create( - projectPath, - size, - command, - type, - isSystemShell - ); - } - rename(shellId: Id, name: string): Promise { - return this.pitcherClient.clients.shell.rename(shellId, name); - } - async getShells(): Promise { + create(...params: Parameters) { + return this.pitcherClient.clients.shell.create(...params); + } + rename(...params: Parameters) { + return this.pitcherClient.clients.shell.rename(...params); + } + async getShells() { return this.pitcherClient.clients.shell.getShells(); } - async open(shellId: Id, size: shell.ShellSize): Promise { - return this.pitcherClient.clients.shell.open(shellId, size); + open(...params: Parameters) { + return this.pitcherClient.clients.shell.open(...params); + } + delete(...params: Parameters) { + return this.pitcherClient.clients.shell.delete(...params); + } + restart(...params: Parameters) { + return this.pitcherClient.clients.shell.restart(...params); + } + send(...params: Parameters) { + return this.pitcherClient.clients.shell.send(...params); + } +} + +class BrowserAgentClientFS implements IAgentClientFS { + constructor(private pitcherClient: IPitcherClient) {} + copy(...params: Parameters) { + return this.pitcherClient.clients.fs.copy(...params); + } + mkdir(...params: Parameters) { + return this.pitcherClient.clients.fs.mkdir(...params); + } + readdir(...params: Parameters) { + return this.pitcherClient.clients.fs.readdir(...params); + } + readFile(...params: Parameters) { + return this.pitcherClient.clients.fs.readFile(...params); + } + stat(...params: Parameters) { + return this.pitcherClient.clients.fs.stat(...params); + } + remove(...params: Parameters) { + return this.pitcherClient.clients.fs.remove(...params); + } + rename(...params: Parameters) { + return this.pitcherClient.clients.fs.rename(...params); + } + watch(...params: Parameters) { + return this.pitcherClient.clients.fs.watch(...params); + } + writeFile(...params: Parameters) { + return this.pitcherClient.clients.fs.writeFile(...params); + } + download(...params: Parameters) { + return this.pitcherClient.clients.fs.download(...params); + } +} + +class BrowserAgentClientGit implements IAgentClientGit { + onStatusUpdated = this.pitcherClient.clients.git.onStatusUpdated; + getStatus() { + return this.pitcherClient.clients.git.getStatus(); + } + constructor(private pitcherClient: IPitcherClient) {} +} + +class BrowserAgentClientPorts implements IAgentClientPorts { + onPortsUpdated = this.pitcherClient.clients.port.onPortsUpdated; + async getPorts() { + return this.pitcherClient.clients.port.getPorts(); } - async delete( - shellId: Id - ): Promise { - return this.pitcherClient.clients.shell.delete(shellId); + constructor(private pitcherClient: IPitcherClient) {} +} + +class BrowserAgentClientSetup implements IAgentClientSetup { + onSetupProgressUpdate = + this.pitcherClient.clients.setup.onSetupProgressUpdate; + constructor(private pitcherClient: IPitcherClient) {} + init() { + return this.pitcherClient.clients.setup.init(); } - async restart(shellId: Id): Promise { - return this.pitcherClient.clients.shell.restart(shellId); + async getProgress() { + return this.pitcherClient.clients.setup.getProgress(); } } -export class BrowserAgent implements IAgentClient { - shells = new Shells(this.pitcherClient); +class BrowserAgentClientTasks implements IAgentClientTasks { + onTaskUpdate = this.pitcherClient.clients.task.onTaskUpdate; + constructor(private pitcherClient: IPitcherClient) {} + async getTasks() { + return this.pitcherClient.clients.task.getTasks(); + } + async getTask(taskId: string) { + return this.pitcherClient.clients.task.getTask(taskId); + } + runTask(taskId: string) { + return this.pitcherClient.clients.task.runTask(taskId); + } + stopTask(taskId: string) { + return this.pitcherClient.clients.task.stopTask(taskId); + } +} +class BrowserAgentClientSystem implements IAgentClientSystem { constructor(private pitcherClient: IPitcherClient) {} + update() { + return this.pitcherClient.clients.system.update(); + } +} + +export class BrowserAgentClient implements IAgentClient { + sandboxId = this.pitcherClient.instanceId; + workspacePath = this.pitcherClient.instanceId; + isUpToDate = this.pitcherClient.isUpToDate(); + state = this.pitcherClient.state.get().state; + private onStateChangeEmitter = new Emitter(); + onStateChange = this.onStateChangeEmitter.event; + shells = new BrowserAgentClientShells(this.pitcherClient); + fs = new BrowserAgentClientFS(this.pitcherClient); + git = new BrowserAgentClientGit(this.pitcherClient); + ports = new BrowserAgentClientPorts(this.pitcherClient); + setup = new BrowserAgentClientSetup(this.pitcherClient); + tasks = new BrowserAgentClientTasks(this.pitcherClient); + system = new BrowserAgentClientSystem(this.pitcherClient); + constructor(private pitcherClient: IPitcherClient) { + pitcherClient.onStateChange((state) => { + this.state = state.state; + this.onStateChangeEmitter.fire(state.state); + }); + } + disconnect(): Promise { + return this.pitcherClient.disconnect(); + } + reconnect(): Promise { + return this.pitcherClient.reconnect(); + } + dispose() { + this.pitcherClient.dispose(); + } } diff --git a/src/browser/index.ts b/src/browser/index.ts index 01937f5..8d2db0a 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -1,6 +1,7 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "../types"; import { Session } from "../Session"; +import { BrowserAgentClient } from "./BrowserAgentClient"; export * from "../Session"; @@ -45,7 +46,8 @@ export async function connectToSandbox(options: { options.initStatusCb || (() => {}) ); - return new Session(pitcherClient, { + const agentClient = new BrowserAgentClient(pitcherClient); + const session = await Session.create(agentClient, { username: options.session.sessionId ? // @ts-ignore pitcherClient["joinResult"].client.username @@ -53,4 +55,6 @@ export async function connectToSandbox(options: { env: options.session.env, hostToken: options.session.hostToken, }); + + return session; } diff --git a/src/index.ts b/src/index.ts index a8c768d..b943adf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,12 +8,11 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; -import { HostTokens } from "./Hosts"; +import { HostTokens } from "./HostTokens"; import { createClient, createConfig } from "@hey-api/client-fetch"; import { getBaseUrl } from "./utils/api"; -export * from "./sessions/WebSocketSession"; -export * from "./sessions/RestSession"; +export * from "./Session"; function ensure(value: T | undefined, message: string): T { if (!value) { diff --git a/src/types.ts b/src/types.ts index 95ac3c4..c5b4b9e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; -import { HostToken } from "./Hosts"; -import { ISession } from "./session-interface"; +import { HostToken } from "./HostTokens"; +import { Session } from "./Session"; export interface SystemMetricsStatus { cpu: { @@ -220,7 +220,7 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { email: string; name?: string; }; - setup?: (session: ISession) => Promise; + setup?: (session: Session) => Promise; }; export type CreateSandboxOpts = From 82bcf74e298c207665bfb3df5c8377e01d9feec7 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 28 May 2025 21:34:12 +0200 Subject: [PATCH 132/241] use ws and drop banner --- esbuild.cjs | 7 ------- package-lock.json | 10 +++++----- package.json | 6 +++--- src/NodeAgentClient/WebSocketClient.ts | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index 7ee01a6..926b590 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -82,13 +82,6 @@ Promise.all([ bundle: true, format: "esm", platform: "node", - banner: { - js: ` -import { fileURLToPath } from 'url'; -import { createRequire as topLevelCreateRequire } from 'module'; -const require = topLevelCreateRequire(import.meta.url); - `.trim(), - }, outfile: "dist/esm/index.js", external: nodeExternals, }), diff --git a/package-lock.json b/package-lock.json index 99eb55a..0bb3647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "@codesandbox/sdk", - "version": "1.1.0", + "version": "2.0.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "1.1.0", + "version": "2.0.0-rc.1", "license": "MIT", "dependencies": { "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", - "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", - "readline": "^1.3.0" + "readline": "^1.3.0", + "ws": "^8.18.2" }, "bin": { "csb": "dist/bin/codesandbox.cjs" @@ -2842,6 +2842,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, "license": "MIT", "peerDependencies": { "ws": "*" @@ -4744,7 +4745,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index af9c618..c0009a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.1.5", + "version": "2.0.0-rc.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -90,10 +90,10 @@ "yargs": "^17.7.2" }, "dependencies": { - "isomorphic-ws": "^5.0.0", "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "ora": "^8.2.0", - "readline": "^1.3.0" + "readline": "^1.3.0", + "ws": "^8.18.2" } } diff --git a/src/NodeAgentClient/WebSocketClient.ts b/src/NodeAgentClient/WebSocketClient.ts index fb36efd..fd8ff56 100644 --- a/src/NodeAgentClient/WebSocketClient.ts +++ b/src/NodeAgentClient/WebSocketClient.ts @@ -1,5 +1,5 @@ import { Disposable, Emitter } from "@codesandbox/pitcher-common"; -import WebSocket from "isomorphic-ws"; +import WebSocket from "ws"; export type WebsocketData = string | Uint8Array; From 475d499c57ca17c7537382acd658c37d198a2cdf Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 2 Jun 2025 09:51:52 +0200 Subject: [PATCH 133/241] remove together-common and update default template id --- package-lock.json | 72 ++++++++------- package.json | 8 +- src/NodeAgentClient/AgentConnection.ts | 4 +- src/NodeAgentClient/PendingPitcherMessage.ts | 3 +- src/NodeAgentClient/WebSocketClient.ts | 3 +- src/NodeAgentClient/index.ts | 23 +++-- src/Session/commands.ts | 4 +- src/Session/index.ts | 2 +- src/Session/tasks.ts | 4 +- src/agent-client-interface.ts | 21 +++-- src/bin/commands/build.ts | 2 +- src/utils/api.ts | 2 +- src/utils/barrier.ts | 96 ++++++++++++++++++++ src/utils/sliceList.ts | 44 +++++++++ 14 files changed, 224 insertions(+), 64 deletions(-) create mode 100644 src/utils/barrier.ts create mode 100644 src/utils/sliceList.ts diff --git a/package-lock.json b/package-lock.json index 0bb3647..168f62b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.3", "license": "MIT", "dependencies": { - "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "ora": "^8.2.0", + "path": "^0.12.7", "readline": "^1.3.0", + "util": "^0.12.5", "ws": "^8.18.2" }, "bin": { @@ -40,7 +41,6 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "util": "0.12.5", "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" } @@ -1064,7 +1064,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -1392,7 +1391,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -1411,7 +1409,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1425,7 +1422,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1840,7 +1836,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -1946,7 +1941,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -1998,7 +1992,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2008,7 +2001,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2018,7 +2010,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2184,7 +2175,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -2243,7 +2233,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2373,7 +2362,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2398,7 +2386,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -2469,7 +2456,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2537,7 +2523,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -2550,7 +2535,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2563,7 +2547,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -2604,7 +2587,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2681,14 +2663,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2705,7 +2685,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2728,7 +2707,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -2768,7 +2746,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2787,7 +2764,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -3049,7 +3025,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3590,6 +3565,16 @@ "node": ">= 0.10" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -3624,6 +3609,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/path/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -3724,7 +3724,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3757,6 +3756,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -3969,7 +3977,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -4004,7 +4011,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -4473,7 +4479,6 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -4555,7 +4560,6 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", diff --git a/package.json b/package.json index c0009a6..42c6779 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.2", + "version": "2.0.0-rc.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -85,15 +85,15 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "util": "0.12.5", "why-is-node-running": "^2.3.0", "yargs": "^17.7.2" }, "dependencies": { - "@codesandbox/pitcher-common": "0.360.2", "@codesandbox/pitcher-protocol": "0.360.4", "ora": "^8.2.0", + "path": "^0.12.7", "readline": "^1.3.0", - "ws": "^8.18.2" + "ws": "^8.18.2", + "util": "^0.12.5" } } diff --git a/src/NodeAgentClient/AgentConnection.ts b/src/NodeAgentClient/AgentConnection.ts index f1218f9..eac0c64 100644 --- a/src/NodeAgentClient/AgentConnection.ts +++ b/src/NodeAgentClient/AgentConnection.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Emitter, SliceList } from "@codesandbox/pitcher-common"; import { PitcherResponseStatus, isNotificationPayload, @@ -19,6 +17,8 @@ import { createWebSocketClient, WebSocketClient } from "./WebSocketClient"; import { IAgentClientState } from "../agent-client-interface"; import { DEFAULT_SUBSCRIPTIONS } from "../types"; import { startVm } from "../Sandboxes"; +import { Emitter } from "../utils/event"; +import { SliceList } from "../utils/sliceList"; export interface IRequestOptions { /** diff --git a/src/NodeAgentClient/PendingPitcherMessage.ts b/src/NodeAgentClient/PendingPitcherMessage.ts index 2123144..d262354 100644 --- a/src/NodeAgentClient/PendingPitcherMessage.ts +++ b/src/NodeAgentClient/PendingPitcherMessage.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Disposable } from "@codesandbox/pitcher-common"; import { PitcherResponseStatus, createRequestPayload, @@ -10,6 +8,7 @@ import type { PitcherRequestPayload, PitcherErrorCode, } from "@codesandbox/pitcher-protocol"; +import { Disposable } from "../utils/disposable"; const PITCHER_MESSAGE_TIMEOUT_MS = 90_000; diff --git a/src/NodeAgentClient/WebSocketClient.ts b/src/NodeAgentClient/WebSocketClient.ts index fd8ff56..fa35008 100644 --- a/src/NodeAgentClient/WebSocketClient.ts +++ b/src/NodeAgentClient/WebSocketClient.ts @@ -1,5 +1,6 @@ -import { Disposable, Emitter } from "@codesandbox/pitcher-common"; import WebSocket from "ws"; +import { Disposable } from "../utils/disposable"; +import { Emitter } from "../utils/event"; export type WebsocketData = string | Uint8Array; diff --git a/src/NodeAgentClient/index.ts b/src/NodeAgentClient/index.ts index 620d5be..3f6d309 100644 --- a/src/NodeAgentClient/index.ts +++ b/src/NodeAgentClient/index.ts @@ -20,13 +20,14 @@ import { PickRawFsResult, } from "../agent-client-interface"; import { AgentConnection } from "./AgentConnection"; -import { Emitter, Id } from "@codesandbox/pitcher-common"; import { Client } from "@hey-api/client-fetch"; import { startVm } from "../Sandboxes"; +import { Emitter } from "../utils/event"; +import { Id } from "@codesandbox/pitcher-client"; class NodeAgentClientShells implements IAgentClientShells { private onShellExitedEmitter = new Emitter<{ - shellId: Id; + shellId: string; exitCode: number; }>(); onShellExited = this.onShellExitedEmitter.event; @@ -73,11 +74,12 @@ class NodeAgentClientShells implements IAgentClientShells { }); } delete( - shellId: Id + shellId: shell.ShellId ): Promise { return this.agentConnection.request({ method: "shell/terminate", params: { + // We do can not import Id from pitcher-client shellId, }, }); @@ -90,7 +92,10 @@ class NodeAgentClientShells implements IAgentClientShells { return result.shells; } - open(shellId: Id, size: shell.ShellSize): Promise { + open( + shellId: shell.ShellId, + size: shell.ShellSize + ): Promise { return this.agentConnection.request({ method: "shell/open", params: { @@ -99,7 +104,7 @@ class NodeAgentClientShells implements IAgentClientShells { }, }); } - rename(shellId: Id, name: string): Promise { + rename(shellId: shell.ShellId, name: string): Promise { return this.agentConnection.request({ method: "shell/rename", params: { @@ -108,7 +113,7 @@ class NodeAgentClientShells implements IAgentClientShells { }, }); } - restart(shellId: Id): Promise { + restart(shellId: shell.ShellId): Promise { return this.agentConnection.request({ method: "shell/restart", params: { @@ -116,7 +121,11 @@ class NodeAgentClientShells implements IAgentClientShells { }, }); } - send(shellId: Id, input: string, size: shell.ShellSize): Promise { + send( + shellId: shell.ShellId, + input: string, + size: shell.ShellSize + ): Promise { return this.agentConnection.request({ method: "shell/in", params: { shellId, input, size }, diff --git a/src/Session/commands.ts b/src/Session/commands.ts index b256ab4..b7e2047 100644 --- a/src/Session/commands.ts +++ b/src/Session/commands.ts @@ -1,8 +1,8 @@ -import { Barrier, DisposableStore } from "@codesandbox/pitcher-common"; -import { Disposable } from "../utils/disposable"; +import { Disposable, DisposableStore } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { IAgentClient } from "../agent-client-interface"; import * as protocol from "@codesandbox/pitcher-protocol"; +import { Barrier } from "../utils/barrier"; type ShellSize = { cols: number; rows: number }; diff --git a/src/Session/index.ts b/src/Session/index.ts index 4d278ec..a80612b 100644 --- a/src/Session/index.ts +++ b/src/Session/index.ts @@ -12,7 +12,7 @@ import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; import { IAgentClient } from "../agent-client-interface"; import { setup } from "@codesandbox/pitcher-protocol"; -import { Barrier } from "@codesandbox/pitcher-common"; +import { Barrier } from "../utils/barrier"; export * from "./filesystem"; export * from "./ports"; diff --git a/src/Session/tasks.ts b/src/Session/tasks.ts index 185b3cb..9908e65 100644 --- a/src/Session/tasks.ts +++ b/src/Session/tasks.ts @@ -1,8 +1,8 @@ import * as protocol from "@codesandbox/pitcher-protocol"; -import { Disposable } from "../utils/disposable"; +import { Disposable, IDisposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; import { IAgentClient } from "../agent-client-interface"; -import { Emitter, IDisposable } from "@codesandbox/pitcher-common"; +import { Emitter } from "../utils/event"; export type TaskDefinition = { name: string; diff --git a/src/agent-client-interface.ts b/src/agent-client-interface.ts index d94bdaa..061f87d 100644 --- a/src/agent-client-interface.ts +++ b/src/agent-client-interface.ts @@ -1,4 +1,3 @@ -import { Id, Event, Emitter } from "@codesandbox/pitcher-common"; import { fs, git, @@ -10,10 +9,11 @@ import { setup, task, } from "@codesandbox/pitcher-protocol"; +import { Event } from "./utils/event"; export interface IAgentClientShells { onShellExited: Event<{ - shellId: Id; + shellId: string; exitCode: number; }>; onShellTerminated: Event; @@ -25,14 +25,21 @@ export interface IAgentClientShells { type?: shell.ShellProcessType, isSystemShell?: boolean ): Promise; - rename(shellId: Id, name: string): Promise; + rename(shellId: shell.ShellId, name: string): Promise; getShells(): Promise; - open(shellId: Id, size: shell.ShellSize): Promise; + open( + shellId: shell.ShellId, + size: shell.ShellSize + ): Promise; delete( - shellId: Id + shellId: shell.ShellId ): Promise; - restart(shellId: Id): Promise; - send(shellId: Id, input: string, size: shell.ShellSize): Promise; + restart(shellId: shell.ShellId): Promise; + send( + shellId: shell.ShellId, + input: string, + size: shell.ShellSize + ): Promise; } export type RawFsResult = diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 32fa201..5d8e8e6 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -18,7 +18,7 @@ import { getDefaultTemplateId, handleResponse } from "../../utils/api"; import { BASE_URL, getApiKey } from "../utils/constants"; import { hashDirectory } from "../utils/hash"; import { startVm } from "../../Sandboxes"; -import { DisposableStore } from "@codesandbox/pitcher-common"; +import { DisposableStore } from "../../utils/disposable"; export type BuildCommandArgs = { directory: string; diff --git a/src/utils/api.ts b/src/utils/api.ts index d3f7a79..6863b9b 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -56,7 +56,7 @@ export function getDefaultTemplateId(apiClient: Client): string { return "7ngcrf"; } - return "pt_UAYyadeQTA9jw8bXqzgy6v"; + return "pt_CW2BcBSPRoGb3AS6Fv7oXs"; } export function handleResponse( diff --git a/src/utils/barrier.ts b/src/utils/barrier.ts new file mode 100644 index 0000000..a9576b7 --- /dev/null +++ b/src/utils/barrier.ts @@ -0,0 +1,96 @@ +import { IDisposable } from "./disposable"; + +/** + * The response from a barrier. If the barrier is disposed, the status is "disposed". + * If the barrier is opened, the status is "resolved" and the value is the value passed to open(). + * This is used instead of rejecting a barrier promise so that error state is explicitly handled. + */ +export type BarrierResponse = + | { + status: "disposed"; + } + | { + status: "resolved"; + value: T; + }; + +/** + * A barrier that is initially closed and then becomes opened permanently. + * You can wait for the barrier to open, but you cannot close it again. + */ +export class Barrier implements IDisposable { + protected _isOpen: boolean; + protected _promise: Promise>; + protected _completePromise!: (v: BarrierResponse) => void; + + constructor() { + this._isOpen = false; + this._promise = new Promise>((resolve) => { + this._completePromise = resolve; + }); + } + + /** + * Returns true if the barrier is open, false if it is closed + * @returns true if the barrier is open, false if it is closed + */ + isOpen(): boolean { + return this._isOpen; + } + + /** + * Opens the barrier. If the barrier is already open, this method does nothing. + * @param value the value to return when the barrier is opened + * @returns + */ + open(value: T): void { + if (this._isOpen) { + return; + } + + this._isOpen = true; + this._completePromise({ status: "resolved", value }); + } + + /** + * + * @returns a promise that resolves when the barrier is opened. If the barrier is already open, the promise resolves immediately. + */ + wait(): Promise> { + return this._promise; + } + + /** + * DO NOT USE THIS METHOD in production code. This is only for tests. + * This is a convenience method that waits for the barrier to open and then returns the value. + * If the Barrier is disposed while waiting to open, an error is thrown. + * @returns the value if the barrier is open, otherwise throws an error + */ + async __waitAndThrowIfDisposed(): Promise { + const r = await this.wait(); + if (r.status === "disposed") { + throw new Error("Barrier was disposed"); + } + return r.value; + } + + /** + * Disposes the barrier. + * If there is a promise waiting for the barrier to open, it will be resolved with a status of "disposed". + */ + dispose(): void { + this._completePromise({ status: "disposed" }); + } +} + +/** + * Like Barrier, but you can close the barrier again + */ +export class ClosableBarrier extends Barrier { + close(): void { + this._isOpen = false; + this._promise = new Promise>((resolve) => { + this._completePromise = resolve; + }); + } +} diff --git a/src/utils/sliceList.ts b/src/utils/sliceList.ts new file mode 100644 index 0000000..d8f0c63 --- /dev/null +++ b/src/utils/sliceList.ts @@ -0,0 +1,44 @@ +export class SliceList { + private idx = 0; + private store: Map = new Map(); + + /** + * Add a value to the list + * + * @returns a unique reference to delete the item + */ + add(value: T): number { + const nextIdx = this.idx + 1; + this.idx = nextIdx; + this.store.set(nextIdx, value); + return nextIdx; + } + + /** + * Remove a value using the unique reference + */ + remove(idx: number): void { + this.store.delete(idx); + } + + /** + * Get values as an iterator + */ + values(): IterableIterator { + return this.store.values(); + } + + /** + * Get values as an array + */ + array(): Array { + return Array.from(this.store.values()); + } + + /** + * Get amount of items in the list + */ + size(): number { + return this.store.size; + } +} From d544e9d8a51a32e19cdcdbc8b74fcc6545f96578 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 2 Jun 2025 15:39:54 +0200 Subject: [PATCH 134/241] fix scheduling hack --- src/Sandbox.ts | 31 +++++++++++++++++ src/Sandboxes.ts | 72 +++++---------------------------------- src/Session/git.ts | 13 ++++++- src/bin/commands/build.ts | 10 ++++++ src/types.ts | 22 ++---------- src/utils/api.ts | 2 +- 6 files changed, 65 insertions(+), 85 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 6715c20..37e34df 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -183,6 +183,37 @@ export class Sandbox { hostToken: customSession?.hostToken, }); + if (customSession?.git) { + const netrc = await session.commands.runBackground([ + `mkdir -p ~/private`, + `cat > ~/private/.netrc < ~/private/.gitconfig < { - switch (opts.source) { - case "git": { - return this.createGitSandbox(opts); - } - case "template": { - return this.createTemplateSandbox(opts); - } - default: { - throw new Error("Invalid source"); - } - } + async create(opts?: CreateSandboxOpts & StartSandboxOpts): Promise { + return this.createTemplateSandbox(opts); } /** diff --git a/src/Session/git.ts b/src/Session/git.ts index 9995e55..4ee2430 100644 --- a/src/Session/git.ts +++ b/src/Session/git.ts @@ -60,6 +60,17 @@ export class Git { * Push all changes to git */ async push() { - await this.commands.run(["git push"]); + await this.commands.run(["git push -u origin HEAD"]); + } + + async clone(opts: { url: string; branch: string }) { + await this.commands.run([ + "rm -rf .git", + "git init", + `git remote add origin ${opts.url}`, + "git fetch origin", + `git checkout -b ${opts.branch}`, + `git reset --hard origin/${opts.branch}`, + ]); } } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 5d8e8e6..68e458e 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -148,6 +148,16 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage(index, "Starting sandbox...", sandboxId) ); + // This is a hack, we need to tell the global scheduler that the VM is running + // in a different cluster than the one it'd like to default to. + const baseUrl = apiClient + .getConfig() + .baseUrl?.replace("api", "global-scheduler"); + + await fetch( + `${baseUrl}/api/v1/cluster/${sandboxId}?preferredManager=${cluster}` + ).then((res) => res.json()); + const startResponse = await startVm(clusterApiClient, sandboxId, { vmTier: VMTier.fromName("Micro"), }); diff --git a/src/types.ts b/src/types.ts index c5b4b9e..df219b6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -185,6 +185,8 @@ export interface SessionCreateOptions { id: string; permission?: "read" | "write"; git?: { + provider: string; + username?: string; accessToken?: string; email: string; name?: string; @@ -201,8 +203,7 @@ export type SandboxSession = { env?: Record; }; -export type CreateSandboxTemplateSourceOpts = CreateSandboxBaseOpts & { - source: "template"; +export type CreateSandboxOpts = CreateSandboxBaseOpts & { /** * What template to fork from, this is the id of another sandbox. Defaults to our * [universal template](https://codesandbox.io/s/github/codesandbox/sandbox-templates/tree/main/universal). @@ -210,23 +211,6 @@ export type CreateSandboxTemplateSourceOpts = CreateSandboxBaseOpts & { id?: string; }; -export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & { - source: "git"; - url: string; - branch: string; - templateId?: string; - config?: { - accessToken: string; - email: string; - name?: string; - }; - setup?: (session: Session) => Promise; -}; - -export type CreateSandboxOpts = - | CreateSandboxTemplateSourceOpts - | CreateSandboxGitSourceOpts; - export type SandboxOpts = { id: string; bootupType: PitcherManagerResponse["bootupType"]; diff --git a/src/utils/api.ts b/src/utils/api.ts index 6863b9b..8a9c306 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -56,7 +56,7 @@ export function getDefaultTemplateId(apiClient: Client): string { return "7ngcrf"; } - return "pt_CW2BcBSPRoGb3AS6Fv7oXs"; + return "pt_TRkPA6ypYZ7cRLQYgCmRHs"; } export function handleResponse( From 791420997388eaa10b3c2183e3fb2d4e741a0940 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 2 Jun 2025 15:47:46 +0200 Subject: [PATCH 135/241] cluster fix --- src/bin/commands/build.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index cde3aee..d36065d 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -149,6 +149,16 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage(index, "Starting sandbox...", sandboxId) ); + // This is a hack, we need to tell the global scheduler that the VM is running + // in a different cluster than the one it'd like to default to. + const baseUrl = apiClient + .getConfig() + .baseUrl?.replace("api", "global-scheduler"); + + await fetch( + `${baseUrl}/api/v1/cluster/${sandboxId}?preferredManager=${cluster}` + ).then((res) => res.json()); + const startResponse = await startVm(clusterApiClient, sandboxId, { vmTier: VMTier.fromName("Micro"), }); From 3765562680914d3440f54e41dad63b31117f1e7c Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 2 Jun 2025 16:34:10 +0200 Subject: [PATCH 136/241] fix building of templates --- src/Sandboxes.ts | 3 ++- src/utils/api.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index f31673c..a9d6b53 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -10,6 +10,7 @@ import { import { Sandbox } from "./Sandbox"; import { getDefaultTemplateId, + getDefaultTemplateTag, getStartOptions, getStartResponse, handleResponse, @@ -60,7 +61,7 @@ export async function startVm( */ export class Sandboxes { get defaultTemplateId() { - return getDefaultTemplateId(this.apiClient); + return getDefaultTemplateTag(this.apiClient); } constructor(private apiClient: Client) {} diff --git a/src/utils/api.ts b/src/utils/api.ts index 8a9c306..8b03775 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -51,7 +51,7 @@ export function getBaseUrl(token: string) { return "https://api.together.ai/csb/sdk"; } -export function getDefaultTemplateId(apiClient: Client): string { +export function getDefaultTemplateTag(apiClient: Client): string { if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; } @@ -59,6 +59,14 @@ export function getDefaultTemplateId(apiClient: Client): string { return "pt_TRkPA6ypYZ7cRLQYgCmRHs"; } +export function getDefaultTemplateId(apiClient: Client): string { + if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } + + return "pcz35m"; +} + export function handleResponse( result: Awaited<{ data?: { data?: D }; error?: E; response: Response }>, errorPrefix: string From e7bb559dd426f6a143282f14839f8fbd1fa8e258 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 2 Jun 2025 16:38:01 +0200 Subject: [PATCH 137/241] fix templates --- package.json | 2 +- src/Sandboxes.ts | 4 ++-- src/utils/api.ts | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9b4d8f7..2339d80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "1.1.5", + "version": "1.1.6", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 3f41b1d..d0daaba 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -9,7 +9,7 @@ import { } from "./api-clients/client"; import { Sandbox } from "./Sandbox"; import { - getDefaultTemplateId, + getDefaultTemplateTag, getStartOptions, getStartResponse, handleResponse, @@ -62,7 +62,7 @@ export async function startVm( */ export class Sandboxes { get defaultTemplateId() { - return getDefaultTemplateId(this.apiClient); + return getDefaultTemplateTag(this.apiClient); } constructor(private apiClient: Client) {} diff --git a/src/utils/api.ts b/src/utils/api.ts index d3f7a79..de2b083 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -51,12 +51,20 @@ export function getBaseUrl(token: string) { return "https://api.together.ai/csb/sdk"; } +export function getDefaultTemplateTag(apiClient: Client): string { + if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { + return "7ngcrf"; + } + + return "pt_LAVK5kxK8XciqgV2642xRk"; +} + export function getDefaultTemplateId(apiClient: Client): string { if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; } - return "pt_UAYyadeQTA9jw8bXqzgy6v"; + return "pcz35m"; } export function handleResponse( From 40123beab9b954d6e3d2fa73f10e22675facc545 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 3 Jun 2025 10:50:24 +0200 Subject: [PATCH 138/241] add aliases --- openapi.json | 174 ++++++++++++++++++++++++++++ src/Sandboxes.ts | 31 ++++- src/Session/index.ts | 4 + src/api-clients/client/sdk.gen.ts | 42 ++++++- src/api-clients/client/types.gen.ts | 81 +++++++++++++ src/bin/commands/build.ts | 63 ++++++++++ 6 files changed, 392 insertions(+), 3 deletions(-) diff --git a/openapi.json b/openapi.json index 1307742..dda6543 100644 --- a/openapi.json +++ b/openapi.json @@ -72,6 +72,51 @@ "title": "VMUpdateHibernationTimeoutRequest", "type": "object" }, + "VMAssignTagAliasResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "alias": { "type": "string" }, + "namespace": { "type": "string" }, + "tag_alias_id": { "type": "string" }, + "tag_id": { "type": "string" }, + "team_id": { "type": "string" } + }, + "required": [ + "tag_alias_id", + "team_id", + "tag_id", + "namespace", + "alias" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMAssignTagAliasResponse", + "type": "object" + }, "PreviewToken": { "properties": { "expires_at": { "nullable": true, "type": "string" }, @@ -212,6 +257,54 @@ "title": "VMCreateTagResponse", "type": "object" }, + "VMListRunningVMsResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "concurrent_vm_count": { "type": "integer" }, + "concurrent_vm_limit": { "type": "integer" }, + "vms": { + "items": { + "properties": { "id": { "type": "string" } }, + "type": "object" + }, + "required": ["id"], + "type": "array" + } + }, + "required": [ + "vms", + "concurrent_vm_count", + "concurrent_vm_limit" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMListRunningVMsResponse", + "type": "object" + }, "SandboxGetResponse": { "allOf": [ { @@ -442,6 +535,13 @@ "title": "TokenUpdateRequest", "type": "object" }, + "VMAssignTagAliasRequest": { + "description": "Assign a tag alias to a VM", + "properties": { "tag_id": { "type": "string" } }, + "required": ["tag_id"], + "title": "VMAssignTagAliasRequest", + "type": "object" + }, "VMHibernateResponse": { "allOf": [ { @@ -1832,6 +1932,57 @@ "tags": [] } }, + "/vm/alias/{namespace}/{alias}": { + "put": { + "callbacks": {}, + "description": "Assign a tag alias to a VM tag.\n", + "operationId": "vm/assign_tag_alias", + "parameters": [ + { + "description": "Tag alias namespace", + "example": "my-project", + "in": "path", + "name": "namespace", + "required": true, + "schema": { "type": "string" } + }, + { + "description": "Tag alias", + "example": "latest", + "in": "path", + "name": "alias", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMAssignTagAliasRequest" + } + } + }, + "description": "VM Assign Tag Alias Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMAssignTagAliasResponse" + } + } + }, + "description": "VM Assign Tag Alias Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Assign a tag alias to a VM tag", + "tags": ["vm"] + } + }, "/vm/clusters": { "get": { "callbacks": {}, @@ -1855,6 +2006,29 @@ "tags": ["vm"] } }, + "/vm/running": { + "get": { + "callbacks": {}, + "description": "List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date.\n", + "operationId": "vm/list_running_vms", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMListRunningVMsResponse" + } + } + }, + "description": "VM List Running VMs Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "List information about currently running VMs", + "tags": ["vm"] + } + }, "/vm/tag": { "post": { "callbacks": {}, diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 0fce905..2bda891 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -26,6 +26,7 @@ import { SessionCreateOptions, } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +import { sleep } from "./utils/sleep"; export async function startVm( apiClient: Client, @@ -144,8 +145,34 @@ export class Sandboxes { * Will resolve once the sandbox is restarted with its setup running. */ public async restart(sandboxId: string, opts?: StartSandboxOpts) { - await this.shutdown(sandboxId); - const startResponse = await startVm(this.apiClient, sandboxId, opts); + let didRestart = false; + + for (let attempt = 1; attempt <= 3; attempt++) { + try { + await this.shutdown(sandboxId); + didRestart = true; + } catch (e) { + await sleep(500); + } + } + + if (!didRestart) { + throw new Error("Failed to shutdown VM after 3 attempts"); + } + + let startResponse: PitcherManagerResponse | undefined; + + for (let attempt = 1; attempt <= 3; attempt++) { + try { + startResponse = await startVm(this.apiClient, sandboxId, opts); + } catch (e) { + await sleep(500); + } + } + + if (!startResponse) { + throw new Error("Failed to start VM after 3 attempts"); + } return new Sandbox(sandboxId, this.apiClient, startResponse); } diff --git a/src/Session/index.ts b/src/Session/index.ts index a80612b..fcf63b3 100644 --- a/src/Session/index.ts +++ b/src/Session/index.ts @@ -61,6 +61,10 @@ export class Session { } private disposable = new Disposable(); + get workspacePath() { + return this.agentClient.workspacePath; + } + /** * Namespace for all filesystem operations on this Sandbox */ diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 6119539..704cfdc 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -250,6 +250,28 @@ export const previewTokenUpdate = (options }); }; +/** + * Assign a tag alias to a VM tag + * Assign a tag alias to a VM tag. + * + */ +export const vmAssignTagAlias = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/alias/{namespace}/{alias}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * List all available clusters * List all available clusters. @@ -268,6 +290,24 @@ export const vmListClusters = (options?: O }); }; +/** + * List information about currently running VMs + * List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date. + * + */ +export const vmListRunningVms = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/running', + ...options + }); +}; + /** * Create a new tag for a VM * Creates a new tag for a VM. diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index e553d64..fa3cb12 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -29,6 +29,21 @@ export type VmUpdateHibernationTimeoutRequest = { hibernation_timeout_seconds: number; }; +export type VmAssignTagAliasResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + alias: string; + namespace: string; + tag_alias_id: string; + tag_id: string; + team_id: string; + }; +}; + export type PreviewToken = { expires_at: string | null; last_used_at: string | null; @@ -95,6 +110,21 @@ export type VmCreateTagResponse = { }; }; +export type VmListRunningVmsResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + concurrent_vm_count: number; + concurrent_vm_limit: number; + vms: Array<{ + id?: string; + }>; + }; +}; + export type SandboxGetResponse = { errors?: Array; }; +/** + * Assign a tag alias to a VM + */ +export type VmAssignTagAliasRequest = { + tag_id: string; +}; + export type VmHibernateResponse = { errors?: Array t.name), }) + .option("alias", { + describe: + "Alias that should point to the created template. Alias namespace defaults to template directory, but you can explicitly pass `namespace@alias`", + type: "string", + }) .positional("directory", { describe: "Path to the project that we'll create a snapshot from", type: "string", @@ -80,6 +87,12 @@ export const buildCommand: yargs.CommandModule< }) ); + let alias: { namespace: string; alias: string } | undefined; + + if (argv.alias) { + alias = createAlias(argv.directory, argv.alias); + } + try { const clustersData = handleResponse( await vmListClusters({ @@ -320,6 +333,25 @@ export const buildCommand: yargs.CommandModule< }), "Failed to create template" ); + + if (alias) { + await vmAssignTagAlias({ + client: apiClient, + path: { + alias: alias.alias, + namespace: alias.namespace, + }, + body: { + tag_id: data.tag_id, + }, + }); + + console.log( + `Alias ${alias.namespace}@${alias.alias} updated to: ${data.tag_id}` + ); + process.exit(0); + } + console.log("Template created: " + data.tag_id); process.exit(0); } catch (error) { @@ -339,6 +371,37 @@ type CreateSandboxParams = { ipcountry?: string; }; +function createAlias(directory: string, alias: string) { + const aliasParts = alias.split("@"); + + if (aliasParts.length > 2) { + throw new Error( + `Alias name "${alias}" is invalid, must be in the format of name@tag` + ); + } + + const namespace = + aliasParts.length === 2 ? aliasParts[0] : path.basename(directory); + const tag = aliasParts.length === 2 ? aliasParts[1] : alias; + + if (namespace.length > 64 || tag.length > 64) { + throw new Error( + `Alias name "${namespace}" or tag "${tag}" is too long, must be 64 characters or less` + ); + } + + if (!/^[a-zA-Z0-9-_]+$/.test(namespace) || !/^[a-zA-Z0-9-_]+$/.test(tag)) { + throw new Error( + `Alias name "${namespace}" or tag "${tag}" is invalid, must only contain upper/lower case letters, numbers, dashes and underscores` + ); + } + + return { + namespace, + alias, + }; +} + async function createSandbox({ apiClient, shaTag, From e535878b006bd9f7c77e960d34a084d512148dea Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 3 Jun 2025 12:17:58 +0200 Subject: [PATCH 139/241] update openapi --- openapi.json | 103 ++++++++++++++++++++++++++++ src/api-clients/client/sdk.gen.ts | 24 ++++++- src/api-clients/client/types.gen.ts | 50 ++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) diff --git a/openapi.json b/openapi.json index 737bb9e..dda6543 100644 --- a/openapi.json +++ b/openapi.json @@ -72,6 +72,51 @@ "title": "VMUpdateHibernationTimeoutRequest", "type": "object" }, + "VMAssignTagAliasResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "alias": { "type": "string" }, + "namespace": { "type": "string" }, + "tag_alias_id": { "type": "string" }, + "tag_id": { "type": "string" }, + "team_id": { "type": "string" } + }, + "required": [ + "tag_alias_id", + "team_id", + "tag_id", + "namespace", + "alias" + ], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "VMAssignTagAliasResponse", + "type": "object" + }, "PreviewToken": { "properties": { "expires_at": { "nullable": true, "type": "string" }, @@ -490,6 +535,13 @@ "title": "TokenUpdateRequest", "type": "object" }, + "VMAssignTagAliasRequest": { + "description": "Assign a tag alias to a VM", + "properties": { "tag_id": { "type": "string" } }, + "required": ["tag_id"], + "title": "VMAssignTagAliasRequest", + "type": "object" + }, "VMHibernateResponse": { "allOf": [ { @@ -1880,6 +1932,57 @@ "tags": [] } }, + "/vm/alias/{namespace}/{alias}": { + "put": { + "callbacks": {}, + "description": "Assign a tag alias to a VM tag.\n", + "operationId": "vm/assign_tag_alias", + "parameters": [ + { + "description": "Tag alias namespace", + "example": "my-project", + "in": "path", + "name": "namespace", + "required": true, + "schema": { "type": "string" } + }, + { + "description": "Tag alias", + "example": "latest", + "in": "path", + "name": "alias", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMAssignTagAliasRequest" + } + } + }, + "description": "VM Assign Tag Alias Request", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VMAssignTagAliasResponse" + } + } + }, + "description": "VM Assign Tag Alias Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Assign a tag alias to a VM tag", + "tags": ["vm"] + } + }, "/vm/clusters": { "get": { "callbacks": {}, diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 492199a..704cfdc 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -250,6 +250,28 @@ export const previewTokenUpdate = (options }); }; +/** + * Assign a tag alias to a VM tag + * Assign a tag alias to a VM tag. + * + */ +export const vmAssignTagAlias = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/alias/{namespace}/{alias}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * List all available clusters * List all available clusters. diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index c92d927..fa3cb12 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -29,6 +29,21 @@ export type VmUpdateHibernationTimeoutRequest = { hibernation_timeout_seconds: number; }; +export type VmAssignTagAliasResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + alias: string; + namespace: string; + tag_alias_id: string; + tag_id: string; + team_id: string; + }; +}; + export type PreviewToken = { expires_at: string | null; last_used_at: string | null; @@ -243,6 +258,13 @@ export type TokenUpdateRequest = { scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; +/** + * Assign a tag alias to a VM + */ +export type VmAssignTagAliasRequest = { + tag_id: string; +}; + export type VmHibernateResponse = { errors?: Array Date: Tue, 3 Jun 2025 13:51:34 +0200 Subject: [PATCH 140/241] first version --- esbuild.cjs | 4 +- package-lock.json | 4 + package.json | 2 +- src/bin/main.tsx | 9 +- src/bin/ui/Dashboard.tsx | 453 +++++++++++++++----------------------- src/bin/ui/api.ts | 37 ++++ src/bin/ui/sdkContext.ts | 16 -- src/bin/ui/sdkContext.tsx | 15 ++ 8 files changed, 242 insertions(+), 298 deletions(-) create mode 100644 src/bin/ui/api.ts delete mode 100644 src/bin/ui/sdkContext.ts create mode 100644 src/bin/ui/sdkContext.tsx diff --git a/esbuild.cjs b/esbuild.cjs index b3e3e17..091fdb0 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -97,9 +97,7 @@ Promise.all([ js: `#!/usr/bin/env node\n\n`, }, // ORA is an ESM module so we have to include it in the build - external: [...nodeExternals, "@codesandbox/sdk"].filter( - (mod) => mod !== "ora" - ), + external: [...nodeExternals, "@codesandbox/sdk", "isbinaryfile"], }), ]).catch(() => { process.exit(1); diff --git a/package-lock.json b/package-lock.json index 05b6b1e..d7af634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,12 @@ "license": "MIT", "dependencies": { "@codesandbox/pitcher-protocol": "0.360.4", + "@inkjs/ui": "^2.0.0", + "@tanstack/react-query": "^5.76.1", + "ink": "^5.2.1", "ora": "^8.2.0", "path": "^0.12.7", + "react": "^18.3.1", "readline": "^1.3.0", "util": "^0.12.5", "ws": "^8.18.2" diff --git a/package.json b/package.json index c629498..73d4c3f 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", - "isbinaryfile": "^5.0.4", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -92,6 +91,7 @@ "dependencies": { "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", + "isbinaryfile": "^5.0.4", "ink": "^5.2.1", "react": "^18.3.1", "@codesandbox/pitcher-protocol": "0.360.4", diff --git a/src/bin/main.tsx b/src/bin/main.tsx index 5e1d2c3..af23250 100644 --- a/src/bin/main.tsx +++ b/src/bin/main.tsx @@ -6,7 +6,6 @@ import { buildCommand } from "./commands/build"; import { sandboxesCommand } from "./commands/sandbox"; import { previewHostsCommand } from "./commands/previewHosts"; import { hostTokensCommand } from "./commands/hostTokens"; -import { CodeSandbox } from "@codesandbox/sdk"; import { Dashboard } from "./ui/Dashboard"; import React from "react"; import { SDKProvider } from "./ui/sdkContext"; @@ -14,13 +13,13 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; if (process.argv.length === 2) { // Clear the screen before rendering the dashboard - process.stdout.write('\x1Bc'); - - const sdk = new CodeSandbox(); + process.stdout.write("\x1Bc"); + const queryClient = new QueryClient(); + render( - + , diff --git a/src/bin/ui/Dashboard.tsx b/src/bin/ui/Dashboard.tsx index a3fa207..a4e2373 100644 --- a/src/bin/ui/Dashboard.tsx +++ b/src/bin/ui/Dashboard.tsx @@ -1,30 +1,8 @@ -import React, { useEffect, useState } from "react"; +import React, { memo, useEffect, useRef, useState } from "react"; import { Box, Text, useInput, useStdout } from "ink"; -import { useQuery } from "@tanstack/react-query"; -import { CodeSandbox } from "@codesandbox/sdk"; import { useSDK } from "./sdkContext"; - -function formatAge(date: Date): string { - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - if (days > 0) return `${days}d`; - if (hours > 0) return `${hours}h`; - if (minutes > 0) return `${minutes}m`; - return `${seconds}s`; -} - -async function fetchSandboxes() { - const sdk = new CodeSandbox(); - // Fetch first 10 sandboxes (can be paginated later) - const { sandboxes } = await sdk.sandboxes.list({ - pagination: { page: 1, pageSize: 10 }, - }); - return sandboxes; -} +import { useQuery } from "@tanstack/react-query"; +import { getSandbox, getRunningVms } from "./api"; // Custom hook to get terminal size function useTerminalSize() { @@ -41,151 +19,22 @@ function useTerminalSize() { return size; } -// Main menu component with navigation +// Component to open a sandbox by ID export function Dashboard() { - const [selectedOption, setSelectedOption] = useState(0); - const [currentView, setCurrentView] = useState(null); - const [stdoutWidth, stdoutHeight] = useTerminalSize(); - - const menuOptions = ["Open Sandbox", "List Sandboxes"]; - - // Handle keyboard input for menu navigation - useInput((input, key) => { - // Navigation is only active when no view is selected - if (currentView === null) { - if (key.upArrow) { - setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); - } else if (key.downArrow) { - setSelectedOption((prev) => - prev < menuOptions.length - 1 ? prev + 1 : prev - ); - } else if (key.return) { - setCurrentView(menuOptions[selectedOption]); - } - } else if (key.escape) { - // Allow going back to main menu with ESC key - setCurrentView(null); - } + // Poll getRunningVms API every 2 seconds + const runningVmsQuery = useQuery({ + queryKey: ["runningVms"], + queryFn: getRunningVms, + refetchInterval: 2000, // Poll every 2 seconds }); - // Render the current view or menu - if (currentView === "List Sandboxes") { - return setCurrentView(null)} />; - } else if (currentView === "Open Sandbox") { - return setCurrentView(null)} />; - } - - // Render main menu - return ( - - - CodeSandbox Menu - - {menuOptions.map((option, index) => ( - - - {selectedOption === index ? "> " : " "} - {option} - - - ))} - - Use arrow keys to navigate, Enter to select - - - ); -} - -// Component to list sandboxes -function ListSandboxes({ onBack }: { onBack: () => void }) { - const { data, isLoading, isError, error } = useQuery({ - queryKey: ["sandboxes"], - queryFn: fetchSandboxes, - }); - - // Get terminal dimensions - const [stdoutWidth, stdoutHeight] = useTerminalSize(); - - useInput((input, key) => { - if (key.escape) { - onBack(); - } - }); - - if (isLoading) return Loading sandboxes...; - if (isError) return Error: {String(error)}; - if (!data || data.length === 0) return No sandboxes found.; - - // Define column widths - const COLS = [ - { key: "id", label: "ID", width: 10 }, - { key: "title", label: "TITLE", width: 24 }, - { key: "privacy", label: "PRIVACY", width: 10 }, - { key: "tags", label: "TAGS", width: 20 }, - { key: "age", label: "AGE", width: 6 }, - ]; - - // Helper to pad and trim cell content - const pad = (str: string, width: number) => { - if (str.length > width) return str.slice(0, width - 1) + "…"; - return str.padEnd(width, " "); - }; - - // Render header - const header = COLS.map((col) => pad(col.label, col.width)).join(" "); - - // Render rows - const rows = data.map((sandbox: any) => { - const tags = Array.isArray(sandbox.tags) ? sandbox.tags.join(",") : ""; - const age = sandbox.updatedAt - ? formatAge(new Date(sandbox.updatedAt)) - : "-"; - const cells = [ - pad(String(sandbox.id), COLS[0].width), - pad(String(sandbox.title), COLS[1].width), - pad(String(sandbox.privacy), COLS[2].width), - pad(tags, COLS[3].width), - pad(age, COLS[4].width), - ]; - return cells.join(" "); - }); - - // Calculate how many rows fit (1 for header) - const maxRows = Math.max(stdoutHeight - 3, 0); // Leave space for instructions - const visibleRows = rows.slice(0, maxRows); - - // Pad with empty rows if needed to fill the window - while (visibleRows.length < maxRows) { - visibleRows.push(""); - } - - return ( - - - Sandboxes List - - {header} - {visibleRows.map((row, i) => ( - {row} - ))} - - Press ESC to return to menu - - - ); -} - -// Component to open a sandbox by ID -function OpenSandbox({ onBack }: { onBack: () => void }) { const [sandboxId, setSandboxId] = useState(""); const [showSandbox, setShowSandbox] = useState(false); const [isFocused, setIsFocused] = useState(true); const [stdoutWidth, stdoutHeight] = useTerminalSize(); useInput((input, key) => { - if (key.escape && !showSandbox) { - onBack(); - } else if (isFocused && !showSandbox) { + if (!showSandbox) { if (key.return) { if (sandboxId.trim()) { setShowSandbox(true); @@ -200,142 +49,200 @@ function OpenSandbox({ onBack }: { onBack: () => void }) { }); if (showSandbox) { - return setShowSandbox(false)} />; + const state = runningVmsQuery.isFetching + ? "PENDING" + : runningVmsQuery.data?.vms.find((vm) => vm.id === sandboxId) + ? "RUNNING" + : "IDLE"; + + return ( + setShowSandbox(false)} + /> + ); } return ( - Open Sandbox + CodeSandbox Enter Sandbox ID: {sandboxId || "_"} - - Type to input ID, press ENTER to open, ESC to return to menu - + Type to input ID, press ENTER to open ); } // Component to display a sandbox -function Sandbox({ id, onBack }: { id: string; onBack: () => void }) { - const sdk = useSDK(); - - // Only two states: RUNNING or IDLE - const [sandboxState, setSandboxState] = useState< - "RUNNING" | "IDLE" | "STARTING" - >("IDLE"); - const [selectedOption, setSelectedOption] = useState(0); - const [stdoutWidth, stdoutHeight] = useTerminalSize(); - - // Define menu options based on state - const getMenuOptions = () => { - switch (sandboxState) { - case "RUNNING": - return ["Hibernate", "Shutdown", "Restart"]; - case "IDLE": - return ["Start"]; - case "STARTING": - return []; - default: - return []; - } - }; +const Sandbox = memo( + ({ + id, + runningState, + onBack, + }: { + id: string; + runningState: "RUNNING" | "IDLE" | "PENDING"; + onBack: () => void; + }) => { + const sandboxQuery = useQuery({ + queryKey: ["sandbox", id], + queryFn: () => getSandbox(id), + }); + const runningStateRef = useRef(runningState); + + const sdk = useSDK(); + + // Only two states: RUNNING or IDLE + const [sandboxState, setSandboxState] = useState< + "RUNNING" | "IDLE" | "PENDING" + >(runningState); + const [selectedOption, setSelectedOption] = useState(0); + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + + // We only want to update the state when the + // running state has ACTUALLY changed (Reconciliation sucks) + useEffect(() => { + if ( + sandboxState !== "PENDING" && + runningStateRef.current !== runningState + ) { + runningStateRef.current = runningState; + setSandboxState(runningState); + } + }, [runningState, sandboxState]); + + // Define menu options based on state + const getMenuOptions = () => { + switch (sandboxState) { + case "RUNNING": + return ["Hibernate", "Shutdown", "Restart"]; + case "IDLE": + return ["Start"]; + default: + return []; + } + }; - const menuOptions = getMenuOptions(); + const menuOptions = getMenuOptions(); - // Handle menu options - const handleAction = async (action: string) => { - switch (action) { - case "Hibernate": - case "Shutdown": - setSandboxState("IDLE"); - setSelectedOption(0); - break; - case "Restart": - setSandboxState("STARTING"); - setTimeout(() => { + // Handle menu options + const handleAction = async (action: string) => { + switch (action) { + case "Hibernate": + case "Shutdown": + setSandboxState("PENDING"); + await sdk.sandboxes.shutdown(id); + setSandboxState("IDLE"); + setSelectedOption(0); + break; + case "Restart": + setSandboxState("PENDING"); + await sdk.sandboxes.restart(id); setSandboxState("RUNNING"); setSelectedOption(0); - }, 2000); - break; - case "Start": - setSandboxState("STARTING"); - await sdk.sandboxes.resume(id); - setSandboxState("RUNNING"); - setSelectedOption(0); - break; - } - }; - - // Handle keyboard navigation - useInput((input, key) => { - if (key.escape) { - onBack(); - } else if (menuOptions.length > 0) { - if (key.upArrow) { - setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); - } else if (key.downArrow) { - setSelectedOption((prev) => - prev < menuOptions.length - 1 ? prev + 1 : prev - ); - } else if (key.return) { - handleAction(menuOptions[selectedOption]); + break; + case "Start": + setSandboxState("PENDING"); + await sdk.sandboxes.resume(id); + setSandboxState("RUNNING"); + setSelectedOption(0); + break; } - } - }); - - return ( - - - Sandbox: {id} - - - Status: - - {sandboxState} - - - - {menuOptions.length > 0 && ( - - Actions: - {menuOptions.map((option, index) => ( - - - {selectedOption === index ? "> " : " "} - {option} - - - ))} - - )} + }; - {sandboxState === "STARTING" && ( + // Handle keyboard navigation + useInput((input, key) => { + if (key.escape) { + onBack(); + } else if (menuOptions.length > 0) { + if (key.upArrow) { + setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); + } else if (key.downArrow) { + setSelectedOption((prev) => + prev < menuOptions.length - 1 ? prev + 1 : prev + ); + } else if (key.return) { + handleAction(menuOptions[selectedOption]); + } + } + }); + + return ( + + {/* Handle query states */} + {sandboxQuery.isLoading && ( + + Loading sandbox information... + + )} + + {sandboxQuery.error && ( + + + Error loading sandbox: {(sandboxQuery.error as Error).message} + + + )} + + {sandboxQuery.data && ( + + + {sandboxQuery.data.title} - {id} + + + {sandboxQuery.data.description && ( + + {sandboxQuery.data.description} + + )} + + )} + + {/* Status display - moved above title and description */} - Sandbox is starting... + Status: + + {sandboxState} + - )} - - - {menuOptions.length > 0 - ? "Use arrow keys to navigate, Enter to select, ESC to go back" - : "Press ESC to go back"} - + {menuOptions.length > 0 && ( + + Actions: + {menuOptions.map((option, index) => ( + + + {selectedOption === index ? "> " : " "} + {option} + + + ))} + + )} + + + + {menuOptions.length > 0 + ? "Use arrow keys to navigate, Enter to select, ESC to go back" + : "Press ESC to go back"} + + - - ); -} + ); + } +); diff --git a/src/bin/ui/api.ts b/src/bin/ui/api.ts new file mode 100644 index 0000000..cef9a8e --- /dev/null +++ b/src/bin/ui/api.ts @@ -0,0 +1,37 @@ +import { createContext, useContext } from "react"; +import { Client, createClient, createConfig } from "@hey-api/client-fetch"; +import { getApiKey } from "../utils/constants"; +import { sandboxGet, vmListRunningVms } from "../../api-clients/client"; + +const apiClient = createClient( + createConfig({ + baseUrl: process.env.BASE_URL || "https://api.codesandbox.io", + headers: { + Authorization: `Bearer ${getApiKey()}`, + }, + }) +); + +function getResponse(resp: { data?: { data?: T }; error: unknown }) { + if (resp.error) { + throw resp.error; + } + + if (!resp.data || !resp.data.data) { + return null; + } + + return resp.data.data; +} + +export async function getSandbox(sandboxId: string) { + const resp = await sandboxGet({ client: apiClient, path: { id: sandboxId } }); + + return getResponse(resp); +} + +export async function getRunningVms() { + const resp = await vmListRunningVms({ client: apiClient }); + + return getResponse(resp); +} diff --git a/src/bin/ui/sdkContext.ts b/src/bin/ui/sdkContext.ts deleted file mode 100644 index e9a1715..0000000 --- a/src/bin/ui/sdkContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createContext, useContext } from "react"; -import { CodeSandbox } from "@codesandbox/sdk"; - -export const SDKContext = createContext(null); - -export const SDKProvider = SDKContext.Provider; - -export function useSDK() { - const sdk = useContext(SDKContext); - - if (!sdk) { - throw new Error("No SDK provided"); - } - - return sdk; -} diff --git a/src/bin/ui/sdkContext.tsx b/src/bin/ui/sdkContext.tsx new file mode 100644 index 0000000..1e92445 --- /dev/null +++ b/src/bin/ui/sdkContext.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; +import { createContext, useContext } from "react"; +import { CodeSandbox } from "@codesandbox/sdk"; + +const sdk = new CodeSandbox(); + +export const SDKContext = createContext(sdk); + +export const SDKProvider = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export function useSDK() { + return useContext(SDKContext); +} From 8b5a4f0df86c67406710557e1df56c03df28eb42 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 4 Jun 2025 12:58:38 +0200 Subject: [PATCH 141/241] recover building templates --- src/Sandbox.ts | 74 ++++++++++++++-------- src/bin/commands/build.ts | 129 +++++++++++++++++++++++++++++++------- 2 files changed, 156 insertions(+), 47 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 3cc05ae..b97c0f2 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -144,6 +144,19 @@ export class Sandbox { return session; } + private getCustomEnv(customSession?: SessionCreateOptions) { + if (!customSession) { + return undefined; + } + + return customSession.git + ? { + ...customSession.env, + GIT_CONFIG: "$HOME/private/.gitconfig", + } + : customSession.env; + } + /** * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. */ @@ -178,39 +191,48 @@ export class Sandbox { const session = await Session.create(agentClient, { username: customSession ? joinResult.client.username : undefined, - env: customSession?.env, + env: this.getCustomEnv(customSession), hostToken: customSession?.hostToken, }); if (customSession?.git) { - const netrc = await session.commands.runBackground([ - `mkdir -p ~/private`, - `cat > ~/private/.netrc < ~/private/.gitconfig < { const clusterApiClient: Client = createClient( createConfig({ @@ -129,14 +130,6 @@ export const buildCommand: yargs.CommandModule< }, }) ); - const sdk = new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); - - let sandboxId: string | undefined; try { const { hash, files: filePaths } = await hashDirectory( @@ -146,7 +139,8 @@ export const buildCommand: yargs.CommandModule< const tag = `sha:${shortHash}-${slug}`; spinner.start(updateSpinnerMessage(index, "Creating sandbox...")); - sandboxId = await createSandbox({ + + const sandboxId = await createSandbox({ apiClient: clusterApiClient, shaTag: tag, fromSandbox: argv.fromSandbox, @@ -157,6 +151,40 @@ export const buildCommand: yargs.CommandModule< : VMTier.fromName("Micro"), }); + return { sandboxId, filePaths, cluster }; + } catch (error) { + spinner.fail( + updateSpinnerMessage( + index, + error instanceof Error + ? error.message + : "Unknown error occurred" + ) + ); + throw error; + } + }) + ); + + const results = await Promise.allSettled( + sandboxes.map(async ({ sandboxId, filePaths, cluster }, index) => { + const clusterApiClient: Client = createClient( + createConfig({ + baseUrl: BASE_URL, + headers: { + Authorization: `Bearer ${API_KEY}`, + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }) + ); + const sdk = new CodeSandbox(API_KEY, { + baseUrl: BASE_URL, + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); + + try { spinner.start( updateSpinnerMessage(index, "Starting sandbox...", sandboxId) ); @@ -209,6 +237,7 @@ export const buildCommand: yargs.CommandModule< ? VMTier.fromName(argv.vmTier) : VMTier.fromName("Micro"), }); + session = await sandbox.connect(); const disposableStore = new DisposableStore(); @@ -295,11 +324,7 @@ export const buildCommand: yargs.CommandModule< } spinner.start( - updateSpinnerMessage( - index, - "Creating memory snapshot...", - sandboxId - ) + updateSpinnerMessage(index, "Creating snapshot...", sandboxId) ); await sdk.sandboxes.hibernate(sandbox.id); spinner.start( @@ -308,27 +333,75 @@ export const buildCommand: yargs.CommandModule< return sandbox.id; } catch (error) { - spinner.fail( + spinner.start( updateSpinnerMessage( index, - error instanceof Error - ? error.message - : "Unknown error occurred", + "Failed, please manually verify at https://codesandbox.io/s/" + + sandboxId, sandboxId ) ); + throw error; } }) ); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); + const failedSandboxes = sandboxes.filter( + (_, index) => results[index].status === "rejected" + ); + + if (failedSandboxes.length > 0) { + spinner.info(`\n${spinnerMessages.join("\n")}`); + + await waitForEnter( + `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes + .map((sandbox) => sandbox.sandboxId) + .join(", ")} and press ENTER to create snapshot...\n` + ); + + failedSandboxes.forEach(({ sandboxId }) => { + updateSpinnerMessage( + sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), + "Creating snapshot...", + sandboxId + ); + }); + + spinner.start(`\n${spinnerMessages.join("\n")}`); + + await Promise.all( + failedSandboxes.map(async ({ sandboxId, cluster }) => { + const sdk = new CodeSandbox(API_KEY, { + baseUrl: BASE_URL, + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); + + await sdk.sandboxes.hibernate(sandboxId); + + spinner.start( + updateSpinnerMessage( + sandboxes.findIndex( + (sandbox) => sandbox.sandboxId === sandboxId + ), + "Snapshot created", + sandboxId + ) + ); + }) + ); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } else { + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } const data = handleResponse( await vmCreateTag({ client: apiClient, body: { - vm_ids: sandboxIds, + vm_ids: sandboxes.map((sandbox) => sandbox.sandboxId), }, }), "Failed to create template" @@ -371,6 +444,20 @@ type CreateSandboxParams = { ipcountry?: string; }; +function waitForEnter(message: string) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + rl.question(message, () => { + rl.close(); + resolve(); + }); + }); +} + function createAlias(directory: string, alias: string) { const aliasParts = alias.split("@"); From d22794f119e44246bd12608c2dc83c83d2fd7fef Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 5 Jun 2025 08:47:28 +0200 Subject: [PATCH 142/241] fix polling of running state --- package.json | 2 +- src/bin/main.tsx | 7 ++++++- src/bin/ui/Dashboard.tsx | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 73d4c3f..089ad04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.3", + "version": "2.0.0-rc.4", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/main.tsx b/src/bin/main.tsx index af23250..52e94ad 100644 --- a/src/bin/main.tsx +++ b/src/bin/main.tsx @@ -9,7 +9,12 @@ import { hostTokensCommand } from "./commands/hostTokens"; import { Dashboard } from "./ui/Dashboard"; import React from "react"; import { SDKProvider } from "./ui/sdkContext"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + focusManager, + onlineManager, + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query"; if (process.argv.length === 2) { // Clear the screen before rendering the dashboard diff --git a/src/bin/ui/Dashboard.tsx b/src/bin/ui/Dashboard.tsx index a4e2373..b7ae6ae 100644 --- a/src/bin/ui/Dashboard.tsx +++ b/src/bin/ui/Dashboard.tsx @@ -25,7 +25,6 @@ export function Dashboard() { const runningVmsQuery = useQuery({ queryKey: ["runningVms"], queryFn: getRunningVms, - refetchInterval: 2000, // Poll every 2 seconds }); const [sandboxId, setSandboxId] = useState(""); @@ -33,6 +32,17 @@ export function Dashboard() { const [isFocused, setIsFocused] = useState(true); const [stdoutWidth, stdoutHeight] = useTerminalSize(); + useEffect(() => { + // have to manually do this because of environment + const interval = setInterval(() => { + runningVmsQuery.refetch(); + }, 2000); + + return () => { + clearInterval(interval); + }; + }, []); + useInput((input, key) => { if (!showSandbox) { if (key.return) { @@ -49,7 +59,7 @@ export function Dashboard() { }); if (showSandbox) { - const state = runningVmsQuery.isFetching + const state = runningVmsQuery.isLoading ? "PENDING" : runningVmsQuery.data?.vms.find((vm) => vm.id === sandboxId) ? "RUNNING" From cf000544eefce185545addfad7ac5bbfe35e9559 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 5 Jun 2025 09:25:17 +0200 Subject: [PATCH 143/241] introduce ci flag for template building --- package.json | 2 +- src/Sandboxes.ts | 6 +- src/bin/commands/build.ts | 168 +++++++++++++++++++++++--------------- 3 files changed, 109 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 089ad04..6910bfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.4", + "version": "2.0.0-rc.5", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 772e0a4..b4aea0b 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -71,7 +71,11 @@ export class Sandboxes { const templateId = opts?.id || this.defaultTemplateId; const privacy = opts?.privacy || "unlisted"; const tags = opts?.tags || ["sdk"]; - const path = opts?.path || "/SDK"; + let path = opts?.path || "/SDK"; + + if (!path.startsWith("/")) { + path = "/" + path; + } // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 012ab17..1642cce 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -28,6 +28,7 @@ export type BuildCommandArgs = { path?: string; alias?: string; ports?: number[]; + ci: boolean; fromSandbox?: string; skipFiles?: boolean; vmTier?: VmUpdateSpecsRequest["tier"]; @@ -71,6 +72,11 @@ export const buildCommand: yargs.CommandModule< "Alias that should point to the created template. Alias namespace defaults to template directory, but you can explicitly pass `namespace@alias`", type: "string", }) + .option("ci", { + describe: "CI mode, will exit process if any error occurs", + default: false, + type: "boolean", + }) .positional("directory", { describe: "Path to the project that we'll create a snapshot from", type: "string", @@ -166,8 +172,8 @@ export const buildCommand: yargs.CommandModule< }) ); - const results = await Promise.allSettled( - sandboxes.map(async ({ sandboxId, filePaths, cluster }, index) => { + const tasks = sandboxes.map( + async ({ sandboxId, filePaths, cluster }, index) => { const clusterApiClient: Client = createClient( createConfig({ baseUrl: BASE_URL, @@ -199,14 +205,18 @@ export const buildCommand: yargs.CommandModule< `${baseUrl}/api/v1/cluster/${sandboxId}?preferredManager=${cluster}` ).then((res) => res.json()); - const startResponse = await startVm(clusterApiClient, sandboxId, { - vmTier: VMTier.fromName("Micro"), - }); + const startResponse = await withCustomError( + startVm(clusterApiClient, sandboxId, { + vmTier: VMTier.fromName("Micro"), + }), + "Failed to start sandbox" + ); let sandbox = new Sandbox( sandboxId, clusterApiClient, startResponse ); + let session = await sandbox.connect(); spinner.start( @@ -216,29 +226,39 @@ export const buildCommand: yargs.CommandModule< sandboxId ) ); - let i = 0; - for (const filePath of filePaths) { - i++; - const fullPath = path.join(argv.directory, filePath); - const content = await fs.readFile(fullPath); - const dirname = path.dirname(filePath); - await session.fs.mkdir(dirname, true); - await session.fs.writeFile(filePath, content, { - create: true, - overwrite: true, - }); + try { + let i = 0; + for (const filePath of filePaths) { + i++; + const fullPath = path.join(argv.directory, filePath); + const content = await fs.readFile(fullPath); + const dirname = path.dirname(filePath); + await session.fs.mkdir(dirname, true); + await session.fs.writeFile(filePath, content, { + create: true, + overwrite: true, + }); + } + } catch (error) { + throw new Error(`Failed to write files to sandbox: ${error}`); } spinner.start( updateSpinnerMessage(index, "Restarting sandbox...", sandboxId) ); - sandbox = await sdk.sandboxes.restart(sandbox.id, { - vmTier: argv.vmTier - ? VMTier.fromName(argv.vmTier) - : VMTier.fromName("Micro"), - }); + sandbox = await withCustomError( + sdk.sandboxes.restart(sandbox.id, { + vmTier: argv.vmTier + ? VMTier.fromName(argv.vmTier) + : VMTier.fromName("Micro"), + }), + "Failed to restart sandbox" + ); - session = await sandbox.connect(); + session = await withCustomError( + sandbox.connect(), + "Failed to connect to sandbox" + ); const disposableStore = new DisposableStore(); @@ -326,7 +346,10 @@ export const buildCommand: yargs.CommandModule< spinner.start( updateSpinnerMessage(index, "Creating snapshot...", sandboxId) ); - await sdk.sandboxes.hibernate(sandbox.id); + await withCustomError( + sdk.sandboxes.hibernate(sandbox.id), + "Failed to hibernate" + ); spinner.start( updateSpinnerMessage(index, "Snapshot created", sandboxId) ); @@ -337,64 +360,73 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage( index, "Failed, please manually verify at https://codesandbox.io/s/" + - sandboxId, + sandboxId + + " - " + + String(error), sandboxId ) ); throw error; } - }) - ); - - const failedSandboxes = sandboxes.filter( - (_, index) => results[index].status === "rejected" + } ); - if (failedSandboxes.length > 0) { - spinner.info(`\n${spinnerMessages.join("\n")}`); + if (argv.ci) { + await Promise.all(tasks); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } else { + const results = await Promise.allSettled(tasks); - await waitForEnter( - `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes - .map((sandbox) => sandbox.sandboxId) - .join(", ")} and press ENTER to create snapshot...\n` + const failedSandboxes = sandboxes.filter( + (_, index) => results[index].status === "rejected" ); - failedSandboxes.forEach(({ sandboxId }) => { - updateSpinnerMessage( - sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), - "Creating snapshot...", - sandboxId + if (failedSandboxes.length > 0) { + spinner.info(`\n${spinnerMessages.join("\n")}`); + + await waitForEnter( + `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes + .map((sandbox) => sandbox.sandboxId) + .join(", ")} and press ENTER to create snapshot...\n` ); - }); - spinner.start(`\n${spinnerMessages.join("\n")}`); + failedSandboxes.forEach(({ sandboxId }) => { + updateSpinnerMessage( + sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), + "Creating snapshot...", + sandboxId + ); + }); - await Promise.all( - failedSandboxes.map(async ({ sandboxId, cluster }) => { - const sdk = new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); + spinner.start(`\n${spinnerMessages.join("\n")}`); - await sdk.sandboxes.hibernate(sandboxId); + await Promise.all( + failedSandboxes.map(async ({ sandboxId, cluster }) => { + const sdk = new CodeSandbox(API_KEY, { + baseUrl: BASE_URL, + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); - spinner.start( - updateSpinnerMessage( - sandboxes.findIndex( - (sandbox) => sandbox.sandboxId === sandboxId - ), - "Snapshot created", - sandboxId - ) - ); - }) - ); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); - } else { - spinner.succeed(`\n${spinnerMessages.join("\n")}`); + await sdk.sandboxes.hibernate(sandboxId); + + spinner.start( + updateSpinnerMessage( + sandboxes.findIndex( + (sandbox) => sandbox.sandboxId === sandboxId + ), + "Snapshot created", + sandboxId + ) + ); + }) + ); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } else { + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } } const data = handleResponse( @@ -444,6 +476,12 @@ type CreateSandboxParams = { ipcountry?: string; }; +function withCustomError>(promise: T, message: string) { + return promise.catch((error) => { + throw new Error(message + ": " + error.message); + }); +} + function waitForEnter(message: string) { const rl = readline.createInterface({ input: process.stdin, From b692478ddd3e39b754e894a52e33af7267b502cc Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 5 Jun 2025 12:19:47 +0200 Subject: [PATCH 144/241] fix retry --- src/Sandboxes.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index b4aea0b..2d05a3d 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -154,6 +154,7 @@ export class Sandboxes { try { await this.shutdown(sandboxId); didRestart = true; + break; } catch (e) { await sleep(500); } @@ -168,6 +169,7 @@ export class Sandboxes { for (let attempt = 1; attempt <= 3; attempt++) { try { startResponse = await startVm(this.apiClient, sandboxId, opts); + break; } catch (e) { await sleep(500); } From a15cf6da5150bec98c32066710993d0cb5f98236 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 5 Jun 2025 12:48:36 +0200 Subject: [PATCH 145/241] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6910bfb..ebd242f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.5", + "version": "2.0.0-rc.6", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 04ee3010721176a4bd5fb7ea98802088b4d4630a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 5 Jun 2025 15:31:47 +0200 Subject: [PATCH 146/241] new template --- package.json | 2 +- src/utils/api.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebd242f..09edb2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.6", + "version": "2.0.0-rc.7", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/utils/api.ts b/src/utils/api.ts index de2b083..800ddae 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -56,7 +56,8 @@ export function getDefaultTemplateTag(apiClient: Client): string { return "7ngcrf"; } - return "pt_LAVK5kxK8XciqgV2642xRk"; + // Universal template created 2025-06-05 + return "universal@latest"; } export function getDefaultTemplateId(apiClient: Client): string { From 5598377bd3fde9a88d0d1a58f4673cd0991ba03e Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 6 Jun 2025 09:12:59 +0200 Subject: [PATCH 147/241] ports fallback --- src/Session/tasks.ts | 122 ++++++++++++++++++++++++-------------- src/bin/commands/build.ts | 89 +++++++++++++-------------- 2 files changed, 118 insertions(+), 93 deletions(-) diff --git a/src/Session/tasks.ts b/src/Session/tasks.ts index 9908e65..5cb8017 100644 --- a/src/Session/tasks.ts +++ b/src/Session/tasks.ts @@ -27,10 +27,13 @@ export class Tasks { */ async getAll(): Promise { if (!this.cachedTasks) { - const tasks = await this.agentClient.tasks.getTasks(); + const [tasks, ports] = await Promise.all([ + this.agentClient.tasks.getTasks(), + this.agentClient.ports.getPorts(), + ]); this.cachedTasks = Object.values(tasks.tasks).map( - (task) => new Task(this.agentClient, task) + (task) => new Task(this.agentClient, task, ports) ); } @@ -78,6 +81,15 @@ export class Task { return Boolean(this.data.runAtStart); } get ports() { + const configuredPort = this.data.preview?.port; + + // If we have configured a specific port, we only return that port. This is used where + // the environment is not able to assign port automatically (e.g. Next JS) + if (configuredPort) { + return this._ports.filter((port) => port.port === configuredPort); + } + + // Otherwise, we return the ports assigned to the task return this.data.ports; } get status() { @@ -85,52 +97,66 @@ export class Task { } constructor( private agentClient: IAgentClient, - private data: protocol.task.TaskDTO + private data: protocol.task.TaskDTO, + private _ports: protocol.port.Port[] ) { - agentClient.tasks.onTaskUpdate(async (task) => { - if (task.id !== this.id) { - return; - } - - const lastStatus = this.status; - const lastShellId = this.shell?.shellId; - - this.data = task; - - if (lastStatus !== this.status) { - this.onStatusChangeEmitter.fire(this.status); - } - - if ( - this.openedShell && - task.shell && - task.shell.shellId !== lastShellId - ) { - const openedShell = await this.agentClient.shells.open( - task.shell.shellId, - this.openedShell.dimensions - ); - - this.openedShell = { - shellId: openedShell.shellId, - output: openedShell.buffer, - dimensions: this.openedShell.dimensions, - }; - - this.onOutputEmitter.fire("\x1B[2J\x1B[3J\x1B[1;1H"); - openedShell.buffer.forEach((out) => this.onOutputEmitter.fire(out)); - } - }); - - this.agentClient.shells.onShellOut(({ shellId, out }) => { - if (!this.shell || this.shell.shellId !== shellId || !this.openedShell) { - return; - } + this.disposable.addDisposable( + agentClient.ports.onPortsUpdated((ports) => { + this._ports = ports; + }) + ); + this.disposable.addDisposable( + agentClient.tasks.onTaskUpdate(async (task) => { + if (task.id !== this.id) { + return; + } + + const lastStatus = this.status; + const lastShellId = this.shell?.shellId; + + this.data = task; + + if (lastStatus !== this.status) { + this.onStatusChangeEmitter.fire(this.status); + } + + if ( + this.openedShell && + task.shell && + task.shell.shellId !== lastShellId + ) { + const openedShell = await this.agentClient.shells.open( + task.shell.shellId, + this.openedShell.dimensions + ); + + this.openedShell = { + shellId: openedShell.shellId, + output: openedShell.buffer, + dimensions: this.openedShell.dimensions, + }; + + this.onOutputEmitter.fire("\x1B[2J\x1B[3J\x1B[1;1H"); + openedShell.buffer.forEach((out) => this.onOutputEmitter.fire(out)); + } + }) + ); - // Update output for shell - this.openedShell.output.push(out); - this.onOutputEmitter.fire(out); - }); + this.disposable.addDisposable( + this.agentClient.shells.onShellOut(({ shellId, out }) => { + if ( + !this.shell || + this.shell.shellId !== shellId || + !this.openedShell + ) { + return; + } + + // Update output for shell + this.openedShell.output.push(out); + this.onOutputEmitter.fire(out); + }) + ); } async open(dimensions = DEFAULT_SHELL_SIZE) { if (!this.shell) { @@ -169,6 +195,7 @@ export class Task { resolve(task.ports[0]); } }); + this.disposable.addDisposable(disposer); }), new Promise((resolve, reject) => { setTimeout(() => { @@ -191,4 +218,7 @@ export class Task { await this.agentClient.tasks.stopTask(this.id); } } + dispose() { + this.disposable.dispose(); + } } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 1642cce..6132091 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -372,61 +372,56 @@ export const buildCommand: yargs.CommandModule< } ); - if (argv.ci) { - await Promise.all(tasks); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); - } else { - const results = await Promise.allSettled(tasks); + const results = await Promise.allSettled(tasks); - const failedSandboxes = sandboxes.filter( - (_, index) => results[index].status === "rejected" - ); + const failedSandboxes = sandboxes.filter( + (_, index) => results[index].status === "rejected" + ); - if (failedSandboxes.length > 0) { - spinner.info(`\n${spinnerMessages.join("\n")}`); + if (failedSandboxes.length > 0) { + spinner.info(`\n${spinnerMessages.join("\n")}`); - await waitForEnter( - `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes - .map((sandbox) => sandbox.sandboxId) - .join(", ")} and press ENTER to create snapshot...\n` - ); + await waitForEnter( + `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes + .map((sandbox) => sandbox.sandboxId) + .join(", ")} and press ENTER to create snapshot...\n` + ); - failedSandboxes.forEach(({ sandboxId }) => { - updateSpinnerMessage( - sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), - "Creating snapshot...", - sandboxId - ); - }); + failedSandboxes.forEach(({ sandboxId }) => { + updateSpinnerMessage( + sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), + "Creating snapshot...", + sandboxId + ); + }); - spinner.start(`\n${spinnerMessages.join("\n")}`); + spinner.start(`\n${spinnerMessages.join("\n")}`); - await Promise.all( - failedSandboxes.map(async ({ sandboxId, cluster }) => { - const sdk = new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); + await Promise.all( + failedSandboxes.map(async ({ sandboxId, cluster }) => { + const sdk = new CodeSandbox(API_KEY, { + baseUrl: BASE_URL, + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); - await sdk.sandboxes.hibernate(sandboxId); + await sdk.sandboxes.hibernate(sandboxId); - spinner.start( - updateSpinnerMessage( - sandboxes.findIndex( - (sandbox) => sandbox.sandboxId === sandboxId - ), - "Snapshot created", - sandboxId - ) - ); - }) - ); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); - } else { - spinner.succeed(`\n${spinnerMessages.join("\n")}`); - } + spinner.start( + updateSpinnerMessage( + sandboxes.findIndex( + (sandbox) => sandbox.sandboxId === sandboxId + ), + "Snapshot created", + sandboxId + ) + ); + }) + ); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } else { + spinner.succeed(`\n${spinnerMessages.join("\n")}`); } const data = handleResponse( From a0f76f98de9e9b7a7d898d2744059994923a28e4 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 6 Jun 2025 10:05:20 +0200 Subject: [PATCH 148/241] fix timeout --- package-lock.json | 14 +++++----- package.json | 11 ++++---- src/Sandbox.ts | 7 +++-- src/Sandboxes.ts | 59 ++++++++++++++++++++++++++++------------- src/Session/commands.ts | 4 ++- 5 files changed, 59 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7af634..c6b052a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.3", + "version": "2.0.0-rc.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.3", + "version": "2.0.0-rc.7", "license": "MIT", "dependencies": { "@codesandbox/pitcher-protocol": "0.360.4", "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", + "isbinaryfile": "^5.0.4", "ora": "^8.2.0", "path": "^0.12.7", "react": "^18.3.1", @@ -29,6 +30,7 @@ "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", + "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", @@ -38,7 +40,6 @@ "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", - "isbinaryfile": "^5.0.4", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -1032,9 +1033,9 @@ } }, "node_modules/@types/node": { - "version": "22.15.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", - "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "dev": true, "license": "MIT", "dependencies": { @@ -3367,7 +3368,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 18.0.0" diff --git a/package.json b/package.json index 09edb2c..e0ad1d8 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@hey-api/openapi-ts": "^0.63.2", "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", + "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", "blessed": "^0.1.81", @@ -89,16 +90,16 @@ "yargs": "^17.7.2" }, "dependencies": { + "@codesandbox/pitcher-protocol": "0.360.4", "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", - "isbinaryfile": "^5.0.4", "ink": "^5.2.1", - "react": "^18.3.1", - "@codesandbox/pitcher-protocol": "0.360.4", + "isbinaryfile": "^5.0.4", "ora": "^8.2.0", "path": "^0.12.7", + "react": "^18.3.1", "readline": "^1.3.0", - "ws": "^8.18.2", - "util": "^0.12.5" + "util": "^0.12.5", + "ws": "^8.18.2" } } diff --git a/src/Sandbox.ts b/src/Sandbox.ts index b97c0f2..cdc76f9 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -150,10 +150,7 @@ export class Sandbox { } return customSession.git - ? { - ...customSession.env, - GIT_CONFIG: "$HOME/private/.gitconfig", - } + ? { GIT_CONFIG: "$HOME/private/.gitconfig", ...customSession.env } : customSession.env; } @@ -216,6 +213,8 @@ export class Sandbox { `[user] name = ${customSession.git.name || customSession.id} email = ${customSession.git.email} +[init] + defaultBranch = main [credential] helper = store --file ~/private/.gitcredentials`, { diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 2d05a3d..020a056 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -32,27 +32,48 @@ export async function startVm( sandboxId: string, startOpts?: StartSandboxOpts ): Promise { - const startResult = await vmStart({ - client: apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }); + // 2 minutes is the configuration for the API and Manager + const TIMEOUT_SECONDS = 120; + const controller = new AbortController(); + const signal = controller.signal; + const timeoutHandle = setTimeout(() => { + controller.abort(); + }, TIMEOUT_SECONDS * 1000); + + try { + const startResult = await vmStart({ + client: apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + signal, + }); - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); - return getStartResponse(response); + return getStartResponse(response); + } catch (err) { + if (err instanceof Error && err.name === "AbortError") { + throw new Error( + `Request took longer than ${TIMEOUT_SECONDS}s, so we aborted.` + ); + } + + throw err; + } finally { + clearTimeout(timeoutHandle); + } } /** diff --git a/src/Session/commands.ts b/src/Session/commands.ts index 736277e..ca24e69 100644 --- a/src/Session/commands.ts +++ b/src/Session/commands.ts @@ -55,13 +55,15 @@ export class Commands { let commandWithEnv = Object.keys(allEnv).length ? `env ${Object.entries(allEnv) .map(([key, value]) => `${key}=${value}`) - .join(" ")} ${command}` + .join(" ")} bash -c '${command}'` : command; if (opts?.cwd) { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; } + console.log("WTF", commandWithEnv); + const shell = await this.agentClient.shells.create( this.agentClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, From 9fd3edc3ac93729fcc3c8fa21b409b7f0ff30a51 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 6 Jun 2025 12:32:21 +0200 Subject: [PATCH 149/241] git running --- src/Sandbox.ts | 28 ++++++++++------------------ src/Session/commands.ts | 2 -- src/bin/commands/build.ts | 12 +++++++----- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index cdc76f9..a348994 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -150,7 +150,9 @@ export class Sandbox { } return customSession.git - ? { GIT_CONFIG: "$HOME/private/.gitconfig", ...customSession.env } + ? { + ...customSession.env, + } : customSession.env; } @@ -194,33 +196,23 @@ export class Sandbox { if (customSession?.git) { try { - await session.fs.mkdir("/root/private"); + await session.commands.run("mkdir -p $HOME/private"); } catch {} await Promise.all([ - session.fs.writeTextFile( - "/root/private/.gitcredentials", - `https://${customSession.git.username || "x-access-token"}:${ + session.commands.run( + `echo "https://${customSession.git.username || "x-access-token"}:${ customSession.git.accessToken - }@${customSession.git.provider}\n`, - { - create: true, - overwrite: true, - } + }@${customSession.git.provider}" > $HOME/private/.gitcredentials` ), - session.fs.writeTextFile( - "/root/private/.gitconfig", - `[user] + session.commands.run( + `echo "[user] name = ${customSession.git.name || customSession.id} email = ${customSession.git.email} [init] defaultBranch = main [credential] - helper = store --file ~/private/.gitcredentials`, - { - create: true, - overwrite: true, - } + helper = store --file ~/private/.gitcredentials" > $HOME/.gitconfig` ), ]); } diff --git a/src/Session/commands.ts b/src/Session/commands.ts index ca24e69..1aa22ce 100644 --- a/src/Session/commands.ts +++ b/src/Session/commands.ts @@ -62,8 +62,6 @@ export class Commands { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; } - console.log("WTF", commandWithEnv); - const shell = await this.agentClient.shells.create( this.agentClient.workspacePath, opts?.dimensions ?? DEFAULT_SHELL_SIZE, diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 6132091..c090bad 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -359,10 +359,12 @@ export const buildCommand: yargs.CommandModule< spinner.start( updateSpinnerMessage( index, - "Failed, please manually verify at https://codesandbox.io/s/" + - sandboxId + - " - " + - String(error), + argv.ci + ? String(error) + : "Failed, please manually verify at https://codesandbox.io/s/" + + sandboxId + + " - " + + String(error), sandboxId ) ); @@ -378,7 +380,7 @@ export const buildCommand: yargs.CommandModule< (_, index) => results[index].status === "rejected" ); - if (failedSandboxes.length > 0) { + if (!argv.ci && failedSandboxes.length > 0) { spinner.info(`\n${spinnerMessages.join("\n")}`); await waitForEnter( From c04ffdfcc63e8045a4bcf954220a11918017989c Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 6 Jun 2025 13:17:44 +0200 Subject: [PATCH 150/241] fix --- package.json | 2 +- src/bin/commands/sandbox/fork.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e0ad1d8..dfeed3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.7", + "version": "2.0.0-rc.8", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/sandbox/fork.ts b/src/bin/commands/sandbox/fork.ts index 0d72b85..42a3aa1 100644 --- a/src/bin/commands/sandbox/fork.ts +++ b/src/bin/commands/sandbox/fork.ts @@ -7,7 +7,6 @@ export async function forkSandbox(sandboxId: string) { const spinner = ora("Forking sandbox...").start(); const sandbox2 = await sdk.sandboxes.create({ - source: "template", id: sandboxId, }); spinner.succeed("Sandbox forked successfully"); From 6f34b3c116d8a33f2d77c4410c7ed928beee293d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 6 Jun 2025 16:31:37 +0200 Subject: [PATCH 151/241] env fix --- src/Sandbox.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index a348994..1d9661a 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -145,15 +145,7 @@ export class Sandbox { } private getCustomEnv(customSession?: SessionCreateOptions) { - if (!customSession) { - return undefined; - } - - return customSession.git - ? { - ...customSession.env, - } - : customSession.env; + return customSession?.env; } /** From 81f63e761ad532865bbd4262d5b0158a564b8fda Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 13 Jun 2025 08:51:23 +0200 Subject: [PATCH 152/241] new node client --- build/plugins.cjs | 38 ++ build/utils.cjs | 8 + esbuild.cjs | 211 ++++--- package-lock.json | 61 ++- package.json | 15 +- src/Sandbox.ts | 250 +++++---- src/{Session => SandboxClient}/commands.ts | 15 +- src/{Session => SandboxClient}/filesystem.ts | 2 +- src/{Session => SandboxClient}/git.ts | 2 +- src/{Session => SandboxClient}/hosts.ts | 0 src/{Session => SandboxClient}/index.ts | 21 +- .../interpreters.ts | 0 src/{Session => SandboxClient}/ports.ts | 2 +- src/{Session => SandboxClient}/setup.ts | 4 +- src/{Session => SandboxClient}/tasks.ts | 4 +- src/{Session => SandboxClient}/terminals.ts | 15 +- src/Sandboxes.ts | 67 +-- src/bin/commands/build.ts | 70 +-- src/bin/commands/previewHosts.ts | 50 +- src/bin/main.tsx | 7 +- src/bin/ui/api.ts | 17 +- src/bin/utils/constants.ts | 14 - src/browser/BrowserAgentClient.ts | 2 +- src/browser/index.ts | 19 +- src/index.ts | 40 +- .../index.ts => node/AgentClient.ts} | 14 +- .../AgentConnection.ts | 7 +- .../PendingPitcherMessage.ts | 4 +- .../WebSocketClient.ts | 2 +- src/{ => node}/agent-client-interface.ts | 4 +- src/node/index.ts | 47 ++ src/pitcher-protocol/capabilities.ts | 190 +++++++ src/pitcher-protocol/errors.ts | 52 ++ src/pitcher-protocol/index.ts | 100 ++++ src/pitcher-protocol/message.ts | 75 +++ src/pitcher-protocol/messages/ai.ts | 297 ++++++++++ src/pitcher-protocol/messages/box.ts | 51 ++ src/pitcher-protocol/messages/channel.ts | 99 ++++ src/pitcher-protocol/messages/client.ts | 116 ++++ src/pitcher-protocol/messages/command.ts | 62 +++ src/pitcher-protocol/messages/container.ts | 25 + src/pitcher-protocol/messages/file.ts | 249 +++++++++ src/pitcher-protocol/messages/fs.ts | 515 ++++++++++++++++++ src/pitcher-protocol/messages/git.ts | 306 +++++++++++ src/pitcher-protocol/messages/language.ts | 125 +++++ src/pitcher-protocol/messages/notification.ts | 50 ++ src/pitcher-protocol/messages/port.ts | 34 ++ src/pitcher-protocol/messages/setup.ts | 103 ++++ src/pitcher-protocol/messages/shell.md | 185 +++++++ src/pitcher-protocol/messages/shell.ts | 230 ++++++++ src/pitcher-protocol/messages/system.ts | 84 +++ src/pitcher-protocol/messages/task.ts | 191 +++++++ src/pitcher-protocol/protocol.ts | 58 ++ src/pitcher-protocol/subscriptions.ts | 76 +++ src/pitcher-protocol/types.ts | 8 + src/types.ts | 20 +- src/utils/api.ts | 78 ++- src/utils/constants.ts | 30 + 58 files changed, 3904 insertions(+), 517 deletions(-) create mode 100644 build/plugins.cjs create mode 100644 build/utils.cjs rename src/{Session => SandboxClient}/commands.ts (94%) rename src/{Session => SandboxClient}/filesystem.ts (98%) rename src/{Session => SandboxClient}/git.ts (96%) rename src/{Session => SandboxClient}/hosts.ts (100%) rename src/{Session => SandboxClient}/index.ts (94%) rename src/{Session => SandboxClient}/interpreters.ts (100%) rename src/{Session => SandboxClient}/ports.ts (98%) rename src/{Session => SandboxClient}/setup.ts (97%) rename src/{Session => SandboxClient}/tasks.ts (97%) rename src/{Session => SandboxClient}/terminals.ts (90%) delete mode 100644 src/bin/utils/constants.ts rename src/{NodeAgentClient/index.ts => node/AgentClient.ts} (96%) rename src/{NodeAgentClient => node}/AgentConnection.ts (97%) rename src/{NodeAgentClient => node}/PendingPitcherMessage.ts (97%) rename src/{NodeAgentClient => node}/WebSocketClient.ts (99%) rename src/{ => node}/agent-client-interface.ts (97%) create mode 100644 src/node/index.ts create mode 100644 src/pitcher-protocol/capabilities.ts create mode 100644 src/pitcher-protocol/errors.ts create mode 100644 src/pitcher-protocol/index.ts create mode 100644 src/pitcher-protocol/message.ts create mode 100644 src/pitcher-protocol/messages/ai.ts create mode 100644 src/pitcher-protocol/messages/box.ts create mode 100644 src/pitcher-protocol/messages/channel.ts create mode 100644 src/pitcher-protocol/messages/client.ts create mode 100644 src/pitcher-protocol/messages/command.ts create mode 100644 src/pitcher-protocol/messages/container.ts create mode 100644 src/pitcher-protocol/messages/file.ts create mode 100644 src/pitcher-protocol/messages/fs.ts create mode 100644 src/pitcher-protocol/messages/git.ts create mode 100644 src/pitcher-protocol/messages/language.ts create mode 100644 src/pitcher-protocol/messages/notification.ts create mode 100644 src/pitcher-protocol/messages/port.ts create mode 100644 src/pitcher-protocol/messages/setup.ts create mode 100644 src/pitcher-protocol/messages/shell.md create mode 100644 src/pitcher-protocol/messages/shell.ts create mode 100644 src/pitcher-protocol/messages/system.ts create mode 100644 src/pitcher-protocol/messages/task.ts create mode 100644 src/pitcher-protocol/protocol.ts create mode 100644 src/pitcher-protocol/subscriptions.ts create mode 100644 src/pitcher-protocol/types.ts create mode 100644 src/utils/constants.ts diff --git a/build/plugins.cjs b/build/plugins.cjs new file mode 100644 index 0000000..863fe2a --- /dev/null +++ b/build/plugins.cjs @@ -0,0 +1,38 @@ +module.exports.moduleReplacementPlugin = function moduleReplacementPlugin( + replacements +) { + return { + name: "module-replacement", + setup(build) { + for (const [key, value] of Object.entries(replacements)) { + build.onResolve({ filter: new RegExp(`^${value}$`) }, (args) => { + return { path: require.resolve(key) }; + }); + } + }, + }; +}; + +module.exports.forbidImportsPlugin = function forbidImportsPlugin(imports) { + return { + name: "forbid-imports", + setup(build) { + for (const packageName of imports) { + // catch `import ... from 'packageName'` **and** sub-paths `packageName/foo` + const pkgFilter = new RegExp(`^${packageName}($|/)`); + build.onResolve({ filter: pkgFilter }, (args) => { + return { + errors: [ + { + text: `❌ Importing “${packageName}” is forbidden in this project.`, + notes: [ + "If you really need it, talk to your team lead about an exception.", + ], + }, + ], + }; + }); + } + }, + }; +}; diff --git a/build/utils.cjs b/build/utils.cjs new file mode 100644 index 0000000..1e74709 --- /dev/null +++ b/build/utils.cjs @@ -0,0 +1,8 @@ +module.exports.nodeExternals = [ + ...Object.keys(require("../package.json").dependencies), + ...require("module").builtinModules, +]; + +module.exports.define = { + CSB_SDK_VERSION: `"${require("../package.json").version}"`, +}; diff --git a/esbuild.cjs b/esbuild.cjs index 091fdb0..eaadea3 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -1,104 +1,137 @@ const esbuild = require("esbuild"); +const { nodeExternals, define } = require("./build/utils.cjs"); +const { + moduleReplacementPlugin, + forbidImportsPlugin, +} = require("./build/plugins.cjs"); -// Common plugin for module replacements -const browserifyPlugin = { - name: "alias", - setup(build) { - // Handle os module replacement - build.onResolve({ filter: /^os$/ }, (args) => { - return { path: require.resolve("os-browserify/browser") }; - }); +// Until pitcher-client is part of SDK we need to forbid these imports in +// Node builds +const preventPitcherClientImportsPlugin = forbidImportsPlugin([ + "@codesandbox/pitcher-protocol", + "@codesandbox/pitcher-common", +]); - // Handle path module replacement - build.onResolve({ filter: /^path$/ }, (args) => { - return { path: require.resolve("path-browserify") }; - }); - }, -}; - -const nodeExternals = [ - ...Object.keys(require("./package.json").dependencies), - ...require("module").builtinModules, -]; +/** + * BROWSER CLIENT BUILD + */ +const browserPlugin = moduleReplacementPlugin({ + "os-browserify/browser": /^os$/, + "path-browserify": /^path$/, +}); -// Build both CJS and ESM versions -Promise.all([ - // Browser builds: - // CommonJS build - esbuild.build({ - entryPoints: ["src/browser/index.ts"], - bundle: true, - format: "cjs", - // .cjs extension is required because "type": "module" is set in package.json - outfile: "dist/cjs/browser.cjs", - platform: "browser", - // pitcher-common currently requires this, but breaks the first experience - banner: { - js: `if (typeof window !== "undefined" && !window.process) { - window.process = { - env: {}, - }; +const browserCjsBuild = esbuild.build({ + entryPoints: ["src/browser/index.ts"], + bundle: true, + format: "cjs", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/browser.cjs", + platform: "browser", + // pitcher-common currently requires this, but breaks the first experience + banner: { + js: `if (typeof window !== "undefined" && !window.process) { +window.process = { + env: {}, +}; } `, - }, - plugins: [browserifyPlugin], - }), + }, + plugins: [browserPlugin], +}); - // ESM build - esbuild.build({ - entryPoints: ["src/browser/index.ts"], - bundle: true, - format: "esm", - outfile: "dist/esm/browser.js", - platform: "browser", - // pitcher-common currently requires this, but breaks the first experience - banner: { - js: `if (typeof window !== "undefined" && !window.process) { - window.process = { - env: {}, - }; +const browserEsmBuild = esbuild.build({ + entryPoints: ["src/browser/index.ts"], + bundle: true, + format: "esm", + outfile: "dist/esm/browser.js", + platform: "browser", + // pitcher-common currently requires this, but breaks the first experience + banner: { + js: `if (typeof window !== "undefined" && !window.process) { +window.process = { + env: {}, +}; } `, - }, - plugins: [browserifyPlugin], - }), + }, + plugins: [browserPlugin], +}); - // Index builds: - // Node: - // CommonJS build - esbuild.build({ - entryPoints: ["src/index.ts"], - bundle: true, - format: "cjs", - platform: "node", - // .cjs extension is required because "type": "module" is set in package.json - outfile: "dist/cjs/index.cjs", - external: nodeExternals, - }), +/** + * NODE CLIENT BUILD + */ - // ESM build - esbuild.build({ - entryPoints: ["src/index.ts"], - bundle: true, - format: "esm", - platform: "node", - outfile: "dist/esm/index.js", - external: nodeExternals, - }), +const nodeClientCjsBuild = esbuild.build({ + entryPoints: ["src/node/index.ts"], + bundle: true, + format: "cjs", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/node.cjs", + platform: "node", + external: nodeExternals, + plugins: [preventPitcherClientImportsPlugin], +}); + +const nodeClientEsmBuild = esbuild.build({ + entryPoints: ["src/node/index.ts"], + bundle: true, + format: "esm", + outfile: "dist/esm/node.js", + platform: "node", + external: nodeExternals, + plugins: [preventPitcherClientImportsPlugin], +}); - // Bin builds: - esbuild.build({ - entryPoints: ["src/bin/main.tsx"], - outfile: "dist/bin/codesandbox.mjs", - bundle: true, - format: "esm", - platform: "node", - banner: { - js: `#!/usr/bin/env node\n\n`, - }, - // ORA is an ESM module so we have to include it in the build - external: [...nodeExternals, "@codesandbox/sdk", "isbinaryfile"], - }), +/** + * SDK BUILD + */ +const sdkCjsBuild = esbuild.build({ + entryPoints: ["src/index.ts"], + bundle: true, + format: "cjs", + define, + platform: "node", + // .cjs extension is required because "type": "module" is set in package.json + outfile: "dist/cjs/index.cjs", + external: nodeExternals, +}); + +const sdkEsmBuild = esbuild.build({ + entryPoints: ["src/index.ts"], + bundle: true, + format: "esm", + define, + platform: "node", + outfile: "dist/esm/index.js", + external: nodeExternals, + plugins: [preventPitcherClientImportsPlugin], +}); + +/** + * CLI BUILD + */ +const cliBuild = esbuild.build({ + entryPoints: ["src/bin/main.tsx"], + outfile: "dist/bin/codesandbox.mjs", + bundle: true, + define, + format: "esm", + platform: "node", + banner: { + js: `#!/usr/bin/env node\n\n`, + }, + external: [...nodeExternals, "@codesandbox/sdk"], + plugins: [preventPitcherClientImportsPlugin], +}); + +Promise.all([ + browserCjsBuild, + browserEsmBuild, + nodeClientCjsBuild, + nodeClientEsmBuild, + sdkCjsBuild, + sdkEsmBuild, + cliBuild, ]).catch(() => { process.exit(1); }); diff --git a/package-lock.json b/package-lock.json index c6b052a..042b66a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,25 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.7", + "version": "2.0.0-rc.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.7", + "version": "2.0.0-rc.9", "license": "MIT", "dependencies": { - "@codesandbox/pitcher-protocol": "0.360.4", "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", + "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", "util": "^0.12.5", - "ws": "^8.18.2" + "yargs": "^17.7.2" }, "bin": { "csb": "dist/bin/codesandbox.mjs" @@ -47,8 +47,7 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "why-is-node-running": "^2.3.0", - "yargs": "^17.7.2" + "why-is-node-running": "^2.3.0" } }, "node_modules/@absinthe/socket": { @@ -204,6 +203,7 @@ "version": "0.360.2", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-common/-/pitcher-common-0.360.2.tgz", "integrity": "sha512-FWy4YgDh0LFZRo9N8j7mTSoKyIftWJVs/9LR5rwzAta0lFUAV2X8hsGMzt0u16Ng5Wpbi92svMZQy6WygC93gg==", + "dev": true, "license": "GPL-3.0", "dependencies": { "@emotion/hash": "^0.8.0", @@ -227,6 +227,7 @@ "version": "0.360.4", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.4.tgz", "integrity": "sha512-oPxA2/F/ywyR73elJwdFOsw3D+rOId2UTNAXnRrTGjh66Ujyx/IFGVqfTlibGaQUg2HENkRoiRsvRJa5qphITA==", + "dev": true, "license": "GPL-3.0", "dependencies": { "@codesandbox/pitcher-common": "0.360.2", @@ -237,6 +238,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "dev": true, "license": "ISC", "engines": { "node": ">= 10" @@ -290,6 +292,7 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "dev": true, "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { @@ -1007,6 +1010,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", + "dev": true, "license": "MIT" }, "node_modules/@types/cookie": { @@ -1027,6 +1031,7 @@ "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/braces": "*" @@ -1056,6 +1061,7 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", + "dev": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -1330,6 +1336,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1827,7 +1834,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -1842,7 +1848,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1852,7 +1857,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1868,7 +1872,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -1881,7 +1884,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -1911,7 +1913,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1924,7 +1925,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -2039,6 +2039,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "dev": true, "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -2273,7 +2274,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/environment": { @@ -2373,7 +2373,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2442,6 +2441,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, "license": "MIT" }, "node_modules/evp_bytestokey": { @@ -2481,6 +2481,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2567,7 +2568,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -3252,7 +3252,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3307,6 +3306,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -3387,7 +3387,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", - "dev": true, "license": "MIT", "peerDependencies": { "ws": "*" @@ -3538,6 +3537,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -3679,6 +3679,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3878,6 +3879,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -3968,6 +3970,7 @@ "version": "1.112.0", "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.112.0.tgz", "integrity": "sha512-qQH4jZSdabcKpwcqvJTi7eQL86UucvMacbaHiiIrOynT8jhTLtKS2ixaXgGlNBMeN9UhFi1wS00Hnxhw9aYLsA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 14.17.0" @@ -4096,6 +4099,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -4105,6 +4109,7 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", @@ -4121,6 +4126,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, "license": "MIT", "dependencies": { "p-finally": "^1.0.0" @@ -4271,6 +4277,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4523,7 +4530,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4549,6 +4555,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4879,7 +4886,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4933,7 +4939,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4943,7 +4948,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4993,6 +4997,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5076,6 +5081,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, "license": "MIT" }, "node_modules/tinyexec": { @@ -5089,6 +5095,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -5101,12 +5108,14 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, "license": "MIT" }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "dev": true, "license": "MIT" }, "node_modules/tslib": { @@ -5120,6 +5129,7 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" @@ -5205,6 +5215,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -5218,6 +5229,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/vscode-diff/-/vscode-diff-2.1.1.tgz", "integrity": "sha512-S2BwZbrQCk4N6FqgYQQPlQ44OZKZNcS2VwhMj4xU8QvixXN9GeNQnN7/7XHFbwrs6h5BiLADDcERTrKvfWeG9g==", + "dev": true, "license": "MIT" }, "node_modules/vscode-jsonrpc": { @@ -5234,12 +5246,14 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -5574,7 +5588,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -5591,7 +5604,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -5610,7 +5622,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index dfeed3e..4b9d0e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.8", + "version": "2.0.0-rc.9", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -27,6 +27,12 @@ "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs", "default": "./dist/cjs/browser.cjs" + }, + "./node": { + "types": "./dist/esm/node/index.d.ts", + "import": "./dist/esm/node.js", + "require": "./dist/cjs/node.cjs", + "default": "./dist/cjs/node.cjs" } }, "types": "./dist/esm/index.d.ts", @@ -86,20 +92,19 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "why-is-node-running": "^2.3.0", - "yargs": "^17.7.2" + "why-is-node-running": "^2.3.0" }, "dependencies": { - "@codesandbox/pitcher-protocol": "0.360.4", "@inkjs/ui": "^2.0.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", + "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", "util": "^0.12.5", - "ws": "^8.18.2" + "yargs": "^17.7.2" } } diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 1d9661a..84f7a6d 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -1,16 +1,8 @@ import { - Disposable, - initPitcherClient, PitcherManagerResponse, type protocol as _protocol, } from "@codesandbox/pitcher-client"; -import { - type SandboxSession, - type SessionCreateOptions, - type SandboxBrowserSession, - DEFAULT_SUBSCRIPTIONS, -} from "./types"; -import { Client } from "@hey-api/client-fetch"; +import { type SessionCreateOptions, type SandboxSession } from "./types"; import { vmCreateSession, vmUpdateHibernationTimeout, @@ -18,13 +10,10 @@ import { } from "./api-clients/client"; import { handleResponse } from "./utils/api"; import { VMTier } from "./VMTier"; -import { version } from "@codesandbox/pitcher-protocol"; -import { AgentConnection } from "./NodeAgentClient/AgentConnection"; -import { Session } from "./Session"; -import { NodeAgentClient } from "./NodeAgentClient"; - -// Timeout for detecting a pong response, leading to a forced disconnect -let PONG_DETECTION_TIMEOUT = 15_000; +import { Client } from "@hey-api/client-fetch"; +import { connectToSandbox } from "./node"; +import { startVm } from "./Sandboxes"; +import { SandboxClient } from "./SandboxClient"; export class Sandbox { /** @@ -55,14 +44,6 @@ export class Sandbox { this.pitcherManagerResponse.pitcherVersion ); } - private get globalSession() { - return { - sandboxId: this.id, - pitcherToken: this.pitcherManagerResponse.pitcherToken, - pitcherUrl: this.pitcherManagerResponse.pitcherURL, - userWorkspacePath: this.pitcherManagerResponse.userWorkspacePath, - }; - } constructor( public id: string, private apiClient: Client, @@ -103,23 +84,82 @@ export class Sandbox { ); } - private async createSession( - opts: SessionCreateOptions + private async initializeCustomSession( + customSession: SessionCreateOptions, + session: SandboxSession + ) { + if (!customSession.git && !customSession.env) { + return; + } + + const client = await connectToSandbox({ + session, + getSession: async () => + this.getSession(await startVm(this.apiClient, this.id), customSession), + }); + + if (customSession.env) { + const envStrings = Object.entries(customSession.env) + .map(([key, value]) => `export ${key}=${value}`) + .join("\n"); + await client.commands.run(`echo "${envStrings}" > $HOME/.private/.env`); + } + + if (customSession.git) { + await Promise.all([ + client.commands.run( + `echo "https://${customSession.git.username || "x-access-token"}:${ + customSession.git.accessToken + }@${customSession.git.provider}" > $HOME/.private/.gitcredentials` + ), + client.commands.run( + `echo "[user] + name = ${customSession.git.name || customSession.id} + email = ${customSession.git.email} +[init] + defaultBranch = main +[credential] + helper = store --file ~/.private/.gitcredentials" > $HOME/.gitconfig` + ), + ]); + } + + return client; + } + + private async getSession( + pitcherManagerResponse: PitcherManagerResponse, + customSession?: SessionCreateOptions ): Promise { - if (opts.id.length > 20) { + if (!customSession) { + return { + sandboxId: this.id, + bootupType: this.bootupType, + cluster: this.cluster, + latestPitcherVersion: pitcherManagerResponse.latestPitcherVersion, + pitcherManagerVersion: pitcherManagerResponse.pitcherManagerVersion, + pitcherToken: pitcherManagerResponse.pitcherToken, + pitcherURL: pitcherManagerResponse.pitcherURL, + userWorkspacePath: pitcherManagerResponse.userWorkspacePath, + workspacePath: pitcherManagerResponse.workspacePath, + pitcherVersion: pitcherManagerResponse.pitcherVersion, + }; + } + + if (customSession.id.length > 20) { throw new Error("Session ID must be 20 characters or less"); } const response = await vmCreateSession({ client: this.apiClient, body: { - session_id: opts.id, - permission: opts.permission ?? "write", - ...(opts.git + session_id: customSession.id, + permission: customSession.permission ?? "write", + ...(customSession.git ? { - git_access_token: opts.git.accessToken, - git_user_email: opts.git.email, - git_user_name: opts.git.name, + git_access_token: customSession.git.accessToken, + git_user_email: customSession.git.email, + git_user_name: customSession.git.name, } : {}), }, @@ -130,121 +170,75 @@ export class Sandbox { const handledResponse = handleResponse( response, - `Failed to create session ${opts.id}` + `Failed to create session ${customSession.id}` ); - const session: SandboxSession = { + return { sandboxId: this.id, + sessionId: customSession?.id, + hostToken: customSession?.hostToken, + bootupType: this.bootupType, + cluster: this.cluster, + latestPitcherVersion: pitcherManagerResponse.latestPitcherVersion, + pitcherManagerVersion: pitcherManagerResponse.pitcherManagerVersion, pitcherToken: handledResponse.pitcher_token, - pitcherUrl: handledResponse.pitcher_url, + pitcherURL: handledResponse.pitcher_url, userWorkspacePath: handledResponse.user_workspace_path, - env: opts.env, + workspacePath: pitcherManagerResponse.workspacePath, + pitcherVersion: pitcherManagerResponse.pitcherVersion, }; - - return session; } - private getCustomEnv(customSession?: SessionCreateOptions) { - return customSession?.env; - } - - /** - * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. - */ - async connect(customSession?: SessionCreateOptions): Promise { - const sessionDetails = customSession - ? await this.createSession(customSession) - : this.globalSession; - const url = `${sessionDetails.pitcherUrl}/?token=${sessionDetails.pitcherToken}`; - - const agentConnection = await AgentConnection.create(url); - const joinResult = await agentConnection.request({ - method: "client/join", - params: { - clientInfo: { - protocolVersion: version, - appId: "sdk", - }, - asyncProgress: true, - subscriptions: DEFAULT_SUBSCRIPTIONS, - }, - }); - - // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects - agentConnection.connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); - - const agentClient = new NodeAgentClient(this.apiClient, agentConnection, { - sandboxId: this.id, - workspacePath: sessionDetails.userWorkspacePath, - reconnectToken: joinResult.reconnectToken, - isUpToDate: this.isUpToDate, - }); - - const session = await Session.create(agentClient, { - username: customSession ? joinResult.client.username : undefined, - env: this.getCustomEnv(customSession), - hostToken: customSession?.hostToken, - }); + async connect(customSession?: SessionCreateOptions) { + const session = await this.getSession( + this.pitcherManagerResponse, + customSession + ); - if (customSession?.git) { - try { - await session.commands.run("mkdir -p $HOME/private"); - } catch {} + let client: SandboxClient | undefined; - await Promise.all([ - session.commands.run( - `echo "https://${customSession.git.username || "x-access-token"}:${ - customSession.git.accessToken - }@${customSession.git.provider}" > $HOME/private/.gitcredentials` - ), - session.commands.run( - `echo "[user] - name = ${customSession.git.name || customSession.id} - email = ${customSession.git.email} -[init] - defaultBranch = main -[credential] - helper = store --file ~/private/.gitcredentials" > $HOME/.gitconfig` - ), - ]); + // We might create a client here if git or env is configured, we can reuse that + if (customSession) { + client = await this.initializeCustomSession(customSession, session); } - return session; + return ( + client || + connectToSandbox({ + session, + getSession: async () => + this.getSession( + await startVm(this.apiClient, this.id), + customSession + ), + }) + ); } /** - * Returns a browser session connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. - }\n[credential]\n helper = store --file ~/private/.gitcredentials\n"` - ); - } - - return session; + * @deprecated Use createSession instead + */ + async createBrowserSession(customSession?: SessionCreateOptions) { + return this.createSession(customSession); } /** - * Returns a browser session connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. + * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. */ - async createBrowserSession( + async createSession( customSession?: SessionCreateOptions - ): Promise { - const session = customSession - ? await this.createSession(customSession) - : this.globalSession; + ): Promise { + const session = await this.getSession( + this.pitcherManagerResponse, + customSession + ); - return { - id: this.id, - env: this.getCustomEnv(customSession), - sessionId: customSession?.id, - hostToken: customSession?.hostToken, - bootupType: this.bootupType, - cluster: this.cluster, - latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion, - pitcherManagerVersion: this.pitcherManagerResponse.pitcherManagerVersion, - pitcherToken: session.pitcherToken, - pitcherURL: session.pitcherUrl, - userWorkspacePath: session.userWorkspacePath, - workspacePath: this.pitcherManagerResponse.workspacePath, - pitcherVersion: this.pitcherManagerResponse.pitcherVersion, - }; + /* + if (customSession) { + const client = await this.initializeCustomSession(customSession, session); + client?.disconnect(); + }*/ + + return session; } } diff --git a/src/Session/commands.ts b/src/SandboxClient/commands.ts similarity index 94% rename from src/Session/commands.ts rename to src/SandboxClient/commands.ts index 1aa22ce..e007f7e 100644 --- a/src/Session/commands.ts +++ b/src/SandboxClient/commands.ts @@ -1,7 +1,7 @@ import { Disposable, DisposableStore } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../agent-client-interface"; -import * as protocol from "@codesandbox/pitcher-protocol"; +import { IAgentClient } from "../node/agent-client-interface"; +import * as protocol from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; type ShellSize = { cols: number; rows: number }; @@ -31,8 +31,7 @@ export class Commands { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient, - private env: Record = {} + private agentClient: IAgentClient ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -49,14 +48,16 @@ export class Commands { command = Array.isArray(command) ? command.join(" && ") : command; - const allEnv = Object.assign(this.env, opts?.env ?? {}); + const allEnv = Object.assign(opts?.env ?? {}); // TODO: use a new shell API that natively supports cwd & env let commandWithEnv = Object.keys(allEnv).length - ? `env ${Object.entries(allEnv) + ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( + allEnv + ) .map(([key, value]) => `${key}=${value}`) .join(" ")} bash -c '${command}'` - : command; + : `source $HOME/.private/.env 2>/dev/null || true && ${command}`; if (opts?.cwd) { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; diff --git a/src/Session/filesystem.ts b/src/SandboxClient/filesystem.ts similarity index 98% rename from src/Session/filesystem.ts rename to src/SandboxClient/filesystem.ts index 5818e8e..4c578d1 100644 --- a/src/Session/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -1,4 +1,4 @@ -import { type IAgentClient } from "../agent-client-interface"; +import { type IAgentClient } from "../node/agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; diff --git a/src/Session/git.ts b/src/SandboxClient/git.ts similarity index 96% rename from src/Session/git.ts rename to src/SandboxClient/git.ts index 4ee2430..b480dfb 100644 --- a/src/Session/git.ts +++ b/src/SandboxClient/git.ts @@ -1,5 +1,5 @@ import { Commands } from "./commands"; -import { IAgentClient } from "../agent-client-interface"; +import { IAgentClient } from "../node/agent-client-interface"; /** * An interface to interact with the git repository. diff --git a/src/Session/hosts.ts b/src/SandboxClient/hosts.ts similarity index 100% rename from src/Session/hosts.ts rename to src/SandboxClient/hosts.ts diff --git a/src/Session/index.ts b/src/SandboxClient/index.ts similarity index 94% rename from src/Session/index.ts rename to src/SandboxClient/index.ts index fcf63b3..534d548 100644 --- a/src/Session/index.ts +++ b/src/SandboxClient/index.ts @@ -10,8 +10,8 @@ import { Commands } from "./commands"; import { Git } from "./git"; import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; -import { IAgentClient } from "../agent-client-interface"; -import { setup } from "@codesandbox/pitcher-protocol"; +import { IAgentClient } from "../node/agent-client-interface"; +import { setup } from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; export * from "./filesystem"; @@ -24,20 +24,19 @@ export * from "./git"; export * from "./interpreters"; export * from "./hosts"; -type SessionParams = { - env?: Record; +type SandboxClientParams = { hostToken?: HostToken; username?: string; }; -export class Session { - static async create(agentClient: IAgentClient, params: SessionParams) { +export class SandboxClient { + static async create(agentClient: IAgentClient, params: SandboxClientParams) { let setupProgress = await agentClient.setup.getProgress(); let hasInitializedSteps = Boolean(setupProgress.steps.length); if (hasInitializedSteps) { - return new Session(agentClient, params, setupProgress); + return new SandboxClient(agentClient, params, setupProgress); } // We have a race condition where we might not have the steps yet and need @@ -57,7 +56,7 @@ export class Session { throw new Error("Failed to get setup progress"); } - return new Session(agentClient, params, response.value); + return new SandboxClient(agentClient, params, response.value); } private disposable = new Disposable(); @@ -112,7 +111,7 @@ export class Session { constructor( protected agentClient: IAgentClient, - { env, hostToken, username }: SessionParams, + { hostToken, username }: SandboxClientParams, initialSetupProgress: setup.SetupProgress ) { // TODO: Bring this back once metrics polling does not reset inactivity @@ -128,8 +127,8 @@ export class Session { initialSetupProgress ); this.fs = new FileSystem(this.disposable, this.agentClient, username); - this.terminals = new Terminals(this.disposable, this.agentClient, env); - this.commands = new Commands(this.disposable, this.agentClient, env); + this.terminals = new Terminals(this.disposable, this.agentClient); + this.commands = new Commands(this.disposable, this.agentClient); this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); diff --git a/src/Session/interpreters.ts b/src/SandboxClient/interpreters.ts similarity index 100% rename from src/Session/interpreters.ts rename to src/SandboxClient/interpreters.ts diff --git a/src/Session/ports.ts b/src/SandboxClient/ports.ts similarity index 98% rename from src/Session/ports.ts rename to src/SandboxClient/ports.ts index e41d56b..4546ff3 100644 --- a/src/Session/ports.ts +++ b/src/SandboxClient/ports.ts @@ -1,6 +1,6 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../agent-client-interface"; +import { IAgentClient } from "../node/agent-client-interface"; export type Port = { host: string; diff --git a/src/Session/setup.ts b/src/SandboxClient/setup.ts similarity index 97% rename from src/Session/setup.ts rename to src/SandboxClient/setup.ts index a83c59d..bb5f1b3 100644 --- a/src/Session/setup.ts +++ b/src/SandboxClient/setup.ts @@ -1,8 +1,8 @@ -import * as protocol from "@codesandbox/pitcher-protocol"; +import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../agent-client-interface"; +import { IAgentClient } from "../node/agent-client-interface"; export class Setup { private disposable = new Disposable(); diff --git a/src/Session/tasks.ts b/src/SandboxClient/tasks.ts similarity index 97% rename from src/Session/tasks.ts rename to src/SandboxClient/tasks.ts index 5cb8017..d3715cb 100644 --- a/src/Session/tasks.ts +++ b/src/SandboxClient/tasks.ts @@ -1,7 +1,7 @@ -import * as protocol from "@codesandbox/pitcher-protocol"; +import * as protocol from "../pitcher-protocol"; import { Disposable, IDisposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../agent-client-interface"; +import { IAgentClient } from "../node/agent-client-interface"; import { Emitter } from "../utils/event"; export type TaskDefinition = { diff --git a/src/Session/terminals.ts b/src/SandboxClient/terminals.ts similarity index 90% rename from src/Session/terminals.ts rename to src/SandboxClient/terminals.ts index f21009b..df6d975 100644 --- a/src/Session/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -1,8 +1,8 @@ -import * as protocol from "@codesandbox/pitcher-protocol"; +import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; -import { IAgentClient } from "../agent-client-interface"; +import { IAgentClient } from "../node/agent-client-interface"; export type ShellSize = { cols: number; rows: number }; @@ -12,8 +12,7 @@ export class Terminals { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient, - private env: Record = {} + private agentClient: IAgentClient ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -24,14 +23,16 @@ export class Terminals { command: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", opts?: ShellRunOpts ): Promise { - const allEnv = Object.assign(this.env, opts?.env ?? {}); + const allEnv = Object.assign(opts?.env ?? {}); // TODO: use a new shell API that natively supports cwd & env let commandWithEnv = Object.keys(allEnv).length - ? `env ${Object.entries(allEnv) + ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( + allEnv + ) .map(([key, value]) => `${key}=${value}`) .join(" ")} ${command}` - : command; + : `source $HOME/.private/.env 2>/dev/null || true && ${command}`; if (opts?.cwd) { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 020a056..62235bd 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -13,6 +13,7 @@ import { getStartOptions, getStartResponse, handleResponse, + withCustomTimeout, } from "./utils/api"; import { @@ -32,16 +33,8 @@ export async function startVm( sandboxId: string, startOpts?: StartSandboxOpts ): Promise { - // 2 minutes is the configuration for the API and Manager - const TIMEOUT_SECONDS = 120; - const controller = new AbortController(); - const signal = controller.signal; - const timeoutHandle = setTimeout(() => { - controller.abort(); - }, TIMEOUT_SECONDS * 1000); - - try { - const startResult = await vmStart({ + const startResult = await withCustomTimeout((signal) => + vmStart({ client: apiClient, body: startOpts ? { @@ -55,25 +48,15 @@ export async function startVm( id: sandboxId, }, signal, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); + }) + ); - return getStartResponse(response); - } catch (err) { - if (err instanceof Error && err.name === "AbortError") { - throw new Error( - `Request took longer than ${TIMEOUT_SECONDS}s, so we aborted.` - ); - } + const response = handleResponse( + startResult, + `Failed to start sandbox ${sandboxId}` + ); - throw err; - } finally { - clearTimeout(timeoutHandle); - } + return getStartResponse(response); } /** @@ -142,12 +125,15 @@ export class Sandboxes { * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. */ async shutdown(sandboxId: string): Promise { - const response = await vmShutdown({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }); + const response = await withCustomTimeout((signal) => + vmShutdown({ + client: this.apiClient, + path: { + id: sandboxId, + }, + signal, + }) + ); handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); } @@ -208,12 +194,15 @@ export class Sandboxes { * you resume the sandbox it will continue from the last state it was in. */ async hibernate(sandboxId: string): Promise { - const response = await vmHibernate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }); + const response = await withCustomTimeout((signal) => + vmHibernate({ + client: this.apiClient, + path: { + id: sandboxId, + }, + signal, + }) + ); handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index c090bad..3ca1fff 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -2,8 +2,7 @@ import { promises as fs } from "fs"; import path from "path"; import { isBinaryFile } from "isbinaryfile"; import * as readline from "readline"; - -import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; +import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; @@ -16,8 +15,12 @@ import { vmListClusters, VmUpdateSpecsRequest, } from "../../api-clients/client"; -import { getDefaultTemplateId, handleResponse } from "../../utils/api"; -import { BASE_URL, getApiKey } from "../utils/constants"; +import { + createApiClient, + getDefaultTemplateId, + handleResponse, +} from "../../utils/api"; +import { getInferredApiKey } from "../../utils/constants"; import { hashDirectory } from "../utils/hash"; import { startVm } from "../../Sandboxes"; import { DisposableStore } from "../../utils/disposable"; @@ -84,15 +87,8 @@ export const buildCommand: yargs.CommandModule< }), handler: async (argv) => { - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - }, - }) - ); + const apiKey = getInferredApiKey(); + const apiClient: Client = createApiClient(apiKey); let alias: { namespace: string; alias: string } | undefined; @@ -127,15 +123,11 @@ export const buildCommand: yargs.CommandModule< const sandboxes = await Promise.all( clusters.map(async ({ host: cluster, slug }, index) => { - const clusterApiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }) - ); + const clusterApiClient: Client = createApiClient(apiKey, { + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); try { const { hash, files: filePaths } = await hashDirectory( @@ -174,17 +166,12 @@ export const buildCommand: yargs.CommandModule< const tasks = sandboxes.map( async ({ sandboxId, filePaths, cluster }, index) => { - const clusterApiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }) - ); - const sdk = new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, + const clusterApiClient: Client = createApiClient(apiKey, { + headers: { + "x-pitcher-manager-url": `https://${cluster}/api/v1`, + }, + }); + const sdk = new CodeSandbox(apiKey, { headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, }, @@ -211,13 +198,13 @@ export const buildCommand: yargs.CommandModule< }), "Failed to start sandbox" ); - let sandbox = new Sandbox( + let sandboxVM = new Sandbox( sandboxId, clusterApiClient, startResponse ); - let session = await sandbox.connect(); + let session = await sandboxVM.connect(); spinner.start( updateSpinnerMessage( @@ -246,8 +233,8 @@ export const buildCommand: yargs.CommandModule< spinner.start( updateSpinnerMessage(index, "Restarting sandbox...", sandboxId) ); - sandbox = await withCustomError( - sdk.sandboxes.restart(sandbox.id, { + sandboxVM = await withCustomError( + sdk.sandboxes.restart(sandboxVM.id, { vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : VMTier.fromName("Micro"), @@ -256,7 +243,7 @@ export const buildCommand: yargs.CommandModule< ); session = await withCustomError( - sandbox.connect(), + sandboxVM.connect(), "Failed to connect to sandbox" ); @@ -347,14 +334,14 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage(index, "Creating snapshot...", sandboxId) ); await withCustomError( - sdk.sandboxes.hibernate(sandbox.id), + sdk.sandboxes.hibernate(sandboxVM.id), "Failed to hibernate" ); spinner.start( updateSpinnerMessage(index, "Snapshot created", sandboxId) ); - return sandbox.id; + return sandboxVM.id; } catch (error) { spinner.start( updateSpinnerMessage( @@ -401,8 +388,7 @@ export const buildCommand: yargs.CommandModule< await Promise.all( failedSandboxes.map(async ({ sandboxId, cluster }) => { - const sdk = new CodeSandbox(API_KEY, { - baseUrl: BASE_URL, + const sdk = new CodeSandbox(apiKey, { headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, }, diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 259ff23..88bd44d 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -1,10 +1,8 @@ -import { createClient, createConfig, type Client } from "@hey-api/client-fetch"; - import type * as yargs from "yargs"; import { previewHostList, previewHostUpdate } from "../../api-clients/client"; -import { handleResponse } from "../../utils/api"; -import { BASE_URL, getApiKey } from "../utils/constants"; +import { createApiClient, handleResponse } from "../../utils/api"; +import { getInferredApiKey } from "../../utils/constants"; export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", @@ -16,15 +14,8 @@ export const previewHostsCommand: yargs.CommandModule = { command: "list", describe: "List current preview hosts", handler: async () => { - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - }, - }) - ); + const apiKey = getInferredApiKey(); + const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); const hosts = data.preview_hosts.map(({ host }) => host); @@ -45,15 +36,8 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - }, - }) - ); + const apiKey = getInferredApiKey(); + const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); let hosts = data.preview_hosts.map(({ host }) => host); @@ -80,15 +64,8 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - }, - }) - ); + const apiKey = getInferredApiKey(); + const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); let hosts = data.preview_hosts.map(({ host }) => host); @@ -109,15 +86,8 @@ export const previewHostsCommand: yargs.CommandModule = { command: "clear", describe: "Clear all preview hosts", handler: async () => { - const API_KEY = getApiKey(); - const apiClient: Client = createClient( - createConfig({ - baseUrl: BASE_URL, - headers: { - Authorization: `Bearer ${API_KEY}`, - }, - }) - ); + const apiKey = getInferredApiKey(); + const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); const hosts = data.preview_hosts.map(({ host }) => host); diff --git a/src/bin/main.tsx b/src/bin/main.tsx index 52e94ad..af23250 100644 --- a/src/bin/main.tsx +++ b/src/bin/main.tsx @@ -9,12 +9,7 @@ import { hostTokensCommand } from "./commands/hostTokens"; import { Dashboard } from "./ui/Dashboard"; import React from "react"; import { SDKProvider } from "./ui/sdkContext"; -import { - focusManager, - onlineManager, - QueryClient, - QueryClientProvider, -} from "@tanstack/react-query"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; if (process.argv.length === 2) { // Clear the screen before rendering the dashboard diff --git a/src/bin/ui/api.ts b/src/bin/ui/api.ts index cef9a8e..d062bc4 100644 --- a/src/bin/ui/api.ts +++ b/src/bin/ui/api.ts @@ -1,17 +1,6 @@ -import { createContext, useContext } from "react"; -import { Client, createClient, createConfig } from "@hey-api/client-fetch"; -import { getApiKey } from "../utils/constants"; +import { Client } from "@hey-api/client-fetch"; import { sandboxGet, vmListRunningVms } from "../../api-clients/client"; -const apiClient = createClient( - createConfig({ - baseUrl: process.env.BASE_URL || "https://api.codesandbox.io", - headers: { - Authorization: `Bearer ${getApiKey()}`, - }, - }) -); - function getResponse(resp: { data?: { data?: T }; error: unknown }) { if (resp.error) { throw resp.error; @@ -24,13 +13,13 @@ function getResponse(resp: { data?: { data?: T }; error: unknown }) { return resp.data.data; } -export async function getSandbox(sandboxId: string) { +export async function getSandbox(apiClient: Client, sandboxId: string) { const resp = await sandboxGet({ client: apiClient, path: { id: sandboxId } }); return getResponse(resp); } -export async function getRunningVms() { +export async function getRunningVms(apiClient: Client) { const resp = await vmListRunningVms({ client: apiClient }); return getResponse(resp); diff --git a/src/bin/utils/constants.ts b/src/bin/utils/constants.ts deleted file mode 100644 index 6b7d6b4..0000000 --- a/src/bin/utils/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable no-console */ - -export const BASE_URL = process.env.BASE_URL ?? "https://api.codesandbox.io"; - -export const getApiKey = () => { - const _API_KEY = process.env.CSB_API_KEY; - if (!_API_KEY) { - console.error("CSB_API_KEY environment variable is not set"); - console.error("You can generate one at https://codesandbox.io/t/api"); - process.exit(1); - } - - return _API_KEY; -}; diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts index ed19a29..0644984 100644 --- a/src/browser/BrowserAgentClient.ts +++ b/src/browser/BrowserAgentClient.ts @@ -9,7 +9,7 @@ import { IAgentClientState, IAgentClientSystem, IAgentClientTasks, -} from "../agent-client-interface"; +} from "../node/agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; diff --git a/src/browser/index.ts b/src/browser/index.ts index 8d2db0a..094e9c2 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -1,9 +1,9 @@ import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxBrowserSession } from "../types"; -import { Session } from "../Session"; +import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; +import { SandboxClient } from "../SandboxClient"; import { BrowserAgentClient } from "./BrowserAgentClient"; -export * from "../Session"; +export * from "../SandboxClient"; export { createPreview, Preview } from "./previews"; @@ -11,16 +11,16 @@ export { createPreview, Preview } from "./previews"; * Connect to a Sandbox from the browser and automatically reconnect. `getSession` requires and endpoint that resumes the Sandbox. `onFocusChange` can be used to notify when a reconnect should happen. */ export async function connectToSandbox(options: { - session: SandboxBrowserSession; - getSession: (id: string) => Promise; + session: SandboxSession; + getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; -}): Promise { +}): Promise { let hasConnected = false; const pitcherClient = await initPitcherClient( { appId: "sdk", - instanceId: options.session.id, + instanceId: options.session.sandboxId, onFocusChange: options.onFocusChange || ((notify) => { @@ -47,12 +47,13 @@ export async function connectToSandbox(options: { ); const agentClient = new BrowserAgentClient(pitcherClient); - const session = await Session.create(agentClient, { + const session = await SandboxClient.create(agentClient, { + // On dedicated sessions we need the username to normalize + // FS events username: options.session.sessionId ? // @ts-ignore pitcherClient["joinResult"].client.username : undefined, - env: options.session.env, hostToken: options.session.hostToken, }); diff --git a/src/index.ts b/src/index.ts index b943adf..edf35d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import { Sandboxes } from "./Sandboxes"; -import { ClientOpts } from "./types"; export { Sandboxes as SandboxClient }; @@ -9,18 +8,11 @@ export * from "./Sandbox"; export * from "./types"; import { HostTokens } from "./HostTokens"; -import { createClient, createConfig } from "@hey-api/client-fetch"; -import { getBaseUrl } from "./utils/api"; - -export * from "./Session"; - -function ensure(value: T | undefined, message: string): T { - if (!value) { - throw new Error(message); - } +import { createApiClient } from "./utils/api"; +import { ClientOpts } from "./types"; +import { getInferredApiKey } from "./utils/constants"; - return value; -} +export * from "./SandboxClient"; export class CodeSandbox { public readonly sandboxes: Sandboxes; @@ -32,28 +24,8 @@ export class CodeSandbox { public readonly hosts: HostTokens; constructor(apiToken?: string, opts: ClientOpts = {}) { - const evaluatedApiToken = - apiToken || - ensure( - typeof process !== "undefined" - ? process.env?.CSB_API_KEY || process.env?.TOGETHER_API_KEY - : undefined, - "CSB_API_KEY or TOGETHER_API_KEY is not set" - ); - - const baseUrl = - process.env.CSB_BASE_URL ?? opts.baseUrl ?? getBaseUrl(evaluatedApiToken); - - const apiClient = createClient( - createConfig({ - baseUrl, - headers: { - Authorization: `Bearer ${evaluatedApiToken}`, - ...(opts.headers ?? {}), - }, - fetch: opts.fetch ?? fetch, - }) - ); + const apiKey = apiToken || getInferredApiKey(); + const apiClient = createApiClient(apiKey, opts); this.sandboxes = new Sandboxes(apiClient); this.hosts = new HostTokens(apiClient); diff --git a/src/NodeAgentClient/index.ts b/src/node/AgentClient.ts similarity index 96% rename from src/NodeAgentClient/index.ts rename to src/node/AgentClient.ts index 3f6d309..420b61d 100644 --- a/src/NodeAgentClient/index.ts +++ b/src/node/AgentClient.ts @@ -7,7 +7,7 @@ import { setup, shell, task, -} from "@codesandbox/pitcher-protocol"; +} from "../pitcher-protocol"; import { IAgentClient, IAgentClientFS, @@ -18,12 +18,10 @@ import { IAgentClientSystem, IAgentClientTasks, PickRawFsResult, -} from "../agent-client-interface"; +} from "./agent-client-interface"; import { AgentConnection } from "./AgentConnection"; -import { Client } from "@hey-api/client-fetch"; -import { startVm } from "../Sandboxes"; import { Emitter } from "../utils/event"; -import { Id } from "@codesandbox/pitcher-client"; +import { SandboxSession } from "../types"; class NodeAgentClientShells implements IAgentClientShells { private onShellExitedEmitter = new Emitter<{ @@ -414,7 +412,7 @@ export class NodeAgentClient implements IAgentClient { ports = new NodeAgentClientPorts(this.agentConnection); constructor( - private apiClient: Client, + private getSession: (sandboxId: string) => Promise, private agentConnection: AgentConnection, private params: { sandboxId: string; @@ -433,9 +431,9 @@ export class NodeAgentClient implements IAgentClient { } async reconnect(): Promise { await this.agentConnection.reconnect(this.reconnectToken, async () => { - const response = await startVm(this.apiClient, this.sandboxId); + const session = await this.getSession(this.params.sandboxId); - return response.pitcherToken; + return session.pitcherToken; }); } dispose() { diff --git a/src/NodeAgentClient/AgentConnection.ts b/src/node/AgentConnection.ts similarity index 97% rename from src/NodeAgentClient/AgentConnection.ts rename to src/node/AgentConnection.ts index eac0c64..9541ffa 100644 --- a/src/NodeAgentClient/AgentConnection.ts +++ b/src/node/AgentConnection.ts @@ -5,18 +5,17 @@ import { isResultPayload, decodeMessage, version, -} from "@codesandbox/pitcher-protocol"; +} from "../pitcher-protocol"; import type { PitcherNotification, PitcherRequest, PitcherResponse, -} from "@codesandbox/pitcher-protocol"; +} from "../pitcher-protocol"; import { PendingPitcherMessage } from "./PendingPitcherMessage"; import { createWebSocketClient, WebSocketClient } from "./WebSocketClient"; -import { IAgentClientState } from "../agent-client-interface"; +import { IAgentClientState } from "./agent-client-interface"; import { DEFAULT_SUBSCRIPTIONS } from "../types"; -import { startVm } from "../Sandboxes"; import { Emitter } from "../utils/event"; import { SliceList } from "../utils/sliceList"; diff --git a/src/NodeAgentClient/PendingPitcherMessage.ts b/src/node/PendingPitcherMessage.ts similarity index 97% rename from src/NodeAgentClient/PendingPitcherMessage.ts rename to src/node/PendingPitcherMessage.ts index d262354..27292ac 100644 --- a/src/NodeAgentClient/PendingPitcherMessage.ts +++ b/src/node/PendingPitcherMessage.ts @@ -1,13 +1,13 @@ import { PitcherResponseStatus, createRequestPayload, -} from "@codesandbox/pitcher-protocol"; +} from "../pitcher-protocol"; import type { PitcherRequest, PitcherResponse, PitcherRequestPayload, PitcherErrorCode, -} from "@codesandbox/pitcher-protocol"; +} from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; const PITCHER_MESSAGE_TIMEOUT_MS = 90_000; diff --git a/src/NodeAgentClient/WebSocketClient.ts b/src/node/WebSocketClient.ts similarity index 99% rename from src/NodeAgentClient/WebSocketClient.ts rename to src/node/WebSocketClient.ts index fa35008..cd8d2c7 100644 --- a/src/NodeAgentClient/WebSocketClient.ts +++ b/src/node/WebSocketClient.ts @@ -1,4 +1,4 @@ -import WebSocket from "ws"; +import WebSocket from "isomorphic-ws"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; diff --git a/src/agent-client-interface.ts b/src/node/agent-client-interface.ts similarity index 97% rename from src/agent-client-interface.ts rename to src/node/agent-client-interface.ts index 061f87d..76aba44 100644 --- a/src/agent-client-interface.ts +++ b/src/node/agent-client-interface.ts @@ -8,8 +8,8 @@ import { shell, setup, task, -} from "@codesandbox/pitcher-protocol"; -import { Event } from "./utils/event"; +} from "../pitcher-protocol"; +import { Event } from "../utils/event"; export interface IAgentClientShells { onShellExited: Event<{ diff --git a/src/node/index.ts b/src/node/index.ts new file mode 100644 index 0000000..04adb2f --- /dev/null +++ b/src/node/index.ts @@ -0,0 +1,47 @@ +import { version } from "../pitcher-protocol"; +import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; +import { AgentConnection } from "./AgentConnection"; +import { NodeAgentClient } from "./AgentClient"; +import { SandboxClient } from "../SandboxClient"; + +// Timeout for detecting a pong response, leading to a forced disconnect +let PONG_DETECTION_TIMEOUT = 15_000; + +export async function connectToSandbox({ + session, + getSession, +}: { + session: SandboxSession; + getSession: (sandboxId: string) => Promise; +}) { + const url = `${session.pitcherURL}/?token=${session.pitcherToken}`; + const agentConnection = await AgentConnection.create(url); + const joinResult = await agentConnection.request({ + method: "client/join", + params: { + clientInfo: { + protocolVersion: version, + appId: "sdk", + }, + asyncProgress: true, + subscriptions: DEFAULT_SUBSCRIPTIONS, + }, + }); + + // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects + agentConnection.connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); + + const agentClient = new NodeAgentClient(getSession, agentConnection, { + sandboxId: session.sandboxId, + workspacePath: session.userWorkspacePath, + reconnectToken: joinResult.reconnectToken, + isUpToDate: session.latestPitcherVersion === session.pitcherVersion, + }); + + const client = await SandboxClient.create(agentClient, { + username: joinResult.client.username, + hostToken: session.hostToken, + }); + + return client; +} diff --git a/src/pitcher-protocol/capabilities.ts b/src/pitcher-protocol/capabilities.ts new file mode 100644 index 0000000..7ebe9ba --- /dev/null +++ b/src/pitcher-protocol/capabilities.ts @@ -0,0 +1,190 @@ +import { NestedKey } from "./types"; + +export interface PitcherCapabilities { + client?: { + status?: boolean; + list?: boolean; + }; + file?: { + status?: boolean; + // open and close by id + openClose?: boolean; + openByPath?: boolean; + save?: boolean; + ot?: boolean; + selection?: boolean; + }; + fs?: { + raw?: boolean; + read?: boolean; + operations?: boolean; + search?: boolean; + streamingSearch?: boolean; + pathSearch?: boolean; + }; + git?: { + status?: boolean; + targetDiff?: boolean; + pull?: boolean; + discard?: boolean; + commit?: boolean; + renameBranch?: boolean; + }; + language?: { + list?: boolean; + // Our lsp proxy using lspRequest and lspNotification + // in the future we might add a more spec-compliant version + pitcherLsp?: boolean; + }; + port?: { + list?: boolean; + status?: boolean; + }; + setup?: { + // Naming of these is a bit vague + get?: boolean; + skip?: boolean; + skipAll?: boolean; + setStep?: boolean; + progress?: boolean; + }; + shell?: { + // in and out + io?: boolean; + list?: boolean; + // open/close is basically subscribe/unsubscribe, not the best naming here? + // Actually opening and closing the shell is create and terminate + openClose?: boolean; + createTerminate?: boolean; + restart?: boolean; + resize?: boolean; + // exit, restart, terminate notifications + status?: boolean; + }; + task?: { + list?: boolean; + run?: boolean; + stop?: boolean; + runCommand?: boolean; + create?: boolean; + update?: boolean; + saveToConfig?: boolean; + generateConfig?: boolean; + createSetupTasks?: boolean; + }; + channel?: { + // join & leave + subscribe?: boolean; + message?: boolean; + }; + system?: { + hibernation?: boolean; + update?: boolean; + metrics: boolean; + containers?: boolean; + }; + command?: { + list?: boolean; + execute?: boolean; + }; + ai?: { + raw?: number; + suggestCommit?: number; + chat?: number; + embeddings?: number; + }; + box?: { + installDependencies?: boolean; + installedDependencies?: boolean; + }; +} + +interface BasePitcherPermissions { + file: { + open: boolean; + documentOperation: boolean; + documentSelection: boolean; + save: boolean; + documentAck: boolean; + close: boolean; + }; + fs: { + read: boolean; + operation: boolean; + search: boolean; + pathSearch: boolean; + upload: boolean; + download: boolean; + }; + language: { + list: boolean; + lspRead: boolean; + lspWrite: boolean; + }; + git: { + status: boolean; + pull: boolean; + commit: boolean; + discard: boolean; + renameBranch: boolean; + remoteContent: boolean; + diffStatus: boolean; + remotes: boolean; + push: boolean; + }; + setup: { + get: boolean; + skip: boolean; + skipAll: boolean; + setStep: boolean; + enable: boolean; + disable: boolean; + init: boolean; + }; + shell: { + create: boolean; + write: boolean; + list: boolean; + open: boolean; + close: boolean; + restart: boolean; + terminate: boolean; + resize: boolean; + }; + task: { + list: boolean; + run: boolean; + stop: boolean; + runCommand: boolean; + create: boolean; + update: boolean; + saveToConfig: boolean; + generateConfig: boolean; + createSetupTasks: boolean; + }; + command: { + list: boolean; + execute: boolean; + }; + system: { + update: boolean; + hibernate: boolean; + metrics: boolean; + containers: boolean; + }; + ai: { + // Generate code/description that would be used for edits + suggest?: boolean; + // Explains existing code, purely visual within the editor, no edits anywhere + explain?: boolean; + // Ability to run raw openai queries, should be reserved for csb employees? + raw?: boolean; + // Ability to create and interact with chat + chat?: boolean; + embeddings?: boolean; + }; +} + +export type PitcherPermissions = Partial; + +export type PitcherPermissionKeys = NestedKey; diff --git a/src/pitcher-protocol/errors.ts b/src/pitcher-protocol/errors.ts new file mode 100644 index 0000000..47f2b42 --- /dev/null +++ b/src/pitcher-protocol/errors.ts @@ -0,0 +1,52 @@ +export enum PitcherErrorCode { + // CORE + CRITICAL_ERROR = 0, + FEATURE_UNAVAILABLE = 1, + NO_ACCESS = 2, + RATE_LIMIT = 3, + + // FS + INVALID_ID = 100, + INVALID_PATH = 101, + RAWFS_ERROR = 102, + + // SHELL + SHELL_NOT_ACCESSIBLE = 200, + SHELL_CLOSED = 201, + SHELL_NOT_FOUND = 204, + + // MODELS + MODEL_NOT_FOUND = 300, + + // GIT + GIT_OPERATION_IN_PROGRESS = 400, + GIT_REMOTE_FILE_NOT_FOUND = 404, + GIT_FETCH_FAIL = 410, + GIT_PULL_CONFLICT = 420, + GIT_RESET_LOCAL_REMOTE_ERROR = 430, + GIT_PUSH_FAIL = 440, + GIT_RESET_CHECKOUT_INITIAL_BRANCH_FAIL = 450, + GIT_PULL_FAIL = 460, + GIT_TRANSPOSE_LINES_FAIL = 470, + + // CHANNEL + CHANNEL_NOT_FOUND = 500, + + // TASKS + CONFIG_FILE_ALREADY_EXISTS = 600, + TASK_NOT_FOUND = 601, + COMMAND_ALREADY_CONFIGURED = 602, + + // COMMANDS + COMMAND_NOT_FOUND = 704, + + // AI + AI_NOT_AVAILABLE = 800, + PROMPT_TOO_BIG = 801, + FAILED_TO_RESPOND = 802, + AI_TOO_FREQUENT_REQUESTS = 803, + AI_CHAT_NOT_FOUND = 814, + + // BOX + BOX_NOT_AVAILABLE = 900, +} diff --git a/src/pitcher-protocol/index.ts b/src/pitcher-protocol/index.ts new file mode 100644 index 0000000..3d031f7 --- /dev/null +++ b/src/pitcher-protocol/index.ts @@ -0,0 +1,100 @@ +export const version = "0.367.10"; + +import * as notification from "./messages/notification"; +import * as container from "./messages/container"; +import * as language from "./messages/language"; +import * as channel from "./messages/channel"; +import * as command from "./messages/command"; +import * as client from "./messages/client"; +import * as system from "./messages/system"; +import * as setup from "./messages/setup"; +import * as shell from "./messages/shell"; +import * as port from "./messages/port"; +import * as task from "./messages/task"; +import * as file from "./messages/file"; +import * as git from "./messages/git"; +import * as fs from "./messages/fs"; +import * as ai from "./messages/ai"; +import * as box from "./messages/box"; + +export * from "./protocol"; +export * from "./message"; + +export type PitcherRequest = + | ai.AiRequest + | fs.FsRequest + | client.ClientRequest + | shell.ShellRequest + | port.PortRequest + | language.LanguageRequest + | file.FileRequest + | git.GitRequest + | setup.SetupRequest + | channel.ChannelRequest + | task.TaskRequest + | system.SystemRequest + | command.CommandRequest + | notification.NotificationRequest + | container.ContainerRequest + | box.BoxRequest; + +export type PitcherResponse = + | ai.AiResponse + | fs.FsResponse + | client.ClientResponse + | shell.ShellResponse + | port.PortResponse + | language.LanguageResponse + | file.FileResponse + | git.GitResponse + | setup.SetupResponse + | channel.ChannelResponse + | task.TaskResponse + | system.SystemResponse + | command.CommandResponse + | notification.NotificationResponse + | container.ContainerResponse + | box.BoxResponse; + +export type PitcherNotification = + | ai.AiNotification + | fs.FsNotification + | shell.ShellNotification + | client.ClientNotification + | port.PortNotification + | language.LanguageNotification + | file.FileNotification + | git.GitNotification + | setup.SetupNotification + | channel.ChannelNotification + | task.TaskNotification + | system.SystemNotification + | command.CommandNotification + | notification.NotificationNotification + | container.ContainerNotification; + +export { PitcherErrorCode } from "./errors"; +export { ClientAuthorization, ClientDisposeReason } from "./messages/client"; +export { + PitcherCapabilities, + PitcherPermissions, + PitcherPermissionKeys, +} from "./capabilities"; + +export { PitcherSubscriptions, ClientSubscriptions } from "./subscriptions"; + +export * as ai from "./messages/ai"; +export * as port from "./messages/port"; +export * as language from "./messages/language"; +export * as git from "./messages/git"; +export * as setup from "./messages/setup"; +export * as fs from "./messages/fs"; +export * as client from "./messages/client"; +export * as channel from "./messages/channel"; +export * as task from "./messages/task"; +export * as file from "./messages/file"; +export * as system from "./messages/system"; +export * as shell from "./messages/shell"; +export * as command from "./messages/command"; +export * as notification from "./messages/notification"; +export * as box from "./messages/box"; diff --git a/src/pitcher-protocol/message.ts b/src/pitcher-protocol/message.ts new file mode 100644 index 0000000..b2080ce --- /dev/null +++ b/src/pitcher-protocol/message.ts @@ -0,0 +1,75 @@ +import { + encode as msgpackEncode, + decode as msgpackDecode, +} from "@msgpack/msgpack"; +import { PitcherResponseStatus } from "./index"; + +export interface PitcherRequestPayload { + id: number; + method: string; + params: unknown; +} + +export interface PitcherNotificationPayload { + method: string; + params: unknown; +} + +export interface PitcherResponsePayload { + id: number; + method: string; + status: PitcherResponseStatus.RESOLVED; + result: unknown; +} + +export interface PitcherErrorPayload { + id: number; + status: PitcherResponseStatus.REJECTED; + error: { + code: number; + data?: unknown; + message: string; + }; +} + +export function encodeMessage(message: any): Uint8Array { + return msgpackEncode(message); +} + +export function decodeMessage(blob: Uint8Array): any { + return msgpackDecode(blob); +} + +export function isNotificationPayload( + payload: any, +): payload is PitcherNotificationPayload { + return !("id" in payload) && "params" in payload; +} + +export function isErrorPayload(payload: any): payload is PitcherErrorPayload { + return "error" in payload; +} + +export function isResultPayload( + payload: any, +): payload is PitcherResponsePayload { + return "result" in payload; +} + +export function createNotificationPayload( + payload: PitcherNotificationPayload, +): Uint8Array { + return encodeMessage(payload); +} + +export function createRequestPayload( + payload: PitcherRequestPayload, +): Uint8Array { + return encodeMessage(payload); +} + +export function createResponsePayload( + payload: PitcherResponsePayload | PitcherErrorPayload, +): Uint8Array { + return encodeMessage(payload); +} diff --git a/src/pitcher-protocol/messages/ai.ts b/src/pitcher-protocol/messages/ai.ts new file mode 100644 index 0000000..a5d929f --- /dev/null +++ b/src/pitcher-protocol/messages/ai.ts @@ -0,0 +1,297 @@ +import type { Id } from "@codesandbox/pitcher-common"; + +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { PitcherErrorCode } from "../errors"; + +export type CommonError = + | ProtocolError + | { + code: PitcherErrorCode.AI_NOT_AVAILABLE; + message: string; + }; + +export type SuggestCommitMessage = TMessage< + "ai/suggestCommit", + { + /** + * Ability to pass a model for a/b testing + * will be removed in the future + **/ + model?: "gpt-3.5-turbo" | "gpt-4"; + files: string[]; + temperature?: number; + }, + { + result: { + commit: string; + }; + error: CommonError; + } +>; + +export type RawMessage = TMessage< + "ai/raw", + { + /** + * Ability to pass a model for a/b testing + * will be removed in the future + **/ + model?: "gpt-3.5-turbo" | "gpt-4" | "gpt-4-1106-preview"; + messages: Array<{ + role: "system" | "user" | "assistant"; + content: string; + }>; + /** + * The maximum number of tokens allowed for the generated answer. By default, the number of tokens the model can return will be (4096 - prompt tokens). + */ + max_tokens?: number; + /** + * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. + */ + temperature?: number; + /** + * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. + */ + top_p?: number; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics. [See more information about frequency and presence penalties.](/docs/api-reference/parameter-details) + */ + presence_penalty?: number; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim. [See more information about frequency and presence penalties.](/docs/api-reference/parameter-details) + */ + frequency_penalty?: number; + /** + * a token to force the completion to end. + */ + stop?: string | string[]; + }, + { + result: { + reply: string; + }; + error: CommonError; + } +>; + +export type StreamMessage = TMessage< + "ai/stream", + { + /** + * Ability to pass a model for a/b testing + * will be removed in the future + **/ + model?: "gpt-3.5-turbo" | "gpt-4"; + messages: Array<{ + role: "system" | "user" | "assistant"; + content: string; + }>; + /** + * The maximum number of tokens allowed for the generated answer. By default, the number of tokens the model can return will be (4096 - prompt tokens). + */ + max_tokens?: number; + /** + * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. + */ + temperature?: number; + /** + * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. + */ + top_p?: number; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics. [See more information about frequency and presence penalties.](/docs/api-reference/parameter-details) + */ + presence_penalty?: number; + /** + * Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim. [See more information about frequency and presence penalties.](/docs/api-reference/parameter-details) + */ + frequency_penalty?: number; + /** + * a token to force the completion to end. + */ + stop?: string | string[]; + }, + { + result: { + messageId: Id; + }; + error: CommonError; + } +>; + +export type IChatContextFragment = + | { + type: "text" | "logs"; + content: string; + } + | { + type: "code"; + content: string; + filepath: string; + startLine: number; + endLine: number; + }; + +export type AIChatMessageMessage = TMessage< + "ai/chatMessage", + { + chatId: Id; + message: string; + messageId: Id; + context: IChatContextFragment[]; + }, + { + result: { + chatId: Id; + title: string; + }; + error: CommonError; + } +>; + +export type AIChatCreatedNotification = TNotification< + "ai/chatCreated", + { + chatId: Id; + title: string; + entries: IChatHistoryEntry[]; + } +>; + +export type AIChatMessageNotification = TNotification< + "ai/chatMessage", + { + chatId: Id; + /** + * idx, indicating the order of the messages, ensures we can resync if a message goes missing + **/ + idx: number; + /** + * User is manual input by a user + * System is intermediate messages, status, errors + * Assistant is the AI responding + **/ + role: "user" | "system" | "assistant"; + username?: string; + /** + * messageId is a unique identifier for the message used to listen for streaming progress + */ + messageId: Id; + message: string; + context: IChatContextFragment[]; + isFinished: boolean; + } +>; + +export type AIMessageProgressNotification = TNotification< + "ai/messageProgress", + { + messageId: Id; + chunk: string; + isFinished: boolean; + } +>; + +export interface IChatHistoryEntry { + /** + * idx, indicating the order of the messages, ensures we can resync if a message goes missing + **/ + idx: number; + /** + * User is manual input by a user + * System is intermediate messages, status, errors + * Assistant is the AI responding + **/ + role: "user" | "system" | "assistant"; + username?: string; + /** + * messageId is a unique identifier for the message used to listen for streaming progress + */ + messageId: Id; + message: string; + context: IChatContextFragment[]; + isFinished: boolean; +} + +export type AIChatsMessage = TMessage< + "ai/chats", + Record, + { + result: { + chats: Array<{ + chatId: Id; + title: string; + }>; + }; + error: CommonError; + } +>; + +export type AiChatHistoryMessage = TMessage< + "ai/chatHistory", + { + chatId: Id; + }, + { + result: { + entries: IChatHistoryEntry[]; + }; + error: CommonError; + } +>; + +export type AiMessageStateMessage = TMessage< + "ai/messageState", + { + messageId: Id; + }, + { + result: { + content: string; + isFinished: boolean; + }; + error: CommonError; + } +>; + +export interface IEmbeddingRecord { + filepath: string; + filetype: "code" | "manifest" | "doc"; + content: string; + startLine: number; + endLine: number; + /** cosine distance between vector and question */ + distance: number; +} + +export type AIEmbeddingsMessage = TMessage< + "ai/embeddings", + { + query: string; + }, + { + result: { + matches: IEmbeddingRecord[]; + }; + error: CommonError; + } +>; + +export type AiMessage = + | SuggestCommitMessage + | RawMessage + | StreamMessage + | AIChatMessageMessage + | AIChatsMessage + | AiChatHistoryMessage + | AIEmbeddingsMessage + | AiMessageStateMessage; + +export type AiRequest = AiMessage["request"]; + +export type AiResponse = AiMessage["response"]; + +export type AiNotification = + | AIChatCreatedNotification + | AIChatMessageNotification + | AIMessageProgressNotification; diff --git a/src/pitcher-protocol/messages/box.ts b/src/pitcher-protocol/messages/box.ts new file mode 100644 index 0000000..015b6c0 --- /dev/null +++ b/src/pitcher-protocol/messages/box.ts @@ -0,0 +1,51 @@ +import { ProtocolError, TMessage } from "../protocol"; +import { PitcherErrorCode } from "../errors"; + +export type CommonError = + | ProtocolError + | { + code: PitcherErrorCode.BOX_NOT_AVAILABLE; + message: string; + }; + +export interface IDependency { + name: string; + version: string; +} + +export type SupportedManagers = "pnpm"; + +export type InstallDependenciesMessage = TMessage< + "box/installDependencies", + { + manager: SupportedManagers; + dependencies: IDependency[]; + }, + { + result: { + success: boolean; + }; + error: CommonError; + } +>; + +export type InstalledDependenciesMessage = TMessage< + "box/installedDependencies", + { + manager: SupportedManagers; + }, + { + result: { + dependencies: IDependency[]; + }; + error: CommonError; + } +>; + +export type BoxMessage = + | InstallDependenciesMessage + | InstalledDependenciesMessage; + +export type BoxRequest = BoxMessage["request"]; + +export type BoxResponse = BoxMessage["response"]; diff --git a/src/pitcher-protocol/messages/channel.ts b/src/pitcher-protocol/messages/channel.ts new file mode 100644 index 0000000..0b60d8e --- /dev/null +++ b/src/pitcher-protocol/messages/channel.ts @@ -0,0 +1,99 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { PitcherErrorCode } from ".."; + +export type CommonError = + | { + code: PitcherErrorCode.CHANNEL_NOT_FOUND; + message: string; + } + | ProtocolError; + +export interface ChannelSubscribeParams { + name: string; +} + +export interface ChannelSubscribeResult { + subscribers: string[]; +} + +export type ChannelJoinMessage = TMessage< + "channel/subscribe", + ChannelSubscribeParams, + { + result: ChannelSubscribeResult; + error: CommonError; + } +>; + +export interface ChannelUnsubscribeParams { + name: string; +} + +export type ChannelUnsubscribeMessage = TMessage< + "channel/unsubscribe", + ChannelUnsubscribeParams, + { + result: Record; + error: CommonError; + } +>; + +export type ChannelMessageMessage = TMessage< + "channel/message", + { + name: string; + /** optional clients, can be undefined for a broadcast */ + clients?: string[]; + data: Record; + }, + { + result: { + clients: string[]; + }; + error: CommonError; + } +>; + +export type ChannelMessage = + | ChannelJoinMessage + | ChannelUnsubscribeMessage + | ChannelMessageMessage; + +export type ChannelRequest = ChannelMessage["request"]; + +export type ChannelResponse = ChannelMessage["response"]; + +export type ChannelSubscribedNotification = TNotification< + "channel/subscribed", + { + name: string; + clientId: string; + subscribers: string[]; + } +>; + +export type ChannelUnsubscribedNotification = TNotification< + "channel/unsubscribed", + { + name: string; + clientId: string; + subscribers: string[]; + } +>; + +export type ChannelMessageNotification = TNotification< + "channel/message", + { + name: string; + /** client that sent the message */ + clientId: string; + /** If the clientId has same username, meaning it is one of your other clients */ + isUser: boolean; + data: Record; + } +>; + +export type ChannelNotification = + | ChannelSubscribedNotification + | ChannelUnsubscribedNotification + | ChannelMessageNotification; diff --git a/src/pitcher-protocol/messages/client.ts b/src/pitcher-protocol/messages/client.ts new file mode 100644 index 0000000..9d57e59 --- /dev/null +++ b/src/pitcher-protocol/messages/client.ts @@ -0,0 +1,116 @@ +import { PitcherCapabilities, PitcherPermissions } from "../capabilities"; +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { ClientSubscriptions } from "../subscriptions"; + +export type ClientJSON = { + clientId: string; + username: string; + name: string; + avatarUrl: string | null; + color: string; + /** app id: "vscode", "playjs", "codesandbox", ... */ + appId: string; +}; + +export enum ClientAuthorization { + READ = "Read", + WRITE = "Write", + OWNER = "Owner", +} + +export enum ClientDisposeReason { + AUTHORIZATION_CHANGED = "AuthorizationChanged", + DISCONNECT = "Disconnect", + PITCHER_SHUTDOWN = "PitcherShutdown", +} + +export type CommonError = ProtocolError; + +export interface ClientJoinParams { + clientInfo: { + // version of @codesandbox/pitcher-protocol + protocolVersion: string; + /** app id: "vscode", "playjs", "codesandbox", ... */ + appId: string; + }; + /** + * This tells pitcher that the client will listen for async progress messages + * If this is false and pitcher is still initializing it will wait with + * sending the result until it's finished with initializing + */ + asyncProgress?: boolean; + subscriptions: ClientSubscriptions; +} + +export interface ClientJoinResult { + client: ClientJSON; + workspacePath: string; + // version of pitcher + version: string; + // version of @codesandbox/pitcher-protocol + protocolVersion: string; + latestPitcherVersion: string; + + capabilities: PitcherCapabilities; + permissions: PitcherPermissions; + isProtected: boolean; + // a token which holds information about clientId and color, for consistency + // between reconnects + reconnectToken: string; +} + +export type ClientJoinMessage = TMessage< + "client/join", + ClientJoinParams, + { + result: ClientJoinResult; + error: CommonError; + } +>; + +export type ClientListMessage = TMessage< + "client/list", + Record, + { + result: ClientJSON[]; + error: CommonError; + } +>; + +export type ClientMessage = ClientListMessage | ClientJoinMessage; + +export type ClientRequest = ClientMessage["request"]; + +export type ClientResponse = ClientMessage["response"]; + +export type ClientDisconnectedNotification = TNotification< + "client/disconnected", + { + clientId: string; + reason: ClientDisposeReason; + } +>; + +export type ClientConnectedNotification = TNotification< + "client/connected", + ClientJSON +>; + +export type ClientUpdatedNotification = TNotification< + "client/updated", + ClientJSON +>; + +export type ClientPermissionsUpdate = TNotification< + "client/permissions", + { + isProtected: boolean; + permissions: PitcherPermissions; + } +>; + +export type ClientNotification = + | ClientDisconnectedNotification + | ClientConnectedNotification + | ClientUpdatedNotification + | ClientPermissionsUpdate; diff --git a/src/pitcher-protocol/messages/command.ts b/src/pitcher-protocol/messages/command.ts new file mode 100644 index 0000000..34bab77 --- /dev/null +++ b/src/pitcher-protocol/messages/command.ts @@ -0,0 +1,62 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { PitcherErrorCode } from "../errors"; + +export type CommandFn = () => void | Promise; + +export type CommonError = + | ProtocolError + | { + code: PitcherErrorCode.COMMAND_NOT_FOUND; + message: string; + }; + +export interface ICommand { + /** + * Unique id of the command, to be sent to pitcher for executing the command + */ + id: string; + + /** + * Name to display in the command palette + * */ + displayName: string; + + // TODO: Add a way to send arguments to commands? +} + +export type ListCommandsMessage = TMessage< + "command/list", + Record, + { + result: { + commands: ICommand[]; + }; + error: CommonError; + } +>; + +export type ExecuteCommandMessage = TMessage< + "command/execute", + { + commandId: string; + }, + { + result: Record; + error: CommonError; + } +>; + +export type CommandsChangedNotification = TNotification< + "command/changed", + { + commands: ICommand[]; + } +>; + +export type CommandMessage = ListCommandsMessage | ExecuteCommandMessage; + +export type CommandRequest = CommandMessage["request"]; + +export type CommandResponse = CommandMessage["response"]; + +export type CommandNotification = CommandsChangedNotification; diff --git a/src/pitcher-protocol/messages/container.ts b/src/pitcher-protocol/messages/container.ts new file mode 100644 index 0000000..6073875 --- /dev/null +++ b/src/pitcher-protocol/messages/container.ts @@ -0,0 +1,25 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { TaskDTO } from "./task"; + +export type SetupContainer = TMessage< + "container/setup", + { + templateId: string; + templateArgs: Record; + features?: { id: string; options: Record }[]; + }, + { + result: TaskDTO; + error: ProtocolError; + } +>; + +export type ContainerNotification = TNotification< + "container/openSetupDevtool", + { dependencies: string[] } +>; + +export type ContainerMessages = SetupContainer; + +export type ContainerRequest = ContainerMessages["request"]; +export type ContainerResponse = ContainerMessages["response"]; diff --git a/src/pitcher-protocol/messages/file.ts b/src/pitcher-protocol/messages/file.ts new file mode 100644 index 0000000..ebfff49 --- /dev/null +++ b/src/pitcher-protocol/messages/file.ts @@ -0,0 +1,249 @@ +import { Id, ot } from "@codesandbox/pitcher-common"; +import { PitcherErrorCode } from "../errors"; + +import { ProtocolError, TMessage, TNotification } from "../protocol"; + +export type IRangeObject = { + anchor: number; + head: number; +}; + +export enum SelectionsUpdateReason { + CONTENT_CHANGE = 0, + SELECTION = 1, + CLIENT_LEFT = 2, +} + +export type ISelection = IRangeObject[]; + +export interface IDocumentSelections { + [clientId: string]: ISelection | null; +} + +export interface IDocumentClients { + [clientId: string]: ISelection | null; +} + +export interface IDocumentObject { + clients: IDocumentClients; + revision: number; +} + +export interface IFileClients { + [clientId: string]: { + username: string; + }; +} + +export interface IFileObject { + id: Id; + isBinary: boolean; + content: Uint8Array | string; + document: IDocumentObject | null; + savedHash: string; + clients: IFileClients; +} + +export type CommonError = ProtocolError; + +export type InvalidIdError = { + code: PitcherErrorCode.INVALID_ID; +}; + +export type InvalidPathError = { + code: PitcherErrorCode.INVALID_PATH; +}; + +/** + * Opens a new collaborative text document + */ +export type OpenFile = TMessage< + "file/open", + { + id: Id; + isResync?: boolean; + }, + { + result: IFileObject; + error: InvalidIdError | CommonError; + } +>; + +/** + * Opens a new collaborative text document + */ +export type OpenFileByPath = TMessage< + "file/openByPath", + { + path: string; + isResync?: boolean; + }, + { + result: IFileObject; + error: InvalidIdError | InvalidPathError | CommonError; + } +>; + +/** + * Client acknowledges receiving a revision, so Pitcher + * can keep track of what revisions each client is on + */ +export type AckDocument = TMessage< + "file/documentAck", + { + id: Id; + revision: number; + }, + { + result: { + revision: number; + }; + error: InvalidIdError | CommonError; + } +>; + +/** + * Closes a file, which is disposed + * when last user closes it + */ +export type CloseFile = TMessage< + "file/close", + { + id: Id; + }, + { + result: null; + error: InvalidIdError | CommonError; + } +>; + +/** + * Saves the document to disk when all clients are at + * revision to save + */ +export type SaveFile = TMessage< + "file/save", + { + id: Id; + /** + * Whether the document should be written to disk + */ + write?: boolean; + }, + { + result: null; + error: InvalidIdError | CommonError; + } +>; + +/** + * OT operation to make change to document + */ +export type DocumentOperation = TMessage< + "file/documentOperation", + { + id: Id; + operation: ot.JSONTextOperation; + revision: number; + }, + { + result: { + id: Id; + revision: number; + }; + error: InvalidIdError | CommonError; + } +>; + +/** + * Client acknowledges receiving a revision, so Pitcher + * can keep track of what revisions each client is on + */ +export type DocumentSelection = TMessage< + "file/documentSelection", + { + id: Id; + selection: ISelection; + /** + * Passing the selection reason allows for clients to separate selections, where + * for example Monaco/VSCode will filter out selections by CONTENT_CHANGE as they + * automatically transform selections with text operations + * + * NOTE! Will become a required property in later BREAKING version + */ + reason?: SelectionsUpdateReason; + }, + { + result: null; + error: InvalidIdError | CommonError; + } +>; + +type FileMessage = + | OpenFile + | OpenFileByPath + | CloseFile + | AckDocument + | SaveFile + | DocumentOperation + | DocumentSelection; + +export type FileRequest = FileMessage["request"]; + +export type FileResponse = FileMessage["response"]; + +export type JoinFileNotification = TNotification< + "file/join", + { + id: Id; + isBinary: boolean; + clientId: string; + username: string; + } +>; + +export type LeaveFileNotification = TNotification< + "file/leave", + { + id: Id; + isBinary: boolean; + clientId: string; + username: string; + } +>; + +export type SaveFileNotification = TNotification< + "file/save", + { + id: Id; + isBinary: boolean; + content: string | Uint8Array; + savedHash: string; + } +>; + +export type DocumentOperationNotification = TNotification< + "file/documentOperation", + { + id: Id; + operation: ot.JSONTextOperation; + revision: number; + reason: ot.OperationReason; + } +>; + +export type DocumentSelectionNotification = TNotification< + "file/documentSelection", + { + id: Id; + selections: IDocumentSelections; + reason?: SelectionsUpdateReason; + } +>; + +export type FileNotification = + | JoinFileNotification + | LeaveFileNotification + | SaveFileNotification + | DocumentOperationNotification + | DocumentSelectionNotification; diff --git a/src/pitcher-protocol/messages/fs.ts b/src/pitcher-protocol/messages/fs.ts new file mode 100644 index 0000000..d8ed271 --- /dev/null +++ b/src/pitcher-protocol/messages/fs.ts @@ -0,0 +1,515 @@ +/** + * # FS Protocol + * + * The filesystem protocol revolves around sending and receiving operations to + * update the {@link MemoryFS} instance on Pitcher's clients and server. + * This allows operations to be conflict resolving by the commutative and + * monotonic properties of MemoryFS's CRDT tree structure. + * + * ## Load Project + * + * To populate MemoryFS with an initial snapshot of the project's files, we send + * {@link LoadProject} to request the list of MemoryFS nodes and children. These + * contain the file paths, ids, metadata, but _not_ the file contents. + * + * This allows us to operate on files by their ids, allowing us not to worry + * about their name or path at any given moment in time. + * + * ## Operations + * + * To inform the server of new filesystem operations, {@link SendOps} is used to + * send a list of {@link OpMove}s generated by MemoryFS. + * + * To keep the client up-to-date, the {@link ListenOps} is used to subscribe to + * new operations sent by other clients to the server, which can then be applied + * to the client's MemoryFS. + */ + +import { bedrockFS, Id } from "@codesandbox/pitcher-common"; +import { PitcherErrorCode } from "../errors"; + +import { ProtocolError, TMessage, TNotification } from "../protocol"; + +export type FsCapabilities = { + reading: boolean; + writing: boolean; + watching: boolean; +}; + +export type FsServerCapabilities = { + reading: boolean; + watching: boolean; + writing: boolean; +}; + +export type FsClientCapabilities = { [key: string]: unknown }; + +export type SearchResult = { + fileId: Id; + lines: { + text: string; + }; + lineNumber: number; + absoluteOffset: number; + submatches: SearchSubMatch[]; +}; + +export type StreamingSearchResult = { + fileId?: Id; + filepath: string; + lines: { + text: string; + }; + lineNumber: number; + absoluteOffset: number; + submatches: SearchSubMatch[]; +}; + +export type SearchSubMatch = { + match: { + text: string; + }; + start: number; + end: number; +}; + +export type CommonError = ProtocolError; + +export type InvalidIdError = { + code: PitcherErrorCode.INVALID_ID; +}; + +export type RawFileSystemError = { + code: PitcherErrorCode.RAWFS_ERROR; + data: { + errno: number | null; + }; +}; + +export interface FSReadResult { + treeNodes: bedrockFS.JSONNode[]; + clock: number; +} + +/** Retreive the latest snapshot of the server's MemoryFS file and children list */ +export type FSReadMessage = TMessage< + "fs/read", + null, + { + result: FSReadResult; + error: CommonError; + } +>; + +export interface FSCreateOperation { + type: "create"; + parentId: Id; + newEntry: { id: Id; type: bedrockFS.NodeType; name: string }; +} + +export interface FSDeleteOperation { + type: "delete"; + id: Id; +} + +export interface FSMoveOperation { + type: "move"; + id: Id; + parentId?: Id; + name?: string; +} + +export type FSOperation = + | FSCreateOperation + | FSDeleteOperation + | FSMoveOperation; + +export enum FSOperationResponseCode { + Success = 0, + Ignored = 1, +} + +/** Send a list of tree operations reflecting filesystem operations */ +export type FSOperationMessage = TMessage< + "fs/operation", + { operation: FSOperation }, + { + result: + | { + code: FSOperationResponseCode; + clock: number; + } + | { + code: FSOperationResponseCode.Ignored; + }; + error: CommonError; + } +>; + +export interface FSSearchParams { + text: string; + glob?: string; + isRegex?: boolean; + caseSensitivity?: "smart" | "enabled" | "disabled"; +} + +export type FSSearchMessage = TMessage< + "fs/search", + FSSearchParams, + { + result: SearchResult[]; + error: CommonError; + } +>; + +export interface FSStreamingSearchParams { + searchId: Id; + text: string; + glob?: string; + isRegex?: boolean; + caseSensitivity?: "smart" | "enabled" | "disabled"; + /** + * That default limit is 10_000 results + */ + maxResults?: number; +} + +export type FSStreamingSearchMessage = TMessage< + "fs/streamingSearch", + FSStreamingSearchParams, + { + result: { + searchId: Id; + }; + error: CommonError; + } +>; + +export type FSCancelStreamingSearchMessage = TMessage< + "fs/cancelStreamingSearch", + { + searchId: Id; + }, + { + result: { + searchId: Id; + }; + error: CommonError; + } +>; + +export interface PathSearchMatch { + path: string; + submatches: SearchSubMatch[]; +} + +export interface PathSearchResult { + matches: PathSearchMatch[]; +} + +export interface PathSearchParams { + text: string; +} + +export type PathSearchMessage = TMessage< + "fs/pathSearch", + PathSearchParams, + { + result: PathSearchResult; + error: CommonError; + } +>; + +export type FSUploadMessage = TMessage< + "fs/upload", + { + parentId: Id; + filename: string; + content: Uint8Array; + }, + { + result: { + fileId: Id; + }; + error: CommonError | InvalidIdError; + } +>; + +export type FSDownloadMessage = TMessage< + "fs/download", + { + path: string; + /** + * Glob patterns of files/folders to exclude from the download. Defaults to + * *\*\/node_modules/\*\*. + */ + excludes?: string[]; + }, + { + result: { + downloadUrl: string; + }; + error: CommonError; + } +>; + +// #region RawFS + +export type FSReadFileParams = { + path: string; +}; + +export type FSReadFileResult = { + content: Uint8Array; +}; + +export type FSReadFileMessage = TMessage< + "fs/readFile", + FSReadFileParams, + { + result: FSReadFileResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSReadDirParams = { + path: string; +}; + +export type FSReadDirResult = { + entries: { + name: string; + type: bedrockFS.NodeType; + isSymlink: boolean; + }[]; +}; + +export type FSReadDirMessage = TMessage< + "fs/readdir", + FSReadDirParams, + { + result: FSReadDirResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSWriteFileParams = { + path: string; + content: Uint8Array; + create?: boolean; + overwrite?: boolean; +}; + +export type FSWRiteFileResult = Record; + +export type FSWriteFileMessage = TMessage< + "fs/writeFile", + FSWriteFileParams, + { + result: FSWRiteFileResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSStatParams = { + path: string; +}; + +export type FSStatResult = { + type: bedrockFS.NodeType; + isSymlink: boolean; + size: number; + mtime: number; + ctime: number; + atime: number; +}; + +export type FSStatMessage = TMessage< + "fs/stat", + FSStatParams, + { + result: FSStatResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSCopyParams = { + from: string; + to: string; + recursive?: boolean; + overwrite?: boolean; +}; + +export type FSCopyResult = Record; + +export type FSCopyMessage = TMessage< + "fs/copy", + FSCopyParams, + { + result: FSCopyResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSRenameParams = { + from: string; + to: string; + overwrite?: boolean; +}; + +export type FSRenameResult = Record; + +export type FSRenameMessage = TMessage< + "fs/rename", + FSRenameParams, + { + result: FSRenameResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSRemoveParams = { + path: string; + recursive?: boolean; +}; + +export type FSRemoveResult = Record; + +export type FSRemoveMessage = TMessage< + "fs/remove", + FSRemoveParams, + { + result: FSRemoveResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSMkdirParams = { + path: string; + recursive?: boolean; +}; + +export type FSMkdirResult = Record; + +export type FSMkdirMessage = TMessage< + "fs/mkdir", + FSMkdirParams, + { + result: FSMkdirResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSWatchParams = { + path: string; + recursive?: boolean; + excludes?: string[]; +}; + +export type FSWatchResult = { + watchId: string; +}; + +export type FSWatchMessage = TMessage< + "fs/watch", + FSWatchParams, + { + result: FSWatchResult; + error: CommonError | RawFileSystemError; + } +>; + +export type FSUnwatchParams = { + watchId: string; +}; + +export type FSUnwatchResult = Record; + +export type FSUnwatchMessage = TMessage< + "fs/unwatch", + FSUnwatchParams, + { + result: FSUnwatchResult; + error: CommonError | RawFileSystemError; + } +>; + +type RawFsMessage = + | FSReadFileMessage + | FSReadDirMessage + | FSWriteFileMessage + | FSStatMessage + | FSCopyMessage + | FSRenameMessage + | FSRemoveMessage + | FSMkdirMessage + | FSWatchMessage + | FSUnwatchMessage; + +// #endregion + +type FsMessage = + | FSReadMessage + | FSOperationMessage + | FSSearchMessage + | FSStreamingSearchMessage + | FSCancelStreamingSearchMessage + | PathSearchMessage + | FSUploadMessage + | FSDownloadMessage + | RawFsMessage; + +export type FsRequest = FsMessage["request"]; + +export type FsResponse = FsMessage["response"]; + +export interface FSWatchEvent { + paths: string[]; + type: "add" | "change" | "remove"; +} + +export interface FSOperationEvent { + operation: FSOperation; + clock: number; +} + +/** + * Listen for tree operations reflecting filesystem operations made by + * other clients + */ +export type FSOperationsNotification = TNotification< + "fs/operations", + { + operations: FSOperationEvent[]; + } +>; + +export type FSWatchNotifiction = TNotification< + "fs/watchEvent", + { + watchId: string; + events: FSWatchEvent[]; + } +>; + +export type FSSearchMatchesNotifiction = TNotification< + "fs/searchMatches", + { + searchId: string; + matches: StreamingSearchResult[]; + } +>; + +export type FSSearchFinishedNotifiction = TNotification< + "fs/searchFinished", + { + searchId: string; + hitLimit: boolean; + } +>; + +export type FsNotification = + | FSOperationsNotification + | FSWatchNotifiction + | FSSearchMatchesNotifiction + | FSSearchFinishedNotifiction; diff --git a/src/pitcher-protocol/messages/git.ts b/src/pitcher-protocol/messages/git.ts new file mode 100644 index 0000000..1d7bbde --- /dev/null +++ b/src/pitcher-protocol/messages/git.ts @@ -0,0 +1,306 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { Id } from "@codesandbox/pitcher-common"; + +import { PitcherErrorCode } from "../errors"; + +// See https://git-scm.com/docs/git-status#_short_format +export enum GitStatusShortFormat { + UnModified = "", + Modified = "M", + Added = "A", + Deleted = "D", + Renamed = "R", + Copied = "C", + /** + * Updated but not merged + */ + Updated = "U", + /** + * New files that have never been committed before + */ + Untracked = "?", +} + +export interface GitItem { + path: string; + index: GitStatusShortFormat; + workingTree: GitStatusShortFormat; + isStaged: boolean; + isConflicted: boolean; + fileId?: Id; +} + +export type GitChangedFiles = { + [fileId: string]: GitItem; +}; + +export interface GitBranchProperties { + head: string | null; + branch: string | null; + ahead: number; + behind: number; + safe: boolean; +} + +export interface GitStatus { + changedFiles: GitChangedFiles; + deletedFiles: GitItem[]; + conflicts: boolean; // remote conflicts, we are exploring how to get these from target + localChanges: boolean; + remote: GitBranchProperties; + target: GitBranchProperties; + head?: string; + commits: GitCommit[]; // Might be revisited if the payload is too big + branch: string | null; + isMerging: boolean; +} + +export interface GitCommit { + hash: string; + date: string; + message: string; + author: string; +} + +export interface GitTargetDiff { + ahead: number; + behind: number; + commits: GitCommit[]; +} + +export type CommonError = + | { + code: + | PitcherErrorCode.GIT_OPERATION_IN_PROGRESS + | PitcherErrorCode.GIT_REMOTE_FILE_NOT_FOUND; + message: string; + } + | ProtocolError; + +export type GitStatusMessage = TMessage< + "git/status", + Record, + { + result: GitStatus; + error: CommonError; + } +>; + +export interface GitRemotes { + origin: string; + upstream: string; +} + +export type GitRemotesMessage = TMessage< + "git/remotes", + Record, + { + result: GitRemotes; + error: CommonError; + } +>; + +export type GitTargetDiffMessage = TMessage< + "git/targetDiff", + { branch: string }, + { result: GitTargetDiff; error: CommonError } +>; + +export type GitPullMessage = TMessage< + "git/pull", + { branch?: string; force?: boolean }, + { result: null; error: CommonError } +>; + +export type GitDiscardChangesMessage = TMessage< + "git/discard", + { paths?: string[] }, + { result: { paths?: string[] }; error: CommonError } +>; + +export type GitCommitMessage = TMessage< + "git/commit", + { paths?: string[]; message: string; push?: boolean }, + { + result: { + shellId: Id; + }; + error: CommonError; + } +>; + +export type GitPushMessage = TMessage< + "git/push", + null, + { + result: null; + error: CommonError; + } +>; + +export type GitPushToRemoteMessage = TMessage< + "git/pushToRemote", + { url: string; branch: string; squashAllCommits?: boolean }, + { result: null; error: CommonError } +>; + +export type GitRenameBranchMessage = TMessage< + "git/renameBranch", + { oldBranch: string; newBranch: string }, + { result: null; error: CommonError } +>; + +export interface GitRemoteParams { + /** branch or commit hash */ + reference: string; + path: string; +} + +export type GitRemoteContentMessage = TMessage< + "git/remoteContent", + GitRemoteParams, + { + result: { + content: string; + }; + error: CommonError; + } +>; + +export interface GitDiffStatusParams { + /** + * Base reference used for diffing, + * Can be any valid git reference: commit, HEAD, branch-name, tag, ... + * executed like "git diff base..head" + **/ + base: string; + /** + * Head reference used for diffing, + * Can be any valid git reference: commit, HEAD, branch-name, tag, ... + * executed like "git diff base..head" + **/ + head: string; +} + +export interface GitDiffStatusItem { + status: GitStatusShortFormat; + path: string; + oldPath?: string; + hunks: Array<{ + original: { start: number; end: number }; + modified: { start: number; end: number }; + }>; +} + +export interface GitDiffStatusResult { + files: GitDiffStatusItem[]; +} + +export type GitDiffStatusMessage = TMessage< + "git/diffStatus", + GitDiffStatusParams, + { + result: GitDiffStatusResult; + error: CommonError; + } +>; + +export type GitResetLocalWithRemote = TMessage< + "git/resetLocalWithRemote", + Record, + { + result: null; + error: CommonError; + } +>; + +export type GitCheckoutInitialBranch = TMessage< + "git/checkoutInitialBranch", + Record, + { result: null; error: CommonError } +>; + +export type GitTransposeLines = TMessage< + "git/transposeLines", + Array<{ sha: string; path: string; line: number }>, + { + result: Array<{ path: string; line: number } | null>; + error: CommonError; + } +>; + +type GitMessage = + | GitPushToRemoteMessage + | GitStatusMessage + | GitTargetDiffMessage + | GitPullMessage + | GitDiscardChangesMessage + | GitCommitMessage + | GitRenameBranchMessage + | GitRemoteContentMessage + | GitDiffStatusMessage + | GitRemotesMessage + | GitResetLocalWithRemote + | GitPushMessage + | GitCheckoutInitialBranch + | GitTransposeLines; + +export type GitRequest = GitMessage["request"]; + +export type GitResponse = GitMessage["response"]; + +export type GitStatusNotification = TNotification<"git/status", GitStatus>; + +export type GitPullStartedNotification = TNotification<"git/pullStarted", null>; + +export type GitPullFinishedNotification = TNotification< + "git/pullFinished", + null | { + exitCode: 1; + error: CommonError; + } +>; + +export type GitCommitStartedNotification = TNotification< + "git/commitStarted", + { + shellId: Id; + message: string; + paths?: string[]; + } +>; + +export type GitCommitFinishedNotification = TNotification< + "git/commitFinished", + { + exitCode: 0 | 1; + } +>; + +/** + * Our VMs are currently bound to the dedicated branch. We prevent users from changing the branch manually, + * but notify when it happens so the clients can act accordingly + */ +export type GitCheckoutBranchNotification = TNotification< + "git/checkoutPrevented", + { + branch: string | null; + } +>; + +export type GitRenameBranchNotification = TNotification< + "git/renameBranch", + { oldBranch: string; newBranch: string } +>; + +export type GitRemotesNotification = TNotification<"git/remotes", GitRemotes>; + +export type GitNotification = + | GitStatusNotification + | GitPullStartedNotification + | GitPullFinishedNotification + | GitCommitStartedNotification + | GitCommitFinishedNotification + | GitRenameBranchNotification + | GitRemotesNotification + | GitCheckoutBranchNotification; diff --git a/src/pitcher-protocol/messages/language.ts b/src/pitcher-protocol/messages/language.ts new file mode 100644 index 0000000..9e3aa48 --- /dev/null +++ b/src/pitcher-protocol/messages/language.ts @@ -0,0 +1,125 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; + +export type CommonError = ProtocolError; + +export interface ILanguageConfig { + id: string; + /** @deprecated in favor of globs, everything listed in extensions is listed in glob form as well */ + extensions: string[]; + /** something/*.json, *.js, *.ts */ + globs: string[]; + hasLanguageServer: boolean; + languageServerIds: string[]; +} + +// List available languages +export type ListLanguagesMessage = TMessage< + "language/list", + Record, + { + result: { + languages: ILanguageConfig[]; + }; + error: CommonError; + } +>; + +export interface LSPResponseError { + /** + * A number indicating the error type that occurred. + */ + code: number; + + /** + * A string providing a short description of the error. + */ + message: string; + + /** + * A primitive or structured value that contains additional + * information about the error. Can be omitted. + */ + data?: unknown; +} + +// Send an LSP Request +export type LSPRequestMessage = TMessage< + "language/lspRequest", + { + languageId: string; + serverId: string; + message: { + method: string; + // Params can be anything as lsp isn't really an exhaustive spec... + params?: unknown; + }; + }, + { + // Result can be anything as lsp isn't really an exhaustive spec... + result: unknown; + error: CommonError | LSPResponseError; + } +>; + +export type LanguageLspNotification = TNotification< + "language/lspNotification", + { + languageId: string; + serverId: string; + message: { + method: string; + // Params can be anything as lsp isn't really an exhaustive spec... + params?: unknown; + }; + } +>; + +/** + * Used for requests from the server to the client + */ +export type LanguageLspServerRequest = TNotification< + "language/lspServerRequest", + { + languageId: string; + serverId: string; + message: { + id: number | string; + method: string; + // Params can be anything as lsp isn't really an exhaustive spec... + params?: unknown; + }; + } +>; + +/** + * Used for responses from the client to the server on a request + */ +export type LanguageLspServerResponse = TMessage< + "language/lspServerResponse", + { + languageId: string; + serverId: string; + message: { + id: number | string; + result?: string | number | boolean | Record | null; + error?: CommonError | LSPResponseError; + }; + }, + { + result: unknown; + error: CommonError | LSPResponseError; + } +>; + +export type LanguageMessage = + | ListLanguagesMessage + | LSPRequestMessage + | LanguageLspServerResponse; + +export type LanguageRequest = LanguageMessage["request"]; + +export type LanguageResponse = LanguageMessage["response"]; + +export type LanguageNotification = + | LanguageLspNotification + | LanguageLspServerRequest; diff --git a/src/pitcher-protocol/messages/notification.ts b/src/pitcher-protocol/messages/notification.ts new file mode 100644 index 0000000..3b3c384 --- /dev/null +++ b/src/pitcher-protocol/messages/notification.ts @@ -0,0 +1,50 @@ +import { TNotification, TMessage, ProtocolError } from "../protocol"; +import { Id } from "@codesandbox/pitcher-common"; + +export type NotificationType = "info" | "warning" | "error"; +export type Action = { + label: string; +}; + +/** + * Is sent from Pitcher to the client, to ask the client to show a notification. + * There's an ID given, this is important, because the client is responsible for + * using this to send a response back to the notification (either dismiss or one of + * the actions are clicked, in case there are actions). + */ +export type NotificationNotify = TNotification< + "notification/notify", + { + type: NotificationType; + notificationId: Id; + message: string; + actions?: Action[]; + } +>; + +/** + * Is sent from Pitcher to the client, to dismiss the notification. + */ +export type NotificationDismiss = TNotification< + "notification/dismiss", + { + notificationId: Id; + } +>; + +export type NotificationAckResponse = TMessage< + "notification/notifyResponse", + { + notificationId: Id; + response: string | null; + }, + { + result: void; + error: ProtocolError; + } +>; + +export type NotificationNotification = NotificationNotify | NotificationDismiss; +export type NotificationMessage = NotificationAckResponse; +export type NotificationRequest = NotificationMessage["request"]; +export type NotificationResponse = NotificationMessage["response"]; diff --git a/src/pitcher-protocol/messages/port.ts b/src/pitcher-protocol/messages/port.ts new file mode 100644 index 0000000..7b48336 --- /dev/null +++ b/src/pitcher-protocol/messages/port.ts @@ -0,0 +1,34 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; + +export type CommonError = ProtocolError; + +export type Port = { + port: number; + url: string; +}; + +export type PortList = TMessage< + "port/list", + Record, + { + result: { + list: Port[]; + }; + error: CommonError; + } +>; + +export type PortMessage = PortList; + +export type PortRequest = PortMessage["request"]; + +export type PortResponse = PortMessage["response"]; + +export type PortChanged = TNotification< + "port/changed", + { + list: Port[]; + } +>; + +export type PortNotification = PortChanged; diff --git a/src/pitcher-protocol/messages/setup.ts b/src/pitcher-protocol/messages/setup.ts new file mode 100644 index 0000000..b7c61b4 --- /dev/null +++ b/src/pitcher-protocol/messages/setup.ts @@ -0,0 +1,103 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { Id } from "@codesandbox/pitcher-common"; + +export interface Step { + name: string; + command: string; + shellId: Id | null; + finishStatus: SetupShellStatus | null; +} + +export type SetupShellStatus = "SUCCEEDED" | "FAILED" | "SKIPPED"; + +export interface SetupProgress { + state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; + steps: Step[]; + currentStepIndex: number; +} + +export type SetupGetMessage = TMessage< + "setup/get", + Record, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +export type SetupSkipStep = TMessage< + "setup/skip", + { stepIndexToSkip: number }, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +export type SetupSkipAll = TMessage< + "setup/skipAll", + null, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +export type SetupDisable = TMessage< + "setup/disable", + null, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +export type SetupEnable = TMessage< + "setup/enable", + null, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +export type SetupInit = TMessage< + "setup/init", + null, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +/** + * Set the current step. This is used to restart the process, for example. + */ +export type SetupSetStep = TMessage< + "setup/setStep", + { stepIndex: number }, + { + result: SetupProgress; + error: ProtocolError; + } +>; + +type SetupMessage = + | SetupGetMessage + | SetupSkipStep + | SetupSkipAll + | SetupSetStep + | SetupDisable + | SetupEnable + | SetupInit; + +export type SetupRequest = SetupMessage["request"]; + +export type SetupResponse = SetupMessage["response"]; + +export type SetupStatusNotification = TNotification< + "setup/progress", + SetupProgress +>; + +export type SetupNotification = SetupStatusNotification; diff --git a/src/pitcher-protocol/messages/shell.md b/src/pitcher-protocol/messages/shell.md new file mode 100644 index 0000000..21e4140 --- /dev/null +++ b/src/pitcher-protocol/messages/shell.md @@ -0,0 +1,185 @@ +# Shell Protocol + +The shell protocol allows users to open shells on the remote machine. This is useful for multiple use cases, the main ones we focus are: + +1. Running a command (eg. `yarn` or `yarn dev`) +2. Creating an interactive terminal (eg. `/bin/bash`) + +The challenge and interesting part of this shell interface is that it's built to be shared with other users. You can give others access to your terminal, you can give others access to write or read from your terminal. This API is built to be collaborative by default. +An inspiration here is [`tmux`](https://github.com/tmux/tmux), `tmux` allows users to create as many terminal windows as they'd like in a single session, and everyone can control and share those terminal windows + +In the first version I'm describing here we won't take users into account. That's the direct next steps as we create the users provider. + +## Common + +These are all common types used across the different requests/notifications/responses. + +```ts +/** + * A shell id should be globally unique for the Pitcher Server as they can be shared between different users. To be safe + * we should refrain from re-using existing shell ids. + */ +type ShellId = Cuid; + +/** + * The shell size is the number of rows and columns on which the shell content renders + */ +type ShellSize = { cols: number; rows: number }; + +/** + * There are two types of shells, commands and terminals. They have slightly different fields, but they also share a common interface. + * A shell can be in one of the three states (status): RUNNING, FINISHED, ERRORED. + * We use DTO (data-transfer-object) here to differentiate between the entities passed by the protocol and the internal interfaces/classes used by pitcher. + * Shells prefixed with `Open` have the content also returned and ready to be displayed. + */ +type BaseShellDTO = { + shellId: ShellId; + name: string; + status: ShellProcessStatus; + exitCode?: number; +}; + +export type CommandShellDTO = BaseShellDTO & { + shellType: "COMMAND"; + startCommand: string; +}; + +export type TerminalShellDTO = BaseShellDTO & { + shellType: "TERMINAL"; + ownerUsername: string; +}; + +export type ShellDTO = CommandShellDTO | TerminalShellDTO; + +export type OpenCommandShellDTO = CommandShellDTO & { + buffer: string[]; +}; + +export type OpenTerminalShellDTO = TerminalShellDTO & { + buffer: string[]; +}; + +export type OpenShellDTO = OpenCommandShellDTO | OpenTerminalShellDTO; +``` + +## Requests + +### shell/create + +Used to create a shell. If it receives a `command` it will run a `COMMAND` type shell, which runs that command until terminated or until an exit code is issued. If it doesn't receive a `command`, it will run a `TERMINAL` type shell, in which the user can type in any command. The `size` is optional, as the client might not know it when the `shell/create` is issued. + +Input params; + +``` +cwd?: string; +command?: string; +size?: ShellSize; +``` + +Returns an object of type `OpenShellDTO`. + +### shell/list + +Get the list of all running shells. + +Returns an array of `ShellDTO`s. + +### shell/open + +Used to connect to the stdio of a shell with the specified shell id. After calling this, you will get the last 10000 rows (in `utf-8`) of the specified shell, and you'll get notifications afterwards for all output on stderr and stdout. + +Input params + +``` +shellId: ShellId; +size: ShellSize; +``` + +Returns an object of type `OpenShellDTO`. + +### shell/resize + +This can only be called by the creator of the shell. + +Input params + +``` +shellId: ShellId; +size: ShellSize; +``` + +No return type. + +### shell/in + +Used to send input to a shell. The `size` will dictate how the content of the shell gets rendered after the user inputs the command. `shell/in` should only be called for TERMINAL type shells. + +```ts +shellId: ShellId; +input: string; +size: ShellSize; +``` + +No return type. + +### shell/restart + +Kills the current process (if it is running) and restarts it with the same input parameters as the original `create` request. + +Input: `shellId`. + +No return type. + +### shell/terminate + +Kills the running process and removes the shell from pitcher. + +Input: `shellId`. + +No return type. + +## Notifications + +Notifications are sent to pitcher clients as events. `pitcher-client` will listen to these notifications and call a listener accordingly. + +### shell/out + +Whenever a shell has output, it will send it to all subscribed clients using this notification. + +``` +shellId: ShellId; +out: string; // in utf-8 +``` + +### shell/create + +Whenever a shell is created, other subscribed clients will receive this notification. + +``` +shell: ShellDTO; +``` + +### shell/exit + +Whenever the process behind a shell exits, this notification will be sent. An exit code will be provided as well. + +``` +shellId: ShellId; +exitCode: number; +``` + +### shell/restart + +Whenever the shell is explicitly restarted, a notification is sent to update the status for all other clients. + +``` +shellId: ShellId; +``` + +### shell/terminate + +Whenever the shell is explicitly terminated/killed, a notification is sent to all subscribed clients. + +``` +shellId: ShellId; +``` diff --git a/src/pitcher-protocol/messages/shell.ts b/src/pitcher-protocol/messages/shell.ts new file mode 100644 index 0000000..864011a --- /dev/null +++ b/src/pitcher-protocol/messages/shell.ts @@ -0,0 +1,230 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { Id } from "@codesandbox/pitcher-common"; +import { PitcherErrorCode } from "../errors"; + +export type ShellId = Id; + +export type ShellSize = { cols: number; rows: number }; + +export type ShellProcessType = "TERMINAL" | "COMMAND"; + +export type ShellProcessStatus = + | "RUNNING" + | "FINISHED" + | "ERROR" + | "KILLED" + | "RESTARTING"; + +type BaseShellDTO = { + shellId: ShellId; + name: string; + status: ShellProcessStatus; + exitCode?: number; +}; + +export type CommandShellDTO = BaseShellDTO & { + shellType: "COMMAND"; + startCommand: string; +}; + +export type TerminalShellDTO = BaseShellDTO & { + shellType: "TERMINAL"; + ownerUsername: string; + isSystemShell: boolean; +}; + +export type ShellDTO = CommandShellDTO | TerminalShellDTO; + +export type OpenCommandShellDTO = CommandShellDTO & { + buffer: string[]; +}; + +export type OpenTerminalShellDTO = TerminalShellDTO & { + buffer: string[]; +}; + +export type OpenShellDTO = OpenCommandShellDTO | OpenTerminalShellDTO; + +export type CommonError = + | { + code: PitcherErrorCode.SHELL_NOT_ACCESSIBLE; + message: string; + } + | ProtocolError; + +export type ShellCreate = TMessage< + "shell/create", + { + command?: string; + cwd?: string; + size?: ShellSize; + type?: ShellProcessType; + /** + * Whether this shell is started by the editor itself to + * run a specific process, like an LSP or internal server. + * This is set to true if the command is not initiated by the user. + */ + isSystemShell?: boolean; + }, + { + result: OpenShellDTO; + error: CommonError; + } +>; + +export type ShellIn = TMessage< + "shell/in", + { + shellId: ShellId; + input: string; + size: ShellSize; + }, + { + result: null; + error: CommonError; + } +>; + +export type ShellList = TMessage< + "shell/list", + Record, + { + result: { + shells: ShellDTO[]; + }; + error: CommonError; + } +>; + +export type ShellOpen = TMessage< + "shell/open", + { + shellId: ShellId; + size: ShellSize; + }, + { + result: OpenShellDTO; + error: CommonError; + } +>; + +export type ShellClose = TMessage< + "shell/close", + { + shellId: ShellId; + }, + { + result: null; + error: CommonError; + } +>; + +export type ShellRestart = TMessage< + "shell/restart", + { + shellId: ShellId; + }, + { + result: null; + error: CommonError; + } +>; + +export type ShellTerminate = TMessage< + "shell/terminate", + { + shellId: ShellId; + }, + { + result: ShellDTO; + error: CommonError; + } +>; + +export type ShellResize = TMessage< + "shell/resize", + { + shellId: ShellId; + size: ShellSize; + }, + { + result: null; + error: CommonError; + } +>; + +export type ShellRename = TMessage< + "shell/rename", + { shellId: ShellId; name: string }, + { + result: null; + error: CommonError; + } +>; + +type ShellMessage = + | ShellCreate + | ShellIn + | ShellList + | ShellClose + | ShellOpen + | ShellRestart + | ShellTerminate + | ShellResize + | ShellRename; + +export type ShellRequest = ShellMessage["request"]; + +export type ShellResponse = ShellMessage["response"]; + +export type ShellOutNotification = TNotification< + "shell/out", + { + shellId: ShellId; + out: string; + } +>; + +export type ShellCreateNotification = TNotification<"shell/create", ShellDTO>; + +// Shell command finished (success or error) +export type ShellExitNotification = TNotification< + "shell/exit", + { + shellId: ShellId; + shellType: ShellProcessType; + exitCode: number; + } +>; + +// Shell command was restarted +export type ShellRestartNotification = TNotification< + "shell/restart", + { + shellId: ShellId; + } +>; + +// User killed shell +export type ShellTerminateNotification = TNotification< + "shell/terminate", + { + shellId: ShellId; + author: string; + } +>; + +export type ShellRenameNotification = TNotification< + "shell/rename", + { + shell: ShellDTO; + } +>; + +export type ShellNotification = + | ShellOutNotification + | ShellCreateNotification + | ShellRestartNotification + | ShellExitNotification + | ShellTerminateNotification + | ShellRenameNotification; diff --git a/src/pitcher-protocol/messages/system.ts b/src/pitcher-protocol/messages/system.ts new file mode 100644 index 0000000..0d96f38 --- /dev/null +++ b/src/pitcher-protocol/messages/system.ts @@ -0,0 +1,84 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +export type SystemError = ProtocolError; + +export interface SystemMetricsStatus { + cpu: { + cores: number; + used: number; + configured: number; + }; + memory: { + used: number; + total: number; + configured: number; + }; + storage: { + used: number; + total: number; + configured: number; + }; +} + +export interface InitStatus { + message: string; + isError?: boolean; + // 0 - 100 + progress: number; + // 0 - 100 + nextProgress: number; + stdout?: string; +} + +export type SystemUpdate = TMessage< + "system/update", + Record, + { + result: Record; + error: SystemError; + } +>; + +export type SystemHibernate = TMessage< + "system/hibernate", + Record, + { result: null; error: SystemError } +>; + +export type SystemMetrics = TMessage< + "system/metrics", + Record, + { + result: SystemMetricsStatus; + error: SystemError; + } +>; + +export type SystemMessage = SystemUpdate | SystemHibernate | SystemMetrics; + +export type SystemRequest = SystemMessage["request"]; + +export type SystemResponse = SystemMessage["response"]; + +export type HibernationNotification = TNotification< + "system/hibernate", + Record +>; + +export type SystemMetricsNotification = TNotification< + "system/metrics", + SystemMetricsStatus +>; + +/** + * Allows clients to listen to the status of pitcher when it is Initializing + * git clone, creating folders, setting up node, ... + */ +export type InitStatusNotification = TNotification< + "system/initStatus", + InitStatus +>; + +export type SystemNotification = + | HibernationNotification + | SystemMetricsNotification + | InitStatusNotification; diff --git a/src/pitcher-protocol/messages/task.ts b/src/pitcher-protocol/messages/task.ts new file mode 100644 index 0000000..ff6fab6 --- /dev/null +++ b/src/pitcher-protocol/messages/task.ts @@ -0,0 +1,191 @@ +import { ProtocolError, TMessage, TNotification } from "../protocol"; +import { shell, port as portProtocol } from ".."; +import { PitcherErrorCode } from "../errors"; + +export type CommonError = ProtocolError; + +export type TaskDefinitionDTO = { + name: string; + command: string; + runAtStart?: boolean; + preview?: { + port?: number; + "pr-link"?: "direct" | "redirect" | "devtool"; + }; +}; + +export type TaskDTO = TaskDefinitionDTO & { + id: string; + unconfigured?: boolean; + shell: null | shell.CommandShellDTO; + ports: portProtocol.Port[]; +}; + +export type TaskListDTO = { + tasks: Record; + setupTasks: TaskDefinitionDTO[]; + validationErrors: string[]; +}; + +/* TODO: Use this later when the client has a UI on top of the config file */ +export type TaskConfigDTO = { + tasks: Record; + setupTasks: TaskDefinitionDTO[]; + validationErrors: string[]; +}; + +export type TaskError = + | { + code: PitcherErrorCode.CONFIG_FILE_ALREADY_EXISTS; + message: string; + } + | { + code: PitcherErrorCode.TASK_NOT_FOUND; + message: string; + } + | { + code: PitcherErrorCode.COMMAND_ALREADY_CONFIGURED; + message: string; + } + | ProtocolError; + +export type TaskList = TMessage< + "task/list", + Record, + { + result: TaskListDTO; + error: CommonError; + } +>; + +export type TaskRun = TMessage< + "task/run", + { + taskId: string; + }, + { + result: TaskDTO; + error: TaskError; + } +>; + +export type TaskRunCommand = TMessage< + "task/runCommand", + { + command: string; + name?: string; + saveToConfig?: boolean; + }, + { + result: TaskDTO; + error: TaskError; + } +>; + +export type TaskStop = TMessage< + "task/stop", + { + taskId: string; + }, + { + // null in case it's an unconfigured task, as it means all references are removed + result: TaskDTO | null; + error: TaskError; + } +>; + +export type TaskCreate = TMessage< + "task/create", + { + taskFields: TaskDefinitionDTO; + startTask?: boolean; + }, + { + // Return the entire list to ensure the list is in sync with all clients + result: TaskListDTO; + error: TaskError; + } +>; + +export type TaskUpdate = TMessage< + "task/update", + { + taskId: string; + taskFields: Partial; + }, + { + result: TaskDTO; + error: TaskError; + } +>; + +export type TaskSaveToConfig = TMessage< + "task/saveToConfig", + { + taskId: string; + }, + { + result: TaskDTO; + error: TaskError; + } +>; + +export type TaskGenerateConfig = TMessage< + "task/generateConfig", + Record, + { + result: null; + error: TaskError; + } +>; + +export type CreateSetupTasks = TMessage< + "task/createSetupTasks", + { tasks: TaskDefinitionDTO[] }, + { + result: null; + error: TaskError; + } +>; + +export type TasksMessage = + | TaskList + | TaskRun + | TaskStop + | TaskSaveToConfig + | TaskRunCommand + | TaskCreate + | TaskUpdate + | TaskGenerateConfig + | CreateSetupTasks; + +export type TaskRequest = TasksMessage["request"]; + +export type TaskResponse = TasksMessage["response"]; + +export type TaskListUpdated = TNotification<"task/listUpdate", TaskListDTO>; + +export type TaskUpdated = TNotification<"task/update", TaskDTO>; + +export type UnassignedPortOpened = TNotification< + "task/unassignedPortOpened", + portProtocol.Port +>; +export type UnassignedPortClosed = TNotification< + "task/unassignedPortClosed", + portProtocol.Port +>; + +export type TaskConfigParseError = TNotification< + "task/configParseError", + { + error: string; + } +>; + +export type TaskNotification = + | TaskListUpdated + | TaskUpdated + | TaskConfigParseError + | UnassignedPortOpened + | UnassignedPortClosed; diff --git a/src/pitcher-protocol/protocol.ts b/src/pitcher-protocol/protocol.ts new file mode 100644 index 0000000..d5b8b8b --- /dev/null +++ b/src/pitcher-protocol/protocol.ts @@ -0,0 +1,58 @@ +import { FsCapabilities } from "./messages/fs"; +import { PitcherErrorCode } from "./errors"; + +export type ProtocolError = { + code: PitcherErrorCode; + data?: unknown; + publicMessage?: string; +}; + +export type TMetadata = { + clientId: string; + permission: "read" | "write" | "owner"; +}; + +export type TCapabilities = { + fs: FsCapabilities | null; +}; + +export enum PitcherResponseStatus { + RESOLVED = 0, + REJECTED = 1, +} + +export type TMessage< + Method extends string, + Params, + Result extends { + result: unknown; + error: { + code: PitcherErrorCode; + data?: unknown; + publicMessage?: string; + }; + }, +> = { + request: { + method: Method; + params: Params; + }; + response: + | { + method: Method; + status: PitcherResponseStatus.RESOLVED; + result: Result["result"]; + } + | { + method: Method; + status: PitcherResponseStatus.REJECTED; + error: Result["error"] & { + message: string; + }; + }; +}; + +export type TNotification = { + method: Method; + params: Params; +}; diff --git a/src/pitcher-protocol/subscriptions.ts b/src/pitcher-protocol/subscriptions.ts new file mode 100644 index 0000000..8c35b99 --- /dev/null +++ b/src/pitcher-protocol/subscriptions.ts @@ -0,0 +1,76 @@ +import { NestedKey } from "./types"; +/** + * If any new subscription is added, + * make sure you add it for BasePitcherSubscriptions + */ + +export interface ClientSubscriptions { + client?: { + // Status updates: join, leave, updated + status?: boolean; + }; + file?: { + // Status updates: open, close, join, save + status?: boolean; + // Supports user selections + selection?: boolean; + // Requires ot updates + ot?: boolean; + }; + fs?: { + // Needs to receive incoming operations + operations?: boolean; + }; + git?: { + // Needs git status updates + status?: boolean; + // Wants to be notified of git operations: commit, pull, push, diff, ... + operations?: boolean; + }; + port?: { + // Get updates whenever a port is opened/closed + status?: boolean; + }; + setup?: { + progress?: boolean; + }; + shell?: { + // Receive status updates: progress, ... + status?: boolean; + }; + system?: { + metrics?: boolean; + }; +} + +export interface BasePitcherSubscriptions { + client: { + status?: boolean; + }; + file: { + status: boolean; + selection: boolean; + ot: boolean; + }; + fs: { + operations: boolean; + }; + git: { + status: boolean; + operations: boolean; + }; + port: { + status: boolean; + }; + setup: { + progress: boolean; + }; + shell: { + status?: boolean; + }; + system: { + metrics: boolean; + }; +} + +export type PitcherSubscriptions = NestedKey; diff --git a/src/pitcher-protocol/types.ts b/src/pitcher-protocol/types.ts new file mode 100644 index 0000000..a513ebd --- /dev/null +++ b/src/pitcher-protocol/types.ts @@ -0,0 +1,8 @@ +// This isn't really great, I'm no TS guru, but at least it adds some type safety +export type NestedKey = { + [K in Extract]: O[K] extends Array + ? K + : O[K] extends any + ? `${K}` | `${K}.${NestedKey}` + : K; +}[Extract]; diff --git a/src/types.ts b/src/types.ts index df219b6..fd2e207 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; import { HostToken } from "./HostTokens"; -import { Session } from "./Session"; +import { Config } from "@hey-api/client-fetch"; export interface SystemMetricsStatus { cpu: { @@ -63,7 +63,7 @@ export interface ClientOpts { * * @default fetch */ - fetch?: typeof fetch; + fetch?: Config["fetch"]; /** * Additional headers to send with each request @@ -174,13 +174,6 @@ export type CreateSandboxBaseOpts = { path?: string; }; -export type SandboxSessionData = { - id: string; - pitcher_token: string; - pitcher_url: string; - user_workspace_path: string; -}; - export interface SessionCreateOptions { id: string; permission?: "read" | "write"; @@ -195,12 +188,11 @@ export interface SessionCreateOptions { hostToken?: HostToken; } -export type SandboxSession = { +export type SandboxSessionDTO = { sandboxId: string; pitcherToken: string; pitcherUrl: string; userWorkspacePath: string; - env?: Record; }; export type CreateSandboxOpts = CreateSandboxBaseOpts & { @@ -216,12 +208,10 @@ export type SandboxOpts = { bootupType: PitcherManagerResponse["bootupType"]; cluster: string; isUpToDate: boolean; - globalSession: SandboxSession; }; -export type SandboxBrowserSession = PitcherManagerResponse & { - id: string; +export type SandboxSession = PitcherManagerResponse & { + sandboxId: string; sessionId?: string; - env?: Record; hostToken?: HostToken; }; diff --git a/src/utils/api.ts b/src/utils/api.ts index 800ddae..f05d91c 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -2,7 +2,49 @@ import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VmStartResponse } from "../api-clients/client"; import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; -import { Client } from "@hey-api/client-fetch"; +import { + Client, + Config, + createClient, + createConfig, +} from "@hey-api/client-fetch"; +import { getInferredBaseUrl } from "./constants"; + +async function fetchOverride(request: Request) { + // Clone the request to modify headers + const headers = new Headers(request.headers); + const existingUserAgent = headers.get("User-Agent") || ""; + + // Extend User-Agent with SDK version + headers.set( + "User-Agent", + `${existingUserAgent ? `${existingUserAgent} ` : ""}codesandbox-sdk/${ + // @ts-expect-error - Replaced at build time + CSB_SDK_VERSION + }`.trim() + ); + + // Create new request with updated headers + return fetch( + new Request(request, { + headers, + }) + ); +} + +export function createApiClient(apiKey: string, config: Config = {}) { + return createClient( + createConfig({ + baseUrl: config.baseUrl || getInferredBaseUrl(apiKey), + fetch: fetchOverride, + ...config, + headers: { + Authorization: `Bearer ${apiKey}`, + ...config.headers, + }, + }) + ); +} export type HandledResponse = { data?: { @@ -43,12 +85,33 @@ export function getStartResponse( }; } -export function getBaseUrl(token: string) { - if (token.startsWith("csb_")) { - return "https://api.codesandbox.io"; +/** + * Our infra has 2 min timeout, so we use that as default + */ +export async function withCustomTimeout( + cb: (signal: AbortSignal) => Promise, + timeoutSeconds: number = 120 +) { + const controller = new AbortController(); + const signal = controller.signal; + const timeoutHandle = setTimeout(() => { + controller.abort(); + }, timeoutSeconds * 1000); + + try { + // We have to await for the finally to run + return await cb(signal); + } catch (err) { + if (err instanceof Error && err.name === "AbortError") { + throw new Error( + `Request took longer than ${timeoutSeconds}s, so we aborted.` + ); + } + + throw err; + } finally { + clearTimeout(timeoutHandle); } - - return "https://api.together.ai/csb/sdk"; } export function getDefaultTemplateTag(apiClient: Client): string { @@ -56,8 +119,9 @@ export function getDefaultTemplateTag(apiClient: Client): string { return "7ngcrf"; } + return "pcz35m"; // Universal template created 2025-06-05 - return "universal@latest"; + // return "universal@latest"; } export function getDefaultTemplateId(apiClient: Client): string { diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..a8aafed --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,30 @@ +/* eslint-disable no-console */ + +function ensure(value: T | undefined, message: string): T { + if (!value) { + throw new Error(message); + } + + return value; +} + +export function getInferredBaseUrl(token: string) { + if (process.env.CSB_BASE_URL) { + return process.env.CSB_BASE_URL; + } + + if (token.startsWith("csb_")) { + return "https://api.codesandbox.io"; + } + + return "https://api.together.ai/csb/sdk"; +} + +export function getInferredApiKey() { + return ensure( + typeof process !== "undefined" + ? process.env?.CSB_API_KEY || process.env?.TOGETHER_API_KEY + : undefined, + "CSB_API_KEY or TOGETHER_API_KEY is not set" + ); +} From 0d639153caea3bb24a799b805e72b0c5f527ffff Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 13 Jun 2025 09:58:04 +0200 Subject: [PATCH 153/241] deprecate fork --- src/Sandboxes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 62235bd..7bb4ece 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -140,6 +140,7 @@ export class Sandboxes { /** * Forks a sandbox. This will create a new sandbox from the given sandbox. + * @deprecated This will be removed shortly to avoid having multiple ways of doing the same thing */ public async fork(sandboxId: string, opts?: StartSandboxOpts) { return this.create({ From 85eb5802b8c8aa95962b85f387146478611a8053 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 16 Jun 2025 09:44:07 +0200 Subject: [PATCH 154/241] add createBranch --- src/SandboxClient/git.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/SandboxClient/git.ts b/src/SandboxClient/git.ts index b480dfb..2272167 100644 --- a/src/SandboxClient/git.ts +++ b/src/SandboxClient/git.ts @@ -41,19 +41,35 @@ export class Git { ]); } + /** + * Checkout a new branch and set upstream + */ + async createBranch(branch: string) { + await this.commands.run([ + "git checkout -b " + branch, + "git push --set-upstream origin " + branch, + ]); + } + /** * Checkout a branch */ - async checkout(branch: string, isNewBranch = false) { + async checkout( + ref: string, + /** + * @deprecated Use createBranch instead + */ + isNewBranch = false + ) { if (isNewBranch) { await this.commands.run([ - "git checkout -b " + branch, - "git push --set-upstream origin " + branch, + "git checkout -b " + ref, + "git push --set-upstream origin " + ref, ]); return; } - await this.commands.run(["git checkout " + branch]); + await this.commands.run(["git checkout " + ref]); } /** From a25abc4298bae919d8767d3ef00d29dce21d87e8 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 17 Jun 2025 12:42:29 +0200 Subject: [PATCH 155/241] sentry and new templates endpoint --- build/utils.cjs | 1 + esbuild.cjs | 4 + openapi.json | 165 ++++- package-lock.json | 914 +++++++++++++++++++++++++++- package.json | 3 +- src/api-clients/client/sdk.gen.ts | 24 +- src/api-clients/client/types.gen.ts | 80 +++ src/bin/commands/build.ts | 229 ++----- src/bin/commands/previewHosts.ts | 11 +- src/bin/main.tsx | 2 +- src/browser/BrowserAgentClient.ts | 1 - src/index.ts | 2 +- src/utils/api.ts | 16 +- src/utils/sentry.ts | 35 ++ 14 files changed, 1275 insertions(+), 212 deletions(-) create mode 100644 src/utils/sentry.ts diff --git a/build/utils.cjs b/build/utils.cjs index 1e74709..4edc191 100644 --- a/build/utils.cjs +++ b/build/utils.cjs @@ -1,6 +1,7 @@ module.exports.nodeExternals = [ ...Object.keys(require("../package.json").dependencies), ...require("module").builtinModules, + ...require("module").builtinModules.map((mod) => `node:${mod}`), ]; module.exports.define = { diff --git a/esbuild.cjs b/esbuild.cjs index eaadea3..a0b9ee0 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -37,6 +37,8 @@ window.process = { `, }, plugins: [browserPlugin], + // pitcher-client seems to have some dynamic dependency on @sentry/node + external: ["@sentry/node"], }); const browserEsmBuild = esbuild.build({ @@ -55,6 +57,8 @@ window.process = { `, }, plugins: [browserPlugin], + // pitcher-client seems to have some dynamic dependency on @sentry/node + external: ["@sentry/node"], }); /** diff --git a/openapi.json b/openapi.json index dda6543..e1898bd 100644 --- a/openapi.json +++ b/openapi.json @@ -117,6 +117,36 @@ "title": "VMAssignTagAliasResponse", "type": "object" }, + "TemplateCreateRequest": { + "properties": { + "description": { + "default": "[Template description]", + "description": "Template description. Maximum 255 characters. Defaults to description of original sandbox.", + "maxLength": 255, + "type": "string" + }, + "forkOf": { + "description": "Short ID of the sandbox to fork.", + "example": "pt_1234567890", + "type": "string" + }, + "tags": { + "default": [], + "description": "Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox.", + "items": { "type": "string" }, + "type": "array" + }, + "title": { + "default": "[Template title]", + "description": "Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked).", + "maxLength": 255, + "type": "string" + } + }, + "required": ["forkOf"], + "title": "TemplateCreateRequest", + "type": "object" + }, "PreviewToken": { "properties": { "expires_at": { "nullable": true, "type": "string" }, @@ -284,10 +314,29 @@ "concurrent_vm_limit": { "type": "integer" }, "vms": { "items": { - "properties": { "id": { "type": "string" } }, + "properties": { + "credit_basis": { "type": "string" }, + "id": { "type": "string" }, + "last_active_at": { "type": "integer" }, + "session_started_at": { "type": "integer" }, + "specs": { + "properties": { + "cpu": { "type": "integer" }, + "memory": { "type": "integer" }, + "storage": { "type": "integer" } + }, + "type": "object" + } + }, "type": "object" }, - "required": ["id"], + "required": [ + "id", + "last_active_at", + "session_started_at", + "specs", + "credit_basis" + ], "type": "array" } }, @@ -492,6 +541,40 @@ }, "required": ["scopes", "team", "version"], "type": "object" + }, + "rate_limits": { + "description": "Current workspace rate limits", + "properties": { + "concurrent_vms": { + "properties": { + "limit": { "type": "integer" }, + "remaining": { "type": "integer" } + }, + "type": "object" + }, + "requests_hourly": { + "properties": { + "limit": { "type": "integer" }, + "remaining": { "type": "integer" }, + "reset": { "type": "integer" } + }, + "type": "object" + }, + "sandboxes_hourly": { + "properties": { + "limit": { "type": "integer" }, + "remaining": { "type": "integer" }, + "reset": { "type": "integer" } + }, + "type": "object" + } + }, + "required": [ + "concurrent_vms", + "sandboxes_hourly", + "requests_hourly" + ], + "type": "object" } }, "required": ["api"], @@ -1120,6 +1203,52 @@ "title": "TokenCreateResponse", "type": "object" }, + "TemplateCreateResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { + "data": { + "properties": { + "sandboxes": { + "items": { + "properties": { + "cluster": { "type": "string" }, + "id": { "type": "string" } + }, + "required": ["id", "cluster"], + "type": "object" + }, + "type": "array" + }, + "tag": { "type": "string" } + }, + "required": ["tag", "sandboxes"], + "type": "object" + } + }, + "type": "object" + } + ], + "title": "TemplateCreateResponse", + "type": "object" + }, "TokenCreateRequest": { "properties": { "default_version": { @@ -1932,6 +2061,38 @@ "tags": [] } }, + "/templates": { + "post": { + "callbacks": {}, + "description": "Create a new template in the current workspace (creates 3 sandboxes and tags them)\n", + "operationId": "templates/create", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TemplateCreateRequest" } + } + }, + "description": "Template Create Request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TemplateCreateResponse" + } + } + }, + "description": "Template Create Response" + } + }, + "security": [{ "authorization": ["template:create"] }], + "summary": "Create a template", + "tags": ["templates"] + } + }, "/vm/alias/{namespace}/{alias}": { "put": { "callbacks": {}, diff --git a/package-lock.json b/package-lock.json index 042b66a..82dca10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@inkjs/ui": "^2.0.0", + "@sentry/node": "^9.29.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", @@ -970,6 +971,668 @@ "dev": true, "license": "MIT" }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", + "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz", + "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz", + "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz", + "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz", + "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz", + "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz", + "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz", + "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", + "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/semantic-conventions": "1.28.0", + "forwarded-parse": "2.1.2", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz", + "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz", + "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz", + "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.47.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz", + "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz", + "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz", + "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz", + "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz", + "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz", + "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz", + "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz", + "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz", + "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz", + "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/instrumentation/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz", + "integrity": "sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.8.2.tgz", + "integrity": "sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@sentry/core": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz", + "integrity": "sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.29.0.tgz", + "integrity": "sha512-oABipgC/fClRuvyMeK43rigv9F+OAaoR84UaMKB7aPXN6iz634wBRVsaoZAwiR3xLL+R7MafEPPA/s9XqlG7ag==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.2", + "@opentelemetry/instrumentation-amqplib": "^0.46.1", + "@opentelemetry/instrumentation-connect": "0.43.1", + "@opentelemetry/instrumentation-dataloader": "0.16.1", + "@opentelemetry/instrumentation-express": "0.47.1", + "@opentelemetry/instrumentation-fs": "0.19.1", + "@opentelemetry/instrumentation-generic-pool": "0.43.1", + "@opentelemetry/instrumentation-graphql": "0.47.1", + "@opentelemetry/instrumentation-hapi": "0.45.2", + "@opentelemetry/instrumentation-http": "0.57.2", + "@opentelemetry/instrumentation-ioredis": "0.47.1", + "@opentelemetry/instrumentation-kafkajs": "0.7.1", + "@opentelemetry/instrumentation-knex": "0.44.1", + "@opentelemetry/instrumentation-koa": "0.47.1", + "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", + "@opentelemetry/instrumentation-mongodb": "0.52.0", + "@opentelemetry/instrumentation-mongoose": "0.46.1", + "@opentelemetry/instrumentation-mysql": "0.45.1", + "@opentelemetry/instrumentation-mysql2": "0.45.2", + "@opentelemetry/instrumentation-pg": "0.51.1", + "@opentelemetry/instrumentation-redis-4": "0.46.1", + "@opentelemetry/instrumentation-tedious": "0.18.1", + "@opentelemetry/instrumentation-undici": "0.10.1", + "@opentelemetry/resources": "^1.30.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@prisma/instrumentation": "6.8.2", + "@sentry/core": "9.29.0", + "@sentry/opentelemetry": "9.29.0", + "import-in-the-middle": "^1.13.1", + "minimatch": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.29.0.tgz", + "integrity": "sha512-QTUmre8i5+832RjzQW+g8IQ3UmBe5fbQXGbCF5hQ0UNuHle9r3Z8UZcIff5W8tm5AXMxPqvptTnDEZUUXHgBiA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "9.29.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.0.0", + "@opentelemetry/core": "^1.30.1 || ^2.0.0", + "@opentelemetry/instrumentation": "^0.57.1 || ^0.200.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + } + }, "node_modules/@tanstack/query-core": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.76.0.tgz", @@ -1013,6 +1676,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cookie": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", @@ -1037,16 +1709,44 @@ "@types/braces": "*" } }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, "node_modules/@types/react": { "version": "19.1.5", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", @@ -1064,6 +1764,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -1102,7 +1817,6 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1111,6 +1825,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/ansi-escapes": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", @@ -1255,7 +1978,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -1326,7 +2048,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -1662,6 +2383,12 @@ "consola": "^3.2.3" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, "node_modules/class-states": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.16.tgz", @@ -2098,7 +2825,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2529,6 +3255,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2984,6 +3716,18 @@ "dev": true, "license": "MIT" }, + "node_modules/import-in-the-middle": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz", + "integrity": "sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "node_modules/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -3248,6 +3992,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3851,11 +4610,16 @@ "dev": true, "license": "MIT" }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/neo-async": { @@ -4203,6 +4967,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", @@ -4266,6 +5036,37 @@ "dev": true, "license": "MIT" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", + "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/phoenix": { "version": "1.7.21", "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.21.tgz", @@ -4341,6 +5142,45 @@ "node": ">= 0.4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/preact": { "version": "10.26.6", "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", @@ -4535,6 +5375,40 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -4717,6 +5591,12 @@ "node": ">=8" } }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5043,6 +5923,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -5177,7 +6069,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/universal-cookie": { @@ -5584,6 +6475,15 @@ "node": ">=4.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 4b9d0e9..830c18a 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,6 @@ "types": "./dist/esm/index.d.ts", "scripts": { "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.mjs", - "build:cjs": "tsc -p ./tsconfig.build-cjs.json", - "build:esm": "tsc -p ./tsconfig.build-esm.json", "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", @@ -96,6 +94,7 @@ }, "dependencies": { "@inkjs/ui": "^2.0.0", + "@sentry/node": "^9.29.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 704cfdc..195250e 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, TemplatesCreateData, TemplatesCreateResponse, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -250,6 +250,28 @@ export const previewTokenUpdate = (options }); }; +/** + * Create a template + * Create a new template in the current workspace (creates 3 sandboxes and tags them) + * + */ +export const templatesCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/templates', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + /** * Assign a tag alias to a VM tag * Assign a tag alias to a VM tag. diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index fa3cb12..6017112 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -44,6 +44,25 @@ export type VmAssignTagAliasResponse = { }; }; +export type TemplateCreateRequest = { + /** + * Template description. Maximum 255 characters. Defaults to description of original sandbox. + */ + description?: string; + /** + * Short ID of the sandbox to fork. + */ + forkOf: string; + /** + * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. + */ + tags?: Array; + /** + * Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + */ + title?: string; +}; + export type PreviewToken = { expires_at: string | null; last_used_at: string | null; @@ -120,7 +139,15 @@ export type VmListRunningVmsResponse = { concurrent_vm_count: number; concurrent_vm_limit: number; vms: Array<{ + credit_basis?: string; id?: string; + last_active_at?: number; + session_started_at?: number; + specs?: { + cpu?: number; + memory?: number; + storage?: number; + }; }>; }; }; @@ -234,6 +261,25 @@ export type MetaInformation = { team: string | null; version: string; }; + /** + * Current workspace rate limits + */ + rate_limits?: { + concurrent_vms: { + limit?: number; + remaining?: number; + }; + requests_hourly: { + limit?: number; + remaining?: number; + reset?: number; + }; + sandboxes_hourly: { + limit?: number; + remaining?: number; + reset?: number; + }; + }; }; /** @@ -536,6 +582,21 @@ export type TokenCreateResponse = { }; }; +export type TemplateCreateResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + sandboxes: Array<{ + cluster: string; + id: string; + }>; + tag: string; + }; +}; + export type TokenCreateRequest = { /** * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. @@ -972,6 +1033,25 @@ export type PreviewTokenUpdateResponses = { export type PreviewTokenUpdateResponse2 = PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; +export type TemplatesCreateData = { + /** + * Template Create Request + */ + body?: TemplateCreateRequest; + path?: never; + query?: never; + url: '/templates'; +}; + +export type TemplatesCreateResponses = { + /** + * Template Create Response + */ + 201: TemplateCreateResponse; +}; + +export type TemplatesCreateResponse = TemplatesCreateResponses[keyof TemplatesCreateResponses]; + export type VmAssignTagAliasData = { /** * VM Assign Tag Alias Request diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 3ca1fff..884b902 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -10,6 +10,7 @@ import { VMTier, CodeSandbox, Sandbox } from "@codesandbox/sdk"; import { sandboxFork, + templatesCreate, vmAssignTagAlias, vmCreateTag, vmListClusters, @@ -59,12 +60,6 @@ export const buildCommand: yargs.CommandModule< type: "number", array: true, }) - .option("path", { - describe: - "Which folder (in the dashboard) the sandbox will be created in", - default: "SDK-Templates", - type: "string", - }) .option("vm-tier", { describe: "Base specs to use for the template sandbox", type: "string", @@ -88,7 +83,7 @@ export const buildCommand: yargs.CommandModule< handler: async (argv) => { const apiKey = getInferredApiKey(); - const apiClient: Client = createApiClient(apiKey); + const apiClient: Client = createApiClient("CLI", apiKey); let alias: { namespace: string; alias: string } | undefined; @@ -96,81 +91,38 @@ export const buildCommand: yargs.CommandModule< alias = createAlias(argv.directory, argv.alias); } + const { hash, files: filePaths } = await hashDirectory(argv.directory); + const tag = `sha:${hash.slice(0, 6)}`; + try { - const clustersData = handleResponse( - await vmListClusters({ + const templateData = handleResponse( + await templatesCreate({ client: apiClient, + body: { + forkOf: argv.fromSandbox || getDefaultTemplateId(apiClient), + title: argv.name, + // We filter out sdk-templates on the dashboard + tags: ["sdk-template", tag], + }, }), - "Failed to list clusters" + "Failed to create template" ); - const clusters = clustersData.clusters; - const spinner = ora({ stream: process.stdout }); - let spinnerMessages: string[] = clusters.map(() => ""); + let spinnerMessages: string[] = templateData.sandboxes.map(() => ""); function updateSpinnerMessage( index: number, message: string, - sandboxId?: string + sandboxId: string ) { - spinnerMessages[index] = `[cluster: ${ - clusters[index].slug - }, sandboxId: ${sandboxId || "-"}]: ${message}`; + spinnerMessages[index] = `[sandboxId: ${sandboxId}]: ${message}`; return `\n${spinnerMessages.join("\n")}`; } - const sandboxes = await Promise.all( - clusters.map(async ({ host: cluster, slug }, index) => { - const clusterApiClient: Client = createApiClient(apiKey, { - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); - - try { - const { hash, files: filePaths } = await hashDirectory( - argv.directory - ); - const shortHash = hash.slice(0, 6); - const tag = `sha:${shortHash}-${slug}`; - - spinner.start(updateSpinnerMessage(index, "Creating sandbox...")); - - const sandboxId = await createSandbox({ - apiClient: clusterApiClient, - shaTag: tag, - fromSandbox: argv.fromSandbox, - collectionPath: argv.path, - name: argv.name, - vmTier: argv.vmTier - ? VMTier.fromName(argv.vmTier) - : VMTier.fromName("Micro"), - }); - - return { sandboxId, filePaths, cluster }; - } catch (error) { - spinner.fail( - updateSpinnerMessage( - index, - error instanceof Error - ? error.message - : "Unknown error occurred" - ) - ); - throw error; - } - }) - ); - - const tasks = sandboxes.map( - async ({ sandboxId, filePaths, cluster }, index) => { - const clusterApiClient: Client = createApiClient(apiKey, { - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); + const tasks = templateData.sandboxes.map( + async ({ id, cluster }, index) => { const sdk = new CodeSandbox(apiKey, { headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, @@ -179,39 +131,21 @@ export const buildCommand: yargs.CommandModule< try { spinner.start( - updateSpinnerMessage(index, "Starting sandbox...", sandboxId) + updateSpinnerMessage(index, "Starting sandbox...", id) ); - // This is a hack, we need to tell the global scheduler that the VM is running - // in a different cluster than the one it'd like to default to. - const baseUrl = apiClient - .getConfig() - .baseUrl?.replace("api", "global-scheduler"); - - await fetch( - `${baseUrl}/api/v1/cluster/${sandboxId}?preferredManager=${cluster}` - ).then((res) => res.json()); - const startResponse = await withCustomError( - startVm(clusterApiClient, sandboxId, { + startVm(apiClient, id, { vmTier: VMTier.fromName("Micro"), }), "Failed to start sandbox" ); - let sandboxVM = new Sandbox( - sandboxId, - clusterApiClient, - startResponse - ); + let sandboxVM = new Sandbox(id, apiClient, startResponse); let session = await sandboxVM.connect(); spinner.start( - updateSpinnerMessage( - index, - "Writing files to sandbox...", - sandboxId - ) + updateSpinnerMessage(index, "Writing files to sandbox...", id) ); try { let i = 0; @@ -231,10 +165,10 @@ export const buildCommand: yargs.CommandModule< } spinner.start( - updateSpinnerMessage(index, "Restarting sandbox...", sandboxId) + updateSpinnerMessage(index, "Restarting sandbox...", id) ); sandboxVM = await withCustomError( - sdk.sandboxes.restart(sandboxVM.id, { + sdk.sandboxes.restart(id, { vmTier: argv.vmTier ? VMTier.fromName(argv.vmTier) : VMTier.fromName("Micro"), @@ -261,7 +195,7 @@ export const buildCommand: yargs.CommandModule< `Running setup ${steps.indexOf(step) + 1} / ${ steps.length } - ${step.name}...`, - sandboxId + id ) ); @@ -292,7 +226,7 @@ export const buildCommand: yargs.CommandModule< `Waiting for ${ isMultiplePorts ? "ports" : "port" } ${ports.join(", ")} to open...`, - sandboxId + id ) ); }; @@ -324,24 +258,22 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage( index, "No ports to open, waiting 5 seconds for tasks to run...", - sandboxId + id ) ); await new Promise((resolve) => setTimeout(resolve, 5000)); } spinner.start( - updateSpinnerMessage(index, "Creating snapshot...", sandboxId) + updateSpinnerMessage(index, "Creating snapshot...", id) ); await withCustomError( - sdk.sandboxes.hibernate(sandboxVM.id), + sdk.sandboxes.hibernate(id), "Failed to hibernate" ); - spinner.start( - updateSpinnerMessage(index, "Snapshot created", sandboxId) - ); + spinner.start(updateSpinnerMessage(index, "Snapshot created", id)); - return sandboxVM.id; + return id; } catch (error) { spinner.start( updateSpinnerMessage( @@ -349,10 +281,10 @@ export const buildCommand: yargs.CommandModule< argv.ci ? String(error) : "Failed, please manually verify at https://codesandbox.io/s/" + - sandboxId + + id + " - " + String(error), - sandboxId + id ) ); @@ -363,7 +295,7 @@ export const buildCommand: yargs.CommandModule< const results = await Promise.allSettled(tasks); - const failedSandboxes = sandboxes.filter( + const failedSandboxes = templateData.sandboxes.filter( (_, index) => results[index].status === "rejected" ); @@ -372,37 +304,37 @@ export const buildCommand: yargs.CommandModule< await waitForEnter( `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes - .map((sandbox) => sandbox.sandboxId) + .map((sandbox) => sandbox.id) .join(", ")} and press ENTER to create snapshot...\n` ); - failedSandboxes.forEach(({ sandboxId }) => { + failedSandboxes.forEach(({ id }) => { updateSpinnerMessage( - sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId), + templateData.sandboxes.findIndex((sandbox) => sandbox.id === id), "Creating snapshot...", - sandboxId + id ); }); spinner.start(`\n${spinnerMessages.join("\n")}`); await Promise.all( - failedSandboxes.map(async ({ sandboxId, cluster }) => { + failedSandboxes.map(async ({ id, cluster }) => { const sdk = new CodeSandbox(apiKey, { headers: { "x-pitcher-manager-url": `https://${cluster}/api/v1`, }, }); - await sdk.sandboxes.hibernate(sandboxId); + await sdk.sandboxes.hibernate(id); spinner.start( updateSpinnerMessage( - sandboxes.findIndex( - (sandbox) => sandbox.sandboxId === sandboxId + templateData.sandboxes.findIndex( + (sandbox) => sandbox.id === id ), "Snapshot created", - sandboxId + id ) ); }) @@ -416,7 +348,7 @@ export const buildCommand: yargs.CommandModule< await vmCreateTag({ client: apiClient, body: { - vm_ids: sandboxes.map((sandbox) => sandbox.sandboxId), + vm_ids: templateData.sandboxes.map((sandbox) => sandbox.id), }, }), "Failed to create template" @@ -449,16 +381,6 @@ export const buildCommand: yargs.CommandModule< }, }; -type CreateSandboxParams = { - apiClient: Client; - shaTag: string; - fromSandbox?: string; - collectionPath?: string; - name?: string; - vmTier?: VMTier; - ipcountry?: string; -}; - function withCustomError>(promise: T, message: string) { return promise.catch((error) => { throw new Error(message + ": " + error.message); @@ -509,64 +431,3 @@ function createAlias(directory: string, alias: string) { alias, }; } - -async function createSandbox({ - apiClient, - shaTag, - collectionPath, - fromSandbox, - name, -}: CreateSandboxParams) { - const sanitizedCollectionPath = collectionPath - ? collectionPath.startsWith("/") - ? collectionPath - : `/${collectionPath}` - : "/SDK-Templates"; - - const sandbox = handleResponse( - await sandboxFork({ - client: apiClient, - path: { - id: fromSandbox || getDefaultTemplateId(apiClient), - }, - body: { - title: name, - privacy: 1, - tags: ["sdk", shaTag], - path: sanitizedCollectionPath, - }, - }), - "Failed to fork sandbox" - ); - - return sandbox.id; -} - -async function getFiles( - filePaths: string[], - rootPath: string -): Promise> { - if (filePaths.length > 30) { - return {}; - } - - let hasBinaryFile = false; - const files: Record = {}; - await Promise.all( - filePaths.map(async (filePath) => { - const content = await fs.readFile(path.join(rootPath, filePath)); - - if (await isBinaryFile(content)) { - hasBinaryFile = true; - } - - files[filePath] = { code: content.toString() }; - }) - ); - - if (hasBinaryFile) { - return {}; - } - - return files; -} diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 88bd44d..123732b 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -4,6 +4,9 @@ import { previewHostList, previewHostUpdate } from "../../api-clients/client"; import { createApiClient, handleResponse } from "../../utils/api"; import { getInferredApiKey } from "../../utils/constants"; +const apiKey = getInferredApiKey(); +const apiClient = createApiClient("CLI", apiKey); + export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", describe: @@ -14,8 +17,6 @@ export const previewHostsCommand: yargs.CommandModule = { command: "list", describe: "List current preview hosts", handler: async () => { - const apiKey = getInferredApiKey(); - const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); const hosts = data.preview_hosts.map(({ host }) => host); @@ -36,8 +37,6 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const apiKey = getInferredApiKey(); - const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); let hosts = data.preview_hosts.map(({ host }) => host); @@ -64,8 +63,6 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const apiKey = getInferredApiKey(); - const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); let hosts = data.preview_hosts.map(({ host }) => host); @@ -86,8 +83,6 @@ export const previewHostsCommand: yargs.CommandModule = { command: "clear", describe: "Clear all preview hosts", handler: async () => { - const apiKey = getInferredApiKey(); - const apiClient = createApiClient(apiKey); const resp = await previewHostList({ client: apiClient }); const data = handleResponse(resp, "Failed to list preview hosts"); const hosts = data.preview_hosts.map(({ host }) => host); diff --git a/src/bin/main.tsx b/src/bin/main.tsx index af23250..0595e3c 100644 --- a/src/bin/main.tsx +++ b/src/bin/main.tsx @@ -1,6 +1,6 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import { render, Text } from "ink"; +import { render } from "ink"; import { buildCommand } from "./commands/build"; import { sandboxesCommand } from "./commands/sandbox"; diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts index 0644984..dbcd884 100644 --- a/src/browser/BrowserAgentClient.ts +++ b/src/browser/BrowserAgentClient.ts @@ -10,7 +10,6 @@ import { IAgentClientSystem, IAgentClientTasks, } from "../node/agent-client-interface"; -import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; class BrowserAgentClientShells implements IAgentClientShells { diff --git a/src/index.ts b/src/index.ts index edf35d9..705bb72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ export class CodeSandbox { constructor(apiToken?: string, opts: ClientOpts = {}) { const apiKey = apiToken || getInferredApiKey(); - const apiClient = createApiClient(apiKey, opts); + const apiClient = createApiClient("SDK", apiKey, opts); this.sandboxes = new Sandboxes(apiClient); this.hosts = new HostTokens(apiClient); diff --git a/src/utils/api.ts b/src/utils/api.ts index f05d91c..26e2df5 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -9,8 +9,9 @@ import { createConfig, } from "@hey-api/client-fetch"; import { getInferredBaseUrl } from "./constants"; +import { instrumentedFetch } from "./sentry"; -async function fetchOverride(request: Request) { +async function enhanceFetch(request: Request, client: "SDK" | "CLI") { // Clone the request to modify headers const headers = new Headers(request.headers); const existingUserAgent = headers.get("User-Agent") || ""; @@ -25,18 +26,23 @@ async function fetchOverride(request: Request) { ); // Create new request with updated headers - return fetch( + return instrumentedFetch( new Request(request, { headers, - }) + }), + client ); } -export function createApiClient(apiKey: string, config: Config = {}) { +export function createApiClient( + client: "SDK" | "CLI", + apiKey: string, + config: Config = {} +) { return createClient( createConfig({ baseUrl: config.baseUrl || getInferredBaseUrl(apiKey), - fetch: fetchOverride, + fetch: (request) => enhanceFetch(request, client), ...config, headers: { Authorization: `Bearer ${apiKey}`, diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts new file mode 100644 index 0000000..1c08c60 --- /dev/null +++ b/src/utils/sentry.ts @@ -0,0 +1,35 @@ +import * as Sentry from "@sentry/node"; + +// This can happen when the CLI uses Sentry for its own requests, but also the SDK for other requests +if (!Sentry.isInitialized()) { + Sentry.init({ + dsn: "https://6b8a654fd32a40bdb146ae7089422e10@sentry.csbops.io/11", + defaultIntegrations: false, + }); +} + +export async function instrumentedFetch( + request: Request, + client: "SDK" | "CLI" +) { + const res = await fetch(request); + + if (res.status >= 400) { + const err = new Error(`HTTP ${res.status}`); + + Sentry.captureException(err, { + extra: { + payload: request.body + ? await new Response(request.body).text() + : undefined, + body: await res.clone().text(), + client, + method: request.method, + url: request.url, + status: res.status, + }, + }); + } + + return res; +} From e17e0ec34028d4e3bdfe9940ac74fceb9891466a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 17 Jun 2025 13:26:03 +0200 Subject: [PATCH 156/241] bring back cluster info to CLI --- package.json | 2 +- src/bin/commands/build.ts | 305 +++++++++++++++++--------------------- src/utils/sentry.ts | 3 +- 3 files changed, 136 insertions(+), 174 deletions(-) diff --git a/package.json b/package.json index 830c18a..701eac6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.9", + "version": "2.0.0-rc.10", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 884b902..470f9b4 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -9,11 +9,9 @@ import type * as yargs from "yargs"; import { VMTier, CodeSandbox, Sandbox } from "@codesandbox/sdk"; import { - sandboxFork, templatesCreate, vmAssignTagAlias, vmCreateTag, - vmListClusters, VmUpdateSpecsRequest, } from "../../api-clients/client"; import { @@ -84,6 +82,7 @@ export const buildCommand: yargs.CommandModule< handler: async (argv) => { const apiKey = getInferredApiKey(); const apiClient: Client = createApiClient("CLI", apiKey); + const sdk = new CodeSandbox(apiKey); let alias: { namespace: string; alias: string } | undefined; @@ -111,187 +110,167 @@ export const buildCommand: yargs.CommandModule< const spinner = ora({ stream: process.stdout }); let spinnerMessages: string[] = templateData.sandboxes.map(() => ""); - function updateSpinnerMessage( - index: number, - message: string, - sandboxId: string - ) { - spinnerMessages[index] = `[sandboxId: ${sandboxId}]: ${message}`; + function updateSpinnerMessage(index: number, message: string) { + spinnerMessages[ + index + ] = `[cluster: ${templateData.sandboxes[index].cluster}, sandboxId: ${templateData.sandboxes[index].id}]: ${message}`; return `\n${spinnerMessages.join("\n")}`; } - const tasks = templateData.sandboxes.map( - async ({ id, cluster }, index) => { - const sdk = new CodeSandbox(apiKey, { - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); + const tasks = templateData.sandboxes.map(async ({ id }, index) => { + try { + spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); - try { - spinner.start( - updateSpinnerMessage(index, "Starting sandbox...", id) - ); - - const startResponse = await withCustomError( - startVm(apiClient, id, { - vmTier: VMTier.fromName("Micro"), - }), - "Failed to start sandbox" - ); - let sandboxVM = new Sandbox(id, apiClient, startResponse); + const startResponse = await withCustomError( + startVm(apiClient, id, { + vmTier: VMTier.fromName("Micro"), + }), + "Failed to start sandbox" + ); + let sandboxVM = new Sandbox(id, apiClient, startResponse); - let session = await sandboxVM.connect(); + let session = await sandboxVM.connect(); - spinner.start( - updateSpinnerMessage(index, "Writing files to sandbox...", id) - ); - try { - let i = 0; - for (const filePath of filePaths) { - i++; - const fullPath = path.join(argv.directory, filePath); - const content = await fs.readFile(fullPath); - const dirname = path.dirname(filePath); - await session.fs.mkdir(dirname, true); - await session.fs.writeFile(filePath, content, { - create: true, - overwrite: true, - }); - } - } catch (error) { - throw new Error(`Failed to write files to sandbox: ${error}`); + spinner.start( + updateSpinnerMessage(index, "Writing files to sandbox...") + ); + try { + let i = 0; + for (const filePath of filePaths) { + i++; + const fullPath = path.join(argv.directory, filePath); + const content = await fs.readFile(fullPath); + const dirname = path.dirname(filePath); + await session.fs.mkdir(dirname, true); + await session.fs.writeFile(filePath, content, { + create: true, + overwrite: true, + }); } + } catch (error) { + throw new Error(`Failed to write files to sandbox: ${error}`); + } - spinner.start( - updateSpinnerMessage(index, "Restarting sandbox...", id) - ); - sandboxVM = await withCustomError( - sdk.sandboxes.restart(id, { - vmTier: argv.vmTier - ? VMTier.fromName(argv.vmTier) - : VMTier.fromName("Micro"), - }), - "Failed to restart sandbox" - ); - - session = await withCustomError( - sandboxVM.connect(), - "Failed to connect to sandbox" - ); - - const disposableStore = new DisposableStore(); - - const steps = await session.setup.getSteps(); - - for (const step of steps) { - const buffer: string[] = []; - - try { - spinner.start( - updateSpinnerMessage( - index, - `Running setup ${steps.indexOf(step) + 1} / ${ - steps.length - } - ${step.name}...`, - id - ) - ); + spinner.start(updateSpinnerMessage(index, "Restarting sandbox...")); + sandboxVM = await withCustomError( + sdk.sandboxes.restart(id, { + vmTier: argv.vmTier + ? VMTier.fromName(argv.vmTier) + : VMTier.fromName("Micro"), + }), + "Failed to restart sandbox" + ); - disposableStore.add( - step.onOutput((output) => { - buffer.push(output); - }) - ); + session = await withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox" + ); - const output = await step.open(); + const disposableStore = new DisposableStore(); - buffer.push(...output.split("\n")); + const steps = await session.setup.getSteps(); - await step.waitUntilComplete(); - } catch (error) { - throw new Error(`Setup step failed: ${step.name}`); - } - } + for (const step of steps) { + const buffer: string[] = []; - disposableStore.dispose(); - - const ports = argv.ports || []; - const updatePortSpinner = () => { - const isMultiplePorts = ports.length > 1; + try { spinner.start( updateSpinnerMessage( index, - `Waiting for ${ - isMultiplePorts ? "ports" : "port" - } ${ports.join(", ")} to open...`, - id + `Running setup ${steps.indexOf(step) + 1} / ${ + steps.length + } - ${step.name}...` ) ); - }; - if (ports.length > 0) { - updatePortSpinner(); - - await Promise.all( - ports.map(async (port) => { - const portInfo = await session.ports.waitForPort(port, { - timeoutMs: 60000, - }); + disposableStore.add( + step.onOutput((output) => { + buffer.push(output); + }) + ); - // eslint-disable-next-line no-constant-condition - while (true) { - const res = await fetch("https://" + portInfo.host); - if (res.status !== 502 && res.status !== 503) { - break; - } + const output = await step.open(); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + buffer.push(...output.split("\n")); - updatePortSpinner(); - }) - ); - } else { - spinner.start( - updateSpinnerMessage( - index, - "No ports to open, waiting 5 seconds for tasks to run...", - id - ) - ); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await step.waitUntilComplete(); + } catch (error) { + throw new Error(`Setup step failed: ${step.name}`); } + } + disposableStore.dispose(); + + const ports = argv.ports || []; + const updatePortSpinner = () => { + const isMultiplePorts = ports.length > 1; spinner.start( - updateSpinnerMessage(index, "Creating snapshot...", id) - ); - await withCustomError( - sdk.sandboxes.hibernate(id), - "Failed to hibernate" + updateSpinnerMessage( + index, + `Waiting for ${isMultiplePorts ? "ports" : "port"} ${ports.join( + ", " + )} to open...` + ) ); - spinner.start(updateSpinnerMessage(index, "Snapshot created", id)); + }; - return id; - } catch (error) { + if (ports.length > 0) { + updatePortSpinner(); + + await Promise.all( + ports.map(async (port) => { + const portInfo = await session.ports.waitForPort(port, { + timeoutMs: 60000, + }); + + // eslint-disable-next-line no-constant-condition + while (true) { + const res = await fetch("https://" + portInfo.host); + if (res.status !== 502 && res.status !== 503) { + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + updatePortSpinner(); + }) + ); + } else { spinner.start( updateSpinnerMessage( index, - argv.ci - ? String(error) - : "Failed, please manually verify at https://codesandbox.io/s/" + - id + - " - " + - String(error), - id + "No ports to open, waiting 5 seconds for tasks to run..." ) ); - - throw error; + await new Promise((resolve) => setTimeout(resolve, 5000)); } + + spinner.start(updateSpinnerMessage(index, "Creating snapshot...")); + await withCustomError( + sdk.sandboxes.hibernate(id), + "Failed to hibernate" + ); + spinner.start(updateSpinnerMessage(index, "Snapshot created")); + + return id; + } catch (error) { + spinner.start( + updateSpinnerMessage( + index, + argv.ci + ? String(error) + : "Failed, please manually verify at https://codesandbox.io/s/" + + id + + " - " + + String(error) + ) + ); + + throw error; } - ); + }); const results = await Promise.allSettled(tasks); @@ -311,21 +290,14 @@ export const buildCommand: yargs.CommandModule< failedSandboxes.forEach(({ id }) => { updateSpinnerMessage( templateData.sandboxes.findIndex((sandbox) => sandbox.id === id), - "Creating snapshot...", - id + "Creating snapshot..." ); }); spinner.start(`\n${spinnerMessages.join("\n")}`); await Promise.all( - failedSandboxes.map(async ({ id, cluster }) => { - const sdk = new CodeSandbox(apiKey, { - headers: { - "x-pitcher-manager-url": `https://${cluster}/api/v1`, - }, - }); - + failedSandboxes.map(async ({ id }) => { await sdk.sandboxes.hibernate(id); spinner.start( @@ -333,8 +305,7 @@ export const buildCommand: yargs.CommandModule< templateData.sandboxes.findIndex( (sandbox) => sandbox.id === id ), - "Snapshot created", - id + "Snapshot created" ) ); }) @@ -344,16 +315,6 @@ export const buildCommand: yargs.CommandModule< spinner.succeed(`\n${spinnerMessages.join("\n")}`); } - const data = handleResponse( - await vmCreateTag({ - client: apiClient, - body: { - vm_ids: templateData.sandboxes.map((sandbox) => sandbox.id), - }, - }), - "Failed to create template" - ); - if (alias) { await vmAssignTagAlias({ client: apiClient, @@ -362,17 +323,17 @@ export const buildCommand: yargs.CommandModule< namespace: alias.namespace, }, body: { - tag_id: data.tag_id, + tag_id: templateData.tag, }, }); console.log( - `Alias ${alias.namespace}@${alias.alias} updated to: ${data.tag_id}` + `Alias ${alias.namespace}@${alias.alias} updated to: ${templateData.tag}` ); process.exit(0); } - console.log("Template created: " + data.tag_id); + console.log("Template created: " + templateData.tag); process.exit(0); } catch (error) { console.error(error); diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 1c08c60..930eb31 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -12,7 +12,8 @@ export async function instrumentedFetch( request: Request, client: "SDK" | "CLI" ) { - const res = await fetch(request); + // We are cloning the request to be able to read its body on errors + const res = await fetch(request.clone()); if (res.status >= 400) { const err = new Error(`HTTP ${res.status}`); From e0cbf3273c59a70b529d952f3077b229a8e3ee66 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 19 Jun 2025 15:31:32 +0200 Subject: [PATCH 157/241] force expiry on host tokens and improved template builder --- esbuild.cjs | 2 + package-lock.json | 223 ++++++++------------------------- package.json | 1 + src/HostTokens.ts | 6 +- src/SandboxClient/commands.ts | 16 ++- src/SandboxClient/hosts.ts | 4 +- src/bin/commands/build.ts | 138 +++++++++++++------- src/bin/commands/hostTokens.ts | 5 +- src/index.ts | 2 +- 9 files changed, 162 insertions(+), 235 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index a0b9ee0..84e2f8c 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -1,3 +1,4 @@ +const fs = require("fs"); const esbuild = require("esbuild"); const { nodeExternals, define } = require("./build/utils.cjs"); const { @@ -47,6 +48,7 @@ const browserEsmBuild = esbuild.build({ format: "esm", outfile: "dist/esm/browser.js", platform: "browser", + metafile: true, // pitcher-common currently requires this, but breaks the first experience banner: { js: `if (typeof window !== "undefined" && !window.process) { diff --git a/package-lock.json b/package-lock.json index 82dca10..70e441e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.9", + "version": "2.0.0-rc.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.9", + "version": "2.0.0-rc.10", "license": "MIT", "dependencies": { "@inkjs/ui": "^2.0.0", @@ -19,6 +19,7 @@ "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", + "strip-ansi": "^7.1.0", "util": "^0.12.5", "yargs": "^17.7.2" }, @@ -834,19 +835,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -872,22 +860,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -2037,6 +2009,19 @@ "x256": ">=0.0.1" } }, + "node_modules/blessed-contrib/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/bn.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", @@ -2326,6 +2311,19 @@ "node": ">=0.10.0" } }, + "node_modules/chalk/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/charm": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", @@ -2467,18 +2465,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/cli-truncate/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -2542,21 +2528,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3809,18 +3780,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ink/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ink/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -3920,21 +3879,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ink/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/ink/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -4783,18 +4727,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ora/node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -4830,21 +4762,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -5837,16 +5754,18 @@ } }, "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -5873,6 +5792,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6220,18 +6151,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/widest-line/node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -6255,21 +6174,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -6353,19 +6257,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -6404,22 +6295,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/ws": { "version": "8.18.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", diff --git a/package.json b/package.json index 701eac6..10c4cb1 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", + "strip-ansi": "^7.1.0", "util": "^0.12.5", "yargs": "^17.7.2" } diff --git a/src/HostTokens.ts b/src/HostTokens.ts index 1c1ad17..16eccec 100644 --- a/src/HostTokens.ts +++ b/src/HostTokens.ts @@ -69,11 +69,11 @@ export class HostTokens extends Disposable { } /** - * Generate a new host token that can be used to access private sandbox hosts. By default the token never expires. + * Generate a new host token that can be used to access private sandbox hosts. */ async createToken( sandboxId: string, - opts: { expiresAt?: Date } = {} + opts: { expiresAt: Date } ): Promise { const response = handleResponse( await previewTokenCreate({ @@ -82,7 +82,7 @@ export class HostTokens extends Disposable { id: sandboxId, }, body: { - expires_at: opts.expiresAt?.toISOString(), + expires_at: opts.expiresAt.toISOString(), }, }), "Failed to create preview token" diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index e007f7e..19ce540 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -83,12 +83,16 @@ export class Commands { if (shell.status !== "FINISHED") { // Only way for us to differentiate between a command and a terminal - this.agentClient.shells.rename( - shell.shellId, - // We embed some details in the name to properly show the command that was run - // , the name and that it is an actual command - JSON.stringify(details) - ); + this.agentClient.shells + .rename( + shell.shellId, + // We embed some details in the name to properly show the command that was run + // , the name and that it is an actual command + JSON.stringify(details) + ) + .catch(() => { + // It is already done + }); } const cmd = new Command( diff --git a/src/SandboxClient/hosts.ts b/src/SandboxClient/hosts.ts index 35bb878..be9e010 100644 --- a/src/SandboxClient/hosts.ts +++ b/src/SandboxClient/hosts.ts @@ -1,6 +1,6 @@ -import { HostToken } from "../HostTokens"; +import type { HostToken } from "../HostTokens"; -export { HostToken }; +export type { HostToken }; export class Hosts { constructor(private sandboxId: string, private hostToken?: HostToken) {} diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 470f9b4..ec0258f 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,12 +1,12 @@ import { promises as fs } from "fs"; import path from "path"; -import { isBinaryFile } from "isbinaryfile"; +import stripAnsi from "strip-ansi"; import * as readline from "readline"; import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; -import { VMTier, CodeSandbox, Sandbox } from "@codesandbox/sdk"; +import { VMTier, CodeSandbox, Sandbox, SandboxClient } from "@codesandbox/sdk"; import { templatesCreate, @@ -34,6 +34,8 @@ export type BuildCommandArgs = { fromSandbox?: string; skipFiles?: boolean; vmTier?: VmUpdateSpecsRequest["tier"]; + vmBuildTier?: VmUpdateSpecsRequest["tier"]; + logPath?: string; }; export const buildCommand: yargs.CommandModule< @@ -63,6 +65,11 @@ export const buildCommand: yargs.CommandModule< type: "string", choices: VMTier.All.map((t) => t.name), }) + .option("vm-build-tier", { + describe: "Specs to use for building the template sandbox.", + type: "string", + choices: VMTier.All.map((t) => t.name), + }) .option("alias", { describe: "Alias that should point to the created template. Alias namespace defaults to template directory, but you can explicitly pass `namespace@alias`", @@ -73,6 +80,10 @@ export const buildCommand: yargs.CommandModule< default: false, type: "boolean", }) + .option("log-path", { + describe: "Relative path to log file, if any", + type: "string", + }) .positional("directory", { describe: "Path to the project that we'll create a snapshot from", type: "string", @@ -83,11 +94,17 @@ export const buildCommand: yargs.CommandModule< const apiKey = getInferredApiKey(); const apiClient: Client = createApiClient("CLI", apiKey); const sdk = new CodeSandbox(apiKey); + const sandboxTier = argv.vmTier + ? VMTier.fromName(argv.vmTier) + : VMTier.Micro; + const buildTier = argv.vmBuildTier + ? VMTier.fromName(argv.vmBuildTier) + : sandboxTier; let alias: { namespace: string; alias: string } | undefined; if (argv.alias) { - alias = createAlias(argv.directory, argv.alias); + alias = createAlias(path.resolve(argv.directory), argv.alias); } const { hash, files: filePaths } = await hashDirectory(argv.directory); @@ -118,14 +135,58 @@ export const buildCommand: yargs.CommandModule< return `\n${spinnerMessages.join("\n")}`; } + const waitForSetup = async (sandbox: SandboxClient, index: number) => { + const steps = await sandbox.setup.getSteps(); + + for (const step of steps) { + const buffer: string[] = []; + + try { + spinner.start( + updateSpinnerMessage( + index, + `Running setup ${steps.indexOf(step) + 1} / ${steps.length} - ${ + step.name + }...` + ) + ); + + step.onOutput((output) => { + buffer.push(stripAnsi(output)); + }); + const output = await step.open(); + + buffer.push(...output.split("\n").map(stripAnsi)); + + await step.waitUntilComplete(); + + throw new Error(`Setup BAD STEP: ${step.name}`); + } catch (error) { + const logPath = argv.logPath || process.cwd(); + const timestamp = new Date().toISOString().replace(/:/g, "-"); + const logFilename = path.join( + logPath, + `setup-failure-${step.name}-${timestamp}.log` + ); + + try { + await fs.writeFile(logFilename, buffer.join("\n")); + console.error(`Log saved to: ${logFilename}`); + } catch (writeError) { + console.error(`Failed to write log file: ${writeError}`); + } + + throw new Error(`Setup step failed: ${step.name}`); + } + } + }; + const tasks = templateData.sandboxes.map(async ({ id }, index) => { try { spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); const startResponse = await withCustomError( - startVm(apiClient, id, { - vmTier: VMTier.fromName("Micro"), - }), + startVm(apiClient, id), "Failed to start sandbox" ); let sandboxVM = new Sandbox(id, apiClient, startResponse); @@ -135,6 +196,7 @@ export const buildCommand: yargs.CommandModule< spinner.start( updateSpinnerMessage(index, "Writing files to sandbox...") ); + try { let i = 0; for (const filePath of filePaths) { @@ -152,12 +214,11 @@ export const buildCommand: yargs.CommandModule< throw new Error(`Failed to write files to sandbox: ${error}`); } - spinner.start(updateSpinnerMessage(index, "Restarting sandbox...")); + spinner.start(updateSpinnerMessage(index, "Building sandbox...")); + sandboxVM = await withCustomError( sdk.sandboxes.restart(id, { - vmTier: argv.vmTier - ? VMTier.fromName(argv.vmTier) - : VMTier.fromName("Micro"), + vmTier: buildTier, }), "Failed to restart sandbox" ); @@ -167,40 +228,22 @@ export const buildCommand: yargs.CommandModule< "Failed to connect to sandbox" ); - const disposableStore = new DisposableStore(); - - const steps = await session.setup.getSteps(); + await waitForSetup(session, index); - for (const step of steps) { - const buffer: string[] = []; + spinner.start(updateSpinnerMessage(index, "Creating fork state...")); + sandboxVM = await withCustomError( + sdk.sandboxes.restart(id, { + vmTier: sandboxTier, + }), + "Failed to restart sandbox" + ); - try { - spinner.start( - updateSpinnerMessage( - index, - `Running setup ${steps.indexOf(step) + 1} / ${ - steps.length - } - ${step.name}...` - ) - ); - - disposableStore.add( - step.onOutput((output) => { - buffer.push(output); - }) - ); - - const output = await step.open(); - - buffer.push(...output.split("\n")); - - await step.waitUntilComplete(); - } catch (error) { - throw new Error(`Setup step failed: ${step.name}`); - } - } + session = await withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox" + ); - disposableStore.dispose(); + await waitForSetup(session, index); const ports = argv.ports || []; const updatePortSpinner = () => { @@ -279,7 +322,7 @@ export const buildCommand: yargs.CommandModule< ); if (!argv.ci && failedSandboxes.length > 0) { - spinner.info(`\n${spinnerMessages.join("\n")}`); + spinner.start(`\n${spinnerMessages.join("\n")}`); await waitForEnter( `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes @@ -331,6 +374,7 @@ export const buildCommand: yargs.CommandModule< `Alias ${alias.namespace}@${alias.alias} updated to: ${templateData.tag}` ); process.exit(0); + return; } console.log("Template created: " + templateData.tag); @@ -373,17 +417,17 @@ function createAlias(directory: string, alias: string) { const namespace = aliasParts.length === 2 ? aliasParts[0] : path.basename(directory); - const tag = aliasParts.length === 2 ? aliasParts[1] : alias; + alias = aliasParts.length === 2 ? aliasParts[1] : alias; - if (namespace.length > 64 || tag.length > 64) { + if (namespace.length > 64 || alias.length > 64) { throw new Error( - `Alias name "${namespace}" or tag "${tag}" is too long, must be 64 characters or less` + `Alias name "${namespace}" or tag "${alias}" is too long, must be 64 characters or less` ); } - if (!/^[a-zA-Z0-9-_]+$/.test(namespace) || !/^[a-zA-Z0-9-_]+$/.test(tag)) { + if (!/^[a-zA-Z0-9-_]+$/.test(namespace) || !/^[a-zA-Z0-9-_]+$/.test(alias)) { throw new Error( - `Alias name "${namespace}" or tag "${tag}" is invalid, must only contain upper/lower case letters, numbers, dashes and underscores` + `Alias name "${namespace}" or tag "${alias}" is invalid, must only contain upper/lower case letters, numbers, dashes and underscores` ); } diff --git a/src/bin/commands/hostTokens.ts b/src/bin/commands/hostTokens.ts index 524096b..69cc44b 100644 --- a/src/bin/commands/hostTokens.ts +++ b/src/bin/commands/hostTokens.ts @@ -39,14 +39,15 @@ export const hostTokensCommand: CommandModule = { .option("expires-at", { alias: "e", describe: - "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to create a token that never expires.", + "Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z).", type: "string", + demandOption: true, }); }, handler: async (argv) => { await createPreviewToken( argv.sandboxId as string, - argv["expires-at"] as string | undefined + argv["expires-at"] as string ); }, }) diff --git a/src/index.ts b/src/index.ts index 705bb72..fde48f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Sandboxes } from "./Sandboxes"; -export { Sandboxes as SandboxClient }; +export { Sandboxes }; export { VMTier } from "./VMTier"; From dd3c46a615f34b3f69bd357c13c7c8ad559ac976 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 19 Jun 2025 15:31:49 +0200 Subject: [PATCH 158/241] remove test step --- src/bin/commands/build.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index ec0258f..8a0e7d8 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -159,8 +159,6 @@ export const buildCommand: yargs.CommandModule< buffer.push(...output.split("\n").map(stripAnsi)); await step.waitUntilComplete(); - - throw new Error(`Setup BAD STEP: ${step.name}`); } catch (error) { const logPath = argv.logPath || process.cwd(); const timestamp = new Date().toISOString().replace(/:/g, "-"); From 5028bb06bcef908c5b48f74063d660f15fa37db7 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 25 Jun 2025 10:39:05 +0200 Subject: [PATCH 159/241] strip ansi codes --- package.json | 1 - src/bin/commands/build.ts | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 10c4cb1..701eac6 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,6 @@ "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", - "strip-ansi": "^7.1.0", "util": "^0.12.5", "yargs": "^17.7.2" } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 8a0e7d8..dc2d493 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,6 +1,5 @@ import { promises as fs } from "fs"; import path from "path"; -import stripAnsi from "strip-ansi"; import * as readline from "readline"; import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; @@ -11,7 +10,6 @@ import { VMTier, CodeSandbox, Sandbox, SandboxClient } from "@codesandbox/sdk"; import { templatesCreate, vmAssignTagAlias, - vmCreateTag, VmUpdateSpecsRequest, } from "../../api-clients/client"; import { @@ -22,7 +20,6 @@ import { import { getInferredApiKey } from "../../utils/constants"; import { hashDirectory } from "../utils/hash"; import { startVm } from "../../Sandboxes"; -import { DisposableStore } from "../../utils/disposable"; export type BuildCommandArgs = { directory: string; @@ -38,6 +35,17 @@ export type BuildCommandArgs = { logPath?: string; }; +function stripAnsiCodes(str: string) { + // Matches ESC [ params … finalChar + // \x1B = ESC + // \[ = literal “[” + // [0-?]* = any parameter bytes (digits, ;, ?) + // [ -/]* = any intermediate bytes (space through /) + // [@-~] = final byte ( @ A–Z [ \ ] ^ _ ` a–z { | } ~ ) + const CSI_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g; + return str.replace(CSI_REGEX, ""); +} + export const buildCommand: yargs.CommandModule< Record, BuildCommandArgs @@ -152,11 +160,11 @@ export const buildCommand: yargs.CommandModule< ); step.onOutput((output) => { - buffer.push(stripAnsi(output)); + buffer.push(stripAnsiCodes(output)); }); const output = await step.open(); - buffer.push(...output.split("\n").map(stripAnsi)); + buffer.push(...output.split("\n").map(stripAnsiCodes)); await step.waitUntilComplete(); } catch (error) { From f551ceda8237e2d4b5fc7c3670447884fcd36887 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 25 Jun 2025 11:10:29 +0200 Subject: [PATCH 160/241] initialize session also on createSession --- src/Sandbox.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 84f7a6d..a99e5ad 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -222,9 +222,6 @@ export class Sandbox { return this.createSession(customSession); } - /** - * Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables. - */ async createSession( customSession?: SessionCreateOptions ): Promise { @@ -233,11 +230,10 @@ export class Sandbox { customSession ); - /* if (customSession) { const client = await this.initializeCustomSession(customSession, session); client?.disconnect(); - }*/ + } return session; } From d93e37deb3212f4b4c64b54b0a4bbf19901c0221 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 25 Jun 2025 15:06:53 +0200 Subject: [PATCH 161/241] remove sentry from SDK, only use in CLI --- package.json | 2 +- src/bin/commands/build.ts | 4 ++-- src/{ => bin}/utils/sentry.ts | 7 ++----- src/index.ts | 2 +- src/utils/api.ts | 31 +++++++++++++++++++------------ 5 files changed, 25 insertions(+), 21 deletions(-) rename src/{ => bin}/utils/sentry.ts (88%) diff --git a/package.json b/package.json index 701eac6..1057805 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.10", + "version": "2.0.0-rc.11", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index dc2d493..2cd6f8f 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -4,7 +4,7 @@ import * as readline from "readline"; import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; - +import { instrumentedFetch } from "../utils/sentry"; import { VMTier, CodeSandbox, Sandbox, SandboxClient } from "@codesandbox/sdk"; import { @@ -100,7 +100,7 @@ export const buildCommand: yargs.CommandModule< handler: async (argv) => { const apiKey = getInferredApiKey(); - const apiClient: Client = createApiClient("CLI", apiKey); + const apiClient: Client = createApiClient(apiKey, {}, instrumentedFetch); const sdk = new CodeSandbox(apiKey); const sandboxTier = argv.vmTier ? VMTier.fromName(argv.vmTier) diff --git a/src/utils/sentry.ts b/src/bin/utils/sentry.ts similarity index 88% rename from src/utils/sentry.ts rename to src/bin/utils/sentry.ts index 930eb31..c09c2a8 100644 --- a/src/utils/sentry.ts +++ b/src/bin/utils/sentry.ts @@ -8,10 +8,7 @@ if (!Sentry.isInitialized()) { }); } -export async function instrumentedFetch( - request: Request, - client: "SDK" | "CLI" -) { +export async function instrumentedFetch(request: Request) { // We are cloning the request to be able to read its body on errors const res = await fetch(request.clone()); @@ -24,7 +21,7 @@ export async function instrumentedFetch( ? await new Response(request.body).text() : undefined, body: await res.clone().text(), - client, + client: "SDK-CLI", method: request.method, url: request.url, status: res.status, diff --git a/src/index.ts b/src/index.ts index fde48f2..7022a57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ export class CodeSandbox { constructor(apiToken?: string, opts: ClientOpts = {}) { const apiKey = apiToken || getInferredApiKey(); - const apiClient = createApiClient("SDK", apiKey, opts); + const apiClient = createApiClient(apiKey, opts); this.sandboxes = new Sandboxes(apiClient); this.hosts = new HostTokens(apiClient); diff --git a/src/utils/api.ts b/src/utils/api.ts index 26e2df5..8cd4649 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -9,9 +9,11 @@ import { createConfig, } from "@hey-api/client-fetch"; import { getInferredBaseUrl } from "./constants"; -import { instrumentedFetch } from "./sentry"; -async function enhanceFetch(request: Request, client: "SDK" | "CLI") { +async function enhanceFetch( + request: Request, + instrumentation?: (request: Request) => Promise +) { // Clone the request to modify headers const headers = new Headers(request.headers); const existingUserAgent = headers.get("User-Agent") || ""; @@ -25,24 +27,29 @@ async function enhanceFetch(request: Request, client: "SDK" | "CLI") { }`.trim() ); - // Create new request with updated headers - return instrumentedFetch( - new Request(request, { - headers, - }), - client - ); + // Create new request with updated headers and optionally add instrumentation + return instrumentation + ? instrumentation( + new Request(request, { + headers, + }) + ) + : fetch( + new Request(request, { + headers, + }) + ); } export function createApiClient( - client: "SDK" | "CLI", apiKey: string, - config: Config = {} + config: Config = {}, + instrumentation?: (request: Request) => Promise ) { return createClient( createConfig({ baseUrl: config.baseUrl || getInferredBaseUrl(apiKey), - fetch: (request) => enhanceFetch(request, client), + fetch: (request) => enhanceFetch(request, instrumentation), ...config, headers: { Authorization: `Bearer ${apiKey}`, From c1d3f688a898f6c3efeb726d0beccd6a0d76fc2f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 26 Jun 2025 11:17:14 +0200 Subject: [PATCH 162/241] finally git and env working --- package.json | 2 +- src/Sandbox.ts | 46 ++++++++++++++++++----------------- src/SandboxClient/commands.ts | 12 +++++---- src/bin/commands/build.ts | 4 ++- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 1057805..c14f049 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.11", + "version": "2.0.0-rc.10-debug", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/Sandbox.ts b/src/Sandbox.ts index a99e5ad..57f822f 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -88,10 +88,6 @@ export class Sandbox { customSession: SessionCreateOptions, session: SandboxSession ) { - if (!customSession.git && !customSession.env) { - return; - } - const client = await connectToSandbox({ session, getSession: async () => @@ -100,9 +96,18 @@ export class Sandbox { if (customSession.env) { const envStrings = Object.entries(customSession.env) - .map(([key, value]) => `export ${key}=${value}`) + .map(([key, value]) => { + // escape any single-quotes in the value + const safe = value.replace(/'/g, `'\\"'`); + return `export ${key}='${safe}'`; + }) .join("\n"); - await client.commands.run(`echo "${envStrings}" > $HOME/.private/.env`); + const cmd = [ + `cat << 'EOF' > "$HOME/.private/.env"`, + envStrings, + `EOF`, + ].join("\n"); + await client.commands.run(cmd); } if (customSession.git) { @@ -155,13 +160,6 @@ export class Sandbox { body: { session_id: customSession.id, permission: customSession.permission ?? "write", - ...(customSession.git - ? { - git_access_token: customSession.git.accessToken, - git_user_email: customSession.git.email, - git_user_name: customSession.git.name, - } - : {}), }, path: { id: this.id, @@ -225,16 +223,20 @@ export class Sandbox { async createSession( customSession?: SessionCreateOptions ): Promise { - const session = await this.getSession( - this.pitcherManagerResponse, - customSession - ); - - if (customSession) { - const client = await this.initializeCustomSession(customSession, session); - client?.disconnect(); + if (customSession?.git || customSession?.env) { + const configureSession = await this.getSession( + this.pitcherManagerResponse, + customSession + ); + + const client = await this.initializeCustomSession( + customSession, + configureSession + ); + + client?.dispose(); } - return session; + return this.getSession(this.pitcherManagerResponse, customSession); } } diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 19ce540..5e9769e 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -48,16 +48,18 @@ export class Commands { command = Array.isArray(command) ? command.join(" && ") : command; - const allEnv = Object.assign(opts?.env ?? {}); + const passedEnv = Object.assign(opts?.env ?? {}); + + const escapedCommand = command.replace(/'/g, "'\\''"); // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(allEnv).length + let commandWithEnv = Object.keys(passedEnv).length ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( - allEnv + passedEnv ) .map(([key, value]) => `${key}=${value}`) - .join(" ")} bash -c '${command}'` - : `source $HOME/.private/.env 2>/dev/null || true && ${command}`; + .join(" ")} bash -c '${escapedCommand}'` + : `source $HOME/.private/.env 2>/dev/null || true && bash -c '${escapedCommand}'`; if (opts?.cwd) { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 2cd6f8f..62bbe73 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -236,7 +236,9 @@ export const buildCommand: yargs.CommandModule< await waitForSetup(session, index); - spinner.start(updateSpinnerMessage(index, "Creating fork state...")); + spinner.start( + updateSpinnerMessage(index, "Optimizing initial state...") + ); sandboxVM = await withCustomError( sdk.sandboxes.restart(id, { vmTier: sandboxTier, From 3637c84274163358ef24b205045e6f1aad88cf47 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 26 Jun 2025 11:25:05 +0200 Subject: [PATCH 163/241] clean podman command errors --- src/SandboxClient/commands.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 5e9769e..22abc62 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -254,11 +254,18 @@ export class Command { async waitUntilComplete(): Promise { await this.barrier.wait(); + const cleaned = this.output + .join("\n") + .replace( + /Error: failed to exec in podman container: exit status 1[\s\S]*$/, + "" + ); + if (this.status === "FINISHED") { - return this.output.join("\n"); + return cleaned; } - throw new Error(`Command ERROR: ${this.output.join("\n")}`); + throw new Error(`Command ERROR: ${cleaned}`); } // TODO: allow for kill signals From ff9ffa8aad4f2a66777d5a018e59a01d0887263d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 26 Jun 2025 15:43:56 +0200 Subject: [PATCH 164/241] remove git --- src/SandboxClient/git.ts | 92 ------------------------------ src/SandboxClient/index.ts | 8 --- src/browser/BrowserAgentClient.ts | 10 ---- src/node/AgentClient.ts | 20 ------- src/node/agent-client-interface.ts | 6 -- 5 files changed, 136 deletions(-) delete mode 100644 src/SandboxClient/git.ts diff --git a/src/SandboxClient/git.ts b/src/SandboxClient/git.ts deleted file mode 100644 index 2272167..0000000 --- a/src/SandboxClient/git.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Commands } from "./commands"; -import { IAgentClient } from "../node/agent-client-interface"; - -/** - * An interface to interact with the git repository. - * - * The class is initialized with a `pitcherClient` and a `commands` object. - * The `pitcherClient` object is used to communicate with the Pitcher server. - * The `commands` object is used to run shell commands. - * - * The interface provides methods to commit changes to git, checkout a branch, - * and push changes to git. - * - * The interface also provides an event `onStatusChange` that is emitted when - * the git status changes. - */ -export class Git { - /** - * An event that is emitted when the git status changes. - */ - onStatusChange = this.agentClient.git.onStatusUpdated; - - constructor(private agentClient: IAgentClient, private commands: Commands) {} - - /** - * Get the current git status. - */ - status() { - return this.agentClient.git.getStatus(); - } - - /** - * Commit all changes to git - */ - async commit(message: string) { - const messageWithQuotesEscaped = message.replace(/"/g, '\\"'); - - await this.commands.run([ - "git add .", - `git commit -m "${messageWithQuotesEscaped}"`, - ]); - } - - /** - * Checkout a new branch and set upstream - */ - async createBranch(branch: string) { - await this.commands.run([ - "git checkout -b " + branch, - "git push --set-upstream origin " + branch, - ]); - } - - /** - * Checkout a branch - */ - async checkout( - ref: string, - /** - * @deprecated Use createBranch instead - */ - isNewBranch = false - ) { - if (isNewBranch) { - await this.commands.run([ - "git checkout -b " + ref, - "git push --set-upstream origin " + ref, - ]); - return; - } - - await this.commands.run(["git checkout " + ref]); - } - - /** - * Push all changes to git - */ - async push() { - await this.commands.run(["git push -u origin HEAD"]); - } - - async clone(opts: { url: string; branch: string }) { - await this.commands.run([ - "rm -rf .git", - "git init", - `git remote add origin ${opts.url}`, - "git fetch origin", - `git checkout -b ${opts.branch}`, - `git reset --hard origin/${opts.branch}`, - ]); - } -} diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 534d548..4902dad 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -7,7 +7,6 @@ import { Tasks } from "./tasks"; import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; import { Commands } from "./commands"; -import { Git } from "./git"; import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; import { IAgentClient } from "../node/agent-client-interface"; @@ -20,7 +19,6 @@ export * from "./setup"; export * from "./tasks"; export * from "./terminals"; export * from "./commands"; -export * from "./git"; export * from "./interpreters"; export * from "./hosts"; @@ -89,11 +87,6 @@ export class SandboxClient { */ public readonly interpreters: Interpreters; - /** - * Namespace for Git operations in the Sandbox - */ - public readonly git: Git; - /** * Namespace for managing ports on this Sandbox */ @@ -132,7 +125,6 @@ export class SandboxClient { this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); - this.git = new Git(this.agentClient, this.commands); this.disposable.onWillDispose(() => this.agentClient.dispose()); } diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts index dbcd884..b44516f 100644 --- a/src/browser/BrowserAgentClient.ts +++ b/src/browser/BrowserAgentClient.ts @@ -2,7 +2,6 @@ import { IPitcherClient } from "@codesandbox/pitcher-client"; import { IAgentClient, IAgentClientFS, - IAgentClientGit, IAgentClientPorts, IAgentClientSetup, IAgentClientShells, @@ -74,14 +73,6 @@ class BrowserAgentClientFS implements IAgentClientFS { } } -class BrowserAgentClientGit implements IAgentClientGit { - onStatusUpdated = this.pitcherClient.clients.git.onStatusUpdated; - getStatus() { - return this.pitcherClient.clients.git.getStatus(); - } - constructor(private pitcherClient: IPitcherClient) {} -} - class BrowserAgentClientPorts implements IAgentClientPorts { onPortsUpdated = this.pitcherClient.clients.port.onPortsUpdated; async getPorts() { @@ -135,7 +126,6 @@ export class BrowserAgentClient implements IAgentClient { onStateChange = this.onStateChangeEmitter.event; shells = new BrowserAgentClientShells(this.pitcherClient); fs = new BrowserAgentClientFS(this.pitcherClient); - git = new BrowserAgentClientGit(this.pitcherClient); ports = new BrowserAgentClientPorts(this.pitcherClient); setup = new BrowserAgentClientSetup(this.pitcherClient); tasks = new BrowserAgentClientTasks(this.pitcherClient); diff --git a/src/node/AgentClient.ts b/src/node/AgentClient.ts index 420b61d..a26f708 100644 --- a/src/node/AgentClient.ts +++ b/src/node/AgentClient.ts @@ -11,7 +11,6 @@ import { import { IAgentClient, IAgentClientFS, - IAgentClientGit, IAgentClientPorts, IAgentClientSetup, IAgentClientShells, @@ -285,24 +284,6 @@ class NodeAgentClientFS implements IAgentClientFS { } } -class NodeAgentClientGit implements IAgentClientGit { - private onStatusUpdatedEmitter = new Emitter(); - onStatusUpdated = this.onStatusUpdatedEmitter.event; - - constructor(private agentConnection: AgentConnection) { - agentConnection.onNotification("git/status", (params) => { - this.onStatusUpdatedEmitter.fire(params); - }); - } - - getStatus() { - return this.agentConnection.request({ - method: "git/status", - params: {}, - }); - } -} - class NodeAgentClientPorts implements IAgentClientPorts { private onPortsUpdatedEmitter = new Emitter(); onPortsUpdated = this.onPortsUpdatedEmitter.event; @@ -405,7 +386,6 @@ export class NodeAgentClient implements IAgentClient { onStateChange = this.agentConnection.onStateChange; shells = new NodeAgentClientShells(this.agentConnection); fs = new NodeAgentClientFS(this.agentConnection, this.params.workspacePath); - git = new NodeAgentClientGit(this.agentConnection); setup = new NodeAgentClientSetup(this.agentConnection); tasks = new NodeAgentClientTasks(this.agentConnection); system = new NodeAgentClientSystem(this.agentConnection); diff --git a/src/node/agent-client-interface.ts b/src/node/agent-client-interface.ts index 76aba44..48e7722 100644 --- a/src/node/agent-client-interface.ts +++ b/src/node/agent-client-interface.ts @@ -96,11 +96,6 @@ export interface IAgentClientFS { download(path?: string): Promise<{ downloadUrl: string }>; } -export interface IAgentClientGit { - onStatusUpdated: Event; - getStatus(): Promise; -} - export interface IAgentClientPorts { onPortsUpdated: Event; getPorts(): Promise; @@ -139,7 +134,6 @@ export interface IAgentClient { onStateChange: Event; shells: IAgentClientShells; fs: IAgentClientFS; - git: IAgentClientGit; ports: IAgentClientPorts; setup: IAgentClientSetup; tasks: IAgentClientTasks; From 44bb498eba35be0a17fe45e9b71a8b78d174025d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 27 Jun 2025 11:06:09 +0200 Subject: [PATCH 165/241] remove import --- src/node/agent-client-interface.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/agent-client-interface.ts b/src/node/agent-client-interface.ts index 48e7722..c1e3cb6 100644 --- a/src/node/agent-client-interface.ts +++ b/src/node/agent-client-interface.ts @@ -1,6 +1,5 @@ import { fs, - git, port, PitcherRequest, PitcherResponse, From 99d38d5fc887e1e6f9f21c8dabc1d1c79194439a Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 30 Jun 2025 07:33:20 +0200 Subject: [PATCH 166/241] more fixes --- package.json | 2 +- src/SandboxClient/commands.ts | 3 ++- src/SandboxClient/index.ts | 6 +++--- src/SandboxClient/interpreters.ts | 7 +++++-- src/bin/commands/build.ts | 12 +++++++++--- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c14f049..57d8c14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.10-debug", + "version": "2.0.0-rc.11-debug.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 22abc62..1e565bf 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -27,7 +27,8 @@ export type CommandStatus = const DEFAULT_SHELL_SIZE = { cols: 128, rows: 24 }; -export class Commands { +// This can not be called Commands due to React Native +export class SandboxCommands { private disposable = new Disposable(); constructor( sessionDisposable: Disposable, diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 4902dad..a2b30b7 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -6,7 +6,7 @@ import { Setup } from "./setup"; import { Tasks } from "./tasks"; import { Interpreters } from "./interpreters"; import { Terminals } from "./terminals"; -import { Commands } from "./commands"; +import { SandboxCommands } from "./commands"; import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; import { IAgentClient } from "../node/agent-client-interface"; @@ -80,7 +80,7 @@ export class SandboxClient { /** * Namespace for running commands in the Sandbox */ - public readonly commands: Commands; + public readonly commands: SandboxCommands; /** * Namespace for running code interpreters in the Sandbox @@ -121,7 +121,7 @@ export class SandboxClient { ); this.fs = new FileSystem(this.disposable, this.agentClient, username); this.terminals = new Terminals(this.disposable, this.agentClient); - this.commands = new Commands(this.disposable, this.agentClient); + this.commands = new SandboxCommands(this.disposable, this.agentClient); this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); diff --git a/src/SandboxClient/interpreters.ts b/src/SandboxClient/interpreters.ts index 8953bad..06ea628 100644 --- a/src/SandboxClient/interpreters.ts +++ b/src/SandboxClient/interpreters.ts @@ -1,9 +1,12 @@ import { Disposable } from "../utils/disposable"; -import { Commands, ShellRunOpts } from "./commands"; +import { SandboxCommands, ShellRunOpts } from "./commands"; export class Interpreters { private disposable = new Disposable(); - constructor(sessionDisposable: Disposable, private commands: Commands) { + constructor( + sessionDisposable: Disposable, + private commands: SandboxCommands + ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 62bbe73..3f17016 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,5 +1,5 @@ import { promises as fs } from "fs"; -import path from "path"; +import path, { dirname } from "path"; import * as readline from "readline"; import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; @@ -20,6 +20,7 @@ import { import { getInferredApiKey } from "../../utils/constants"; import { hashDirectory } from "../utils/hash"; import { startVm } from "../../Sandboxes"; +import { mkdir, writeFile } from "fs/promises"; export type BuildCommandArgs = { directory: string; @@ -35,6 +36,11 @@ export type BuildCommandArgs = { logPath?: string; }; +async function writeFileEnsureDir(filePath, data) { + await mkdir(dirname(filePath), { recursive: true }); + await writeFile(filePath, data); +} + function stripAnsiCodes(str: string) { // Matches ESC [ params … finalChar // \x1B = ESC @@ -172,11 +178,11 @@ export const buildCommand: yargs.CommandModule< const timestamp = new Date().toISOString().replace(/:/g, "-"); const logFilename = path.join( logPath, - `setup-failure-${step.name}-${timestamp}.log` + `setup-failure-${sandbox.id}-${timestamp}.log` ); try { - await fs.writeFile(logFilename, buffer.join("\n")); + await writeFileEnsureDir(logFilename, buffer.join("\n")); console.error(`Log saved to: ${logFilename}`); } catch (writeError) { console.error(`Failed to write log file: ${writeError}`); From 233999e59686fac3e02e4ae69465b3344f8cec13 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 30 Jun 2025 11:32:34 +0200 Subject: [PATCH 167/241] properly exit as error when failing Sandbox creation --- src/bin/commands/build.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 3f17016..81f880d 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -388,10 +388,14 @@ export const buildCommand: yargs.CommandModule< `Alias ${alias.namespace}@${alias.alias} updated to: ${templateData.tag}` ); process.exit(0); - return; } console.log("Template created: " + templateData.tag); + + if (argv.ci && failedSandboxes.length > 0) { + process.exit(1); + } + process.exit(0); } catch (error) { console.error(error); From 790f08c26a875d6799947431fd0cddf6f9007078 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 30 Jun 2025 12:07:27 +0200 Subject: [PATCH 168/241] fix error in ci --- package.json | 2 +- src/bin/commands/build.ts | 87 +++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 57d8c14..bb01226 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.11-debug.2", + "version": "2.0.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 81f880d..a1d797e 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -329,47 +329,58 @@ export const buildCommand: yargs.CommandModule< } }); - const results = await Promise.allSettled(tasks); - - const failedSandboxes = templateData.sandboxes.filter( - (_, index) => results[index].status === "rejected" - ); - - if (!argv.ci && failedSandboxes.length > 0) { - spinner.start(`\n${spinnerMessages.join("\n")}`); + if (argv.ci) { + try { + await Promise.all(tasks); + } catch { + spinner.fail(`\n${spinnerMessages.join("\n")}`); + process.exit(1); + } + } else { + const results = await Promise.allSettled(tasks); - await waitForEnter( - `\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes - .map((sandbox) => sandbox.id) - .join(", ")} and press ENTER to create snapshot...\n` + const failedSandboxes = templateData.sandboxes.filter( + (_, index) => results[index].status === "rejected" ); - failedSandboxes.forEach(({ id }) => { - updateSpinnerMessage( - templateData.sandboxes.findIndex((sandbox) => sandbox.id === id), - "Creating snapshot..." + if (failedSandboxes.length > 0) { + spinner.start( + `\n${spinnerMessages.join( + "\n" + )}\n\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes + .map((sandbox) => sandbox.id) + .join(", ")} and press ENTER to create snapshot...\n` ); - }); - spinner.start(`\n${spinnerMessages.join("\n")}`); + await waitForEnter(); - await Promise.all( - failedSandboxes.map(async ({ id }) => { - await sdk.sandboxes.hibernate(id); - - spinner.start( - updateSpinnerMessage( - templateData.sandboxes.findIndex( - (sandbox) => sandbox.id === id - ), - "Snapshot created" - ) + failedSandboxes.forEach(({ id }) => { + updateSpinnerMessage( + templateData.sandboxes.findIndex((sandbox) => sandbox.id === id), + "Creating snapshot..." ); - }) - ); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); - } else { - spinner.succeed(`\n${spinnerMessages.join("\n")}`); + }); + + spinner.start(`\n${spinnerMessages.join("\n")}`); + + await Promise.all( + failedSandboxes.map(async ({ id }) => { + await sdk.sandboxes.hibernate(id); + + spinner.start( + updateSpinnerMessage( + templateData.sandboxes.findIndex( + (sandbox) => sandbox.id === id + ), + "Snapshot created" + ) + ); + }) + ); + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } else { + spinner.succeed(`\n${spinnerMessages.join("\n")}`); + } } if (alias) { @@ -392,10 +403,6 @@ export const buildCommand: yargs.CommandModule< console.log("Template created: " + templateData.tag); - if (argv.ci && failedSandboxes.length > 0) { - process.exit(1); - } - process.exit(0); } catch (error) { console.error(error); @@ -410,14 +417,14 @@ function withCustomError>(promise: T, message: string) { }); } -function waitForEnter(message: string) { +function waitForEnter() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { - rl.question(message, () => { + rl.question("", () => { rl.close(); resolve(); }); From 15965d180586cbc204add1e1a802f49b6453224e Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 30 Jun 2025 17:07:52 -0400 Subject: [PATCH 169/241] fix keepAliveWhileConnected logic --- src/SandboxClient/index.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index a2b30b7..93553ff 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -234,17 +234,19 @@ export class SandboxClient { * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ public keepActiveWhileConnected(enabled: boolean) { - if (enabled && !this.keepAliveInterval) { - this.keepAliveInterval = setInterval(() => { - this.agentClient.system.update(); - }, 1000 * 30); - - this.disposable.onWillDispose(() => { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - }); + if (enabled) + if (!this.keepAliveInterval) { + this.keepAliveInterval = setInterval(() => { + this.agentClient.system.update(); + }, 1000 * 30); + + this.disposable.onWillDispose(() => { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + }); + } } else { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); From 890c6c1fac2cfadc85f91c41cf252d0c731000ec Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 30 Jun 2025 17:12:46 -0400 Subject: [PATCH 170/241] fix typo --- src/SandboxClient/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 93553ff..c18e3ff 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -234,7 +234,7 @@ export class SandboxClient { * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ public keepActiveWhileConnected(enabled: boolean) { - if (enabled) + if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { this.agentClient.system.update(); From ba61554965250d33809beccf4b2f71b8dcee65d9 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 1 Jul 2025 08:39:25 +0200 Subject: [PATCH 171/241] Make readme reflect current state of lib --- README.md | 47 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 954cb88..ab35c99 100644 --- a/README.md +++ b/README.md @@ -27,53 +27,20 @@ Create an API token by going to https://codesandbox.io/t/api, and clicking on th ```javascript import { CodeSandbox } from "@codesandbox/sdk"; -// Create the client with your token -const sdk = new CodeSandbox(token); +const sdk = new CodeSandbox(process.env.CSB_API_KEY); +const sandbox = await sdk.sandboxes.create(); +const client = await sandbox.connect(); -// This creates a new sandbox by forking our default template sandbox. -// You can also pass in other template ids, or create your own template to fork from. -const sandbox = await sdk.sandbox.create(); +const output = await client.commands.run("echo 'Hello World'"); -// You can run JS code directly -await sandbox.shells.js.run("console.log(1+1)"); -// Or Python code (if it's installed in the template) -await sandbox.shells.python.run("print(1+1)"); - -// Or anything else -await sandbox.shells.run("echo 'Hello, world!'"); - -// We have a FS API to interact with the filesystem -await sandbox.fs.writeTextFile("./hello.txt", "world"); - -// And you can clone sandboxes! This does not only clone the filesystem, processes that are running in the original sandbox will also be cloned! -const sandbox2 = await sandbox.fork(); - -// Check that the file is still there -await sandbox2.fs.readTextFile("./hello.txt"); - -// You can also get the opened ports, with the URL to access those -console.log(sandbox2.ports.getOpenedPorts()); - -// Getting metrics... -const metrics = await sandbox2.getMetrics(); -console.log( - `Memory: ${metrics.memory.usedKiB} KiB / ${metrics.memory.totalKiB} KiB` -); -console.log(`CPU: ${(metrics.cpu.used / metrics.cpu.cores) * 100}%`); - -// Finally, you can hibernate a sandbox. This will snapshot the sandbox and stop it. Next time you start the sandbox, it will continue where it left off, as we created a memory snapshot. -await sandbox.hibernate(); -await sandbox2.hibernate(); - -// Open the sandbox again -const resumedSandbox = await sdk.sandbox.open(sandbox.id); +console.log(output); // Hello World ``` ## CodeSandbox Integration This SDK uses the API token from your workspace in CodeSandbox to authenticate and create sandboxes. Because of this, the sandboxes will be created inside your workspace, and the resources will be billed to your workspace. -You could, for example, create a private template in your workspace that has all the dependencies you need (even running servers), and then use that template to fork sandboxes from. This way, you can control the environment that the sandboxes run in. +Build your own template that has all the dependencies you need (even running servers), and then use that template to create sandboxes from. This way, you can control the environment that the sandboxes run in. ## Example Use Cases @@ -83,6 +50,6 @@ Code interpretation: Run code in a sandbox to interpret it. This way, you can ru Development environments: Create a sandbox for each developer, and run their code in the sandbox. This way, you can run multiple development environments in parallel without them interfering with each other. -AI Agents: Create a sandbox for each AI agent, and run the agent in the sandbox. This way, you can run multiple agents in parallel without them interfering with each other. Using the forking mechanism, you can also A/B test different agents. +AI Agents: Create a sandbox for each AI agent, and run the agent in the sandbox. This way, you can run multiple agents in parallel without them interfering with each other. CI/CD: Run tests inside a sandbox, and hibernate the sandbox when the tests are done. This way, you can quickly start the sandbox again when you need to run the tests again or evaluate the results. From 2cbc69e9ade129697495d7a382cfb18ca36977c1 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 1 Jul 2025 09:26:20 +0200 Subject: [PATCH 172/241] fix keep active while connected --- src/SandboxClient/index.ts | 41 +++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index c18e3ff..379fc6e 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -12,6 +12,7 @@ import { Hosts } from "./hosts"; import { IAgentClient } from "../node/agent-client-interface"; import { setup } from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; +import { clear } from "console"; export * from "./filesystem"; export * from "./ports"; @@ -126,6 +127,30 @@ export class SandboxClient { this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); this.interpreters = new Interpreters(this.disposable, this.commands); this.disposable.onWillDispose(() => this.agentClient.dispose()); + + this.disposable.onWillDispose(() => { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + }); + + this.agentClient.onStateChange((state) => { + if (!this.shouldKeepAlive) { + return; + } + + // We can not call `keepActiveWhileConnected` here, because it would + // reset the interval, which would turn off the "shouldKeepAlive" flag + if (state === "DISCONNECTED" || state === "HIBERNATED") { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + } + this.keepAliveInterval = null; + } else if (state === "CONNECTED") { + this.keepActiveWhileConnected(true); + } + }); } /** @@ -219,6 +244,11 @@ export class SandboxClient { * reconnect to the sandbox. */ public disconnect() { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + return this.agentClient.disconnect(); } @@ -230,22 +260,19 @@ export class SandboxClient { } private keepAliveInterval: NodeJS.Timeout | null = null; + private shouldKeepAlive = false; /** * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ public keepActiveWhileConnected(enabled: boolean) { + // Used to manage the interval when disconnects happen + this.shouldKeepAlive = enabled; + if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { this.agentClient.system.update(); }, 1000 * 30); - - this.disposable.onWillDispose(() => { - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } - }); } } else { if (this.keepAliveInterval) { From 29cad670a807ebc4cf65b992a3e9449a5682f93b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 1 Jul 2025 11:39:25 +0200 Subject: [PATCH 173/241] Better output for template creation, also creating Sandbox to verify --- package.json | 2 +- src/bin/commands/build.ts | 36 +++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index bb01226..7a70fae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0", + "version": "2.1.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index a1d797e..6d60c90 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -377,12 +377,21 @@ export const buildCommand: yargs.CommandModule< ); }) ); - spinner.succeed(`\n${spinnerMessages.join("\n")}`); + spinner.start(`\n${spinnerMessages.join("\n")}`); } else { - spinner.succeed(`\n${spinnerMessages.join("\n")}`); + spinner.start(`\n${spinnerMessages.join("\n")}`); } } + spinner.start( + `\n${spinnerMessages.join( + "\n" + )}\n\nCreating template reference and example...` + ); + + let referenceString; + let id; + if (alias) { await vmAssignTagAlias({ client: apiClient, @@ -395,13 +404,26 @@ export const buildCommand: yargs.CommandModule< }, }); - console.log( - `Alias ${alias.namespace}@${alias.alias} updated to: ${templateData.tag}` - ); - process.exit(0); + id = `${alias.namespace}@${alias.alias}`; + referenceString = `Alias ${id} now referencing: ${templateData.tag}`; + } else { + id = templateData.tag; + referenceString = `Template created with tag: ${templateData.tag}`; } - console.log("Template created: " + templateData.tag); + const sandbox = await sdk.sandboxes.create({ + id, + }); + + spinner.succeed( + `\n${spinnerMessages.join("\n")}\n\n${referenceString} + +sdk.sandboxes.create({ + id: "${id}" +}) + +Verify Sandbox at: https://codesandbox.io/s/${sandbox.id}` + ); process.exit(0); } catch (error) { From a5a9bb1c5115d2b3a426dfee9983b079a1f85328 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 1 Jul 2025 11:51:45 +0200 Subject: [PATCH 174/241] adjust lines --- src/bin/commands/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 6d60c90..f08e516 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -422,7 +422,7 @@ sdk.sandboxes.create({ id: "${id}" }) -Verify Sandbox at: https://codesandbox.io/s/${sandbox.id}` +Verify Sandbox at: https://codesandbox.io/s/${sandbox.id}\n\n` ); process.exit(0); From 78efcfb78c758412d5aad83b8ce2b0754c3e1723 Mon Sep 17 00:00:00 2001 From: tristandubbeld Date: Wed, 2 Jul 2025 09:34:12 +0200 Subject: [PATCH 175/241] Update package-lock after installing --- package-lock.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70e441e..096c88f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.10", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.0-rc.10", + "version": "2.1.0", "license": "MIT", "dependencies": { "@inkjs/ui": "^2.0.0", @@ -19,7 +19,6 @@ "path": "^0.12.7", "react": "^18.3.1", "readline": "^1.3.0", - "strip-ansi": "^7.1.0", "util": "^0.12.5", "yargs": "^17.7.2" }, From 75f56dafb1fe93ad395d9727ebab39425875de5d Mon Sep 17 00:00:00 2001 From: tristandubbeld Date: Wed, 2 Jul 2025 09:35:52 +0200 Subject: [PATCH 176/241] fix: add apiClient to context and pass to query fn --- src/bin/ui/Dashboard.tsx | 8 +++++--- src/bin/ui/sdkContext.tsx | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/bin/ui/Dashboard.tsx b/src/bin/ui/Dashboard.tsx index b7ae6ae..c8f5ad8 100644 --- a/src/bin/ui/Dashboard.tsx +++ b/src/bin/ui/Dashboard.tsx @@ -21,10 +21,12 @@ function useTerminalSize() { // Component to open a sandbox by ID export function Dashboard() { + const { apiClient } = useSDK(); + // Poll getRunningVms API every 2 seconds const runningVmsQuery = useQuery({ queryKey: ["runningVms"], - queryFn: getRunningVms, + queryFn: () => getRunningVms(apiClient), }); const [sandboxId, setSandboxId] = useState(""); @@ -103,11 +105,11 @@ const Sandbox = memo( }) => { const sandboxQuery = useQuery({ queryKey: ["sandbox", id], - queryFn: () => getSandbox(id), + queryFn: () => getSandbox(apiClient, id), }); const runningStateRef = useRef(runningState); - const sdk = useSDK(); + const { sdk, apiClient } = useSDK(); // Only two states: RUNNING or IDLE const [sandboxState, setSandboxState] = useState< diff --git a/src/bin/ui/sdkContext.tsx b/src/bin/ui/sdkContext.tsx index 1e92445..9b26887 100644 --- a/src/bin/ui/sdkContext.tsx +++ b/src/bin/ui/sdkContext.tsx @@ -1,13 +1,28 @@ import * as React from "react"; import { createContext, useContext } from "react"; import { CodeSandbox } from "@codesandbox/sdk"; +import { createApiClient } from "../../utils/api"; +import { Client } from "@hey-api/client-fetch"; +import { getInferredApiKey } from "../../utils/constants"; +import { instrumentedFetch } from "../utils/sentry"; const sdk = new CodeSandbox(); -export const SDKContext = createContext(sdk); +const apiKey = getInferredApiKey(); +const apiClient: Client = createApiClient(apiKey, {}, instrumentedFetch); + +export const SDKContext = createContext<{ sdk: CodeSandbox; apiClient: Client }>({ + sdk, + apiClient, +}); + export const SDKProvider = ({ children }: { children: React.ReactNode }) => { - return {children}; + return ( + + {children} + + ); }; export function useSDK() { From 6d455ad7eefe69f70b8ff4f20efb18d929e57613 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 2 Jul 2025 14:41:19 +0200 Subject: [PATCH 177/241] fix: Template resolve files fixes (#121) * fix: Remove hashing of files. Not needed with template tags and aliases * fix: Show error when file write fails on template creation --- src/bin/commands/build.ts | 21 ++++++++++--------- src/bin/utils/{hash.ts => files.ts} | 32 +++++------------------------ 2 files changed, 16 insertions(+), 37 deletions(-) rename src/bin/utils/{hash.ts => files.ts} (63%) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index f08e516..aed7fe8 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -18,7 +18,7 @@ import { handleResponse, } from "../../utils/api"; import { getInferredApiKey } from "../../utils/constants"; -import { hashDirectory } from "../utils/hash"; +import { hashDirectory as getFilePaths } from "../utils/files"; import { startVm } from "../../Sandboxes"; import { mkdir, writeFile } from "fs/promises"; @@ -121,8 +121,7 @@ export const buildCommand: yargs.CommandModule< alias = createAlias(path.resolve(argv.directory), argv.alias); } - const { hash, files: filePaths } = await hashDirectory(argv.directory); - const tag = `sha:${hash.slice(0, 6)}`; + const filePaths = await getFilePaths(argv.directory); try { const templateData = handleResponse( @@ -132,7 +131,7 @@ export const buildCommand: yargs.CommandModule< forkOf: argv.fromSandbox || getDefaultTemplateId(apiClient), title: argv.name, // We filter out sdk-templates on the dashboard - tags: ["sdk-template", tag], + tags: ["sdk-template"], }, }), "Failed to create template" @@ -209,10 +208,10 @@ export const buildCommand: yargs.CommandModule< updateSpinnerMessage(index, "Writing files to sandbox...") ); - try { - let i = 0; - for (const filePath of filePaths) { - i++; + let i = 0; + for (const filePath of filePaths) { + i++; + try { const fullPath = path.join(argv.directory, filePath); const content = await fs.readFile(fullPath); const dirname = path.dirname(filePath); @@ -221,9 +220,11 @@ export const buildCommand: yargs.CommandModule< create: true, overwrite: true, }); + } catch (error) { + throw new Error( + `Failed to write "${filePath}" to sandbox: ${error}` + ); } - } catch (error) { - throw new Error(`Failed to write files to sandbox: ${error}`); } spinner.start(updateSpinnerMessage(index, "Building sandbox...")); diff --git a/src/bin/utils/hash.ts b/src/bin/utils/files.ts similarity index 63% rename from src/bin/utils/hash.ts rename to src/bin/utils/files.ts index 8dc82e1..2879909 100644 --- a/src/bin/utils/hash.ts +++ b/src/bin/utils/files.ts @@ -1,18 +1,12 @@ -import { createHash } from "crypto"; import { existsSync, readFileSync } from "fs"; -import { readFile, stat, readdir } from "fs/promises"; +import { stat, readdir } from "fs/promises"; import { join, relative } from "path"; import ignore from "ignore"; -interface HashResult { - hash: string; - files: string[]; -} - const MAX_FILES = 50_000; -export async function hashDirectory(dirPath: string): Promise { +export async function hashDirectory(dirPath: string): Promise { // Initialize ignore rules from .gitignore, .dockerignore and .csbignore const ig = ignore(); const ignoreFiles = [".gitignore", ".dockerignore", ".csbignore"]; @@ -24,11 +18,10 @@ export async function hashDirectory(dirPath: string): Promise { } }); - // Always ignore .git folder - ig.add(".git/**"); + // Always ignore root .git folder + ig.add("/.git/"); const relevantFiles: string[] = []; - const fileHashes: string[] = []; async function processDirectory(currentPath: string) { const files = await readdir(currentPath); @@ -51,11 +44,6 @@ export async function hashDirectory(dirPath: string): Promise { if (stats.isDirectory()) { await processDirectory(fullPath); } else if (stats.isFile()) { - const fileContent = await readFile(fullPath); - const fileHash = createHash("sha256") - .update(fileContent) - .digest("hex"); - fileHashes.push(fileHash); relevantFiles.push(relativePath); } }) @@ -64,17 +52,7 @@ export async function hashDirectory(dirPath: string): Promise { await processDirectory(dirPath); - // Sort for consistent hashing - fileHashes.sort(); relevantFiles.sort(); - // Create final hash from all file hashes - const finalHash = createHash("sha256") - .update(fileHashes.join("")) - .digest("hex"); - - return { - hash: finalHash, - files: relevantFiles, - }; + return relevantFiles; } From 82ec4c35aba2188a7b6014c1d57f3223bc12ee35 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 2 Jul 2025 14:43:28 +0200 Subject: [PATCH 178/241] chore(main): release 2.0.2 (#123) --- CHANGELOG.md | 9 +++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 539bcaa..d370dbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [2.0.2](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.1...v2.0.2) (2025-07-02) + + +### Bug Fixes + +* add api client to runningvms query fn ([0119379](https://github.com/codesandbox/codesandbox-sdk/commit/0119379482320dd1142366838ec0679380800909)) +* add apiClient to context and pass to query fn ([75f56da](https://github.com/codesandbox/codesandbox-sdk/commit/75f56dafb1fe93ad395d9727ebab39425875de5d)) +* Template resolve files fixes ([#121](https://github.com/codesandbox/codesandbox-sdk/issues/121)) ([6d455ad](https://github.com/codesandbox/codesandbox-sdk/commit/6d455ad7eefe69f70b8ff4f20efb18d929e57613)) + ## [0.12.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.2...v0.12.0) (2025-04-24) diff --git a/package-lock.json b/package-lock.json index 096c88f..17ae19b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.0.2", "license": "MIT", "dependencies": { "@inkjs/ui": "^2.0.0", diff --git a/package.json b/package.json index 7a70fae..7973e3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.0.2", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 09d9e8a9a678c35169f8ff98b6e283fb45e23ed1 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 8 Jul 2025 16:28:46 +0200 Subject: [PATCH 179/241] fix: disable sentry by default, make it optional (#126) * chore: make sentry optional and off by default * bump * remove readme thingie * update lockfile --- README.md | 6 +++ package-lock.json | 97 ++++++++++++++++++++++++++++++++++++----- package.json | 8 ++-- src/bin/utils/sentry.ts | 38 ++++++++++++---- 4 files changed, 128 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ab35c99..6a354bd 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,12 @@ const output = await client.commands.run("echo 'Hello World'"); console.log(output); // Hello World ``` +## Configuration + +The SDK supports the following environment variables for configuration: + +- `CSB_API_KEY`: Your CodeSandbox API token for authentication + ## CodeSandbox Integration This SDK uses the API token from your workspace in CodeSandbox to authenticate and create sandboxes. Because of this, the sandboxes will be created inside your workspace, and the resources will be billed to your workspace. diff --git a/package-lock.json b/package-lock.json index 17ae19b..1e7894c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "@codesandbox/sdk", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.2", + "version": "2.0.3", "license": "MIT", "dependencies": { "@inkjs/ui": "^2.0.0", - "@sentry/node": "^9.29.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", @@ -49,6 +48,9 @@ "tslib": "^2.1.0", "typescript": "^5.7.2", "why-is-node-running": "^2.3.0" + }, + "optionalDependencies": { + "@sentry/node": "^9.29.0" } }, "node_modules/@absinthe/socket": { @@ -947,6 +949,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8.0.0" } @@ -956,6 +959,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/api": "^1.3.0" }, @@ -968,6 +972,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" }, @@ -980,6 +985,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, @@ -995,6 +1001,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1004,6 +1011,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", @@ -1024,6 +1032,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1041,6 +1050,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz", "integrity": "sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1059,6 +1069,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz", "integrity": "sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, @@ -1074,6 +1085,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz", "integrity": "sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1091,6 +1103,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz", "integrity": "sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1" @@ -1107,6 +1120,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz", "integrity": "sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, @@ -1122,6 +1136,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz", "integrity": "sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, @@ -1137,6 +1152,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz", "integrity": "sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1154,6 +1170,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz", "integrity": "sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/instrumentation": "0.57.2", @@ -1173,6 +1190,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1182,6 +1200,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -1194,6 +1213,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz", "integrity": "sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/redis-common": "^0.36.2", @@ -1211,6 +1231,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz", "integrity": "sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" @@ -1227,6 +1248,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz", "integrity": "sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" @@ -1243,6 +1265,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz", "integrity": "sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1260,6 +1283,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz", "integrity": "sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1" }, @@ -1275,6 +1299,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz", "integrity": "sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" @@ -1291,6 +1316,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz", "integrity": "sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1308,6 +1334,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz", "integrity": "sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", @@ -1325,6 +1352,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz", "integrity": "sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", @@ -1342,6 +1370,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz", "integrity": "sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.26.0", "@opentelemetry/instrumentation": "^0.57.1", @@ -1362,6 +1391,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz", "integrity": "sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/redis-common": "^0.36.2", @@ -1379,6 +1409,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz", "integrity": "sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0", @@ -1396,6 +1427,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz", "integrity": "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1" @@ -1412,6 +1444,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -1424,6 +1457,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1433,6 +1467,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" @@ -1449,6 +1484,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1458,6 +1494,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1", @@ -1475,6 +1512,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1484,6 +1522,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz", "integrity": "sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=14" } @@ -1493,6 +1532,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/core": "^1.1.0" }, @@ -1508,6 +1548,7 @@ "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.8.2.tgz", "integrity": "sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, @@ -1520,6 +1561,7 @@ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz", "integrity": "sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=18" } @@ -1529,6 +1571,7 @@ "resolved": "https://registry.npmjs.org/@sentry/node/-/node-9.29.0.tgz", "integrity": "sha512-oABipgC/fClRuvyMeK43rigv9F+OAaoR84UaMKB7aPXN6iz634wBRVsaoZAwiR3xLL+R7MafEPPA/s9XqlG7ag==", "license": "MIT", + "optional": true, "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", @@ -1574,6 +1617,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", + "optional": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1589,6 +1633,7 @@ "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-9.29.0.tgz", "integrity": "sha512-QTUmre8i5+832RjzQW+g8IQ3UmBe5fbQXGbCF5hQ0UNuHle9r3Z8UZcIff5W8tm5AXMxPqvptTnDEZUUXHgBiA==", "license": "MIT", + "optional": true, "dependencies": { "@sentry/core": "9.29.0" }, @@ -1652,6 +1697,7 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", + "optional": true, "dependencies": { "@types/node": "*" } @@ -1685,6 +1731,7 @@ "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", "license": "MIT", + "optional": true, "dependencies": { "@types/node": "*" } @@ -1693,6 +1740,7 @@ "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1703,6 +1751,7 @@ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", "license": "MIT", + "optional": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -1714,6 +1763,7 @@ "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", "license": "MIT", + "optional": true, "dependencies": { "@types/pg": "*" } @@ -1739,13 +1789,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", "license": "MIT", + "optional": true, "dependencies": { "@types/node": "*" } @@ -1788,6 +1840,7 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1801,6 +1854,7 @@ "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "license": "MIT", + "optional": true, "peerDependencies": { "acorn": "^8" } @@ -1949,6 +2003,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2032,6 +2087,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2384,7 +2440,8 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/class-states": { "version": "1.0.16", @@ -2795,6 +2852,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3229,7 +3287,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fs-minipass": { "version": "2.1.0", @@ -3691,6 +3750,7 @@ "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz", "integrity": "sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==", "license": "Apache-2.0", + "optional": true, "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", @@ -3940,6 +4000,7 @@ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", + "optional": true, "dependencies": { "hasown": "^2.0.2" }, @@ -4557,12 +4618,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, "license": "MIT" }, "node_modules/neo-async": { @@ -4887,7 +4950,8 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/path-scurry": { "version": "2.0.0", @@ -4957,6 +5021,7 @@ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "license": "ISC", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -4965,13 +5030,15 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "license": "MIT", + "optional": true, "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", @@ -5063,6 +5130,7 @@ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "license": "MIT", + "optional": true, "engines": { "node": ">=4" } @@ -5072,6 +5140,7 @@ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -5081,6 +5150,7 @@ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -5090,6 +5160,7 @@ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "license": "MIT", + "optional": true, "dependencies": { "xtend": "^4.0.0" }, @@ -5296,6 +5367,7 @@ "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", "license": "MIT", + "optional": true, "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", @@ -5310,6 +5382,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "license": "MIT", + "optional": true, "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", @@ -5511,7 +5584,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "optional": true }, "node_modules/siginfo": { "version": "2.0.0", @@ -5858,6 +5932,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", + "optional": true, "engines": { "node": ">= 0.4" }, @@ -5999,6 +6074,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, "license": "MIT" }, "node_modules/universal-cookie": { @@ -6354,6 +6430,7 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.4" } diff --git a/package.json b/package.json index 7973e3b..ee20dcc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.2", + "version": "2.0.3", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -94,7 +94,6 @@ }, "dependencies": { "@inkjs/ui": "^2.0.0", - "@sentry/node": "^9.29.0", "@tanstack/react-query": "^5.76.1", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", @@ -105,5 +104,8 @@ "readline": "^1.3.0", "util": "^0.12.5", "yargs": "^17.7.2" + }, + "optionalDependencies": { + "@sentry/node": "^9.29.0" } -} +} \ No newline at end of file diff --git a/src/bin/utils/sentry.ts b/src/bin/utils/sentry.ts index c09c2a8..30a9a80 100644 --- a/src/bin/utils/sentry.ts +++ b/src/bin/utils/sentry.ts @@ -1,18 +1,40 @@ -import * as Sentry from "@sentry/node"; +let Sentry: typeof import("@sentry/node") | null = null; +let sentryInitialized = false; -// This can happen when the CLI uses Sentry for its own requests, but also the SDK for other requests -if (!Sentry.isInitialized()) { - Sentry.init({ - dsn: "https://6b8a654fd32a40bdb146ae7089422e10@sentry.csbops.io/11", - defaultIntegrations: false, - }); +// Function to initialize Sentry if conditions are met +async function initializeSentry() { + if (sentryInitialized) return; + + // Only initialize Sentry if the CODESANDBOX_SENTRY_ENABLED environment variable is set to "true" + // and the @sentry/node package is available + if (process.env.CODESANDBOX_SENTRY_ENABLED === "true") { + try { + Sentry = await import("@sentry/node"); + + // This can happen when the CLI uses Sentry for its own requests, but also the SDK for other requests + if (!Sentry.isInitialized()) { + Sentry.init({ + dsn: "https://6b8a654fd32a40bdb146ae7089422e10@sentry.csbops.io/11", + defaultIntegrations: false, + }); + } + } catch (error) { + // Sentry is not available, continue without it + console.warn("Sentry error reporting is enabled but @sentry/node is not available. Install it as a dependency to enable error reporting."); + } + } + + sentryInitialized = true; } export async function instrumentedFetch(request: Request) { + // Initialize Sentry if needed + await initializeSentry(); + // We are cloning the request to be able to read its body on errors const res = await fetch(request.clone()); - if (res.status >= 400) { + if (res.status >= 400 && Sentry) { const err = new Error(`HTTP ${res.status}`); Sentry.captureException(err, { From e6f2caf8cbccaf4cf466e0bc5aec91660ab3655f Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 8 Jul 2025 16:29:15 +0200 Subject: [PATCH 180/241] chore(main): release 2.0.3 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d370dbb..af5354e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.0.3](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.2...v2.0.3) (2025-07-08) + + +### Bug Fixes + +* disable sentry by default, make it optional ([#126](https://github.com/codesandbox/codesandbox-sdk/issues/126)) ([09d9e8a](https://github.com/codesandbox/codesandbox-sdk/commit/09d9e8a9a678c35169f8ff98b6e283fb45e23ed1)) + ## [2.0.2](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.1...v2.0.2) (2025-07-02) From 02116601a6f33aa56c465eff63f3d44e2220ccf4 Mon Sep 17 00:00:00 2001 From: Joji Augustine Date: Tue, 15 Jul 2025 18:01:20 +0200 Subject: [PATCH 181/241] fix: fix dependencies --- package-lock.json | 179 +++------------------------------------------- package.json | 12 ++-- 2 files changed, 14 insertions(+), 177 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e7894c..933d9f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,14 @@ "version": "2.0.3", "license": "MIT", "dependencies": { + "@codesandbox/pitcher-client": "1.1.7", + "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", + "@msgpack/msgpack": "^3.1.0", "@tanstack/react-query": "^5.76.1", + "blessed": "^0.1.81", + "blessed-contrib": "^4.11.0", + "cli-table3": "^0.6.3", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", @@ -25,18 +31,12 @@ "csb": "dist/bin/codesandbox.mjs" }, "devDependencies": { - "@codesandbox/pitcher-client": "1.1.7", - "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", - "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", - "blessed": "^0.1.81", - "blessed-contrib": "^4.11.0", "buffer-browserify": "^0.2.5", - "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", @@ -57,7 +57,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", @@ -112,7 +111,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", - "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.12.0" @@ -122,7 +120,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-1.1.3.tgz", "integrity": "sha512-GHrxd/tL4CHy/+42Qx0RC65EHUH6jjzuRm+JcBgAlRcMCJPvSTANpoNgI6fQKofvEJIVc4ghqR7X87j6emEZyg==", - "dev": true, "license": "MIT", "dependencies": { "@absinthe/socket": "^0.2.1", @@ -138,14 +135,12 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.15.tgz", "integrity": "sha512-ReR0LKl1C3tK+Wwe2zlAtXEjLavuR3+m+oFFNELdJmywEoh00iDYCWLxWJof83YSpqIBK/iegy0blUuktJ7+6A==", - "dev": true, "license": "ISC" }, "node_modules/@codesandbox/create-gql-api": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-1.0.1.tgz", "integrity": "sha512-k+C8OS2wYdakIoYEtHjW+pvwb7uq+emHQX5W1tqJQ9gDEYYy2dHItQNulY/8H0ymudhW3b6zcKTVNTMl9QgGWQ==", - "dev": true, "license": "ISC", "dependencies": { "get-graphql-schema": "^2.1.2", @@ -159,7 +154,6 @@ "version": "0.1.9", "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.9.tgz", "integrity": "sha512-h/SCSA5iasmEUlxWWQf2ecv1WgGoI5KLV5iZoN9YA1Hhz4DIacE3vwwD24Vn+1qczny4TEr8zwMQYZ1K8TbvzQ==", - "dev": true, "license": "SEE LICENSE IN ./LICENSE", "dependencies": { "outvariant": "^1.4.0", @@ -170,7 +164,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-1.1.7.tgz", "integrity": "sha512-D3KkU+nDUR6kXCsnsEFaYMjkWhsum7imD6aduG44cxYmZi1jlqnUb1FCoDW2Sj4+6LmhFOnpUdSE1nnkJ2eoFQ==", - "dev": true, "license": "GPL-3.0", "dependencies": { "@codesandbox/api": "^1.1.3", @@ -193,7 +186,6 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -206,7 +198,6 @@ "version": "0.360.2", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-common/-/pitcher-common-0.360.2.tgz", "integrity": "sha512-FWy4YgDh0LFZRo9N8j7mTSoKyIftWJVs/9LR5rwzAta0lFUAV2X8hsGMzt0u16Ng5Wpbi92svMZQy6WygC93gg==", - "dev": true, "license": "GPL-3.0", "dependencies": { "@emotion/hash": "^0.8.0", @@ -230,7 +221,6 @@ "version": "0.360.4", "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.4.tgz", "integrity": "sha512-oPxA2/F/ywyR73elJwdFOsw3D+rOId2UTNAXnRrTGjh66Ujyx/IFGVqfTlibGaQUg2HENkRoiRsvRJa5qphITA==", - "dev": true, "license": "GPL-3.0", "dependencies": { "@codesandbox/pitcher-common": "0.360.2", @@ -241,7 +231,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "dev": true, "license": "ISC", "engines": { "node": ">= 10" @@ -251,7 +240,6 @@ "version": "2.19.8", "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@codesandbox/nodebox": "0.1.8", @@ -266,7 +254,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", - "dev": true, "license": "SEE LICENSE IN ./LICENSE", "dependencies": { "outvariant": "^1.4.0", @@ -277,14 +264,12 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", - "dev": true, "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -295,7 +280,6 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true, "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { @@ -727,7 +711,6 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.7.3.tgz", "integrity": "sha512-nysIXMag9nr5ENy+47G0AYsegdT7vT6S4KLfY7NVgM6HsyZ0DrhCZvz5nP70M16x9i860SrnXhjpcuHx0g5sDQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/hey-api" @@ -872,7 +855,6 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", - "dev": true, "license": "MIT", "dependencies": { "babel-polyfill": "6.26.0", @@ -884,7 +866,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", - "dev": true, "license": "MIT", "dependencies": { "@jumpn/utils-array": "0.3.4", @@ -898,14 +879,12 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", - "dev": true, "license": "MIT" }, "node_modules/@jumpn/utils-graphql": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "7.2.0", @@ -918,7 +897,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", - "dev": true, "license": "MIT", "dependencies": { "iterall": "^1.2.2" @@ -931,7 +909,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.1.tgz", "integrity": "sha512-DnBpqkMOUGayNVKyTLlkM6ILmU/m/+VUxGkuQlPQVAcvreLz5jn1OlQnWd8uHKL/ZSiljpM12rjRhr51VtvJUQ==", - "dev": true, "license": "ISC", "engines": { "node": ">= 18" @@ -941,7 +918,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true, "license": "MIT" }, "node_modules/@opentelemetry/api": { @@ -1689,7 +1665,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", - "dev": true, "license": "MIT" }, "node_modules/@types/connect": { @@ -1706,7 +1681,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1720,7 +1694,6 @@ "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", - "dev": true, "license": "MIT", "dependencies": { "@types/braces": "*" @@ -1740,7 +1713,6 @@ "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1782,7 +1754,6 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", - "dev": true, "license": "MIT" }, "node_modules/@types/shimmer": { @@ -1806,7 +1777,6 @@ "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1833,7 +1803,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, "license": "ISC" }, "node_modules/acorn": { @@ -1863,7 +1832,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -1876,7 +1844,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -1886,7 +1853,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -1896,7 +1862,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/ansi-term/-/ansi-term-0.0.2.tgz", "integrity": "sha512-jLnGE+n8uAjksTJxiWZf/kcUmXq+cRWSl550B9NmQ8YiqaTM+lILcSe5dHdp8QkJPhaOghDjnMKwyYSMjosgAA==", - "dev": true, "license": "ISC", "dependencies": { "x256": ">=0.0.1" @@ -1906,7 +1871,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", - "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -1966,7 +1930,6 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", - "dev": true, "license": "MIT", "dependencies": { "babel-runtime": "^6.26.0", @@ -1978,14 +1941,12 @@ "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "dev": true, "license": "MIT" }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, "license": "MIT", "dependencies": { "core-js": "^2.4.0", @@ -1996,21 +1957,18 @@ "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true, "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2031,7 +1989,6 @@ "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", - "dev": true, "license": "MIT", "bin": { "blessed": "bin/tput.js" @@ -2044,7 +2001,6 @@ "version": "4.11.0", "resolved": "https://registry.npmjs.org/blessed-contrib/-/blessed-contrib-4.11.0.tgz", "integrity": "sha512-P00Xji3xPp53+FdU9f74WpvnOAn/SS0CKLy4vLAf5Ps7FGDOTY711ruJPZb3/7dpFuP+4i7f4a/ZTZdLlKG9WA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-term": ">=0.0.2", @@ -2067,7 +2023,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" @@ -2087,7 +2042,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2097,7 +2051,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2110,7 +2063,6 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/bresenham/-/bresenham-0.0.3.tgz", "integrity": "sha512-wbMxoJJM1p3+6G7xEFXYNCJ30h2qkwmVxebkbwIl4OcnWtno5R3UT9VuYLfStlVNAQCmRjkGwjPFdfaPd4iNXw==", - "dev": true, "license": "MIT" }, "node_modules/brorand": { @@ -2201,7 +2153,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "funding": [ { "type": "github", @@ -2254,7 +2205,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "dev": true, "engines": { "node": ">=0.2.0" } @@ -2339,7 +2289,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", - "dev": true, "license": "MIT", "dependencies": { "ansicolors": "~0.3.2", @@ -2353,7 +2302,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^2.2.1", @@ -2370,7 +2318,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" @@ -2383,7 +2330,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", - "dev": true, "license": "MIT/X11" }, "node_modules/chokidar": { @@ -2447,7 +2393,6 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.16.tgz", "integrity": "sha512-DhQHga7pUo3LRfZawNIw+4yHBx9AncBAjXv4YXjwOLf06ZnxCR/hi2hPqMhZr//NhwhLlq9EmmWXM0o7BZgnNw==", - "dev": true, "license": "ISC" }, "node_modules/cli-boxes": { @@ -2493,7 +2438,6 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, "license": "MIT", "dependencies": { "string-width": "^4.2.0" @@ -2721,7 +2665,6 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -2732,14 +2675,12 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/create-ecdh": { @@ -2793,7 +2734,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "dev": true, "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -2852,7 +2792,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2903,7 +2842,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2950,7 +2888,6 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2963,14 +2900,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/drawille-blessed-contrib/-/drawille-blessed-contrib-1.0.0.tgz", "integrity": "sha512-WnHMgf5en/hVOsFhxLI8ZX0qTJmerOsVjIMQmn4cR1eI8nLGu+L7w5ENbul+lZ6w827A3JakCuernES5xbHLzQ==", - "dev": true, "license": "MIT" }, "node_modules/drawille-canvas-blessed-contrib": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/drawille-canvas-blessed-contrib/-/drawille-canvas-blessed-contrib-0.1.3.tgz", "integrity": "sha512-bdDvVJOxlrEoPLifGDPaxIzFh3cD7QH05ePoQ4fwnqfi08ZSxzEhOUpI5Z0/SQMlWgcCQOEtuw0zrwezacXglw==", - "dev": true, "license": "MIT", "dependencies": { "ansi-term": ">=0.0.2", @@ -3136,7 +3071,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -3146,7 +3080,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -3160,7 +3093,6 @@ "version": "0.9.8", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-0.9.8.tgz", "integrity": "sha512-o5h0Mp1bkoR6B0i7pTCAzRy+VzdsRWH997KQD4Psb0EOPoKEIiaRx/EsOdUl7p1Ktjw7aIWvweI/OY1R9XrlUg==", - "dev": true, "dependencies": { "optimist": "0.2" }, @@ -3172,7 +3104,6 @@ "version": "0.2.8", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.2.8.tgz", "integrity": "sha512-Wy7E3cQDpqsTIFyW7m22hSevyTLxw850ahYv7FWsw4G6MIKVTZ8NSA95KBrQ95a4SMsMr1UGUUnwEFKhVaSzIg==", - "dev": true, "license": "MIT/X11", "dependencies": { "wordwrap": ">=0.0.1 <0.1.0" @@ -3185,7 +3116,6 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3195,7 +3125,6 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, "license": "MIT" }, "node_modules/evp_bytestokey": { @@ -3213,7 +3142,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", - "dev": true, "license": "MIT" }, "node_modules/figures": { @@ -3235,7 +3163,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3248,7 +3175,6 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", - "dev": true, "license": "MIT" }, "node_modules/for-each": { @@ -3350,7 +3276,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", "integrity": "sha512-1z5Hw91VrE3GrpCZE6lE8Dy+jz4kXWesLS7rCSjwOxf5BOcIedAZeTUJRIeIzmmR+PA9CKOkPTYFRJbdgUtrxA==", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^2.4.1", @@ -3366,7 +3291,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -3379,7 +3303,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -3394,7 +3317,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -3404,7 +3326,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, "license": "MIT" }, "node_modules/get-graphql-schema/node_modules/graphql": { @@ -3412,7 +3333,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", - "dev": true, "license": "MIT", "dependencies": { "iterall": "^1.2.2" @@ -3425,7 +3345,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3435,7 +3354,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -3511,7 +3429,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-2.8.1.tgz", "integrity": "sha512-0YCjVpE3pS5XWlN3J4X7AiAx65+nqAI54LndtVFnQZB6G/FVLkZH8y8V6R3cIoOQR4pUdfwQGd1iwyoXHJ4Qfw==", - "dev": true, "license": "MIT" }, "node_modules/glob": { @@ -3554,7 +3471,6 @@ "version": "16.11.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", - "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -3586,7 +3502,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" @@ -3599,7 +3514,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3685,7 +3599,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/here/-/here-0.0.2.tgz", "integrity": "sha512-U7VYImCTcPoY27TSmzoiFsmWLEqQFaYNdpsPb9K0dXJhE6kufUqycaz51oR09CW85dDU9iWyy7At8M+p7hb3NQ==", - "dev": true, "license": "MIT" }, "node_modules/hmac-drbg": { @@ -3704,14 +3617,12 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", - "dev": true, "license": "MIT" }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -3742,7 +3653,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, "license": "MIT" }, "node_modules/import-in-the-middle": { @@ -4069,7 +3979,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4124,7 +4033,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -4159,7 +4067,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "dev": true, "license": "MIT" }, "node_modules/jackspeak": { @@ -4198,7 +4105,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", "integrity": "sha512-7CsDLrYQMbLxDt2zl9uKaPZSdmJMvGGQ7wo9hoB3J+z/VcO2w63bXFgHVnjF1+S9wD3zAu8FBVj7EYWjTQ3Z7g==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4218,7 +4124,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", @@ -4231,7 +4136,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, "license": "MIT", "dependencies": { "immediate": "~3.0.5" @@ -4241,7 +4145,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/log-symbols": { @@ -4300,7 +4203,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -4317,7 +4219,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", "integrity": "sha512-f7M3sOuL9+up0NCOZbb1rQpWDLZwR/ftCiNbyscjl9LUUEwrRaoumH4sz6swgs58lF21DQ0hsYOCw5C6Zz7hbg==", - "dev": true, "license": "ISC", "dependencies": { "drawille-canvas-blessed-contrib": ">=0.0.1", @@ -4328,7 +4229,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -4341,7 +4241,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-escapes": "^6.2.0", @@ -4362,7 +4261,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -4396,7 +4294,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.3.tgz", "integrity": "sha512-qVQ/CjkMyMInPaaRMrwWNDvf6boRZXaT/DbQeMYcCWuXPEBf1v8qChOc9OlEVQp2uOvRXa1Qu30fLmKhY6NipA==", - "dev": true, "license": "MIT", "dependencies": { "readable-stream": "~1.0.2" @@ -4406,14 +4303,12 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true, "license": "MIT" }, "node_modules/memory-streams/node_modules/readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -4426,14 +4321,12 @@ "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true, "license": "MIT" }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, "engines": { "node": ">= 0.10.0" } @@ -4442,7 +4335,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4477,7 +4369,6 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4522,7 +4413,6 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4538,7 +4428,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4625,7 +4514,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, "license": "MIT" }, "node_modules/neo-async": { @@ -4639,7 +4527,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.21" @@ -4649,7 +4536,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -4677,7 +4563,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", - "dev": true, "license": "MIT", "dependencies": { "abbrev": "1" @@ -4740,7 +4625,6 @@ "version": "1.112.0", "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.112.0.tgz", "integrity": "sha512-qQH4jZSdabcKpwcqvJTi7eQL86UucvMacbaHiiIrOynT8jhTLtKS2ixaXgGlNBMeN9UhFi1wS00Hnxhw9aYLsA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 14.17.0" @@ -4750,7 +4634,6 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==", - "dev": true, "license": "MIT/X11", "dependencies": { "wordwrap": "~0.0.2" @@ -4760,7 +4643,6 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4835,14 +4717,12 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true, "license": "MIT" }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -4852,7 +4732,6 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", @@ -4869,7 +4748,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, "license": "MIT", "dependencies": { "p-finally": "^1.0.0" @@ -4889,7 +4767,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parse-asn1": { @@ -5054,14 +4931,12 @@ "version": "1.7.21", "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.21.tgz", "integrity": "sha512-8wOvJ8pQXRxNbyFlMI+wQhK3bvX4Ps3FtUX2+0cV6lkcebe9VTIl+xS60FLAeaPieFg80djor5z/AKofnwqAUw==", - "dev": true, "license": "MIT" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5074,7 +4949,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/picture-tuber/-/picture-tuber-1.0.2.tgz", "integrity": "sha512-49/xq+wzbwDeI32aPvwQJldM8pr7dKDRuR76IjztrkmiCkAQDaWFJzkmfVqCHmt/iFoPFhHmI9L0oKhthrTOQw==", - "dev": true, "license": "MIT", "dependencies": { "buffers": "~0.1.1", @@ -5113,8 +4987,7 @@ "node_modules/png-js": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", - "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==", - "dev": true + "integrity": "sha512-NTtk2SyfjBm+xYl2/VZJBhFnTQ4kU5qWC7VC4/iGbrgiU4FuB4xC+74erxADYJIqZICOR1HCvRA7EBHkpjTg9g==" }, "node_modules/possible-typed-array-names": { "version": "1.1.0", @@ -5172,7 +5045,6 @@ "version": "10.26.6", "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -5208,7 +5080,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/public-encrypt": { @@ -5297,7 +5168,6 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -5313,7 +5183,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/readdirp": { @@ -5340,7 +5209,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", - "dev": true, "license": "MIT", "dependencies": { "esprima": "~4.0.0" @@ -5350,7 +5218,6 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", - "dev": true, "license": "MIT" }, "node_modules/require-directory": { @@ -5418,7 +5285,6 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -5497,7 +5363,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, "license": "ISC" }, "node_modules/scheduler": { @@ -5540,7 +5405,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, "license": "MIT" }, "node_modules/sha.js": { @@ -5663,7 +5527,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", "integrity": "sha512-t//aVOiWt9fi/e22ea1vXVWBDX+gp18y+Ch9sKqmHl828bRfvP2VtfTJVEcgWFBQHd0yDPNQRiHdqzCvbcYSDA==", - "dev": true, "dependencies": { "here": "0.0.2", "nopt": "~2.1.2" @@ -5707,7 +5570,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@open-draft/deferred-promise": "^2.1.0", @@ -5732,14 +5594,12 @@ "version": "0.4.6", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", - "dev": true, "license": "MIT" }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -5749,7 +5609,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/string-width": { @@ -5881,7 +5740,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5894,7 +5752,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -5904,7 +5761,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0", @@ -5918,7 +5774,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -5971,14 +5826,12 @@ "node_modules/term-canvas": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", - "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==", - "dev": true + "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==" }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, "license": "MIT" }, "node_modules/tinyexec": { @@ -5992,7 +5845,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -6005,14 +5857,12 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "dev": true, "license": "MIT" }, "node_modules/tslib": { @@ -6026,7 +5876,6 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" @@ -6074,14 +5923,12 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/universal-cookie": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "dev": true, "license": "MIT", "dependencies": { "@types/cookie": "^0.3.3", @@ -6105,14 +5952,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -6126,14 +5971,12 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/vscode-diff/-/vscode-diff-2.1.1.tgz", "integrity": "sha512-S2BwZbrQCk4N6FqgYQQPlQ44OZKZNcS2VwhMj4xU8QvixXN9GeNQnN7/7XHFbwrs6h5BiLADDcERTrKvfWeG9g==", - "dev": true, "license": "MIT" }, "node_modules/vscode-jsonrpc": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -6143,14 +5986,12 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -6395,7 +6236,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/x256/-/x256-0.0.2.tgz", "integrity": "sha512-ZsIH+sheoF8YG9YG+QKEEIdtqpHRA9FYuD7MqhfyB1kayXU43RUNBFSxBEnF8ywSUxdg+8no4+bPr5qLbyxKgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -6405,7 +6245,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -6419,7 +6258,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4.0" @@ -6488,7 +6326,6 @@ "version": "0.8.11", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", - "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index ee20dcc..8d62c46 100644 --- a/package.json +++ b/package.json @@ -68,18 +68,12 @@ "README.md" ], "devDependencies": { - "@codesandbox/pitcher-client": "1.1.7", - "@hey-api/client-fetch": "^0.7.3", "@hey-api/openapi-ts": "^0.63.2", - "@msgpack/msgpack": "^3.1.0", "@types/blessed": "^0.1.25", "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", - "blessed": "^0.1.81", - "blessed-contrib": "^4.11.0", "buffer-browserify": "^0.2.5", - "cli-table3": "^0.6.3", "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", @@ -93,8 +87,14 @@ "why-is-node-running": "^2.3.0" }, "dependencies": { + "@codesandbox/pitcher-client": "1.1.7", "@inkjs/ui": "^2.0.0", + "@hey-api/client-fetch": "^0.7.3", + "@msgpack/msgpack": "^3.1.0", "@tanstack/react-query": "^5.76.1", + "blessed": "^0.1.81", + "blessed-contrib": "^4.11.0", + "cli-table3": "^0.6.3", "ink": "^5.2.1", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", From a5e3e890eb585c6be2435c99366763ed6e15c8cb Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 16 Jul 2025 15:35:55 +0200 Subject: [PATCH 182/241] chore(main): release 2.0.4 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af5354e..f1f54e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [2.0.4](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.3...v2.0.4) (2025-07-16) + + +### Bug Fixes + +* fix dependencies ([0211660](https://github.com/codesandbox/codesandbox-sdk/commit/02116601a6f33aa56c465eff63f3d44e2220ccf4)) +* move some devdependencies to dependencies ([77f74f1](https://github.com/codesandbox/codesandbox-sdk/commit/77f74f1c81f35c9f9ccd3d159b51857df0b3fc81)) + ## [2.0.3](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.2...v2.0.3) (2025-07-08) diff --git a/package-lock.json b/package-lock.json index 933d9f1..e61931d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.3", + "version": "2.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.3", + "version": "2.0.4", "license": "MIT", "dependencies": { "@codesandbox/pitcher-client": "1.1.7", diff --git a/package.json b/package.json index 8d62c46..5bd80f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.3", + "version": "2.0.4", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From fd7364de7dd0f665f1472f9908cba018ca2945dc Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 28 Jul 2025 07:52:14 +0200 Subject: [PATCH 183/241] Fix timeout errors on keepActiveWhileConnected Currently users can see errors on the "keepActiveWhileConnected" as we never catch those errors. --- src/SandboxClient/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 379fc6e..9576d77 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -271,7 +271,9 @@ export class SandboxClient { if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { - this.agentClient.system.update(); + this.agentClient.system.update().catch(() => { + // We do not care about errors here + }) }, 1000 * 30); } } else { From 0beb619fe9258ec4096fdf8c277d100798193493 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 29 Jul 2025 09:37:56 +0200 Subject: [PATCH 184/241] chore(main): release 2.0.5 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f54e3..9c90428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.0.5](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.4...v2.0.5) (2025-07-29) + + +### Bug Fixes + +* timeout errors on keepActiveWhileConnected ([a519bcf](https://github.com/codesandbox/codesandbox-sdk/commit/a519bcfe86abe2f978718169490a54d9977a9d88)) + ## [2.0.4](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.3...v2.0.4) (2025-07-16) diff --git a/package-lock.json b/package-lock.json index e61931d..5b29390 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.4", + "version": "2.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.4", + "version": "2.0.5", "license": "MIT", "dependencies": { "@codesandbox/pitcher-client": "1.1.7", diff --git a/package.json b/package.json index 5bd80f7..86303eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.4", + "version": "2.0.5", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From be1735fdd6501daa999f9b948a193dff5f370053 Mon Sep 17 00:00:00 2001 From: Fernando Tapia Rico Date: Wed, 6 Aug 2025 08:20:12 +0200 Subject: [PATCH 185/241] Fix field name in SandboxSession type (#133) --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index fd2e207..ef40d82 100644 --- a/src/types.ts +++ b/src/types.ts @@ -191,7 +191,7 @@ export interface SessionCreateOptions { export type SandboxSessionDTO = { sandboxId: string; pitcherToken: string; - pitcherUrl: string; + pitcherURL: string; userWorkspacePath: string; }; From 60335a9638e08bb08b3b75e03ba3ea7f24ce3a7c Mon Sep 17 00:00:00 2001 From: Tristan Date: Wed, 6 Aug 2025 08:20:50 +0200 Subject: [PATCH 186/241] chore: remove reference to TOGETHER_API_KEY (#132) --- src/utils/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a8aafed..e7f2caf 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -25,6 +25,6 @@ export function getInferredApiKey() { typeof process !== "undefined" ? process.env?.CSB_API_KEY || process.env?.TOGETHER_API_KEY : undefined, - "CSB_API_KEY or TOGETHER_API_KEY is not set" + "CSB_API_KEY is not set" ); } From 62da4fef50a3497b84c71413b1c0e3337c73e59f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 6 Aug 2025 09:19:38 +0200 Subject: [PATCH 187/241] fix: Update to latest Ink and React 19 and bundle React and Ink into CLI (#138) * Update to latest Ink and React 19 * bundle into CLI --- build/fakeReactDevtoolsCore.js | 1 + build/utils.cjs | 2 +- esbuild.cjs | 38 ++++++++++++++++++++++------- package-lock.json | 44 ++++++++++++++++++---------------- package.json | 10 ++++---- 5 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 build/fakeReactDevtoolsCore.js diff --git a/build/fakeReactDevtoolsCore.js b/build/fakeReactDevtoolsCore.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/build/fakeReactDevtoolsCore.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/build/utils.cjs b/build/utils.cjs index 4edc191..1231e66 100644 --- a/build/utils.cjs +++ b/build/utils.cjs @@ -1,4 +1,4 @@ -module.exports.nodeExternals = [ +module.exports.externalModules = [ ...Object.keys(require("../package.json").dependencies), ...require("module").builtinModules, ...require("module").builtinModules.map((mod) => `node:${mod}`), diff --git a/esbuild.cjs b/esbuild.cjs index 84e2f8c..55aee88 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -1,6 +1,6 @@ -const fs = require("fs"); +const { join } = require("path"); const esbuild = require("esbuild"); -const { nodeExternals, define } = require("./build/utils.cjs"); +const { externalModules, define } = require("./build/utils.cjs"); const { moduleReplacementPlugin, forbidImportsPlugin, @@ -13,6 +13,17 @@ const preventPitcherClientImportsPlugin = forbidImportsPlugin([ "@codesandbox/pitcher-common", ]); +const devtoolsStubPlugin = { + name: "stub-react-devtools", + setup(build) { + // whenever someone does `import 'react-devtools-core'`, redirect to our empty.js + build.onResolve({ filter: /^react-devtools-core$/ }, () => ({ + path: join(__dirname, "build/fakeReactDevtoolsCore.js"), + namespace: "file", + })); + }, +}; + /** * BROWSER CLIENT BUILD */ @@ -74,7 +85,7 @@ const nodeClientCjsBuild = esbuild.build({ // .cjs extension is required because "type": "module" is set in package.json outfile: "dist/cjs/node.cjs", platform: "node", - external: nodeExternals, + external: externalModules, plugins: [preventPitcherClientImportsPlugin], }); @@ -84,7 +95,7 @@ const nodeClientEsmBuild = esbuild.build({ format: "esm", outfile: "dist/esm/node.js", platform: "node", - external: nodeExternals, + external: externalModules, plugins: [preventPitcherClientImportsPlugin], }); @@ -99,7 +110,7 @@ const sdkCjsBuild = esbuild.build({ platform: "node", // .cjs extension is required because "type": "module" is set in package.json outfile: "dist/cjs/index.cjs", - external: nodeExternals, + external: externalModules, }); const sdkEsmBuild = esbuild.build({ @@ -109,7 +120,7 @@ const sdkEsmBuild = esbuild.build({ define, platform: "node", outfile: "dist/esm/index.js", - external: nodeExternals, + external: externalModules, plugins: [preventPitcherClientImportsPlugin], }); @@ -124,10 +135,19 @@ const cliBuild = esbuild.build({ format: "esm", platform: "node", banner: { - js: `#!/usr/bin/env node\n\n`, + js: `#!/usr/bin/env node\n\nimport { createRequire } from "module";\nconst require = createRequire(import.meta.url);\n`, }, - external: [...nodeExternals, "@codesandbox/sdk"], - plugins: [preventPitcherClientImportsPlugin], + external: [ + // We have to bundle React and Ink into the bundle because Ink supports React 18 in v5 and React 19 in v6, + // but this breaks when running the CLI in the project folder as it might have either React version and we do not + // want users to manually install React and correct Ink version as peer dependencies + ...externalModules.filter( + (mod) => + mod !== "react" && mod !== "ink" && mod !== "@tanstack/react-query" + ), + "@codesandbox/sdk", + ], + plugins: [preventPitcherClientImportsPlugin, devtoolsStubPlugin], }); Promise.all([ diff --git a/package-lock.json b/package-lock.json index 5b29390..b449d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,12 +17,12 @@ "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "cli-table3": "^0.6.3", - "ink": "^5.2.1", + "ink": "^6.1.0", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", - "react": "^18.3.1", + "react": "^19.1.1", "readline": "^1.3.0", "util": "^0.12.5", "yargs": "^17.7.2" @@ -3687,9 +3687,9 @@ "license": "ISC" }, "node_modules/ink": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", - "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.1.0.tgz", + "integrity": "sha512-YQ+lbMD79y3FBAJXXZnuRajLEgaMFp102361eY5NrBIEVCi9oFo7gNZU4z2LBWlcjZFiTt7jetlkIbKCCH4KJA==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", @@ -3705,7 +3705,7 @@ "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", - "react-reconciler": "^0.29.0", + "react-reconciler": "^0.32.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", @@ -3718,11 +3718,11 @@ "yoga-layout": "~3.2.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { - "@types/react": ">=18.0.0", - "react": ">=18.0.0", + "@types/react": ">=19.0.0", + "react": ">=19.0.0", "react-devtools-core": "^4.19.1" }, "peerDependenciesMeta": { @@ -5137,33 +5137,35 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-reconciler": { - "version": "0.29.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", - "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.32.0.tgz", + "integrity": "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.26.0" }, "engines": { "node": ">=0.10.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.1.0" } }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", diff --git a/package.json b/package.json index 86303eb..6ff4cb6 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,9 @@ "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", + "ink": "^6.1.0", + "react": "^19.1.1", + "@tanstack/react-query": "^5.76.1", "buffer-browserify": "^0.2.5", "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", @@ -88,19 +91,16 @@ }, "dependencies": { "@codesandbox/pitcher-client": "1.1.7", - "@inkjs/ui": "^2.0.0", "@hey-api/client-fetch": "^0.7.3", + "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", - "@tanstack/react-query": "^5.76.1", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "cli-table3": "^0.6.3", - "ink": "^5.2.1", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", - "react": "^18.3.1", "readline": "^1.3.0", "util": "^0.12.5", "yargs": "^17.7.2" @@ -108,4 +108,4 @@ "optionalDependencies": { "@sentry/node": "^9.29.0" } -} \ No newline at end of file +} From 71b38b4b12a9438864296fb599d20760c6b0a728 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 6 Aug 2025 09:26:09 +0200 Subject: [PATCH 188/241] fix: Fix broken authorization in preview hosts --- src/bin/commands/previewHosts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 123732b..62753ed 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -5,7 +5,7 @@ import { createApiClient, handleResponse } from "../../utils/api"; import { getInferredApiKey } from "../../utils/constants"; const apiKey = getInferredApiKey(); -const apiClient = createApiClient("CLI", apiKey); +const apiClient = createApiClient(apiKey); export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", From 957c25735990657ba5bfc4a965fe843f24b95d24 Mon Sep 17 00:00:00 2001 From: Tristan Date: Wed, 6 Aug 2025 09:29:24 +0200 Subject: [PATCH 189/241] chore: automatic CLI rebuild on file change (#125) * chore: automatic CLI rebuild on file change * feat(cli): Show running VMs in CLI dashboard (#129) * organize code and add view context to manage views * better types for views * add list of running VMs * Install chalk and date-fns packages * Update CLI tool look and feel a bit * Create custom components and show vms on dashboard --- package-lock.json | 458 ++++++++++++++++++++++------ package.json | 4 + scripts/dev-cli.cjs | 102 +++++++ src/bin/main.tsx | 9 +- src/bin/ui/App.tsx | 21 ++ src/bin/ui/Dashboard.tsx | 260 ---------------- src/bin/ui/components/Table.tsx | 108 +++++++ src/bin/ui/components/TextInput.tsx | 130 ++++++++ src/bin/ui/components/VmTable.tsx | 123 ++++++++ src/bin/ui/hooks/useTerminalSize.ts | 16 + src/bin/ui/hooks/useVmInput.ts | 73 +++++ src/bin/ui/viewContext.tsx | 35 +++ src/bin/ui/views/Dashboard.tsx | 92 ++++++ src/bin/ui/views/Sandbox.tsx | 192 ++++++++++++ 14 files changed, 1275 insertions(+), 348 deletions(-) create mode 100755 scripts/dev-cli.cjs create mode 100644 src/bin/ui/App.tsx delete mode 100644 src/bin/ui/Dashboard.tsx create mode 100644 src/bin/ui/components/Table.tsx create mode 100644 src/bin/ui/components/TextInput.tsx create mode 100644 src/bin/ui/components/VmTable.tsx create mode 100644 src/bin/ui/hooks/useTerminalSize.ts create mode 100644 src/bin/ui/hooks/useVmInput.ts create mode 100644 src/bin/ui/viewContext.tsx create mode 100644 src/bin/ui/views/Dashboard.tsx create mode 100644 src/bin/ui/views/Sandbox.tsx diff --git a/package-lock.json b/package-lock.json index b449d11..47b80d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,16 +13,15 @@ "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", - "@tanstack/react-query": "^5.76.1", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", + "chalk": "^5.4.1", "cli-table3": "^0.6.3", - "ink": "^6.1.0", + "date-fns": "^4.1.0", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", - "react": "^19.1.1", "readline": "^1.3.0", "util": "^0.12.5", "yargs": "^17.7.2" @@ -32,6 +31,8 @@ }, "devDependencies": { "@hey-api/openapi-ts": "^0.63.2", + "@parcel/watcher": "^2.5.1", + "@tanstack/react-query": "^5.76.1", "@types/blessed": "^0.1.25", "@types/node": "^22.15.30", "@types/react": "^19.1.5", @@ -40,9 +41,11 @@ "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", + "ink": "^6.1.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", + "react": "^19.1.1", "rimraf": "^6.0.1", "semver": "^6.3.0", "tslib": "^2.1.0", @@ -777,18 +780,6 @@ "ink": ">=5" } }, - "node_modules/@inkjs/ui/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@inkjs/ui/node_modules/cli-spinners": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.2.0.tgz", @@ -1519,6 +1510,301 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@prisma/instrumentation": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.8.2.tgz", @@ -1629,6 +1915,7 @@ "version": "5.76.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.76.0.tgz", "integrity": "sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==", + "dev": true, "license": "MIT", "funding": { "type": "github", @@ -1639,6 +1926,7 @@ "version": "5.76.1", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.76.1.tgz", "integrity": "sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==", + "dev": true, "license": "MIT", "dependencies": { "@tanstack/query-core": "5.76.0" @@ -2019,6 +2307,22 @@ "x256": ">=0.0.1" } }, + "node_modules/blessed-contrib/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/blessed-contrib/node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -2299,31 +2603,15 @@ } }, "node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chalk/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/charm": { @@ -2788,6 +3076,15 @@ "devOptional": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2865,6 +3162,18 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3761,18 +4070,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ink/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ink/node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -3921,6 +4218,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3948,6 +4254,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-in-ci": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", @@ -4163,18 +4481,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/log-symbols/node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -4257,18 +4563,6 @@ "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4523,6 +4817,12 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -4671,18 +4971,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ora/node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", diff --git a/package.json b/package.json index 6ff4cb6..55c6b28 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "types": "./dist/esm/index.d.ts", "scripts": { + "dev:cli": "node scripts/dev-cli.cjs", "build": "npm run clean && npm run build:esbuild && npm run build:cjs:types && npm run build:esm:types && chmod +x dist/bin/codesandbox.mjs", "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", @@ -69,6 +70,7 @@ ], "devDependencies": { "@hey-api/openapi-ts": "^0.63.2", + "@parcel/watcher": "^2.5.1", "@types/blessed": "^0.1.25", "@types/node": "^22.15.30", "@types/react": "^19.1.5", @@ -97,6 +99,8 @@ "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "cli-table3": "^0.6.3", + "chalk": "^5.4.1", + "date-fns": "^4.1.0", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", diff --git a/scripts/dev-cli.cjs b/scripts/dev-cli.cjs new file mode 100755 index 0000000..ff946fd --- /dev/null +++ b/scripts/dev-cli.cjs @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +const watcher = require("@parcel/watcher"); +const { execSync } = require("child_process"); +const path = require("path"); + +console.log("🚀 Starting CLI development mode..."); +console.log(""); + +const buildCliOnly = () => { + console.log("🔨 Building CLI..."); + console.log(""); + + const startTime = Date.now(); + + try { + execSync("node esbuild.cjs", { stdio: "inherit" }); + execSync("chmod +x dist/bin/codesandbox.mjs", { stdio: "inherit" }); + + const duration = Date.now() - startTime; + + console.log(`✅ CLI build completed in ${duration}ms`); + console.log(""); + + return true; + } catch (error) { + console.error("❌ CLI build failed:", error.message); + console.log(""); + + return false; + } +}; + +console.log("🔨 Initial build..."); +console.log(""); + +try { + execSync("npm run build", { stdio: "inherit" }); + + console.log("✅ Initial build completed, watching for changes..."); + console.log(""); +} catch (error) { + console.error("❌ Initial build failed:", error.message); + process.exit(1); +} + +const startWatching = async () => { + const watchPath = path.join(__dirname, "../src/bin"); + + try { + let subscription = await watcher.subscribe(watchPath, (err, events) => { + if (err) { + console.error("❌ Watcher error:", err); + return; + } + + // Filter out temporary files and directories + const relevantEvents = events.filter((event) => { + const filename = path.basename(event.path); + const validExtensions = [".ts", ".tsx", ".js", ".jsx"]; + + return ( + !filename.startsWith(".") && + !filename.includes("node_modules") && + validExtensions.some(ext => filename.endsWith(ext)) + ); + }); + + if (relevantEvents.length === 0) { + return; + } + + console.log(`📝 File${relevantEvents.length > 1 ? "s" : ""} changed:`); + + relevantEvents.forEach((event) => { + const relativePath = path.relative(process.cwd(), event.path); + console.log(` ${event.type}: ${relativePath}`); + }); + + console.log(""); + + const shouldRebuild = buildCliOnly(); + + if (!shouldRebuild) { + console.log("🔄 Watching for changes..."); + console.log(""); + } + }); + + // Handle graceful shutdown + process.on("SIGINT", async () => { + console.log("\n🛑 Stopping watcher..."); + await subscription.unsubscribe(); + process.exit(0); + }); + } catch (error) { + console.error("❌ Failed to start watcher:", error); + process.exit(1); + } +}; + +startWatching(); diff --git a/src/bin/main.tsx b/src/bin/main.tsx index 0595e3c..944de98 100644 --- a/src/bin/main.tsx +++ b/src/bin/main.tsx @@ -6,13 +6,14 @@ import { buildCommand } from "./commands/build"; import { sandboxesCommand } from "./commands/sandbox"; import { previewHostsCommand } from "./commands/previewHosts"; import { hostTokensCommand } from "./commands/hostTokens"; -import { Dashboard } from "./ui/Dashboard"; +import { App } from "./ui/App"; import React from "react"; import { SDKProvider } from "./ui/sdkContext"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ViewProvider } from "./ui/viewContext"; if (process.argv.length === 2) { - // Clear the screen before rendering the dashboard + // Clear the screen before rendering the App process.stdout.write("\x1Bc"); const queryClient = new QueryClient(); @@ -20,7 +21,9 @@ if (process.argv.length === 2) { render( - + + + , { diff --git a/src/bin/ui/App.tsx b/src/bin/ui/App.tsx new file mode 100644 index 0000000..fb0a43e --- /dev/null +++ b/src/bin/ui/App.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Box, Text } from "ink"; +import { Dashboard } from "./views/Dashboard"; +import { useView } from "./viewContext"; +import { Sandbox } from "./views/Sandbox"; +import { useTerminalSize } from "./hooks/useTerminalSize"; + +export function App() { + const [stdoutWidth, stdoutHeight] = useTerminalSize(); + const { view } = useView(); + + return ( + + + □ CodeSandbox SDK + + {view.name === "dashboard" && } + {view.name === "sandbox" && } + + ); +} diff --git a/src/bin/ui/Dashboard.tsx b/src/bin/ui/Dashboard.tsx deleted file mode 100644 index c8f5ad8..0000000 --- a/src/bin/ui/Dashboard.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import React, { memo, useEffect, useRef, useState } from "react"; -import { Box, Text, useInput, useStdout } from "ink"; -import { useSDK } from "./sdkContext"; -import { useQuery } from "@tanstack/react-query"; -import { getSandbox, getRunningVms } from "./api"; - -// Custom hook to get terminal size -function useTerminalSize() { - const { stdout } = useStdout(); - const [size, setSize] = useState([stdout?.columns || 80, stdout?.rows || 24]); - useEffect(() => { - if (!stdout) return undefined; - const handler = () => setSize([stdout.columns, stdout.rows]); - stdout.on("resize", handler); - return () => { - stdout.off("resize", handler); - }; - }, [stdout]); - return size; -} - -// Component to open a sandbox by ID -export function Dashboard() { - const { apiClient } = useSDK(); - - // Poll getRunningVms API every 2 seconds - const runningVmsQuery = useQuery({ - queryKey: ["runningVms"], - queryFn: () => getRunningVms(apiClient), - }); - - const [sandboxId, setSandboxId] = useState(""); - const [showSandbox, setShowSandbox] = useState(false); - const [isFocused, setIsFocused] = useState(true); - const [stdoutWidth, stdoutHeight] = useTerminalSize(); - - useEffect(() => { - // have to manually do this because of environment - const interval = setInterval(() => { - runningVmsQuery.refetch(); - }, 2000); - - return () => { - clearInterval(interval); - }; - }, []); - - useInput((input, key) => { - if (!showSandbox) { - if (key.return) { - if (sandboxId.trim()) { - setShowSandbox(true); - } - } else if (key.backspace || key.delete) { - setSandboxId((prev) => prev.slice(0, -1)); - } else if (input && !key.ctrl && !key.meta && !key.shift) { - // Only add printable characters - setSandboxId((prev) => prev + input); - } - } - }); - - if (showSandbox) { - const state = runningVmsQuery.isLoading - ? "PENDING" - : runningVmsQuery.data?.vms.find((vm) => vm.id === sandboxId) - ? "RUNNING" - : "IDLE"; - - return ( - setShowSandbox(false)} - /> - ); - } - - return ( - - - CodeSandbox - - - Enter Sandbox ID: - {sandboxId || "_"} - - - Type to input ID, press ENTER to open - - - ); -} - -// Component to display a sandbox -const Sandbox = memo( - ({ - id, - runningState, - onBack, - }: { - id: string; - runningState: "RUNNING" | "IDLE" | "PENDING"; - onBack: () => void; - }) => { - const sandboxQuery = useQuery({ - queryKey: ["sandbox", id], - queryFn: () => getSandbox(apiClient, id), - }); - const runningStateRef = useRef(runningState); - - const { sdk, apiClient } = useSDK(); - - // Only two states: RUNNING or IDLE - const [sandboxState, setSandboxState] = useState< - "RUNNING" | "IDLE" | "PENDING" - >(runningState); - const [selectedOption, setSelectedOption] = useState(0); - const [stdoutWidth, stdoutHeight] = useTerminalSize(); - - // We only want to update the state when the - // running state has ACTUALLY changed (Reconciliation sucks) - useEffect(() => { - if ( - sandboxState !== "PENDING" && - runningStateRef.current !== runningState - ) { - runningStateRef.current = runningState; - setSandboxState(runningState); - } - }, [runningState, sandboxState]); - - // Define menu options based on state - const getMenuOptions = () => { - switch (sandboxState) { - case "RUNNING": - return ["Hibernate", "Shutdown", "Restart"]; - case "IDLE": - return ["Start"]; - default: - return []; - } - }; - - const menuOptions = getMenuOptions(); - - // Handle menu options - const handleAction = async (action: string) => { - switch (action) { - case "Hibernate": - case "Shutdown": - setSandboxState("PENDING"); - await sdk.sandboxes.shutdown(id); - setSandboxState("IDLE"); - setSelectedOption(0); - break; - case "Restart": - setSandboxState("PENDING"); - await sdk.sandboxes.restart(id); - setSandboxState("RUNNING"); - setSelectedOption(0); - break; - case "Start": - setSandboxState("PENDING"); - await sdk.sandboxes.resume(id); - setSandboxState("RUNNING"); - setSelectedOption(0); - break; - } - }; - - // Handle keyboard navigation - useInput((input, key) => { - if (key.escape) { - onBack(); - } else if (menuOptions.length > 0) { - if (key.upArrow) { - setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); - } else if (key.downArrow) { - setSelectedOption((prev) => - prev < menuOptions.length - 1 ? prev + 1 : prev - ); - } else if (key.return) { - handleAction(menuOptions[selectedOption]); - } - } - }); - - return ( - - {/* Handle query states */} - {sandboxQuery.isLoading && ( - - Loading sandbox information... - - )} - - {sandboxQuery.error && ( - - - Error loading sandbox: {(sandboxQuery.error as Error).message} - - - )} - - {sandboxQuery.data && ( - - - {sandboxQuery.data.title} - {id} - - - {sandboxQuery.data.description && ( - - {sandboxQuery.data.description} - - )} - - )} - - {/* Status display - moved above title and description */} - - Status: - - {sandboxState} - - - - {menuOptions.length > 0 && ( - - Actions: - {menuOptions.map((option, index) => ( - - - {selectedOption === index ? "> " : " "} - {option} - - - ))} - - )} - - - - {menuOptions.length > 0 - ? "Use arrow keys to navigate, Enter to select, ESC to go back" - : "Press ESC to go back"} - - - - ); - } -); diff --git a/src/bin/ui/components/Table.tsx b/src/bin/ui/components/Table.tsx new file mode 100644 index 0000000..0a0788f --- /dev/null +++ b/src/bin/ui/components/Table.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import { Box, Text } from "ink"; + +interface TableProps { + marginTop?: number; + renderHeader: () => React.ReactNode; + renderBody: (totalWidth: number) => React.ReactNode; +} + +export const Table = ({ renderHeader, renderBody }: TableProps) => { + // Calculate total width from TableHeader children + const calculateTotalWidth = () => { + let totalWidth = 0; + + const headerElement = renderHeader(); + + if ( + React.isValidElement(headerElement) && + headerElement.type === TableHeader + ) { + React.Children.forEach( + (headerElement.props as any).children, + (headerChild) => { + if ( + React.isValidElement(headerChild) && + headerChild.type === TableColumn + ) { + totalWidth += (headerChild.props as any).width || 0; + } + } + ); + } + + return totalWidth; + }; + + const totalWidth = calculateTotalWidth(); + + return ( + + {renderHeader()} + {renderBody(totalWidth)} + + ); +}; + +interface TableHeaderProps { + children: React.ReactNode; +} + +export const TableHeader = ({ children }: TableHeaderProps) => ( + {children} +); + +interface TableBodyProps { + children: React.ReactNode; + totalWidth: number; +} + +export const TableBody = ({ children, totalWidth }: TableBodyProps) => ( + + + {"─".repeat(Math.max(totalWidth, 50))} + + {children} + +); + +interface TableRowProps { + children: React.ReactNode; + isSelected?: boolean; +} + +export const TableRow = ({ children, isSelected = false }: TableRowProps) => ( + + {React.Children.map(children, (child) => { + if (React.isValidElement(child) && child.type === TableColumn) { + return React.cloneElement(child, { inverse: isSelected } as any); + } + return child; + })} + +); + +interface TableColumnProps { + children: React.ReactNode; + width?: number; + bold?: boolean; + inverse?: boolean; +} + +export const TableColumn = ({ + children, + width, + bold = false, + inverse = false, +}: TableColumnProps) => ( + + + {children} + + +); diff --git a/src/bin/ui/components/TextInput.tsx b/src/bin/ui/components/TextInput.tsx new file mode 100644 index 0000000..7da9777 --- /dev/null +++ b/src/bin/ui/components/TextInput.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import chalk from "chalk"; + +interface TextInputProps { + value: string; + onChange: (value: string) => void; + onSubmit?: () => void; + isFocused?: boolean; + showCursor?: boolean; +} + +// Helper function to check if a key is a regular printable character +const isRegularKey = (key: Key) => { + return ( + !key.ctrl && + !key.meta && + !key.shift && + !key.upArrow && + !key.downArrow && + !key.return && + !key.backspace && + !key.delete && + !key.tab && + !key.escape && + !key.leftArrow && + !key.rightArrow + ); +}; + +export const TextInput: React.FC = ({ + value, + onChange, + onSubmit, + isFocused = true, + showCursor = true, +}) => { + const [cursorPosition, setCursorPosition] = useState(value.length); + const isInternalChange = useRef(false); + + // Only update cursor position when value changes externally + useEffect(() => { + if (!isInternalChange.current) { + setCursorPosition(value.length); + } + isInternalChange.current = false; + }, [value]); + + useInput( + (input, key) => { + if (key.return && onSubmit) { + onSubmit(); + } else if (key.leftArrow) { + setCursorPosition(Math.max(0, cursorPosition - 1)); + } else if (key.rightArrow) { + setCursorPosition(Math.min(value.length, cursorPosition + 1)); + } else if (key.backspace || key.delete) { + // Both backspace and delete remove the character before the cursor + if (cursorPosition > 0) { + const newValue = + value.slice(0, cursorPosition - 1) + value.slice(cursorPosition); + const newCursorPosition = cursorPosition - 1; + isInternalChange.current = true; + + onChange(newValue); + setCursorPosition(newCursorPosition); + } + } else if (input && isRegularKey(key)) { + // Insert character(s) at cursor position + const newValue = + value.slice(0, cursorPosition) + input + value.slice(cursorPosition); + let newCursorPosition = cursorPosition + input.length; + + // Ensure cursor stays within bounds + if (newCursorPosition < 0) { + newCursorPosition = 0; + } + if (newCursorPosition > newValue.length) { + newCursorPosition = newValue.length; + } + + isInternalChange.current = true; + onChange(newValue); + setCursorPosition(newCursorPosition); + } + }, + { isActive: isFocused } + ); + + const renderText = () => { + const displayValue = value || ""; + + if (!showCursor || !isFocused) { + // When cursor is hidden, just show the text + return {displayValue}; + } + + let renderedValue = ""; + + if (displayValue.length === 0) { + // Show cursor when there's no text + renderedValue = chalk.inverse(" "); + } else { + // Show value with cursor + renderedValue = ""; + + for (let i = 0; i < displayValue.length; i++) { + const char = displayValue[i]; + if (i === cursorPosition) { + renderedValue += chalk.inverse(char); + } else { + renderedValue += char; + } + } + + // Add cursor at the end if position is at the end + if (cursorPosition === displayValue.length) { + renderedValue += chalk.inverse(" "); + } + } + + return {renderedValue}; + }; + + return ( + + {renderText()} + + ); +}; diff --git a/src/bin/ui/components/VmTable.tsx b/src/bin/ui/components/VmTable.tsx new file mode 100644 index 0000000..6ff3f4e --- /dev/null +++ b/src/bin/ui/components/VmTable.tsx @@ -0,0 +1,123 @@ +import React from "react"; +import { Box, Text } from "ink"; +import { Table, TableHeader, TableBody, TableRow, TableColumn } from "./Table"; +import { format, parseISO } from "date-fns"; + +const formatDate = (dateString: string | undefined): string => { + if (!dateString) return "N/A"; + + try { + const date = parseISO(dateString); + return format(date, "d MMMM yyyy 'at' HH:mm 'UTC'"); + } catch (error) { + return "Invalid date"; + } +}; + +const calculateRuntime = (startedAt: string | undefined, lastActiveAt: string | undefined): string => { + if (!startedAt || !lastActiveAt) { + return "N/A" + }; + + try { + const startDate = parseISO(startedAt); + const lastActiveDate = parseISO(lastActiveAt); + + // Calculate difference in milliseconds + const diffMs = lastActiveDate.getTime() - startDate.getTime(); + + if (diffMs < 0) return "N/A"; + + // Convert to seconds, minutes, hours + const totalSeconds = Math.floor(diffMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + // Format output + let result = ""; + + if (hours > 0) { + result += `${hours}h `; + } + + if (minutes > 0) { + result += `${minutes}m `; + } + + if (seconds > 0 || result === "") { + result += `${seconds}s`; + } + + return result.trim(); + } catch (error) { + return "N/A"; + } +}; + +interface VmData { + id?: string; + credit_basis?: string; + last_active_at?: string; + session_started_at?: string; + specs?: { + cpu?: number; + memory?: number; + storage?: number; + }; +} + +interface VmTableProps { + vms: VmData[]; + selectedIndex: number; + onSelect: (index: number, vmId: string) => void; +} + +export const VmTable = ({ vms, selectedIndex, onSelect }: VmTableProps) => { + const columnWidths = { + id: 20, + lastActive: 28, + startedAt: 28, + runtime: 14, + creditBasis: 20, + }; + + if (vms.length === 0) { + return ( + + Running VMs + No running VMs found. + + ); + } + + return ( + + Running VMs + ( + + VM ID + Last Active + Started At + Runtime + Credit Basis + + )} + renderBody={(totalWidth) => ( + + {vms.map((vm, index) => ( + + {vm.id || "N/A"} + {formatDate(vm.last_active_at)} + {formatDate(vm.session_started_at)} + {calculateRuntime(vm.session_started_at, vm.last_active_at)} + {vm.credit_basis || "N/A"} credits / hour + + ))} + + )} + /> + + ); +}; \ No newline at end of file diff --git a/src/bin/ui/hooks/useTerminalSize.ts b/src/bin/ui/hooks/useTerminalSize.ts new file mode 100644 index 0000000..27e9ba3 --- /dev/null +++ b/src/bin/ui/hooks/useTerminalSize.ts @@ -0,0 +1,16 @@ +import { useStdout } from "ink"; +import { useEffect, useState } from "react"; + +export function useTerminalSize() { + const { stdout } = useStdout(); + const [size, setSize] = useState([stdout?.columns || 80, stdout?.rows || 24]); + useEffect(() => { + if (!stdout) return undefined; + const handler = () => setSize([stdout.columns, stdout.rows]); + stdout.on("resize", handler); + return () => { + stdout.off("resize", handler); + }; + }, [stdout]); + return size; +} diff --git a/src/bin/ui/hooks/useVmInput.ts b/src/bin/ui/hooks/useVmInput.ts new file mode 100644 index 0000000..0a24981 --- /dev/null +++ b/src/bin/ui/hooks/useVmInput.ts @@ -0,0 +1,73 @@ +import { useState } from "react"; +import { useInput } from "ink"; + +interface VmData { + id?: string; + [key: string]: any; +} + +interface UseVmInputOptions { + vms?: VmData[]; + onSubmit: (id: string) => void; +} + +export const useVmInput = ({ vms, onSubmit }: UseVmInputOptions) => { + const [sandboxId, setSandboxId] = useState(""); + const [selectedVm, setSelectedVm] = useState(null); + const [selectedVmIndex, setSelectedVmIndex] = useState(-1); + + const handleInputChange = (value: string) => { + setSandboxId(value); + + // Clear VM selection when user types manually + if (selectedVm) { + setSelectedVm(null); + setSelectedVmIndex(-1); + } + }; + + const handleInputSubmit = () => { + if (selectedVm) { + onSubmit(selectedVm); + } else if (sandboxId.trim()) { + onSubmit(sandboxId); + } + }; + + const handleVmSelect = (index: number, vmId: string) => { + setSelectedVmIndex(index); + setSelectedVm(vmId); + }; + + useInput((_input, key) => { + if (key.upArrow || key.downArrow) { + if (vms && vms.length > 0) { + let newIndex = selectedVmIndex; + + if (key.upArrow) { + newIndex = selectedVmIndex <= 0 ? vms.length - 1 : selectedVmIndex - 1; + } else if (key.downArrow) { + newIndex = selectedVmIndex >= vms.length - 1 ? 0 : selectedVmIndex + 1; + } + + setSelectedVmIndex(newIndex); + const vmId = vms[newIndex]?.id || null; + setSelectedVm(vmId); + + // Set the selected VM ID in the text input + if (vmId) { + setSandboxId(vmId); + } + } + } + }); + + return { + sandboxId, + selectedVm, + selectedVmIndex, + handleInputChange, + handleInputSubmit, + handleVmSelect, + }; +}; \ No newline at end of file diff --git a/src/bin/ui/viewContext.tsx b/src/bin/ui/viewContext.tsx new file mode 100644 index 0000000..7283766 --- /dev/null +++ b/src/bin/ui/viewContext.tsx @@ -0,0 +1,35 @@ +import React, { createContext, useContext, useState } from "react"; + +type ViewState = + | { name: "dashboard" } + | { name: "sandbox"; params: { id: string } }; + +export const ViewContext = createContext<{ + view: ViewState; + setView: (view: ViewState) => void; +}>({ + view: { name: "dashboard" }, + setView: () => {}, +}); + +export const ViewProvider = ({ children }: { children: React.ReactNode }) => { + const [view, setView] = useState({ + name: "dashboard", + }); + + return ( + + {children} + + ); +}; + +export const useView = () => { + const { view, setView } = useContext(ViewContext); + const typedView = view as Extract; + + return { + view: typedView, + setView, + }; +}; diff --git a/src/bin/ui/views/Dashboard.tsx b/src/bin/ui/views/Dashboard.tsx new file mode 100644 index 0000000..d7c9b10 --- /dev/null +++ b/src/bin/ui/views/Dashboard.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { Box, Text } from "ink"; +import { useView } from "../viewContext"; +import { getRunningVms } from "../api"; +import { useQuery } from "@tanstack/react-query"; +import { useSDK } from "../sdkContext"; +import { TextInput } from "../components/TextInput"; +import { VmTable } from "../components/VmTable"; +import { useVmInput } from "../hooks/useVmInput"; + +export const Dashboard = () => { + const { apiClient } = useSDK(); + + const { data, isLoading } = useQuery({ + queryKey: ["runningVms"], + queryFn: () => getRunningVms(apiClient), + }); + + const { setView } = useView(); + + const { + sandboxId, + selectedVm, + selectedVmIndex, + handleInputChange, + handleInputSubmit, + handleVmSelect, + } = useVmInput({ + vms: data?.vms, + onSubmit: (id: string) => { + setView({ name: "sandbox", params: { id } }); + }, + }); + + // Cursor is shown when no VM is selected (user typed manually) + const showCursor = selectedVm === null; + + const renderVmSection = () => { + if (isLoading) { + return ( + + Running VMs + + Loading VM data... + + + ); + } + + if (data?.vms && data.vms.length > 0) { + // TODO: Fix type mismatch - API types are being updated + return ( + + ); + } + + return ( + + Running VMs + + No running VMs found. + + + ); + }; + + return ( + + + Start typing to input an ID or use ↑/↓ arrows to select from running VMs. Press ENTER to view VM details. + + + + Sandbox ID + + + + + + {renderVmSection()} + + ); +}; diff --git a/src/bin/ui/views/Sandbox.tsx b/src/bin/ui/views/Sandbox.tsx new file mode 100644 index 0000000..62e48fd --- /dev/null +++ b/src/bin/ui/views/Sandbox.tsx @@ -0,0 +1,192 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Box, Text, useInput } from "ink"; +import { useView } from "../viewContext"; +import { useQuery } from "@tanstack/react-query"; +import { useSDK } from "../sdkContext"; +import { getRunningVms, getSandbox } from "../api"; + +export const Sandbox = () => { + const { view, setView } = useView<"sandbox">(); + + // Poll getRunningVms API every 2 seconds + const runningVmsQuery = useQuery({ + queryKey: ["runningVms"], + queryFn: () => getRunningVms(apiClient), + }); + + useEffect(() => { + // have to manually do this because of environment + const interval = setInterval(() => { + runningVmsQuery.refetch(); + }, 2000); + + return () => { + clearInterval(interval); + }; + }, []); + + const sandboxQuery = useQuery({ + queryKey: ["sandbox", view.params.id], + queryFn: () => getSandbox(apiClient, view.params.id), + enabled: !!view.params.id, + }); + + const runningState = runningVmsQuery.isLoading + ? "PENDING" + : runningVmsQuery.data?.vms.find((vm) => vm.id === view.params.id) + ? "RUNNING" + : "IDLE"; + + const runningStateRef = useRef(runningState); + + const { sdk, apiClient } = useSDK(); + + // Only two states: RUNNING or IDLE + const [sandboxState, setSandboxState] = useState< + "RUNNING" | "IDLE" | "PENDING" + >(runningState); + const [selectedOption, setSelectedOption] = useState(0); + + // We only want to update the state when the + // running state has ACTUALLY changed (Reconciliation sucks) + useEffect(() => { + if ( + sandboxState !== "PENDING" && + runningStateRef.current !== runningState + ) { + runningStateRef.current = runningState; + setSandboxState(runningState); + } + }, [runningState, sandboxState]); + + // Define menu options based on state + const getMenuOptions = () => { + switch (sandboxState) { + case "RUNNING": + return ["Hibernate", "Shutdown", "Restart"]; + case "IDLE": + return ["Start"]; + default: + return []; + } + }; + + const menuOptions = getMenuOptions(); + + // Handle menu options + const handleAction = async (action: string) => { + switch (action) { + case "Hibernate": + case "Shutdown": + setSandboxState("PENDING"); + await sdk.sandboxes.shutdown(view.params.id); + setSandboxState("IDLE"); + setSelectedOption(0); + break; + case "Restart": + setSandboxState("PENDING"); + await sdk.sandboxes.restart(view.params.id); + setSandboxState("RUNNING"); + setSelectedOption(0); + break; + case "Start": + setSandboxState("PENDING"); + await sdk.sandboxes.resume(view.params.id); + setSandboxState("RUNNING"); + setSelectedOption(0); + break; + } + }; + + // Handle keyboard navigation + useInput((input, key) => { + if (key.escape) { + setView({ name: "dashboard" }); + } else if (menuOptions.length > 0) { + if (key.upArrow) { + setSelectedOption((prev) => (prev > 0 ? prev - 1 : prev)); + } else if (key.downArrow) { + setSelectedOption((prev) => + prev < menuOptions.length - 1 ? prev + 1 : prev + ); + } else if (key.return) { + handleAction(menuOptions[selectedOption]); + } + } + }); + + if (!view.params.id) { + return No sandbox ID provided. Press escape to go back.; + } + + return ( + + {/* Handle query states */} + {sandboxQuery.isLoading && ( + + Loading sandbox information... + + )} + + {sandboxQuery.error && ( + + + Error loading sandbox: {(sandboxQuery.error as Error).message} + + + )} + + {sandboxQuery.data && ( + + + {sandboxQuery.data.title} - {view.params.id} + + + {sandboxQuery.data.description && ( + + {sandboxQuery.data.description} + + )} + + )} + + {/* Status display - moved above title and description */} + + Status: + + {sandboxState} + + + + {menuOptions.length > 0 && ( + + Actions: + {menuOptions.map((option, index) => ( + + + {selectedOption === index ? "> " : " "} + {option} + + + ))} + + )} + + + + {menuOptions.length > 0 + ? "Use arrow keys to navigate, Enter to select, ESC to go back" + : "Press ESC to go back"} + + + + ); +}; From db8aded97e1844cc31f70b08b6a294b458069656 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 6 Aug 2025 10:56:39 +0200 Subject: [PATCH 190/241] fix: Add retries to all idempotent endpoints and added parallel file writing (#140) --- src/Sandboxes.ts | 125 ++++++++++++++++++-------------------- src/bin/commands/build.ts | 80 ++++++++++++++---------- src/utils/api.ts | 53 ++++++++-------- 3 files changed, 130 insertions(+), 128 deletions(-) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 7bb4ece..41a07e9 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -13,7 +13,7 @@ import { getStartOptions, getStartResponse, handleResponse, - withCustomTimeout, + retryWithDelay, } from "./utils/api"; import { @@ -33,22 +33,24 @@ export async function startVm( sandboxId: string, startOpts?: StartSandboxOpts ): Promise { - const startResult = await withCustomTimeout((signal) => - vmStart({ - client: apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - signal, - }) + const startResult = await retryWithDelay( + () => + vmStart({ + client: apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }), + 3, + 200 ); const response = handleResponse( @@ -91,7 +93,6 @@ export class Sandboxes { description: opts?.description, tags: tagsWithSdk, path, - start_options: getStartOptions(opts), }, path: { id: templateId, @@ -100,11 +101,13 @@ export class Sandboxes { const sandbox = handleResponse(result, "Failed to create sandbox"); - return new Sandbox( - sandbox.id, - this.apiClient, - getStartResponse(sandbox.start_response) + const startResponse = await retryWithDelay( + () => startVm(this.apiClient, sandbox.id, getStartOptions(opts)), + 3, + 200 ); + + return new Sandbox(sandbox.id, this.apiClient, startResponse); } /** @@ -125,14 +128,16 @@ export class Sandboxes { * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. */ async shutdown(sandboxId: string): Promise { - const response = await withCustomTimeout((signal) => - vmShutdown({ - client: this.apiClient, - path: { - id: sandboxId, - }, - signal, - }) + const response = await retryWithDelay( + () => + vmShutdown({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }), + 3, + 200 ); handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); @@ -156,53 +161,39 @@ export class Sandboxes { * Will resolve once the sandbox is restarted with its setup running. */ public async restart(sandboxId: string, opts?: StartSandboxOpts) { - let didRestart = false; - - for (let attempt = 1; attempt <= 3; attempt++) { - try { - await this.shutdown(sandboxId); - didRestart = true; - break; - } catch (e) { - await sleep(500); - } + try { + await this.shutdown(sandboxId); + } catch (e) { + throw new Error("Failed to shutdown VM, " + String(e)); } - if (!didRestart) { - throw new Error("Failed to shutdown VM after 3 attempts"); - } - - let startResponse: PitcherManagerResponse | undefined; - - for (let attempt = 1; attempt <= 3; attempt++) { - try { - startResponse = await startVm(this.apiClient, sandboxId, opts); - break; - } catch (e) { - await sleep(500); - } - } + try { + const startResponse = await retryWithDelay( + () => startVm(this.apiClient, sandboxId, opts), + 3, + 200 + ); - if (!startResponse) { - throw new Error("Failed to start VM after 3 attempts"); + return new Sandbox(sandboxId, this.apiClient, startResponse); + } catch (e) { + throw new Error("Failed to start VM, " + String(e)); } - - return new Sandbox(sandboxId, this.apiClient, startResponse); } - /** * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time * you resume the sandbox it will continue from the last state it was in. */ async hibernate(sandboxId: string): Promise { - const response = await withCustomTimeout((signal) => - vmHibernate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - signal, - }) + const response = await retryWithDelay( + () => + vmHibernate({ + client: this.apiClient, + path: { + id: sandboxId, + }, + }), + 3, + 200 ); handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index aed7fe8..0b29358 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -16,11 +16,13 @@ import { createApiClient, getDefaultTemplateId, handleResponse, + retryWithDelay, } from "../../utils/api"; import { getInferredApiKey } from "../../utils/constants"; import { hashDirectory as getFilePaths } from "../utils/files"; import { startVm } from "../../Sandboxes"; import { mkdir, writeFile } from "fs/promises"; +import { sleep } from "../../utils/sleep"; export type BuildCommandArgs = { directory: string; @@ -196,36 +198,40 @@ export const buildCommand: yargs.CommandModule< try { spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); - const startResponse = await withCustomError( - startVm(apiClient, id), - "Failed to start sandbox" + const startResponse = await retryWithDelay(() => + withCustomError( + startVm(apiClient, id), + "Failed to start sandbox at all" + ) ); let sandboxVM = new Sandbox(id, apiClient, startResponse); - let session = await sandboxVM.connect(); + let session = await retryWithDelay(() => sandboxVM.connect(), 3, 100); spinner.start( updateSpinnerMessage(index, "Writing files to sandbox...") ); - let i = 0; - for (const filePath of filePaths) { - i++; - try { - const fullPath = path.join(argv.directory, filePath); - const content = await fs.readFile(fullPath); - const dirname = path.dirname(filePath); - await session.fs.mkdir(dirname, true); - await session.fs.writeFile(filePath, content, { - create: true, - overwrite: true, - }); - } catch (error) { - throw new Error( - `Failed to write "${filePath}" to sandbox: ${error}` - ); - } - } + await Promise.all( + filePaths.map((filePath) => + retryWithDelay( + async () => { + const fullPath = path.join(argv.directory, filePath); + const content = await fs.readFile(fullPath); + const dirname = path.dirname(filePath); + await session.fs.mkdir(dirname, true); + await session.fs.writeFile(filePath, content, { + create: true, + overwrite: true, + }); + }, + 3, + 200 + ) + ) + ).catch((error) => { + throw new Error(`Failed to write files to sandbox: ${error}`); + }); spinner.start(updateSpinnerMessage(index, "Building sandbox...")); @@ -233,12 +239,17 @@ export const buildCommand: yargs.CommandModule< sdk.sandboxes.restart(id, { vmTier: buildTier, }), - "Failed to restart sandbox" + "Failed to restart sandbox after building" ); - session = await withCustomError( - sandboxVM.connect(), - "Failed to connect to sandbox" + session = await retryWithDelay( + () => + withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox after building" + ), + 3, + 100 ); await waitForSetup(session, index); @@ -250,12 +261,17 @@ export const buildCommand: yargs.CommandModule< sdk.sandboxes.restart(id, { vmTier: sandboxTier, }), - "Failed to restart sandbox" + "Failed to restart sandbox after optimizing initial state" ); - session = await withCustomError( - sandboxVM.connect(), - "Failed to connect to sandbox" + session = await retryWithDelay( + () => + withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox after optimizing initial state" + ), + 3, + 100 ); await waitForSetup(session, index); @@ -289,7 +305,7 @@ export const buildCommand: yargs.CommandModule< break; } - await new Promise((resolve) => setTimeout(resolve, 1000)); + await sleep(1000); } updatePortSpinner(); @@ -308,7 +324,7 @@ export const buildCommand: yargs.CommandModule< spinner.start(updateSpinnerMessage(index, "Creating snapshot...")); await withCustomError( sdk.sandboxes.hibernate(id), - "Failed to hibernate" + "Failed to hibernate after building and optimizing sandbox" ); spinner.start(updateSpinnerMessage(index, "Snapshot created")); diff --git a/src/utils/api.ts b/src/utils/api.ts index 8cd4649..09e06f5 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -98,35 +98,6 @@ export function getStartResponse( }; } -/** - * Our infra has 2 min timeout, so we use that as default - */ -export async function withCustomTimeout( - cb: (signal: AbortSignal) => Promise, - timeoutSeconds: number = 120 -) { - const controller = new AbortController(); - const signal = controller.signal; - const timeoutHandle = setTimeout(() => { - controller.abort(); - }, timeoutSeconds * 1000); - - try { - // We have to await for the finally to run - return await cb(signal); - } catch (err) { - if (err instanceof Error && err.name === "AbortError") { - throw new Error( - `Request took longer than ${timeoutSeconds}s, so we aborted.` - ); - } - - throw err; - } finally { - clearTimeout(timeoutHandle); - } -} - export function getDefaultTemplateTag(apiClient: Client): string { if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; @@ -145,6 +116,30 @@ export function getDefaultTemplateId(apiClient: Client): string { return "pcz35m"; } +export async function retryWithDelay( + callback: () => Promise, + retries: number = 3, + delay: number = 500 +): Promise { + let lastError: Error; + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + return await callback(); + } catch (error) { + lastError = error as Error; + + if (attempt === retries) { + throw lastError; + } + + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + + throw lastError!; +} + export function handleResponse( result: Awaited<{ data?: { data?: D }; error?: E; response: Response }>, errorPrefix: string From 207dd067edde27f8bdf1d3297120fdc0ad5833d0 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 6 Aug 2025 11:01:05 +0200 Subject: [PATCH 191/241] chore(main): release 2.0.6 (#139) --- CHANGELOG.md | 10 ++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c90428..114b69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [2.0.6](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.5...v2.0.6) (2025-08-06) + + +### Bug Fixes + +* Add retries to all idempotent endpoints and added parallel file writing ([#140](https://github.com/codesandbox/codesandbox-sdk/issues/140)) ([db8aded](https://github.com/codesandbox/codesandbox-sdk/commit/db8aded97e1844cc31f70b08b6a294b458069656)) +* Fix broken authorization in preview hosts ([20a4e53](https://github.com/codesandbox/codesandbox-sdk/commit/20a4e538e3b473007783831c7592670cf99c8a96)) +* Fix broken authorization in preview hosts ([71b38b4](https://github.com/codesandbox/codesandbox-sdk/commit/71b38b4b12a9438864296fb599d20760c6b0a728)) +* Update to latest Ink and React 19 and bundle React and Ink into CLI ([#138](https://github.com/codesandbox/codesandbox-sdk/issues/138)) ([62da4fe](https://github.com/codesandbox/codesandbox-sdk/commit/62da4fef50a3497b84c71413b1c0e3337c73e59f)) + ## [2.0.5](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.4...v2.0.5) (2025-07-29) diff --git a/package-lock.json b/package-lock.json index 47b80d7..a6d6c3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.5", + "version": "2.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.5", + "version": "2.0.6", "license": "MIT", "dependencies": { "@codesandbox/pitcher-client": "1.1.7", diff --git a/package.json b/package.json index 55c6b28..4f008cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.5", + "version": "2.0.6", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 8b69036d0b586917db05f9c51046ae8b26660835 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 6 Aug 2025 11:31:11 +0200 Subject: [PATCH 192/241] fix: also retry resume (#143) * fix: also retry resume * fix: retry resume and prevent double retries --- src/Sandbox.ts | 13 ++++++++++--- src/Sandboxes.ts | 40 +++++++++++++++++++-------------------- src/bin/commands/build.ts | 13 ++++++++----- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 57f822f..77494c8 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -8,7 +8,7 @@ import { vmUpdateHibernationTimeout, vmUpdateSpecs, } from "./api-clients/client"; -import { handleResponse } from "./utils/api"; +import { handleResponse, retryWithDelay } from "./utils/api"; import { VMTier } from "./VMTier"; import { Client } from "@hey-api/client-fetch"; import { connectToSandbox } from "./node"; @@ -91,7 +91,10 @@ export class Sandbox { const client = await connectToSandbox({ session, getSession: async () => - this.getSession(await startVm(this.apiClient, this.id), customSession), + this.getSession( + await retryWithDelay(() => startVm(this.apiClient, this.id), 3, 200), + customSession + ), }); if (customSession.env) { @@ -206,7 +209,11 @@ export class Sandbox { session, getSession: async () => this.getSession( - await startVm(this.apiClient, this.id), + await retryWithDelay( + () => startVm(this.apiClient, this.id), + 3, + 200 + ), customSession ), }) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 41a07e9..786f1b1 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -26,32 +26,26 @@ import { StartSandboxOpts, } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; -import { sleep } from "./utils/sleep"; export async function startVm( apiClient: Client, sandboxId: string, startOpts?: StartSandboxOpts ): Promise { - const startResult = await retryWithDelay( - () => - vmStart({ - client: apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }), - 3, - 200 - ); + const startResult = await vmStart({ + client: apiClient, + body: startOpts + ? { + ipcountry: startOpts.ipcountry, + tier: startOpts.vmTier?.name, + hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, + automatic_wakeup_config: startOpts.automaticWakeupConfig, + } + : undefined, + path: { + id: sandboxId, + }, + }); const response = handleResponse( startResult, @@ -120,7 +114,11 @@ export class Sandboxes { * Note! On CLEAN bootups the setup will run again. When hibernated a new snapshot will be created. */ async resume(sandboxId: string) { - const startResponse = await startVm(this.apiClient, sandboxId); + const startResponse = await retryWithDelay( + () => startVm(this.apiClient, sandboxId), + 3, + 200 + ); return new Sandbox(sandboxId, this.apiClient, startResponse); } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 0b29358..131b465 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -198,11 +198,14 @@ export const buildCommand: yargs.CommandModule< try { spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); - const startResponse = await retryWithDelay(() => - withCustomError( - startVm(apiClient, id), - "Failed to start sandbox at all" - ) + const startResponse = await retryWithDelay( + () => + withCustomError( + startVm(apiClient, id), + "Failed to start sandbox at all" + ), + 3, + 200 ); let sandboxVM = new Sandbox(id, apiClient, startResponse); From fbb30a65132f90257816ad7a1e4d5ee21b5a08f4 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 6 Aug 2025 11:46:20 +0200 Subject: [PATCH 193/241] chore(main): release 2.0.7 (#144) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 114b69f..9812f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.0.7](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.6...v2.0.7) (2025-08-06) + + +### Bug Fixes + +* also retry resume ([#143](https://github.com/codesandbox/codesandbox-sdk/issues/143)) ([8b69036](https://github.com/codesandbox/codesandbox-sdk/commit/8b69036d0b586917db05f9c51046ae8b26660835)) + ## [2.0.6](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.5...v2.0.6) (2025-08-06) diff --git a/package-lock.json b/package-lock.json index a6d6c3a..076f042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.0.6", + "version": "2.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.6", + "version": "2.0.7", "license": "MIT", "dependencies": { "@codesandbox/pitcher-client": "1.1.7", diff --git a/package.json b/package.json index 4f008cc..2c3ee0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.6", + "version": "2.0.7", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From a2f7de0a33677efaf2162d5a8b7077b3cd918a81 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 7 Aug 2025 10:42:13 +0200 Subject: [PATCH 194/241] chore: Refactor to centralized API with retries for idempotent endpoints (#146) * chore: Centralize api calls * remove opentelemtry --- package.json | 8 +- src/API.ts | 429 ++++++++++++++++++++++++ src/HostTokens.ts | 80 +---- src/Sandbox.ts | 99 ++---- src/Sandboxes.ts | 142 ++------ src/bin/commands/build.ts | 90 ++--- src/bin/commands/previewHosts.ts | 40 +-- src/bin/commands/sandbox/host-tokens.ts | 7 +- src/bin/ui/api.ts | 26 -- src/bin/ui/sdkContext.tsx | 15 +- src/bin/ui/views/Dashboard.tsx | 10 +- src/bin/ui/views/Sandbox.tsx | 8 +- src/index.ts | 9 +- src/utils/api.ts | 20 -- 14 files changed, 568 insertions(+), 415 deletions(-) create mode 100644 src/API.ts delete mode 100644 src/bin/ui/api.ts diff --git a/package.json b/package.json index 2c3ee0d..be3cd29 100644 --- a/package.json +++ b/package.json @@ -71,20 +71,20 @@ "devDependencies": { "@hey-api/openapi-ts": "^0.63.2", "@parcel/watcher": "^2.5.1", + "@tanstack/react-query": "^5.76.1", "@types/blessed": "^0.1.25", "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", - "ink": "^6.1.0", - "react": "^19.1.1", - "@tanstack/react-query": "^5.76.1", "buffer-browserify": "^0.2.5", "crypto-browserify": "^3.12.1", "esbuild": "^0.25.0", "ignore": "^6.0.2", + "ink": "^6.1.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", + "react": "^19.1.1", "rimraf": "^6.0.1", "semver": "^6.3.0", "tslib": "^2.1.0", @@ -98,8 +98,8 @@ "@msgpack/msgpack": "^3.1.0", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", - "cli-table3": "^0.6.3", "chalk": "^5.4.1", + "cli-table3": "^0.6.3", "date-fns": "^4.1.0", "isbinaryfile": "^5.0.4", "isomorphic-ws": "^5.0.0", diff --git a/src/API.ts b/src/API.ts new file mode 100644 index 0000000..2a6507f --- /dev/null +++ b/src/API.ts @@ -0,0 +1,429 @@ +import type { Client, Config } from "@hey-api/client-fetch"; +import { createClient, createConfig } from "@hey-api/client-fetch"; +import { handleResponse, retryWithDelay } from "./utils/api"; +import { getInferredBaseUrl } from "./utils/constants"; +import { + metaInfo, + workspaceCreate, + tokenCreate, + tokenUpdate, + sandboxList, + sandboxCreate, + sandboxGet, + sandboxFork, + previewTokenRevokeAll, + previewTokenList, + previewTokenCreate, + previewTokenUpdate, + templatesCreate, + vmAssignTagAlias, + vmListClusters, + vmListRunningVms, + vmCreateTag, + vmHibernate, + vmUpdateHibernationTimeout, + vmCreateSession, + vmShutdown, + vmUpdateSpecs, + vmStart, + vmUpdateSpecs2, + previewHostList, + previewHostCreate, + previewHostUpdate, +} from "./api-clients/client"; +import type { + WorkspaceCreateData, + TokenCreateData, + TokenUpdateData, + SandboxListData, + SandboxCreateData, + SandboxForkData, + PreviewTokenCreateData, + PreviewTokenUpdateData, + TemplatesCreateData, + VmAssignTagAliasData, + VmCreateTagData, + VmHibernateData, + VmUpdateHibernationTimeoutData, + VmCreateSessionData, + VmShutdownData, + VmUpdateSpecsData, + VmStartData, + VmUpdateSpecs2Data, + PreviewHostListData, + PreviewHostCreateData, + PreviewHostUpdateData, +} from "./api-clients/client"; +import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; + +async function enhanceFetch( + request: Request, + instrumentation?: (request: Request) => Promise +) { + // Clone the request to modify headers + const headers = new Headers(request.headers); + const existingUserAgent = headers.get("User-Agent") || ""; + + // Extend User-Agent with SDK version + headers.set( + "User-Agent", + `${existingUserAgent ? `${existingUserAgent} ` : ""}codesandbox-sdk/${ + // @ts-expect-error - Replaced at build time + CSB_SDK_VERSION + }`.trim() + ); + + // Create new request with updated headers and optionally add instrumentation + return instrumentation + ? instrumentation( + new Request(request, { + headers, + }) + ) + : fetch( + new Request(request, { + headers, + }) + ); +} + +function createApiClient( + apiKey: string, + config: Config = {}, + instrumentation?: (request: Request) => Promise +) { + return createClient( + createConfig({ + baseUrl: config.baseUrl || getInferredBaseUrl(apiKey), + fetch: (request) => enhanceFetch(request, instrumentation), + ...config, + headers: { + Authorization: `Bearer ${apiKey}`, + ...config.headers, + }, + }) + ); +} + +export interface APIOptions { + apiKey: string; + config?: Config; + instrumentation?: (request: Request) => Promise; +} + +export class API { + private client: Client; + + constructor(options: APIOptions) { + this.client = createApiClient( + options.apiKey, + options.config || {}, + options.instrumentation + ); + } + + // Meta endpoints + async getMetaInfo() { + return metaInfo({ client: this.client }); + } + + // Workspace endpoints + async createWorkspace(data?: WorkspaceCreateData["body"]) { + const response = await workspaceCreate({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to create workspace"); + } + + // Token endpoints + async createToken(teamId: string, data: TokenCreateData["body"]) { + const response = await tokenCreate({ + client: this.client, + path: { team_id: teamId }, + body: data, + }); + return handleResponse( + response, + `Failed to create token for team ${teamId}` + ); + } + + async updateToken( + teamId: string, + tokenId: string, + data: TokenUpdateData["body"] + ) { + const response = await tokenUpdate({ + client: this.client, + path: { team_id: teamId, token_id: tokenId }, + body: data, + }); + return handleResponse( + response, + `Failed to update token ${tokenId} for team ${teamId}` + ); + } + + // Sandbox endpoints + async listSandboxes(query?: SandboxListData["query"]) { + const response = await sandboxList({ + client: this.client, + query, + }); + return handleResponse(response, "Failed to list sandboxes"); + } + + async createSandbox(data?: SandboxCreateData["body"]) { + const response = await sandboxCreate({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to create sandbox"); + } + + async getSandbox(id: string) { + const response = await sandboxGet({ + client: this.client, + path: { id }, + }); + return handleResponse(response, `Failed to get sandbox ${id}`); + } + + async forkSandbox(id: string, data?: SandboxForkData["body"]) { + const response = await sandboxFork({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse(response, `Failed to fork sandbox ${id}`); + } + + // Preview token endpoints + async revokeAllPreviewTokens(id: string) { + const response = await previewTokenRevokeAll({ + client: this.client, + path: { id }, + }); + return handleResponse( + response, + `Failed to revoke all preview tokens for ${id}` + ); + } + + async listPreviewTokens(id: string) { + const response = await previewTokenList({ + client: this.client, + path: { id }, + }); + return handleResponse(response, `Failed to list preview tokens for ${id}`); + } + + async createPreviewToken(id: string, data?: PreviewTokenCreateData["body"]) { + const response = await previewTokenCreate({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse(response, `Failed to create preview token for ${id}`); + } + + async updatePreviewToken( + id: string, + tokenId: string, + data: PreviewTokenUpdateData["body"] + ) { + const response = await previewTokenUpdate({ + client: this.client, + path: { id, token_id: tokenId }, + body: data, + }); + return handleResponse( + response, + `Failed to update preview token ${tokenId} for ${id}` + ); + } + + // Template endpoints + async createTemplate(data: TemplatesCreateData["body"]) { + const response = await templatesCreate({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to create template"); + } + + // VM endpoints + async assignVmTagAlias( + namespace: string, + alias: string, + data: VmAssignTagAliasData["body"] + ) { + const response = await vmAssignTagAlias({ + client: this.client, + path: { namespace, alias }, + body: data, + }); + return handleResponse( + response, + `Failed to assign tag alias ${namespace}@${alias}` + ); + } + + async listVmClusters() { + const response = await vmListClusters({ + client: this.client, + }); + return handleResponse(response, "Failed to list VM clusters"); + } + + async listRunningVms() { + const response = await vmListRunningVms({ + client: this.client, + }); + return handleResponse(response, "Failed to list running VMs"); + } + + async createVmTag(data?: VmCreateTagData["body"]) { + const response = await vmCreateTag({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to create VM tag"); + } + + async hibernate(id: string, data?: VmHibernateData["body"]) { + const response = await retryWithDelay( + () => + vmHibernate({ + client: this.client, + path: { id }, + body: data, + }), + 3, + 200 + ); + return handleResponse(response, `Failed to hibernate VM ${id}`); + } + + async updateHibernationTimeout( + id: string, + data: VmUpdateHibernationTimeoutData["body"] + ) { + const response = await vmUpdateHibernationTimeout({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse( + response, + `Failed to update hibernation timeout for VM ${id}` + ); + } + + async createSession(id: string, data: VmCreateSessionData["body"]) { + const response = await vmCreateSession({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse(response, `Failed to create session for VM ${id}`); + } + + async shutdown(id: string, data?: VmShutdownData["body"]) { + const response = await retryWithDelay( + () => + vmShutdown({ + client: this.client, + path: { id }, + body: data, + }), + 3, + 200 + ); + return handleResponse(response, `Failed to shutdown VM ${id}`); + } + + async updateSpecs(id: string, data: VmUpdateSpecsData["body"]) { + const response = await vmUpdateSpecs({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse(response, `Failed to update specs for VM ${id}`); + } + + async startVm(id: string, data?: VmStartData["body"]) { + const response = await retryWithDelay( + () => + vmStart({ + client: this.client, + path: { id }, + body: data, + }), + 3, + 200 + ); + const handledResponse = handleResponse( + response, + `Failed to start VM ${id}` + ); + + return { + bootupType: + handledResponse.bootup_type as PitcherManagerResponse["bootupType"], + cluster: handledResponse.cluster, + pitcherURL: handledResponse.pitcher_url, + workspacePath: handledResponse.workspace_path, + userWorkspacePath: handledResponse.user_workspace_path, + pitcherManagerVersion: handledResponse.pitcher_manager_version, + pitcherVersion: handledResponse.pitcher_version, + latestPitcherVersion: handledResponse.latest_pitcher_version, + pitcherToken: handledResponse.pitcher_token, + }; + } + + async updateVmSpecs2(id: string, data: VmUpdateSpecs2Data["body"]) { + const response = await vmUpdateSpecs2({ + client: this.client, + path: { id }, + body: data, + }); + return handleResponse(response, `Failed to update VM specs2 for ${id}`); + } + + // Preview host endpoints + async listPreviewHosts(query?: PreviewHostListData["query"]) { + const response = await previewHostList({ + client: this.client, + query, + }); + return handleResponse(response, "Failed to list preview hosts"); + } + + async createPreviewHost(data: PreviewHostCreateData["body"]) { + const response = await previewHostCreate({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to create preview host"); + } + + async updatePreviewHost(data: PreviewHostUpdateData["body"]) { + const response = await previewHostUpdate({ + client: this.client, + body: data, + }); + return handleResponse(response, "Failed to update preview host"); + } + + // Get the underlying client for advanced use cases + getClient(): Client { + return this.client; + } + + // Get client configuration + getConfig(): Config { + return this.client.getConfig(); + } +} diff --git a/src/HostTokens.ts b/src/HostTokens.ts index 16eccec..ff1b738 100644 --- a/src/HostTokens.ts +++ b/src/HostTokens.ts @@ -1,12 +1,5 @@ import { Disposable } from "./utils/disposable"; -import { Client } from "@hey-api/client-fetch"; -import { handleResponse } from "./utils/api"; -import { - previewTokenCreate, - previewTokenList, - previewTokenRevokeAll, - previewTokenUpdate, -} from "./api-clients/client"; +import { API } from "./API"; interface BaseHostTokenInfo { expiresAt: Date | null; @@ -29,7 +22,7 @@ export interface HostToken extends BaseHostTokenInfo { * with an authenticated API client (like Node.js). */ export class HostTokens extends Disposable { - constructor(private apiClient: Client) { + constructor(private api: API) { super(); } @@ -43,7 +36,7 @@ export class HostTokens extends Disposable { port: number, protocol: string = "https" ): string { - const domain = this.apiClient.getConfig().baseUrl?.includes(".stream") + const domain = this.api.getConfig().baseUrl?.includes(".stream") ? "csb.dev" : "csb.app"; @@ -75,18 +68,9 @@ export class HostTokens extends Disposable { sandboxId: string, opts: { expiresAt: Date } ): Promise { - const response = handleResponse( - await previewTokenCreate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - body: { - expires_at: opts.expiresAt.toISOString(), - }, - }), - "Failed to create preview token" - ); + const response = await this.api.createPreviewToken(sandboxId, { + expires_at: opts.expiresAt.toISOString(), + }); if (!response.token?.token) { throw new Error("No token returned from API"); @@ -109,15 +93,7 @@ export class HostTokens extends Disposable { * List all active host tokens for this sandbox. */ async listTokens(sandboxId: string): Promise { - const response = handleResponse( - await previewTokenList({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - "Failed to list host tokens" - ); + const response = await this.api.listPreviewTokens(sandboxId); if (!response.tokens) { return []; @@ -135,19 +111,9 @@ export class HostTokens extends Disposable { * Revoke a single host token for this sandbox. */ async revokeToken(sandboxId: string, tokenId: string): Promise { - handleResponse( - await previewTokenUpdate({ - client: this.apiClient, - path: { - id: sandboxId, - token_id: tokenId, - }, - body: { - expires_at: new Date().toISOString(), - }, - }), - "Failed to revoke host token" - ); + await this.api.updatePreviewToken(sandboxId, tokenId, { + expires_at: new Date().toISOString(), + }); } /** @@ -156,15 +122,7 @@ export class HostTokens extends Disposable { * to access the sandbox host. */ async revokeAllTokens(sandboxId: string): Promise { - handleResponse( - await previewTokenRevokeAll({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - "Failed to revoke host tokens" - ); + await this.api.revokeAllPreviewTokens(sandboxId); } /** @@ -175,19 +133,9 @@ export class HostTokens extends Disposable { tokenId: string, expiresAt: Date | null ): Promise { - const response = handleResponse( - await previewTokenUpdate({ - client: this.apiClient, - path: { - id: sandboxId, - token_id: tokenId, - }, - body: { - expires_at: expiresAt?.toISOString(), - }, - }), - "Failed to update host token" - ); + const response = await this.api.updatePreviewToken(sandboxId, tokenId, { + expires_at: expiresAt?.toISOString(), + }); if (!response.token) { throw new Error("No token returned from API"); diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 77494c8..c32ac79 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -3,17 +3,12 @@ import { type protocol as _protocol, } from "@codesandbox/pitcher-client"; import { type SessionCreateOptions, type SandboxSession } from "./types"; -import { - vmCreateSession, - vmUpdateHibernationTimeout, - vmUpdateSpecs, -} from "./api-clients/client"; -import { handleResponse, retryWithDelay } from "./utils/api"; import { VMTier } from "./VMTier"; -import { Client } from "@hey-api/client-fetch"; +import { API } from "./API"; import { connectToSandbox } from "./node"; -import { startVm } from "./Sandboxes"; import { SandboxClient } from "./SandboxClient"; +import { StartSandboxOpts } from "./types"; +import { retryWithDelay } from "./utils/api"; export class Sandbox { /** @@ -46,7 +41,7 @@ export class Sandbox { } constructor( public id: string, - private apiClient: Client, + private api: API, private pitcherManagerResponse: PitcherManagerResponse ) {} @@ -56,15 +51,9 @@ export class Sandbox { * than it can scale down to, it can become very slow. */ async updateTier(tier: VMTier): Promise { - const response = await vmUpdateSpecs({ - client: this.apiClient, - path: { id: this.id }, - body: { - tier: tier.name, - }, + await this.api.updateSpecs(this.id, { + tier: tier.name, }); - - handleResponse(response, `Failed to update sandbox tier ${this.id}`); } /** @@ -72,16 +61,9 @@ export class Sandbox { * will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox. */ async updateHibernationTimeout(timeoutSeconds: number): Promise { - const response = await vmUpdateHibernationTimeout({ - client: this.apiClient, - path: { id: this.id }, - body: { hibernation_timeout_seconds: timeoutSeconds }, + await this.api.updateHibernationTimeout(this.id, { + hibernation_timeout_seconds: timeoutSeconds, }); - - handleResponse( - response, - `Failed to update hibernation timeout for sandbox ${this.id}` - ); } private async initializeCustomSession( @@ -91,10 +73,7 @@ export class Sandbox { const client = await connectToSandbox({ session, getSession: async () => - this.getSession( - await retryWithDelay(() => startVm(this.apiClient, this.id), 3, 200), - customSession - ), + this.getSession(await this.api.startVm(this.id), customSession), }); if (customSession.env) { @@ -158,22 +137,11 @@ export class Sandbox { throw new Error("Session ID must be 20 characters or less"); } - const response = await vmCreateSession({ - client: this.apiClient, - body: { - session_id: customSession.id, - permission: customSession.permission ?? "write", - }, - path: { - id: this.id, - }, + const handledResponse = await this.api.createSession(this.id, { + session_id: customSession.id, + permission: customSession.permission ?? "write", }); - const handledResponse = handleResponse( - response, - `Failed to create session ${customSession.id}` - ); - return { sandboxId: this.id, sessionId: customSession?.id, @@ -191,33 +159,28 @@ export class Sandbox { } async connect(customSession?: SessionCreateOptions) { - const session = await this.getSession( - this.pitcherManagerResponse, - customSession - ); + return await retryWithDelay(async () => { + const session = await this.getSession( + this.pitcherManagerResponse, + customSession + ); - let client: SandboxClient | undefined; + let client: SandboxClient | undefined; - // We might create a client here if git or env is configured, we can reuse that - if (customSession) { - client = await this.initializeCustomSession(customSession, session); - } + // We might create a client here if git or env is configured, we can reuse that + if (customSession) { + client = await this.initializeCustomSession(customSession, session); + } - return ( - client || - connectToSandbox({ - session, - getSession: async () => - this.getSession( - await retryWithDelay( - () => startVm(this.apiClient, this.id), - 3, - 200 - ), - customSession - ), - }) - ); + return ( + client || + connectToSandbox({ + session, + getSession: async () => + this.getSession(await this.api.startVm(this.id), customSession), + }) + ); + }, 3, 100); } /** diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 786f1b1..164dcd3 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -1,20 +1,6 @@ -import type { Client } from "@hey-api/client-fetch"; - -import { - sandboxFork, - sandboxList, - vmHibernate, - vmShutdown, - vmStart, -} from "./api-clients/client"; import { Sandbox } from "./Sandbox"; -import { - getDefaultTemplateTag, - getStartOptions, - getStartResponse, - handleResponse, - retryWithDelay, -} from "./utils/api"; +import { API } from "./API"; +import { getDefaultTemplateTag, getStartOptions } from "./utils/api"; import { CreateSandboxOpts, @@ -27,43 +13,15 @@ import { } from "./types"; import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; -export async function startVm( - apiClient: Client, - sandboxId: string, - startOpts?: StartSandboxOpts -): Promise { - const startResult = await vmStart({ - client: apiClient, - body: startOpts - ? { - ipcountry: startOpts.ipcountry, - tier: startOpts.vmTier?.name, - hibernation_timeout_seconds: startOpts.hibernationTimeoutSeconds, - automatic_wakeup_config: startOpts.automaticWakeupConfig, - } - : undefined, - path: { - id: sandboxId, - }, - }); - - const response = handleResponse( - startResult, - `Failed to start sandbox ${sandboxId}` - ); - - return getStartResponse(response); -} - /** * This class provides methods for creating and managing sandboxes. */ export class Sandboxes { get defaultTemplateId() { - return getDefaultTemplateTag(this.apiClient); + return getDefaultTemplateTag(this.api.getClient()); } - constructor(private apiClient: Client) {} + constructor(private api: API) {} private async createTemplateSandbox( opts?: CreateSandboxOpts & StartSandboxOpts @@ -79,29 +37,20 @@ export class Sandboxes { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - const result = await sandboxFork({ - client: this.apiClient, - body: { - privacy: privacyToNumber(privacy), - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - }, - path: { - id: templateId, - }, + const sandbox = await this.api.forkSandbox(templateId, { + privacy: privacyToNumber(privacy), + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, }); - const sandbox = handleResponse(result, "Failed to create sandbox"); - - const startResponse = await retryWithDelay( - () => startVm(this.apiClient, sandbox.id, getStartOptions(opts)), - 3, - 200 + const startResponse = await this.api.startVm( + sandbox.id, + getStartOptions(opts) ); - return new Sandbox(sandbox.id, this.apiClient, startResponse); + return new Sandbox(sandbox.id, this.api, startResponse); } /** @@ -114,31 +63,15 @@ export class Sandboxes { * Note! On CLEAN bootups the setup will run again. When hibernated a new snapshot will be created. */ async resume(sandboxId: string) { - const startResponse = await retryWithDelay( - () => startVm(this.apiClient, sandboxId), - 3, - 200 - ); - return new Sandbox(sandboxId, this.apiClient, startResponse); + const startResponse = await this.api.startVm(sandboxId); + return new Sandbox(sandboxId, this.api, startResponse); } /** * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. */ async shutdown(sandboxId: string): Promise { - const response = await retryWithDelay( - () => - vmShutdown({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - 3, - 200 - ); - - handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`); + await this.api.shutdown(sandboxId); } /** @@ -166,13 +99,9 @@ export class Sandboxes { } try { - const startResponse = await retryWithDelay( - () => startVm(this.apiClient, sandboxId, opts), - 3, - 200 - ); + const startResponse = await this.api.startVm(sandboxId, opts); - return new Sandbox(sandboxId, this.apiClient, startResponse); + return new Sandbox(sandboxId, this.api, startResponse); } catch (e) { throw new Error("Failed to start VM, " + String(e)); } @@ -182,19 +111,7 @@ export class Sandboxes { * you resume the sandbox it will continue from the last state it was in. */ async hibernate(sandboxId: string): Promise { - const response = await retryWithDelay( - () => - vmHibernate({ - client: this.apiClient, - path: { - id: sandboxId, - }, - }), - 3, - 200 - ); - - handleResponse(response, `Failed to hibernate sandbox ${sandboxId}`); + await this.api.hibernate(sandboxId); } /** @@ -247,19 +164,14 @@ export class Sandboxes { let nextPage: number | null = null; while (true) { - const response = await sandboxList({ - client: this.apiClient, - query: { - tags: opts.tags?.join(","), - page: currentPage, - page_size: pageSize, - order_by: opts.orderBy, - direction: opts.direction, - status: opts.status, - }, + const info = await this.api.listSandboxes({ + tags: opts.tags?.join(","), + page: currentPage, + page_size: pageSize, + order_by: opts.orderBy, + direction: opts.direction, + status: opts.status, }); - - const info = handleResponse(response, "Failed to list sandboxes"); totalCount = info.pagination.total_records; nextPage = info.pagination.next_page; diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 131b465..c9440e9 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -1,26 +1,20 @@ import { promises as fs } from "fs"; import path, { dirname } from "path"; import * as readline from "readline"; -import { type Client } from "@hey-api/client-fetch"; import ora from "ora"; import type * as yargs from "yargs"; import { instrumentedFetch } from "../utils/sentry"; -import { VMTier, CodeSandbox, Sandbox, SandboxClient } from "@codesandbox/sdk"; - -import { - templatesCreate, - vmAssignTagAlias, - VmUpdateSpecsRequest, -} from "../../api-clients/client"; import { - createApiClient, - getDefaultTemplateId, - handleResponse, - retryWithDelay, -} from "../../utils/api"; + VMTier, + CodeSandbox, + Sandbox, + SandboxClient, + API, +} from "@codesandbox/sdk"; +import { VmUpdateSpecsRequest } from "../../api-clients/client"; +import { getDefaultTemplateId, retryWithDelay } from "../../utils/api"; import { getInferredApiKey } from "../../utils/constants"; import { hashDirectory as getFilePaths } from "../utils/files"; -import { startVm } from "../../Sandboxes"; import { mkdir, writeFile } from "fs/promises"; import { sleep } from "../../utils/sleep"; @@ -108,7 +102,7 @@ export const buildCommand: yargs.CommandModule< handler: async (argv) => { const apiKey = getInferredApiKey(); - const apiClient: Client = createApiClient(apiKey, {}, instrumentedFetch); + const api = new API({ apiKey, instrumentation: instrumentedFetch }); const sdk = new CodeSandbox(apiKey); const sandboxTier = argv.vmTier ? VMTier.fromName(argv.vmTier) @@ -126,18 +120,12 @@ export const buildCommand: yargs.CommandModule< const filePaths = await getFilePaths(argv.directory); try { - const templateData = handleResponse( - await templatesCreate({ - client: apiClient, - body: { - forkOf: argv.fromSandbox || getDefaultTemplateId(apiClient), - title: argv.name, - // We filter out sdk-templates on the dashboard - tags: ["sdk-template"], - }, - }), - "Failed to create template" - ); + const templateData = await api.createTemplate({ + forkOf: argv.fromSandbox || getDefaultTemplateId(api.getClient()), + title: argv.name, + // We filter out sdk-templates on the dashboard + tags: ["sdk-template"], + }); const spinner = ora({ stream: process.stdout }); let spinnerMessages: string[] = templateData.sandboxes.map(() => ""); @@ -198,18 +186,13 @@ export const buildCommand: yargs.CommandModule< try { spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); - const startResponse = await retryWithDelay( - () => - withCustomError( - startVm(apiClient, id), - "Failed to start sandbox at all" - ), - 3, - 200 + const startResponse = await withCustomError( + api.startVm(id), + "Failed to start sandbox at all" ); - let sandboxVM = new Sandbox(id, apiClient, startResponse); + let sandboxVM = new Sandbox(id, api, startResponse); - let session = await retryWithDelay(() => sandboxVM.connect(), 3, 100); + let session = await sandboxVM.connect(); spinner.start( updateSpinnerMessage(index, "Writing files to sandbox...") @@ -245,14 +228,9 @@ export const buildCommand: yargs.CommandModule< "Failed to restart sandbox after building" ); - session = await retryWithDelay( - () => - withCustomError( - sandboxVM.connect(), - "Failed to connect to sandbox after building" - ), - 3, - 100 + session = await withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox after building" ); await waitForSetup(session, index); @@ -267,14 +245,9 @@ export const buildCommand: yargs.CommandModule< "Failed to restart sandbox after optimizing initial state" ); - session = await retryWithDelay( - () => - withCustomError( - sandboxVM.connect(), - "Failed to connect to sandbox after optimizing initial state" - ), - 3, - 100 + session = await withCustomError( + sandboxVM.connect(), + "Failed to connect to sandbox after optimizing initial state" ); await waitForSetup(session, index); @@ -413,15 +386,8 @@ export const buildCommand: yargs.CommandModule< let id; if (alias) { - await vmAssignTagAlias({ - client: apiClient, - path: { - alias: alias.alias, - namespace: alias.namespace, - }, - body: { - tag_id: templateData.tag, - }, + await api.assignVmTagAlias(alias.namespace, alias.alias, { + tag_id: templateData.tag, }); id = `${alias.namespace}@${alias.alias}`; diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 62753ed..91a1b94 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -1,11 +1,10 @@ import type * as yargs from "yargs"; -import { previewHostList, previewHostUpdate } from "../../api-clients/client"; -import { createApiClient, handleResponse } from "../../utils/api"; +import { API } from "../../API"; import { getInferredApiKey } from "../../utils/constants"; const apiKey = getInferredApiKey(); -const apiClient = createApiClient(apiKey); +const api = new API({ apiKey }); export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", @@ -17,9 +16,8 @@ export const previewHostsCommand: yargs.CommandModule = { command: "list", describe: "List current preview hosts", handler: async () => { - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - const hosts = data.preview_hosts.map(({ host }) => host); + const response = await api.listPreviewHosts(); + const hosts = response.preview_hosts.map(({ host }) => host); if (hosts.length) { console.log(hosts.join("\n")); } else { @@ -37,19 +35,15 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - let hosts = data.preview_hosts.map(({ host }) => host); + const response = await api.listPreviewHosts(); + let hosts = response.preview_hosts.map(({ host }) => host); const hostToAdd = (argv.host as string).trim(); if (hosts.includes(hostToAdd)) { console.log(`Host already exists: ${hostToAdd}`); return; } hosts.push(hostToAdd); - await previewHostUpdate({ - client: apiClient, - body: { hosts }, - }); + await api.updatePreviewHost({ hosts }); console.log(`Added preview host: ${hostToAdd}`); }, }) @@ -63,19 +57,15 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - let hosts = data.preview_hosts.map(({ host }) => host); + const response = await api.listPreviewHosts(); + let hosts = response.preview_hosts.map(({ host }) => host); const hostToRemove = (argv.host as string).trim(); if (!hosts.includes(hostToRemove)) { console.log(`Host not found: ${hostToRemove}`); return; } hosts = hosts.filter((h) => h !== hostToRemove); - await previewHostUpdate({ - client: apiClient, - body: { hosts }, - }); + await api.updatePreviewHost({ hosts }); console.log(`Removed preview host: ${hostToRemove}`); }, }) @@ -83,17 +73,13 @@ export const previewHostsCommand: yargs.CommandModule = { command: "clear", describe: "Clear all preview hosts", handler: async () => { - const resp = await previewHostList({ client: apiClient }); - const data = handleResponse(resp, "Failed to list preview hosts"); - const hosts = data.preview_hosts.map(({ host }) => host); + const response = await api.listPreviewHosts(); + const hosts = response.preview_hosts.map(({ host }) => host); if (hosts.length === 0) { console.log("Preview host list is already empty."); return; } - await previewHostUpdate({ - client: apiClient, - body: { hosts: [] }, - }); + await api.updatePreviewHost({ hosts: [] }); console.log("Cleared all preview hosts."); }, }); diff --git a/src/bin/commands/sandbox/host-tokens.ts b/src/bin/commands/sandbox/host-tokens.ts index 029ec08..b3ac8ea 100644 --- a/src/bin/commands/sandbox/host-tokens.ts +++ b/src/bin/commands/sandbox/host-tokens.ts @@ -60,16 +60,13 @@ export async function listPreviewTokens(sandboxId: string) { } } -export async function createPreviewToken( - sandboxId: string, - expiresAt?: string -) { +export async function createPreviewToken(sandboxId: string, expiresAt: string) { const sdk = new CodeSandbox(); const spinner = ora("Creating preview token...").start(); try { const token = await sdk.hosts.createToken(sandboxId, { - expiresAt: expiresAt ? new Date(expiresAt) : undefined, + expiresAt: new Date(expiresAt), }); spinner.stop(); diff --git a/src/bin/ui/api.ts b/src/bin/ui/api.ts deleted file mode 100644 index d062bc4..0000000 --- a/src/bin/ui/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Client } from "@hey-api/client-fetch"; -import { sandboxGet, vmListRunningVms } from "../../api-clients/client"; - -function getResponse(resp: { data?: { data?: T }; error: unknown }) { - if (resp.error) { - throw resp.error; - } - - if (!resp.data || !resp.data.data) { - return null; - } - - return resp.data.data; -} - -export async function getSandbox(apiClient: Client, sandboxId: string) { - const resp = await sandboxGet({ client: apiClient, path: { id: sandboxId } }); - - return getResponse(resp); -} - -export async function getRunningVms(apiClient: Client) { - const resp = await vmListRunningVms({ client: apiClient }); - - return getResponse(resp); -} diff --git a/src/bin/ui/sdkContext.tsx b/src/bin/ui/sdkContext.tsx index 9b26887..50a390e 100644 --- a/src/bin/ui/sdkContext.tsx +++ b/src/bin/ui/sdkContext.tsx @@ -1,25 +1,22 @@ import * as React from "react"; import { createContext, useContext } from "react"; import { CodeSandbox } from "@codesandbox/sdk"; -import { createApiClient } from "../../utils/api"; -import { Client } from "@hey-api/client-fetch"; +import { API } from "../../API"; import { getInferredApiKey } from "../../utils/constants"; import { instrumentedFetch } from "../utils/sentry"; -const sdk = new CodeSandbox(); - const apiKey = getInferredApiKey(); -const apiClient: Client = createApiClient(apiKey, {}, instrumentedFetch); +const sdk = new CodeSandbox(apiKey); +const api = new API({ apiKey, instrumentation: instrumentedFetch }); -export const SDKContext = createContext<{ sdk: CodeSandbox; apiClient: Client }>({ +export const SDKContext = createContext<{ sdk: CodeSandbox; api: API }>({ sdk, - apiClient, + api, }); - export const SDKProvider = ({ children }: { children: React.ReactNode }) => { return ( - + {children} ); diff --git a/src/bin/ui/views/Dashboard.tsx b/src/bin/ui/views/Dashboard.tsx index d7c9b10..d19bb00 100644 --- a/src/bin/ui/views/Dashboard.tsx +++ b/src/bin/ui/views/Dashboard.tsx @@ -1,7 +1,6 @@ import React from "react"; import { Box, Text } from "ink"; import { useView } from "../viewContext"; -import { getRunningVms } from "../api"; import { useQuery } from "@tanstack/react-query"; import { useSDK } from "../sdkContext"; import { TextInput } from "../components/TextInput"; @@ -9,11 +8,11 @@ import { VmTable } from "../components/VmTable"; import { useVmInput } from "../hooks/useVmInput"; export const Dashboard = () => { - const { apiClient } = useSDK(); + const { api } = useSDK(); const { data, isLoading } = useQuery({ queryKey: ["runningVms"], - queryFn: () => getRunningVms(apiClient), + queryFn: () => api.listRunningVms(), }); const { setView } = useView(); @@ -71,7 +70,10 @@ export const Dashboard = () => { return ( - Start typing to input an ID or use ↑/↓ arrows to select from running VMs. Press ENTER to view VM details. + + Start typing to input an ID or use ↑/↓ arrows to select from running + VMs. Press ENTER to view VM details. + diff --git a/src/bin/ui/views/Sandbox.tsx b/src/bin/ui/views/Sandbox.tsx index 62e48fd..31baeb1 100644 --- a/src/bin/ui/views/Sandbox.tsx +++ b/src/bin/ui/views/Sandbox.tsx @@ -3,15 +3,15 @@ import { Box, Text, useInput } from "ink"; import { useView } from "../viewContext"; import { useQuery } from "@tanstack/react-query"; import { useSDK } from "../sdkContext"; -import { getRunningVms, getSandbox } from "../api"; export const Sandbox = () => { const { view, setView } = useView<"sandbox">(); + const { sdk, api } = useSDK(); // Poll getRunningVms API every 2 seconds const runningVmsQuery = useQuery({ queryKey: ["runningVms"], - queryFn: () => getRunningVms(apiClient), + queryFn: () => api.listRunningVms(), }); useEffect(() => { @@ -27,7 +27,7 @@ export const Sandbox = () => { const sandboxQuery = useQuery({ queryKey: ["sandbox", view.params.id], - queryFn: () => getSandbox(apiClient, view.params.id), + queryFn: () => api.getSandbox(view.params.id), enabled: !!view.params.id, }); @@ -39,8 +39,6 @@ export const Sandbox = () => { const runningStateRef = useRef(runningState); - const { sdk, apiClient } = useSDK(); - // Only two states: RUNNING or IDLE const [sandboxState, setSandboxState] = useState< "RUNNING" | "IDLE" | "PENDING" diff --git a/src/index.ts b/src/index.ts index 7022a57..9a45cbe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,10 @@ export { VMTier } from "./VMTier"; export * from "./Sandbox"; export * from "./types"; +export { API } from "./API"; import { HostTokens } from "./HostTokens"; -import { createApiClient } from "./utils/api"; +import { API } from "./API"; import { ClientOpts } from "./types"; import { getInferredApiKey } from "./utils/constants"; @@ -25,9 +26,9 @@ export class CodeSandbox { constructor(apiToken?: string, opts: ClientOpts = {}) { const apiKey = apiToken || getInferredApiKey(); - const apiClient = createApiClient(apiKey, opts); + const api = new API({ apiKey, config: opts }); - this.sandboxes = new Sandboxes(apiClient); - this.hosts = new HostTokens(apiClient); + this.sandboxes = new Sandboxes(api); + this.hosts = new HostTokens(api); } } diff --git a/src/utils/api.ts b/src/utils/api.ts index 09e06f5..a47c93f 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -78,26 +78,6 @@ export function getStartOptions(opts: StartSandboxOpts | undefined) { }; } -export function getStartResponse( - response: VmStartResponse["data"] | null -): PitcherManagerResponse { - if (!response) { - throw new Error("No start response"); - } - - return { - bootupType: response.bootup_type as PitcherManagerResponse["bootupType"], - cluster: response.cluster, - pitcherURL: response.pitcher_url, - workspacePath: response.workspace_path, - userWorkspacePath: response.user_workspace_path, - pitcherManagerVersion: response.pitcher_manager_version, - pitcherVersion: response.pitcher_version, - latestPitcherVersion: response.latest_pitcher_version, - pitcherToken: response.pitcher_token, - }; -} - export function getDefaultTemplateTag(apiClient: Client): string { if (apiClient.getConfig().baseUrl?.includes("codesandbox.stream")) { return "7ngcrf"; From 3a6f9ea213d978dc5a896bfc4d275deae6608abe Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 09:35:26 +0200 Subject: [PATCH 195/241] fix: Decouple pitcher-client (#148) --- build/plugins.cjs | 24 - esbuild.cjs | 17 +- package-lock.json | 852 +----------------- package.json | 1 - src/API.ts | 2 +- src/{node => AgentClient}/AgentConnection.ts | 0 .../PendingPitcherMessage.ts | 0 src/AgentClient/SerialQueue.ts | 101 +++ src/{node => AgentClient}/WebSocketClient.ts | 12 + .../agent-client-interface.ts | 3 + .../AgentClient.ts => AgentClient/index.ts} | 87 +- src/Sandbox.ts | 61 +- src/SandboxClient/commands.ts | 2 +- src/SandboxClient/filesystem.ts | 2 +- src/SandboxClient/index.ts | 73 +- src/SandboxClient/ports.ts | 2 +- src/SandboxClient/setup.ts | 2 +- src/SandboxClient/tasks.ts | 2 +- src/SandboxClient/terminals.ts | 2 +- src/Sandboxes.ts | 1 - src/browser/BrowserAgentClient.ts | 148 --- src/browser/index.ts | 84 +- src/browser/previews/Preview.ts | 2 +- src/node/index.ts | 50 +- src/pitcher-protocol/messages/ai.ts | 26 +- src/pitcher-protocol/messages/file.ts | 108 +-- src/pitcher-protocol/messages/fs.ts | 122 +-- src/pitcher-protocol/messages/git.ts | 7 +- src/pitcher-protocol/messages/notification.ts | 7 +- src/pitcher-protocol/messages/setup.ts | 3 +- src/pitcher-protocol/messages/shell.ts | 3 +- src/types.ts | 13 +- src/utils/api.ts | 2 - 33 files changed, 408 insertions(+), 1413 deletions(-) rename src/{node => AgentClient}/AgentConnection.ts (100%) rename src/{node => AgentClient}/PendingPitcherMessage.ts (100%) create mode 100644 src/AgentClient/SerialQueue.ts rename src/{node => AgentClient}/WebSocketClient.ts (95%) rename src/{node => AgentClient}/agent-client-interface.ts (98%) rename src/{node/AgentClient.ts => AgentClient/index.ts} (78%) delete mode 100644 src/browser/BrowserAgentClient.ts diff --git a/build/plugins.cjs b/build/plugins.cjs index 863fe2a..29ed21b 100644 --- a/build/plugins.cjs +++ b/build/plugins.cjs @@ -12,27 +12,3 @@ module.exports.moduleReplacementPlugin = function moduleReplacementPlugin( }, }; }; - -module.exports.forbidImportsPlugin = function forbidImportsPlugin(imports) { - return { - name: "forbid-imports", - setup(build) { - for (const packageName of imports) { - // catch `import ... from 'packageName'` **and** sub-paths `packageName/foo` - const pkgFilter = new RegExp(`^${packageName}($|/)`); - build.onResolve({ filter: pkgFilter }, (args) => { - return { - errors: [ - { - text: `❌ Importing “${packageName}” is forbidden in this project.`, - notes: [ - "If you really need it, talk to your team lead about an exception.", - ], - }, - ], - }; - }); - } - }, - }; -}; diff --git a/esbuild.cjs b/esbuild.cjs index 55aee88..6eee9e1 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -1,17 +1,7 @@ const { join } = require("path"); const esbuild = require("esbuild"); const { externalModules, define } = require("./build/utils.cjs"); -const { - moduleReplacementPlugin, - forbidImportsPlugin, -} = require("./build/plugins.cjs"); - -// Until pitcher-client is part of SDK we need to forbid these imports in -// Node builds -const preventPitcherClientImportsPlugin = forbidImportsPlugin([ - "@codesandbox/pitcher-protocol", - "@codesandbox/pitcher-common", -]); +const { moduleReplacementPlugin } = require("./build/plugins.cjs"); const devtoolsStubPlugin = { name: "stub-react-devtools", @@ -86,7 +76,6 @@ const nodeClientCjsBuild = esbuild.build({ outfile: "dist/cjs/node.cjs", platform: "node", external: externalModules, - plugins: [preventPitcherClientImportsPlugin], }); const nodeClientEsmBuild = esbuild.build({ @@ -96,7 +85,6 @@ const nodeClientEsmBuild = esbuild.build({ outfile: "dist/esm/node.js", platform: "node", external: externalModules, - plugins: [preventPitcherClientImportsPlugin], }); /** @@ -121,7 +109,6 @@ const sdkEsmBuild = esbuild.build({ platform: "node", outfile: "dist/esm/index.js", external: externalModules, - plugins: [preventPitcherClientImportsPlugin], }); /** @@ -147,7 +134,7 @@ const cliBuild = esbuild.build({ ), "@codesandbox/sdk", ], - plugins: [preventPitcherClientImportsPlugin, devtoolsStubPlugin], + plugins: [devtoolsStubPlugin], }); Promise.all([ diff --git a/package-lock.json b/package-lock.json index 076f042..a8e8f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.0.7", "license": "MIT", "dependencies": { - "@codesandbox/pitcher-client": "1.1.7", "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", @@ -56,23 +55,6 @@ "@sentry/node": "^9.29.0" } }, - "node_modules/@absinthe/socket": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@absinthe/socket/-/socket-0.2.1.tgz", - "integrity": "sha512-rCuMRG4WndooGR+QfU5v+xL6U8YKEXFyvjqYt0qTHupAh+k+tpD6a5dlxcLO0g38p/hb1I12OzKvl+0G1XYCkA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "7.2.0", - "@jumpn/utils-array": "0.3.4", - "@jumpn/utils-composite": "0.7.0", - "@jumpn/utils-graphql": "0.6.0", - "core-js": "2.6.0", - "zen-observable": "0.8.11" - }, - "peerDependencies": { - "phoenix": "^1.4.0" - } - }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", @@ -110,165 +92,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/runtime": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz", - "integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.12.0" - } - }, - "node_modules/@codesandbox/api": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@codesandbox/api/-/api-1.1.3.tgz", - "integrity": "sha512-GHrxd/tL4CHy/+42Qx0RC65EHUH6jjzuRm+JcBgAlRcMCJPvSTANpoNgI6fQKofvEJIVc4ghqR7X87j6emEZyg==", - "license": "MIT", - "dependencies": { - "@absinthe/socket": "^0.2.1", - "@codesandbox/create-gql-api": "^1.0.1", - "class-states": "1.0.15", - "humps": "^2.0.1", - "phoenix": "^1.6.6", - "preact": "^10.22.0", - "universal-cookie": "^4.0.4" - } - }, - "node_modules/@codesandbox/api/node_modules/class-states": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.15.tgz", - "integrity": "sha512-ReR0LKl1C3tK+Wwe2zlAtXEjLavuR3+m+oFFNELdJmywEoh00iDYCWLxWJof83YSpqIBK/iegy0blUuktJ7+6A==", - "license": "ISC" - }, - "node_modules/@codesandbox/create-gql-api": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@codesandbox/create-gql-api/-/create-gql-api-1.0.1.tgz", - "integrity": "sha512-k+C8OS2wYdakIoYEtHjW+pvwb7uq+emHQX5W1tqJQ9gDEYYy2dHItQNulY/8H0ymudhW3b6zcKTVNTMl9QgGWQ==", - "license": "ISC", - "dependencies": { - "get-graphql-schema": "^2.1.2", - "graphql": "^16.8.1" - }, - "bin": { - "create-gql-api": "bin/index.js" - } - }, - "node_modules/@codesandbox/nodebox": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.9.tgz", - "integrity": "sha512-h/SCSA5iasmEUlxWWQf2ecv1WgGoI5KLV5iZoN9YA1Hhz4DIacE3vwwD24Vn+1qczny4TEr8zwMQYZ1K8TbvzQ==", - "license": "SEE LICENSE IN ./LICENSE", - "dependencies": { - "outvariant": "^1.4.0", - "strict-event-emitter": "^0.4.3" - } - }, - "node_modules/@codesandbox/pitcher-client": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-client/-/pitcher-client-1.1.7.tgz", - "integrity": "sha512-D3KkU+nDUR6kXCsnsEFaYMjkWhsum7imD6aduG44cxYmZi1jlqnUb1FCoDW2Sj4+6LmhFOnpUdSE1nnkJ2eoFQ==", - "license": "GPL-3.0", - "dependencies": { - "@codesandbox/api": "^1.1.3", - "@codesandbox/nodebox": "^0.1.8", - "@codesandbox/pitcher-common": "^0.360.2", - "@codesandbox/pitcher-protocol": "^0.360.4", - "@codesandbox/sandpack-client": "^2.18.0", - "@types/ws": "^7.4.7", - "class-states": "1.0.16", - "debug": "^4.3.4", - "isomorphic-ws": "^5.0.0", - "js-untar": "^2.0.0", - "jszip": "^3.10.1", - "minimatch": "^10.0.1", - "semver": "^7.3.5", - "vscode-jsonrpc": "^8.2.0" - } - }, - "node_modules/@codesandbox/pitcher-client/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@codesandbox/pitcher-common": { - "version": "0.360.2", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-common/-/pitcher-common-0.360.2.tgz", - "integrity": "sha512-FWy4YgDh0LFZRo9N8j7mTSoKyIftWJVs/9LR5rwzAta0lFUAV2X8hsGMzt0u16Ng5Wpbi92svMZQy6WygC93gg==", - "license": "GPL-3.0", - "dependencies": { - "@emotion/hash": "^0.8.0", - "@types/micromatch": "^4.0.2", - "@types/retry": "^0.12.2", - "cross-fetch": "^4.0.0", - "lru_map": "^0.4.1", - "micromatch": "^4.0.4", - "oo-ascii-tree": "^1.34.0", - "p-queue": "^6.6.2", - "retry": "^0.13.1", - "strip-json-comments": "^3.1.1", - "tiny-invariant": "^1.2.0", - "ts-mixer": "^6.0.0", - "type-fest": "^2.11.1", - "uuid": "^11.0.2", - "vscode-diff": "^2.0.1" - } - }, - "node_modules/@codesandbox/pitcher-protocol": { - "version": "0.360.4", - "resolved": "https://registry.npmjs.org/@codesandbox/pitcher-protocol/-/pitcher-protocol-0.360.4.tgz", - "integrity": "sha512-oPxA2/F/ywyR73elJwdFOsw3D+rOId2UTNAXnRrTGjh66Ujyx/IFGVqfTlibGaQUg2HENkRoiRsvRJa5qphITA==", - "license": "GPL-3.0", - "dependencies": { - "@codesandbox/pitcher-common": "0.360.2", - "@msgpack/msgpack": "^2.7.1" - } - }, - "node_modules/@codesandbox/pitcher-protocol/node_modules/@msgpack/msgpack": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", - "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@codesandbox/sandpack-client": { - "version": "2.19.8", - "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", - "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", - "license": "Apache-2.0", - "dependencies": { - "@codesandbox/nodebox": "0.1.8", - "buffer": "^6.0.3", - "dequal": "^2.0.2", - "mime-db": "^1.52.0", - "outvariant": "1.4.0", - "static-browser-server": "1.0.3" - } - }, - "node_modules/@codesandbox/sandpack-client/node_modules/@codesandbox/nodebox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", - "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", - "license": "SEE LICENSE IN ./LICENSE", - "dependencies": { - "outvariant": "^1.4.0", - "strict-event-emitter": "^0.4.3" - } - }, - "node_modules/@codesandbox/sandpack-client/node_modules/outvariant": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", - "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", - "license": "MIT" - }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -279,12 +102,6 @@ "node": ">=0.1.90" } }, - "node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "license": "MIT" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -842,60 +659,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jumpn/utils-array": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jumpn/utils-array/-/utils-array-0.3.4.tgz", - "integrity": "sha512-ExRwf0b0NMyMn6HLStMeqEEtmblV0BKVZ4YT3FhEJ5ErZSPN4Vv6xYUJQGNG0b7QGZJIN2KetxEoOm4MYmXygw==", - "license": "MIT", - "dependencies": { - "babel-polyfill": "6.26.0", - "babel-runtime": "6.26.0", - "flow-static-land": "0.2.7" - } - }, - "node_modules/@jumpn/utils-composite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@jumpn/utils-composite/-/utils-composite-0.7.0.tgz", - "integrity": "sha512-kamRVYJLNvjMrnKKeu2RSFQHLUO/IYFo05gLI7GQcCk063mJzsjCCfRycCievIBI+5Sg8C7A5gwRYxkBA5jY8w==", - "license": "MIT", - "dependencies": { - "@jumpn/utils-array": "0.3.4", - "babel-polyfill": "6.26.0", - "babel-runtime": "6.26.0", - "fast-deep-equal": "1.0.0", - "flow-static-land": "0.2.8" - } - }, - "node_modules/@jumpn/utils-composite/node_modules/flow-static-land": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.8.tgz", - "integrity": "sha512-pOZFExu2rbscCgcEo7nL7FNhBubMi18dn1Un4lm8LOmQkYhgsHLsrBGMWmuJXRWcYMrOC7I/bPsiqqVjdD3K1g==", - "license": "MIT" - }, - "node_modules/@jumpn/utils-graphql": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@jumpn/utils-graphql/-/utils-graphql-0.6.0.tgz", - "integrity": "sha512-I5OSEh8Ed4FdLIcUTYzWdpO9noQOoWptdgF8yOZ0xhDD7h7E9IgPYxfy36qbC6v9xlpGTwQMu3Wn8ulkinG/MQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "7.2.0", - "core-js": "2.6.0", - "graphql": "14.0.2" - } - }, - "node_modules/@jumpn/utils-graphql/node_modules/graphql": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", - "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", - "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", - "license": "MIT", - "dependencies": { - "iterall": "^1.2.2" - }, - "engines": { - "node": ">= 6.x" - } - }, "node_modules/@msgpack/msgpack": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.1.tgz", @@ -905,12 +668,6 @@ "node": ">= 18" } }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "license": "MIT" - }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -1949,12 +1706,6 @@ "@types/node": "*" } }, - "node_modules/@types/braces": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", - "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", - "license": "MIT" - }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1965,12 +1716,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1978,15 +1723,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/micromatch": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", - "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", - "license": "MIT", - "dependencies": { - "@types/braces": "*" - } - }, "node_modules/@types/mysql": { "version": "2.15.26", "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", @@ -2001,6 +1737,7 @@ "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2038,12 +1775,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/retry": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.5.tgz", - "integrity": "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==", - "license": "MIT" - }, "node_modules/@types/shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", @@ -2061,15 +1792,6 @@ "@types/node": "*" } }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -2214,63 +1936,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - } - }, - "node_modules/babel-polyfill/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "license": "MIT" - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "devOptional": true, "license": "MIT" }, "node_modules/blessed": { @@ -2346,6 +2016,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2355,6 +2026,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2453,30 +2125,6 @@ "node": ">= 0.12" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-browserify": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/buffer-browserify/-/buffer-browserify-0.2.5.tgz", @@ -2677,12 +2325,6 @@ "license": "MIT", "optional": true }, - "node_modules/class-states": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/class-states/-/class-states-1.0.16.tgz", - "integrity": "sha512-DhQHga7pUo3LRfZawNIw+4yHBx9AncBAjXv4YXjwOLf06ZnxCR/hi2hPqMhZr//NhwhLlq9EmmWXM0o7BZgnNw==", - "license": "ISC" - }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -2949,22 +2591,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-js": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", - "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "license": "MIT" - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3018,15 +2644,6 @@ "sha.js": "^2.4.8" } }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", - "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3090,6 +2707,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", + "optional": true, "dependencies": { "ms": "^2.1.3" }, @@ -3135,15 +2753,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -3197,6 +2806,7 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3430,12 +3040,6 @@ "node": ">=0.4.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -3447,12 +3051,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha512-46+Jxk9Yj/nQY+3a1KTnpbBTemcAbPySTKya8iM9D7EsiONpSWbvzesalcCJ6tmJrCUITT2fmAQfNHFG+OHM6Q==", - "license": "MIT" - }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -3472,6 +3070,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3480,12 +3079,6 @@ "node": ">=8" } }, - "node_modules/flow-static-land": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/flow-static-land/-/flow-static-land-0.2.7.tgz", - "integrity": "sha512-SmGgln4qcqLAysXM5BF8EQS+clj0TUf9+KlZUJgwuzNHvwbput+opz3CMyee9sDuLf4J/3sJbYGjdNsd2jSurA==", - "license": "MIT" - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3581,96 +3174,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-graphql-schema": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/get-graphql-schema/-/get-graphql-schema-2.1.2.tgz", - "integrity": "sha512-1z5Hw91VrE3GrpCZE6lE8Dy+jz4kXWesLS7rCSjwOxf5BOcIedAZeTUJRIeIzmmR+PA9CKOkPTYFRJbdgUtrxA==", - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "graphql": "^14.0.2", - "minimist": "^1.2.0", - "node-fetch": "^2.2.0" - }, - "bin": { - "get-graphql-schema": "dist/index.js" - } - }, - "node_modules/get-graphql-schema/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-graphql-schema/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-graphql-schema/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/get-graphql-schema/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/get-graphql-schema/node_modules/graphql": { - "version": "14.7.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", - "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", - "deprecated": "No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support", - "license": "MIT", - "dependencies": { - "iterall": "^1.2.2" - }, - "engines": { - "node": ">= 6.x" - } - }, - "node_modules/get-graphql-schema/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/get-graphql-schema/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3776,15 +3279,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphql": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", - "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -3922,32 +3416,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/humps": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", - "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", - "license": "MIT" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", @@ -3958,12 +3426,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/import-in-the-middle": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz", @@ -4297,6 +3759,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4351,6 +3814,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -4381,12 +3845,6 @@ "ws": "*" } }, - "node_modules/iterall": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", - "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", - "license": "MIT" - }, "node_modules/jackspeak": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", @@ -4419,12 +3877,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, - "node_modules/js-untar": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", - "integrity": "sha512-7CsDLrYQMbLxDt2zl9uKaPZSdmJMvGGQ7wo9hoB3J+z/VcO2w63bXFgHVnjF1+S9wD3zAu8FBVj7EYWjTQ3Z7g==", - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4438,27 +3890,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -4505,12 +3936,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru_map": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", - "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", @@ -4629,6 +4054,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4659,15 +4085,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4707,6 +4124,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4722,6 +4140,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4808,7 +4227,8 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/neo-async": { "version": "2.6.2", @@ -4832,26 +4252,6 @@ "lodash": "^4.17.21" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-fetch-native": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", @@ -4921,15 +4321,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/oo-ascii-tree": { - "version": "1.112.0", - "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-1.112.0.tgz", - "integrity": "sha512-qQH4jZSdabcKpwcqvJTi7eQL86UucvMacbaHiiIrOynT8jhTLtKS2ixaXgGlNBMeN9UhFi1wS00Hnxhw9aYLsA==", - "license": "Apache-2.0", - "engines": { - "node": ">= 14.17.0" - } - }, "node_modules/optimist": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", @@ -5001,49 +4392,6 @@ "dev": true, "license": "MIT" }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "license": "MIT" - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -5051,12 +4399,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parse-asn1": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", @@ -5215,16 +4557,11 @@ "node": ">=4" } }, - "node_modules/phoenix": { - "version": "1.7.21", - "resolved": "https://registry.npmjs.org/phoenix/-/phoenix-1.7.21.tgz", - "integrity": "sha512-8wOvJ8pQXRxNbyFlMI+wQhK3bvX4Ps3FtUX2+0cV6lkcebe9VTIl+xS60FLAeaPieFg80djor5z/AKofnwqAUw==", - "license": "MIT" - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5329,16 +4666,6 @@ "node": ">=0.10.0" } }, - "node_modules/preact": { - "version": "10.26.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", - "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -5368,6 +4695,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, "license": "MIT" }, "node_modules/public-encrypt": { @@ -5458,6 +4786,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -5473,6 +4802,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/readdirp": { @@ -5504,12 +4834,6 @@ "esprima": "~4.0.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5571,15 +4895,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/rimraf": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", @@ -5691,12 +5006,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -5856,18 +5165,6 @@ "dev": true, "license": "MIT" }, - "node_modules/static-browser-server": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", - "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", - "license": "Apache-2.0", - "dependencies": { - "@open-draft/deferred-promise": "^2.1.0", - "dotenv": "^16.0.3", - "mime-db": "^1.52.0", - "outvariant": "^1.3.0" - } - }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -5880,16 +5177,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strict-event-emitter": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", - "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", - "license": "MIT" - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -5899,6 +5191,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/string-width": { @@ -6026,18 +5319,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -6118,12 +5399,6 @@ "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==" }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -6135,6 +5410,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -6143,18 +5419,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6162,18 +5426,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -6213,18 +5465,9 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, "license": "MIT" }, - "node_modules/universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "license": "MIT", - "dependencies": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" - } - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -6242,52 +5485,9 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/vscode-diff": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/vscode-diff/-/vscode-diff-2.1.1.tgz", - "integrity": "sha512-S2BwZbrQCk4N6FqgYQQPlQ44OZKZNcS2VwhMj4xU8QvixXN9GeNQnN7/7XHFbwrs6h5BiLADDcERTrKvfWeG9g==", - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6611,12 +5811,6 @@ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "license": "MIT" - }, - "node_modules/zen-observable": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.11.tgz", - "integrity": "sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==", - "license": "MIT" } } } diff --git a/package.json b/package.json index be3cd29..17b4966 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "why-is-node-running": "^2.3.0" }, "dependencies": { - "@codesandbox/pitcher-client": "1.1.7", "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", diff --git a/src/API.ts b/src/API.ts index 2a6507f..ae0bda7 100644 --- a/src/API.ts +++ b/src/API.ts @@ -54,7 +54,7 @@ import type { PreviewHostCreateData, PreviewHostUpdateData, } from "./api-clients/client"; -import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; +import { PitcherManagerResponse } from "./types"; async function enhanceFetch( request: Request, diff --git a/src/node/AgentConnection.ts b/src/AgentClient/AgentConnection.ts similarity index 100% rename from src/node/AgentConnection.ts rename to src/AgentClient/AgentConnection.ts diff --git a/src/node/PendingPitcherMessage.ts b/src/AgentClient/PendingPitcherMessage.ts similarity index 100% rename from src/node/PendingPitcherMessage.ts rename to src/AgentClient/PendingPitcherMessage.ts diff --git a/src/AgentClient/SerialQueue.ts b/src/AgentClient/SerialQueue.ts new file mode 100644 index 0000000..a025484 --- /dev/null +++ b/src/AgentClient/SerialQueue.ts @@ -0,0 +1,101 @@ +export type PromiseCallback = () => Promise; + +export type QueueCallback = PromiseCallback | (() => T); + +interface QueueItem { + key?: string; + callback: QueueCallback; + resolves: ((val: any) => void)[]; + rejects: ((err: any) => void)[]; +} + +/** + * SerialQueue runs 1 promise at a time, very useful for reducing load and working around lock files + */ +export class SerialQueue { + private items: QueueItem[] = []; + private isProcessing = false; + + constructor(private name: string, private debug: boolean = false) {} + + private async processQueue() { + if (this.isProcessing) return; + + const item = this.items.shift(); + if (item) { + if (this.debug) { + console.log(`Running queue item ${this.name}#${item.key ?? "unknown"}`); + } + + this.isProcessing = true; + try { + const result = await item.callback(); + for (const resolve of item.resolves) { + try { + resolve(result); + } catch (err) { + // do nothing + } + } + } catch (err) { + for (const reject of item.rejects) { + try { + reject(err); + } catch (err) { + // do nothing + } + } + } + this.isProcessing = false; + + if (this.debug) { + console.log( + `Processed queue item ${this.name}#${item.key ?? "unknown"}` + ); + } + + // Process next item + this.processQueue(); + } + } + + /** + * Add a new promise callback to the queue + * + * in case you provide a key it will be used to de-duplicate against existing items in the queue + * if there is an existing item, the callback of that item will be used and this function will + * return the result of that callback instead + */ + add(callback: QueueCallback, key?: string): Promise { + if (this.debug) { + console.log(`Adding item ${this.name}#${key ?? "unknown"} to the queue`); + } + + return new Promise((resolve, reject) => { + let shouldAdd = true; + let item: QueueItem = { + key, + callback, + resolves: [], + rejects: [], + }; + + if (key) { + const foundItem = this.items.find((i) => i.key === key); + if (foundItem) { + item = foundItem; + shouldAdd = false; + } + } + + item.resolves.push(resolve); + item.rejects.push(reject); + + if (shouldAdd) { + this.items.push(item); + } + + this.processQueue().catch(console.error); + }); + } +} diff --git a/src/node/WebSocketClient.ts b/src/AgentClient/WebSocketClient.ts similarity index 95% rename from src/node/WebSocketClient.ts rename to src/AgentClient/WebSocketClient.ts index cd8d2c7..acda7bb 100644 --- a/src/node/WebSocketClient.ts +++ b/src/AgentClient/WebSocketClient.ts @@ -1,6 +1,7 @@ import WebSocket from "isomorphic-ws"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; +import { SerialQueue } from "./SerialQueue"; export type WebsocketData = string | Uint8Array; @@ -50,6 +51,7 @@ if (typeof process !== "undefined" && process.env.NODE_ENV === "test") { export class WebSocketClient extends Disposable { private ws: WebSocket; private pongDetectionTimeout = INIT_DETECT_PONG_TIMEOUT; + private bufferQueue = new SerialQueue("websocket-buffer-queue"); private onMessageEmitter: Emitter = new Emitter(); /** * Whenever we are disconnected we will create a new WebSocketClient @@ -112,6 +114,16 @@ export class WebSocketClient extends Disposable { // We clear the PONG detection regardless of what message we got clearTimeout(this.detectDisconnectByPongTimeout); + // Browser environment + if (typeof window !== "undefined" && data instanceof window.Blob) { + // To ensure that messages are emitted in order we use a serial queue + this.bufferQueue.add(async () => { + this.emitMessage(new Uint8Array(await data.arrayBuffer())); + }); + + return; + } + // Node environment if (typeof data !== "string") { this.emitMessage(data); diff --git a/src/node/agent-client-interface.ts b/src/AgentClient/agent-client-interface.ts similarity index 98% rename from src/node/agent-client-interface.ts rename to src/AgentClient/agent-client-interface.ts index c1e3cb6..c84dca5 100644 --- a/src/node/agent-client-interface.ts +++ b/src/AgentClient/agent-client-interface.ts @@ -7,6 +7,7 @@ import { shell, setup, task, + system, } from "../pitcher-protocol"; import { Event } from "../utils/event"; @@ -115,6 +116,7 @@ export interface IAgentClientTasks { } export interface IAgentClientSystem { + onInitStatusUpdate: Event; update(): Promise>; } @@ -137,6 +139,7 @@ export interface IAgentClient { setup: IAgentClientSetup; tasks: IAgentClientTasks; system: IAgentClientSystem; + ping(): void; disconnect(): Promise; reconnect(): Promise; dispose(): void; diff --git a/src/node/AgentClient.ts b/src/AgentClient/index.ts similarity index 78% rename from src/node/AgentClient.ts rename to src/AgentClient/index.ts index a26f708..e195fb5 100644 --- a/src/node/AgentClient.ts +++ b/src/AgentClient/index.ts @@ -1,12 +1,12 @@ import { fs, - git, PitcherErrorCode, PitcherRequest, port, setup, shell, task, + version, } from "../pitcher-protocol"; import { IAgentClient, @@ -19,10 +19,18 @@ import { PickRawFsResult, } from "./agent-client-interface"; import { AgentConnection } from "./AgentConnection"; -import { Emitter } from "../utils/event"; -import { SandboxSession } from "../types"; +import { Emitter, Event } from "../utils/event"; +import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; +import { SandboxClient } from "../SandboxClient"; +import { InitStatus } from "../pitcher-protocol/messages/system"; -class NodeAgentClientShells implements IAgentClientShells { +// Timeout for detecting a pong response, leading to a forced disconnect +let PONG_DETECTION_TIMEOUT = 15_000; + +// When focusing the app we do a lower timeout to more quickly detect a potential disconnect +const FOCUS_PONG_DETECTION_TIMEOUT = 5_000; + +class AgentClientShells implements IAgentClientShells { private onShellExitedEmitter = new Emitter<{ shellId: string; exitCode: number; @@ -76,7 +84,6 @@ class NodeAgentClientShells implements IAgentClientShells { return this.agentConnection.request({ method: "shell/terminate", params: { - // We do can not import Id from pitcher-client shellId, }, }); @@ -130,7 +137,7 @@ class NodeAgentClientShells implements IAgentClientShells { } } -class NodeAgentClientFS implements IAgentClientFS { +class AgentClientFS implements IAgentClientFS { constructor( private agentConnection: AgentConnection, private workspacePath: string @@ -284,7 +291,7 @@ class NodeAgentClientFS implements IAgentClientFS { } } -class NodeAgentClientPorts implements IAgentClientPorts { +class AgentClientPorts implements IAgentClientPorts { private onPortsUpdatedEmitter = new Emitter(); onPortsUpdated = this.onPortsUpdatedEmitter.event; @@ -303,7 +310,7 @@ class NodeAgentClientPorts implements IAgentClientPorts { } } -class NodeAgentClientSetup implements IAgentClientSetup { +class AgentClientSetup implements IAgentClientSetup { private onSetupProgressUpdateEmitter = new Emitter(); onSetupProgressUpdate = this.onSetupProgressUpdateEmitter.event; constructor(private agentConnection: AgentConnection) { @@ -325,7 +332,7 @@ class NodeAgentClientSetup implements IAgentClientSetup { } } -class NodeAgentClientTasks implements IAgentClientTasks { +class AgentClientTasks implements IAgentClientTasks { private onTaskUpdateEmitter = new Emitter(); onTaskUpdate = this.onTaskUpdateEmitter.event; constructor(private agentConnection: AgentConnection) { @@ -365,8 +372,14 @@ class NodeAgentClientTasks implements IAgentClientTasks { } } -class NodeAgentClientSystem implements IAgentClientSystem { - constructor(private agentConnection: AgentConnection) {} +class AgentClientSystem implements IAgentClientSystem { + constructor(private agentConnection: AgentConnection) { + this.agentConnection.onNotification("system/initStatus", (params) => { + this.onInitStatusUpdateEmitter.fire(params); + }); + } + private onInitStatusUpdateEmitter = new Emitter(); + onInitStatusUpdate = this.onInitStatusUpdateEmitter.event; update() { return this.agentConnection.request({ method: "system/update", @@ -375,7 +388,42 @@ class NodeAgentClientSystem implements IAgentClientSystem { } } -export class NodeAgentClient implements IAgentClient { +export class AgentClient implements IAgentClient { + static async create({ + session, + getSession, + }: { + session: SandboxSession; + getSession: (sandboxId: string) => Promise; + }) { + const url = `${session.pitcherURL}/?token=${session.pitcherToken}`; + const agentConnection = await AgentConnection.create(url); + const joinResult = await agentConnection.request({ + method: "client/join", + params: { + clientInfo: { + protocolVersion: version, + appId: "sdk", + }, + asyncProgress: true, + subscriptions: DEFAULT_SUBSCRIPTIONS, + }, + }); + + // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects + agentConnection.connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); + + return { + client: new AgentClient(getSession, agentConnection, { + sandboxId: session.sandboxId, + workspacePath: session.userWorkspacePath, + reconnectToken: joinResult.reconnectToken, + isUpToDate: session.latestPitcherVersion === session.pitcherVersion, + }), + joinResult, + }; + } + sandboxId: string; workspacePath: string; isUpToDate: boolean; @@ -384,12 +432,12 @@ export class NodeAgentClient implements IAgentClient { return this.agentConnection.state; } onStateChange = this.agentConnection.onStateChange; - shells = new NodeAgentClientShells(this.agentConnection); - fs = new NodeAgentClientFS(this.agentConnection, this.params.workspacePath); - setup = new NodeAgentClientSetup(this.agentConnection); - tasks = new NodeAgentClientTasks(this.agentConnection); - system = new NodeAgentClientSystem(this.agentConnection); - ports = new NodeAgentClientPorts(this.agentConnection); + shells = new AgentClientShells(this.agentConnection); + fs = new AgentClientFS(this.agentConnection, this.params.workspacePath); + setup = new AgentClientSetup(this.agentConnection); + tasks = new AgentClientTasks(this.agentConnection); + system = new AgentClientSystem(this.agentConnection); + ports = new AgentClientPorts(this.agentConnection); constructor( private getSession: (sandboxId: string) => Promise, @@ -406,6 +454,9 @@ export class NodeAgentClient implements IAgentClient { this.isUpToDate = params.isUpToDate; this.reconnectToken = params.reconnectToken; } + ping() { + this.agentConnection.connection.ping(FOCUS_PONG_DETECTION_TIMEOUT); + } async disconnect(): Promise { await this.agentConnection.disconnect(); } diff --git a/src/Sandbox.ts b/src/Sandbox.ts index c32ac79..9a0c0bc 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -1,11 +1,10 @@ import { + type SessionCreateOptions, + type SandboxSession, PitcherManagerResponse, - type protocol as _protocol, -} from "@codesandbox/pitcher-client"; -import { type SessionCreateOptions, type SandboxSession } from "./types"; +} from "./types"; import { VMTier } from "./VMTier"; import { API } from "./API"; -import { connectToSandbox } from "./node"; import { SandboxClient } from "./SandboxClient"; import { StartSandboxOpts } from "./types"; import { retryWithDelay } from "./utils/api"; @@ -70,11 +69,9 @@ export class Sandbox { customSession: SessionCreateOptions, session: SandboxSession ) { - const client = await connectToSandbox({ - session, - getSession: async () => - this.getSession(await this.api.startVm(this.id), customSession), - }); + const client = await SandboxClient.create(session, async () => + this.getSession(await this.api.startVm(this.id), customSession) + ); if (customSession.env) { const envStrings = Object.entries(customSession.env) @@ -159,28 +156,30 @@ export class Sandbox { } async connect(customSession?: SessionCreateOptions) { - return await retryWithDelay(async () => { - const session = await this.getSession( - this.pitcherManagerResponse, - customSession - ); - - let client: SandboxClient | undefined; - - // We might create a client here if git or env is configured, we can reuse that - if (customSession) { - client = await this.initializeCustomSession(customSession, session); - } - - return ( - client || - connectToSandbox({ - session, - getSession: async () => - this.getSession(await this.api.startVm(this.id), customSession), - }) - ); - }, 3, 100); + return await retryWithDelay( + async () => { + const session = await this.getSession( + this.pitcherManagerResponse, + customSession + ); + + let client: SandboxClient | undefined; + + // We might create a client here if git or env is configured, we can reuse that + if (customSession) { + client = await this.initializeCustomSession(customSession, session); + } + + return ( + client || + SandboxClient.create(session, async () => + this.getSession(await this.api.startVm(this.id), customSession) + ) + ); + }, + 3, + 100 + ); } /** diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 1e565bf..98c1db6 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -1,6 +1,6 @@ import { Disposable, DisposableStore } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../node/agent-client-interface"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; import * as protocol from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index 4c578d1..0f8612b 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -1,4 +1,4 @@ -import { type IAgentClient } from "../node/agent-client-interface"; +import { type IAgentClient } from "../AgentClient/agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 9576d77..337ee13 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -1,4 +1,5 @@ import { Disposable } from "../utils/disposable"; +import { retryWithDelay } from "../utils/api"; import { FileSystem } from "./filesystem"; import { Ports } from "./ports"; @@ -9,10 +10,11 @@ import { Terminals } from "./terminals"; import { SandboxCommands } from "./commands"; import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; -import { IAgentClient } from "../node/agent-client-interface"; -import { setup } from "../pitcher-protocol"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { setup, system } from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; -import { clear } from "console"; +import { AgentClient } from "../AgentClient"; +import { SandboxSession } from "../types"; export * from "./filesystem"; export * from "./ports"; @@ -29,7 +31,27 @@ type SandboxClientParams = { }; export class SandboxClient { - static async create(agentClient: IAgentClient, params: SandboxClientParams) { + static async create( + session: SandboxSession, + getSession: (id: string) => Promise, + initStatusCb?: (event: system.InitStatus) => void + ) { + const { client: agentClient, joinResult } = await AgentClient.create({ + session, + getSession, + }); + + if (initStatusCb) { + agentClient.system.onInitStatusUpdate(initStatusCb); + } + + const params = { + // On dedicated sessions we need the username to normalize + // FS events + username: session.sessionId ? joinResult.client.username : undefined, + hostToken: session.hostToken, + }; + let setupProgress = await agentClient.setup.getProgress(); let hasInitializedSteps = Boolean(setupProgress.steps.length); @@ -136,19 +158,20 @@ export class SandboxClient { }); this.agentClient.onStateChange((state) => { - if (!this.shouldKeepAlive) { - return; - } - - // We can not call `keepActiveWhileConnected` here, because it would - // reset the interval, which would turn off the "shouldKeepAlive" flag if (state === "DISCONNECTED" || state === "HIBERNATED") { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); } this.keepAliveInterval = null; + + // Only attempt auto-reconnect on DISCONNECTED, not HIBERNATED + if (state === "DISCONNECTED" && !this.isExplicitlyDisconnected) { + this.attemptAutoReconnect(); + } } else if (state === "CONNECTED") { - this.keepActiveWhileConnected(true); + if (this.shouldKeepAlive) { + this.keepActiveWhileConnected(true); + } } }); } @@ -244,6 +267,7 @@ export class SandboxClient { * reconnect to the sandbox. */ public disconnect() { + this.isExplicitlyDisconnected = true; if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; @@ -256,11 +280,36 @@ export class SandboxClient { * Explicitly reconnect to the sandbox. */ public reconnect() { + this.isExplicitlyDisconnected = false; return this.agentClient.reconnect(); } + /** + * Attempt automatic reconnection with retry logic + */ + private async attemptAutoReconnect() { + try { + await retryWithDelay( + async () => { + if (this.isExplicitlyDisconnected) { + throw new Error("Explicit disconnect - stopping auto-reconnect"); + } + await this.agentClient.reconnect(); + }, + 3, // retries + 2000 // delay in ms + ); + // Clear the disconnect flag on successful reconnection + this.isExplicitlyDisconnected = false; + } catch (error) { + // Auto-reconnect failed, but we don't throw to avoid unhandled rejections + console.warn("Auto-reconnect failed:", error); + } + } + private keepAliveInterval: NodeJS.Timeout | null = null; private shouldKeepAlive = false; + private isExplicitlyDisconnected = false; /** * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ @@ -273,7 +322,7 @@ export class SandboxClient { this.keepAliveInterval = setInterval(() => { this.agentClient.system.update().catch(() => { // We do not care about errors here - }) + }); }, 1000 * 30); } } else { diff --git a/src/SandboxClient/ports.ts b/src/SandboxClient/ports.ts index 4546ff3..403c01b 100644 --- a/src/SandboxClient/ports.ts +++ b/src/SandboxClient/ports.ts @@ -1,6 +1,6 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../node/agent-client-interface"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; export type Port = { host: string; diff --git a/src/SandboxClient/setup.ts b/src/SandboxClient/setup.ts index bb5f1b3..dbcb8cb 100644 --- a/src/SandboxClient/setup.ts +++ b/src/SandboxClient/setup.ts @@ -2,7 +2,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../node/agent-client-interface"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; export class Setup { private disposable = new Disposable(); diff --git a/src/SandboxClient/tasks.ts b/src/SandboxClient/tasks.ts index d3715cb..a2a33af 100644 --- a/src/SandboxClient/tasks.ts +++ b/src/SandboxClient/tasks.ts @@ -1,7 +1,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable, IDisposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../node/agent-client-interface"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; import { Emitter } from "../utils/event"; export type TaskDefinition = { diff --git a/src/SandboxClient/terminals.ts b/src/SandboxClient/terminals.ts index df6d975..9c92ba1 100644 --- a/src/SandboxClient/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -2,7 +2,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; -import { IAgentClient } from "../node/agent-client-interface"; +import { IAgentClient } from "../AgentClient/agent-client-interface"; export type ShellSize = { cols: number; rows: number }; diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 164dcd3..63ca78f 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -11,7 +11,6 @@ import { SandboxPrivacy, StartSandboxOpts, } from "./types"; -import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; /** * This class provides methods for creating and managing sandboxes. diff --git a/src/browser/BrowserAgentClient.ts b/src/browser/BrowserAgentClient.ts deleted file mode 100644 index b44516f..0000000 --- a/src/browser/BrowserAgentClient.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { IPitcherClient } from "@codesandbox/pitcher-client"; -import { - IAgentClient, - IAgentClientFS, - IAgentClientPorts, - IAgentClientSetup, - IAgentClientShells, - IAgentClientState, - IAgentClientSystem, - IAgentClientTasks, -} from "../node/agent-client-interface"; -import { Emitter } from "../utils/event"; - -class BrowserAgentClientShells implements IAgentClientShells { - onShellExited = this.pitcherClient.clients.shell.onShellExited; - onShellTerminated = this.pitcherClient.clients.shell.onShellTerminated; - onShellOut = this.pitcherClient.clients.shell.onShellOut; - constructor(private pitcherClient: IPitcherClient) {} - create(...params: Parameters) { - return this.pitcherClient.clients.shell.create(...params); - } - rename(...params: Parameters) { - return this.pitcherClient.clients.shell.rename(...params); - } - async getShells() { - return this.pitcherClient.clients.shell.getShells(); - } - open(...params: Parameters) { - return this.pitcherClient.clients.shell.open(...params); - } - delete(...params: Parameters) { - return this.pitcherClient.clients.shell.delete(...params); - } - restart(...params: Parameters) { - return this.pitcherClient.clients.shell.restart(...params); - } - send(...params: Parameters) { - return this.pitcherClient.clients.shell.send(...params); - } -} - -class BrowserAgentClientFS implements IAgentClientFS { - constructor(private pitcherClient: IPitcherClient) {} - copy(...params: Parameters) { - return this.pitcherClient.clients.fs.copy(...params); - } - mkdir(...params: Parameters) { - return this.pitcherClient.clients.fs.mkdir(...params); - } - readdir(...params: Parameters) { - return this.pitcherClient.clients.fs.readdir(...params); - } - readFile(...params: Parameters) { - return this.pitcherClient.clients.fs.readFile(...params); - } - stat(...params: Parameters) { - return this.pitcherClient.clients.fs.stat(...params); - } - remove(...params: Parameters) { - return this.pitcherClient.clients.fs.remove(...params); - } - rename(...params: Parameters) { - return this.pitcherClient.clients.fs.rename(...params); - } - watch(...params: Parameters) { - return this.pitcherClient.clients.fs.watch(...params); - } - writeFile(...params: Parameters) { - return this.pitcherClient.clients.fs.writeFile(...params); - } - download(...params: Parameters) { - return this.pitcherClient.clients.fs.download(...params); - } -} - -class BrowserAgentClientPorts implements IAgentClientPorts { - onPortsUpdated = this.pitcherClient.clients.port.onPortsUpdated; - async getPorts() { - return this.pitcherClient.clients.port.getPorts(); - } - constructor(private pitcherClient: IPitcherClient) {} -} - -class BrowserAgentClientSetup implements IAgentClientSetup { - onSetupProgressUpdate = - this.pitcherClient.clients.setup.onSetupProgressUpdate; - constructor(private pitcherClient: IPitcherClient) {} - init() { - return this.pitcherClient.clients.setup.init(); - } - async getProgress() { - return this.pitcherClient.clients.setup.getProgress(); - } -} - -class BrowserAgentClientTasks implements IAgentClientTasks { - onTaskUpdate = this.pitcherClient.clients.task.onTaskUpdate; - constructor(private pitcherClient: IPitcherClient) {} - async getTasks() { - return this.pitcherClient.clients.task.getTasks(); - } - async getTask(taskId: string) { - return this.pitcherClient.clients.task.getTask(taskId); - } - runTask(taskId: string) { - return this.pitcherClient.clients.task.runTask(taskId); - } - stopTask(taskId: string) { - return this.pitcherClient.clients.task.stopTask(taskId); - } -} - -class BrowserAgentClientSystem implements IAgentClientSystem { - constructor(private pitcherClient: IPitcherClient) {} - update() { - return this.pitcherClient.clients.system.update(); - } -} - -export class BrowserAgentClient implements IAgentClient { - sandboxId = this.pitcherClient.instanceId; - workspacePath = this.pitcherClient.instanceId; - isUpToDate = this.pitcherClient.isUpToDate(); - state = this.pitcherClient.state.get().state; - private onStateChangeEmitter = new Emitter(); - onStateChange = this.onStateChangeEmitter.event; - shells = new BrowserAgentClientShells(this.pitcherClient); - fs = new BrowserAgentClientFS(this.pitcherClient); - ports = new BrowserAgentClientPorts(this.pitcherClient); - setup = new BrowserAgentClientSetup(this.pitcherClient); - tasks = new BrowserAgentClientTasks(this.pitcherClient); - system = new BrowserAgentClientSystem(this.pitcherClient); - constructor(private pitcherClient: IPitcherClient) { - pitcherClient.onStateChange((state) => { - this.state = state.state; - this.onStateChangeEmitter.fire(state.state); - }); - } - disconnect(): Promise { - return this.pitcherClient.disconnect(); - } - reconnect(): Promise { - return this.pitcherClient.reconnect(); - } - dispose() { - this.pitcherClient.dispose(); - } -} diff --git a/src/browser/index.ts b/src/browser/index.ts index 094e9c2..fab7497 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -1,61 +1,53 @@ -import { initPitcherClient, protocol } from "@codesandbox/pitcher-client"; -import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; +import * as protocol from "../pitcher-protocol"; +import { SandboxSession } from "../types"; import { SandboxClient } from "../SandboxClient"; -import { BrowserAgentClient } from "./BrowserAgentClient"; export * from "../SandboxClient"; export { createPreview, Preview } from "./previews"; -/** - * Connect to a Sandbox from the browser and automatically reconnect. `getSession` requires and endpoint that resumes the Sandbox. `onFocusChange` can be used to notify when a reconnect should happen. - */ -export async function connectToSandbox(options: { +type ConnectToSandboxOptions = { session: SandboxSession; getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; -}): Promise { - let hasConnected = false; - const pitcherClient = await initPitcherClient( - { - appId: "sdk", - instanceId: options.session.sandboxId, - onFocusChange: - options.onFocusChange || - ((notify) => { - const listener = () => { - notify(document.hasFocus()); - }; - window.addEventListener("visibilitychange", listener); - return () => { - window.removeEventListener("visibilitychange", listener); - }; - }), - requestPitcherInstance: async (id) => { - const session = hasConnected - ? await options.getSession(id) - : options.session; - - hasConnected = true; +}; - return session; - }, - subscriptions: DEFAULT_SUBSCRIPTIONS, - }, - options.initStatusCb || (() => {}) - ); +/** + * Connect to a Sandbox from the browser and automatically reconnect. `getSession` requires and endpoint that resumes the Sandbox. `onFocusChange` can be used to notify when a reconnect should happen. + */ +export async function connectToSandbox({ + session, + getSession, + onFocusChange = (notify) => { + const listener = () => { + notify(document.hasFocus()); + }; + window.addEventListener("visibilitychange", listener); + return () => { + window.removeEventListener("visibilitychange", listener); + }; + }, + initStatusCb = () => {}, +}: ConnectToSandboxOptions): Promise { + const client = await SandboxClient.create(session, getSession, initStatusCb); - const agentClient = new BrowserAgentClient(pitcherClient); - const session = await SandboxClient.create(agentClient, { - // On dedicated sessions we need the username to normalize - // FS events - username: options.session.sessionId - ? // @ts-ignore - pitcherClient["joinResult"].client.username - : undefined, - hostToken: options.session.hostToken, + onFocusChange((isFocused) => { + // We immediately ping the connection when focusing, so that + // we detect a disconnect as early as possible + if (isFocused && client.state === "CONNECTED") { + client["agentClient"].ping(); + // If we happen to be disconnected when focusing we try to reconnect, but only if we are currently + // hibernated and we did not do a manual disconnect + } else if ( + isFocused && + (client.state === "DISCONNECTED" || client.state === "HIBERNATED") + ) { + client.reconnect().catch((err) => { + console.error("Failed to reconnect to sandbox:", err); + }); + } }); - return session; + return client; } diff --git a/src/browser/previews/Preview.ts b/src/browser/previews/Preview.ts index 476b897..e156c99 100644 --- a/src/browser/previews/Preview.ts +++ b/src/browser/previews/Preview.ts @@ -1,4 +1,3 @@ -import { Emitter } from "@codesandbox/pitcher-client"; import { BaseMessageToPreview, BaseMessageFromPreview, @@ -7,6 +6,7 @@ import { Message, } from "./types"; import { injectAndInvokeInsidePreview } from "./preview-script"; +import { Emitter } from "../../utils/event"; type PreviewStatus = "DISCONNECTED" | "CONNECTED"; diff --git a/src/node/index.ts b/src/node/index.ts index 04adb2f..7b6ce81 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -1,47 +1,17 @@ -import { version } from "../pitcher-protocol"; -import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; -import { AgentConnection } from "./AgentConnection"; -import { NodeAgentClient } from "./AgentClient"; +import * as protocol from "../pitcher-protocol"; import { SandboxClient } from "../SandboxClient"; +import { SandboxSession } from "../types"; -// Timeout for detecting a pong response, leading to a forced disconnect -let PONG_DETECTION_TIMEOUT = 15_000; +type ConnectToSandboxOptions = { + session: SandboxSession; + getSession: (id: string) => Promise; + initStatusCb?: (event: protocol.system.InitStatus) => void; +}; export async function connectToSandbox({ session, getSession, -}: { - session: SandboxSession; - getSession: (sandboxId: string) => Promise; -}) { - const url = `${session.pitcherURL}/?token=${session.pitcherToken}`; - const agentConnection = await AgentConnection.create(url); - const joinResult = await agentConnection.request({ - method: "client/join", - params: { - clientInfo: { - protocolVersion: version, - appId: "sdk", - }, - asyncProgress: true, - subscriptions: DEFAULT_SUBSCRIPTIONS, - }, - }); - - // Now that we have initialized we set an appropriate timeout to more efficiently detect disconnects - agentConnection.connection.setPongDetectionTimeout(PONG_DETECTION_TIMEOUT); - - const agentClient = new NodeAgentClient(getSession, agentConnection, { - sandboxId: session.sandboxId, - workspacePath: session.userWorkspacePath, - reconnectToken: joinResult.reconnectToken, - isUpToDate: session.latestPitcherVersion === session.pitcherVersion, - }); - - const client = await SandboxClient.create(agentClient, { - username: joinResult.client.username, - hostToken: session.hostToken, - }); - - return client; + initStatusCb = () => {}, +}: ConnectToSandboxOptions): Promise { + return SandboxClient.create(session, getSession, initStatusCb); } diff --git a/src/pitcher-protocol/messages/ai.ts b/src/pitcher-protocol/messages/ai.ts index a5d929f..933bc96 100644 --- a/src/pitcher-protocol/messages/ai.ts +++ b/src/pitcher-protocol/messages/ai.ts @@ -1,5 +1,3 @@ -import type { Id } from "@codesandbox/pitcher-common"; - import { ProtocolError, TMessage, TNotification } from "../protocol"; import { PitcherErrorCode } from "../errors"; @@ -113,7 +111,7 @@ export type StreamMessage = TMessage< }, { result: { - messageId: Id; + messageId: string; }; error: CommonError; } @@ -135,14 +133,14 @@ export type IChatContextFragment = export type AIChatMessageMessage = TMessage< "ai/chatMessage", { - chatId: Id; + chatId: string; message: string; - messageId: Id; + messageId: string; context: IChatContextFragment[]; }, { result: { - chatId: Id; + chatId: string; title: string; }; error: CommonError; @@ -152,7 +150,7 @@ export type AIChatMessageMessage = TMessage< export type AIChatCreatedNotification = TNotification< "ai/chatCreated", { - chatId: Id; + chatId: string; title: string; entries: IChatHistoryEntry[]; } @@ -161,7 +159,7 @@ export type AIChatCreatedNotification = TNotification< export type AIChatMessageNotification = TNotification< "ai/chatMessage", { - chatId: Id; + chatId: string; /** * idx, indicating the order of the messages, ensures we can resync if a message goes missing **/ @@ -176,7 +174,7 @@ export type AIChatMessageNotification = TNotification< /** * messageId is a unique identifier for the message used to listen for streaming progress */ - messageId: Id; + messageId: string; message: string; context: IChatContextFragment[]; isFinished: boolean; @@ -186,7 +184,7 @@ export type AIChatMessageNotification = TNotification< export type AIMessageProgressNotification = TNotification< "ai/messageProgress", { - messageId: Id; + messageId: string; chunk: string; isFinished: boolean; } @@ -207,7 +205,7 @@ export interface IChatHistoryEntry { /** * messageId is a unique identifier for the message used to listen for streaming progress */ - messageId: Id; + messageId: string; message: string; context: IChatContextFragment[]; isFinished: boolean; @@ -219,7 +217,7 @@ export type AIChatsMessage = TMessage< { result: { chats: Array<{ - chatId: Id; + chatId: string; title: string; }>; }; @@ -230,7 +228,7 @@ export type AIChatsMessage = TMessage< export type AiChatHistoryMessage = TMessage< "ai/chatHistory", { - chatId: Id; + chatId: string; }, { result: { @@ -243,7 +241,7 @@ export type AiChatHistoryMessage = TMessage< export type AiMessageStateMessage = TMessage< "ai/messageState", { - messageId: Id; + messageId: string; }, { result: { diff --git a/src/pitcher-protocol/messages/file.ts b/src/pitcher-protocol/messages/file.ts index ebfff49..3002554 100644 --- a/src/pitcher-protocol/messages/file.ts +++ b/src/pitcher-protocol/messages/file.ts @@ -1,4 +1,3 @@ -import { Id, ot } from "@codesandbox/pitcher-common"; import { PitcherErrorCode } from "../errors"; import { ProtocolError, TMessage, TNotification } from "../protocol"; @@ -36,7 +35,7 @@ export interface IFileClients { } export interface IFileObject { - id: Id; + id: string; isBinary: boolean; content: Uint8Array | string; document: IDocumentObject | null; @@ -60,7 +59,7 @@ export type InvalidPathError = { export type OpenFile = TMessage< "file/open", { - id: Id; + id: string; isResync?: boolean; }, { @@ -84,24 +83,6 @@ export type OpenFileByPath = TMessage< } >; -/** - * Client acknowledges receiving a revision, so Pitcher - * can keep track of what revisions each client is on - */ -export type AckDocument = TMessage< - "file/documentAck", - { - id: Id; - revision: number; - }, - { - result: { - revision: number; - }; - error: InvalidIdError | CommonError; - } ->; - /** * Closes a file, which is disposed * when last user closes it @@ -109,7 +90,7 @@ export type AckDocument = TMessage< export type CloseFile = TMessage< "file/close", { - id: Id; + id: string; }, { result: null; @@ -124,7 +105,7 @@ export type CloseFile = TMessage< export type SaveFile = TMessage< "file/save", { - id: Id; + id: string; /** * Whether the document should be written to disk */ @@ -136,57 +117,7 @@ export type SaveFile = TMessage< } >; -/** - * OT operation to make change to document - */ -export type DocumentOperation = TMessage< - "file/documentOperation", - { - id: Id; - operation: ot.JSONTextOperation; - revision: number; - }, - { - result: { - id: Id; - revision: number; - }; - error: InvalidIdError | CommonError; - } ->; - -/** - * Client acknowledges receiving a revision, so Pitcher - * can keep track of what revisions each client is on - */ -export type DocumentSelection = TMessage< - "file/documentSelection", - { - id: Id; - selection: ISelection; - /** - * Passing the selection reason allows for clients to separate selections, where - * for example Monaco/VSCode will filter out selections by CONTENT_CHANGE as they - * automatically transform selections with text operations - * - * NOTE! Will become a required property in later BREAKING version - */ - reason?: SelectionsUpdateReason; - }, - { - result: null; - error: InvalidIdError | CommonError; - } ->; - -type FileMessage = - | OpenFile - | OpenFileByPath - | CloseFile - | AckDocument - | SaveFile - | DocumentOperation - | DocumentSelection; +type FileMessage = OpenFile | OpenFileByPath | CloseFile | SaveFile; export type FileRequest = FileMessage["request"]; @@ -195,7 +126,7 @@ export type FileResponse = FileMessage["response"]; export type JoinFileNotification = TNotification< "file/join", { - id: Id; + id: string; isBinary: boolean; clientId: string; username: string; @@ -205,7 +136,7 @@ export type JoinFileNotification = TNotification< export type LeaveFileNotification = TNotification< "file/leave", { - id: Id; + id: string; isBinary: boolean; clientId: string; username: string; @@ -215,35 +146,14 @@ export type LeaveFileNotification = TNotification< export type SaveFileNotification = TNotification< "file/save", { - id: Id; + id: string; isBinary: boolean; content: string | Uint8Array; savedHash: string; } >; -export type DocumentOperationNotification = TNotification< - "file/documentOperation", - { - id: Id; - operation: ot.JSONTextOperation; - revision: number; - reason: ot.OperationReason; - } ->; - -export type DocumentSelectionNotification = TNotification< - "file/documentSelection", - { - id: Id; - selections: IDocumentSelections; - reason?: SelectionsUpdateReason; - } ->; - export type FileNotification = | JoinFileNotification | LeaveFileNotification - | SaveFileNotification - | DocumentOperationNotification - | DocumentSelectionNotification; + | SaveFileNotification; diff --git a/src/pitcher-protocol/messages/fs.ts b/src/pitcher-protocol/messages/fs.ts index d8ed271..7be7802 100644 --- a/src/pitcher-protocol/messages/fs.ts +++ b/src/pitcher-protocol/messages/fs.ts @@ -1,31 +1,3 @@ -/** - * # FS Protocol - * - * The filesystem protocol revolves around sending and receiving operations to - * update the {@link MemoryFS} instance on Pitcher's clients and server. - * This allows operations to be conflict resolving by the commutative and - * monotonic properties of MemoryFS's CRDT tree structure. - * - * ## Load Project - * - * To populate MemoryFS with an initial snapshot of the project's files, we send - * {@link LoadProject} to request the list of MemoryFS nodes and children. These - * contain the file paths, ids, metadata, but _not_ the file contents. - * - * This allows us to operate on files by their ids, allowing us not to worry - * about their name or path at any given moment in time. - * - * ## Operations - * - * To inform the server of new filesystem operations, {@link SendOps} is used to - * send a list of {@link OpMove}s generated by MemoryFS. - * - * To keep the client up-to-date, the {@link ListenOps} is used to subscribe to - * new operations sent by other clients to the server, which can then be applied - * to the client's MemoryFS. - */ - -import { bedrockFS, Id } from "@codesandbox/pitcher-common"; import { PitcherErrorCode } from "../errors"; import { ProtocolError, TMessage, TNotification } from "../protocol"; @@ -45,7 +17,7 @@ export type FsServerCapabilities = { export type FsClientCapabilities = { [key: string]: unknown }; export type SearchResult = { - fileId: Id; + fileId: string; lines: { text: string; }; @@ -55,7 +27,7 @@ export type SearchResult = { }; export type StreamingSearchResult = { - fileId?: Id; + fileId?: string; filepath: string; lines: { text: string; @@ -86,66 +58,6 @@ export type RawFileSystemError = { }; }; -export interface FSReadResult { - treeNodes: bedrockFS.JSONNode[]; - clock: number; -} - -/** Retreive the latest snapshot of the server's MemoryFS file and children list */ -export type FSReadMessage = TMessage< - "fs/read", - null, - { - result: FSReadResult; - error: CommonError; - } ->; - -export interface FSCreateOperation { - type: "create"; - parentId: Id; - newEntry: { id: Id; type: bedrockFS.NodeType; name: string }; -} - -export interface FSDeleteOperation { - type: "delete"; - id: Id; -} - -export interface FSMoveOperation { - type: "move"; - id: Id; - parentId?: Id; - name?: string; -} - -export type FSOperation = - | FSCreateOperation - | FSDeleteOperation - | FSMoveOperation; - -export enum FSOperationResponseCode { - Success = 0, - Ignored = 1, -} - -/** Send a list of tree operations reflecting filesystem operations */ -export type FSOperationMessage = TMessage< - "fs/operation", - { operation: FSOperation }, - { - result: - | { - code: FSOperationResponseCode; - clock: number; - } - | { - code: FSOperationResponseCode.Ignored; - }; - error: CommonError; - } ->; - export interface FSSearchParams { text: string; glob?: string; @@ -163,7 +75,7 @@ export type FSSearchMessage = TMessage< >; export interface FSStreamingSearchParams { - searchId: Id; + searchId: string; text: string; glob?: string; isRegex?: boolean; @@ -179,7 +91,7 @@ export type FSStreamingSearchMessage = TMessage< FSStreamingSearchParams, { result: { - searchId: Id; + searchId: string; }; error: CommonError; } @@ -188,11 +100,11 @@ export type FSStreamingSearchMessage = TMessage< export type FSCancelStreamingSearchMessage = TMessage< "fs/cancelStreamingSearch", { - searchId: Id; + searchId: string; }, { result: { - searchId: Id; + searchId: string; }; error: CommonError; } @@ -223,13 +135,13 @@ export type PathSearchMessage = TMessage< export type FSUploadMessage = TMessage< "fs/upload", { - parentId: Id; + parentId: string; filename: string; content: Uint8Array; }, { result: { - fileId: Id; + fileId: string; }; error: CommonError | InvalidIdError; } @@ -279,7 +191,7 @@ export type FSReadDirParams = { export type FSReadDirResult = { entries: { name: string; - type: bedrockFS.NodeType; + type: 0 | 1; // 0 = file, 1 = directory isSymlink: boolean; }[]; }; @@ -316,7 +228,7 @@ export type FSStatParams = { }; export type FSStatResult = { - type: bedrockFS.NodeType; + type: 0 | 1; // 0 = file, 1 = directory isSymlink: boolean; size: number; mtime: number; @@ -449,8 +361,6 @@ type RawFsMessage = // #endregion type FsMessage = - | FSReadMessage - | FSOperationMessage | FSSearchMessage | FSStreamingSearchMessage | FSCancelStreamingSearchMessage @@ -468,21 +378,10 @@ export interface FSWatchEvent { type: "add" | "change" | "remove"; } -export interface FSOperationEvent { - operation: FSOperation; - clock: number; -} - /** * Listen for tree operations reflecting filesystem operations made by * other clients */ -export type FSOperationsNotification = TNotification< - "fs/operations", - { - operations: FSOperationEvent[]; - } ->; export type FSWatchNotifiction = TNotification< "fs/watchEvent", @@ -509,7 +408,6 @@ export type FSSearchFinishedNotifiction = TNotification< >; export type FsNotification = - | FSOperationsNotification | FSWatchNotifiction | FSSearchMatchesNotifiction | FSSearchFinishedNotifiction; diff --git a/src/pitcher-protocol/messages/git.ts b/src/pitcher-protocol/messages/git.ts index 1d7bbde..ae228fa 100644 --- a/src/pitcher-protocol/messages/git.ts +++ b/src/pitcher-protocol/messages/git.ts @@ -1,5 +1,4 @@ import { ProtocolError, TMessage, TNotification } from "../protocol"; -import { Id } from "@codesandbox/pitcher-common"; import { PitcherErrorCode } from "../errors"; @@ -27,7 +26,7 @@ export interface GitItem { workingTree: GitStatusShortFormat; isStaged: boolean; isConflicted: boolean; - fileId?: Id; + fileId?: string; } export type GitChangedFiles = { @@ -123,7 +122,7 @@ export type GitCommitMessage = TMessage< { paths?: string[]; message: string; push?: boolean }, { result: { - shellId: Id; + shellId: string; }; error: CommonError; } @@ -264,7 +263,7 @@ export type GitPullFinishedNotification = TNotification< export type GitCommitStartedNotification = TNotification< "git/commitStarted", { - shellId: Id; + shellId: string; message: string; paths?: string[]; } diff --git a/src/pitcher-protocol/messages/notification.ts b/src/pitcher-protocol/messages/notification.ts index 3b3c384..7db4336 100644 --- a/src/pitcher-protocol/messages/notification.ts +++ b/src/pitcher-protocol/messages/notification.ts @@ -1,5 +1,4 @@ import { TNotification, TMessage, ProtocolError } from "../protocol"; -import { Id } from "@codesandbox/pitcher-common"; export type NotificationType = "info" | "warning" | "error"; export type Action = { @@ -16,7 +15,7 @@ export type NotificationNotify = TNotification< "notification/notify", { type: NotificationType; - notificationId: Id; + notificationId: string; message: string; actions?: Action[]; } @@ -28,14 +27,14 @@ export type NotificationNotify = TNotification< export type NotificationDismiss = TNotification< "notification/dismiss", { - notificationId: Id; + notificationId: string; } >; export type NotificationAckResponse = TMessage< "notification/notifyResponse", { - notificationId: Id; + notificationId: string; response: string | null; }, { diff --git a/src/pitcher-protocol/messages/setup.ts b/src/pitcher-protocol/messages/setup.ts index b7c61b4..820baa0 100644 --- a/src/pitcher-protocol/messages/setup.ts +++ b/src/pitcher-protocol/messages/setup.ts @@ -1,10 +1,9 @@ import { ProtocolError, TMessage, TNotification } from "../protocol"; -import { Id } from "@codesandbox/pitcher-common"; export interface Step { name: string; command: string; - shellId: Id | null; + shellId: string | null; finishStatus: SetupShellStatus | null; } diff --git a/src/pitcher-protocol/messages/shell.ts b/src/pitcher-protocol/messages/shell.ts index 864011a..ad2ee09 100644 --- a/src/pitcher-protocol/messages/shell.ts +++ b/src/pitcher-protocol/messages/shell.ts @@ -1,8 +1,7 @@ import { ProtocolError, TMessage, TNotification } from "../protocol"; -import { Id } from "@codesandbox/pitcher-common"; import { PitcherErrorCode } from "../errors"; -export type ShellId = Id; +export type ShellId = string; export type ShellSize = { cols: number; rows: number }; diff --git a/src/types.ts b/src/types.ts index ef40d82..a5002f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,19 @@ -import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; import { VMTier } from "./VMTier"; import { HostToken } from "./HostTokens"; import { Config } from "@hey-api/client-fetch"; +export interface PitcherManagerResponse { + bootupType: "RUNNING" | "CLEAN" | "RESUME" | "FORK"; + pitcherURL: string; + workspacePath: string; + userWorkspacePath: string; + pitcherManagerVersion: string; + pitcherVersion: string; + latestPitcherVersion: string; + pitcherToken: string; + cluster: string; +} + export interface SystemMetricsStatus { cpu: { cores: number; diff --git a/src/utils/api.ts b/src/utils/api.ts index a47c93f..ef06a49 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,5 +1,3 @@ -import { PitcherManagerResponse } from "@codesandbox/pitcher-client"; -import { VmStartResponse } from "../api-clients/client"; import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; import { From 6050dbd289058c782e634a7ce9e25d9cf5921276 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 09:42:38 +0200 Subject: [PATCH 196/241] feat: add listRunning method to sandboxes namespace (#145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose the vmListRunningVms endpoint as sdk.sandboxes.listRunning() to list currently running VMs with their specs, activity timestamps, and concurrent VM limits. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- src/Sandboxes.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 63ca78f..f2671ba 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -209,6 +209,45 @@ export class Sandboxes { }, }; } + + /** + * List information about currently running VMs. + * + * This information is updated roughly every 30 seconds, so this data is not + * guaranteed to be perfectly up-to-date. + */ + async listRunning() { + const data = await this.api.listRunningVms(); + + return { + concurrentVmCount: data.concurrent_vm_count, + concurrentVmLimit: data.concurrent_vm_limit, + vms: data.vms.map(vm => ({ + id: vm.id, + creditBasis: vm.credit_basis, + lastActiveAt: vm.last_active_at ? parseTimestamp(vm.last_active_at) : undefined, + sessionStartedAt: vm.session_started_at ? parseTimestamp(vm.session_started_at) : undefined, + specs: vm.specs ? { + cpu: vm.specs.cpu, + memory: vm.specs.memory, + storage: vm.specs.storage, + } : undefined, + })), + }; + } +} + +function parseTimestamp(timestamp: number): Date | undefined { + if (!timestamp || timestamp === 0) { + return undefined; + } + + // Handle both seconds and milliseconds timestamps + const ts = timestamp < 10000000000 ? timestamp * 1000 : timestamp; + const date = new Date(ts); + + // Return undefined if the date is invalid + return isNaN(date.getTime()) ? undefined : date; } function privacyToNumber(privacy: SandboxPrivacy): number { From b3313153b357dfda0d666a6a56ea79f6aa3dbbdf Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 09:47:31 +0200 Subject: [PATCH 197/241] feat: add open telemetry for sandboxes methods (#147) --- package-lock.json | 2 +- package.json | 1 + src/Sandboxes.ts | 224 ++++++++++++++++++++++++++++++++-------------- src/index.ts | 2 +- src/types.ts | 6 ++ 5 files changed, 165 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index a8e8f50..5e44705 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", + "@opentelemetry/api": "^1.9.0", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "chalk": "^5.4.1", @@ -673,7 +674,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8.0.0" } diff --git a/package.json b/package.json index 17b4966..7028620 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", + "@opentelemetry/api": "^1.9.0", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "chalk": "^5.4.1", diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index f2671ba..c56ed16 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -1,6 +1,7 @@ import { Sandbox } from "./Sandbox"; import { API } from "./API"; import { getDefaultTemplateTag, getStartOptions } from "./utils/api"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; import { CreateSandboxOpts, @@ -16,11 +17,48 @@ import { * This class provides methods for creating and managing sandboxes. */ export class Sandboxes { + private tracer?: Tracer; + get defaultTemplateId() { return getDefaultTemplateTag(this.api.getClient()); } - constructor(private api: API) {} + constructor(private api: API, tracer?: Tracer) { + this.tracer = tracer; + } + + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } private async createTemplateSandbox( opts?: CreateSandboxOpts & StartSandboxOpts @@ -62,15 +100,27 @@ export class Sandboxes { * Note! On CLEAN bootups the setup will run again. When hibernated a new snapshot will be created. */ async resume(sandboxId: string) { - const startResponse = await this.api.startVm(sandboxId); - return new Sandbox(sandboxId, this.api, startResponse); + return this.withSpan( + "sandboxes.resume", + { "sandbox.id": sandboxId }, + async () => { + const startResponse = await this.api.startVm(sandboxId); + return new Sandbox(sandboxId, this.api, startResponse); + } + ); } /** * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped. */ async shutdown(sandboxId: string): Promise { - await this.api.shutdown(sandboxId); + return this.withSpan( + "sandboxes.shutdown", + { "sandbox.id": sandboxId }, + async () => { + await this.api.shutdown(sandboxId); + } + ); } /** @@ -78,10 +128,16 @@ export class Sandboxes { * @deprecated This will be removed shortly to avoid having multiple ways of doing the same thing */ public async fork(sandboxId: string, opts?: StartSandboxOpts) { - return this.create({ - id: sandboxId, - ...opts, - }); + return this.withSpan( + "sandboxes.fork", + { "sandbox.id": sandboxId }, + async () => { + return this.create({ + id: sandboxId, + ...opts, + }); + } + ); } /** @@ -91,33 +147,54 @@ export class Sandboxes { * Will resolve once the sandbox is restarted with its setup running. */ public async restart(sandboxId: string, opts?: StartSandboxOpts) { - try { - await this.shutdown(sandboxId); - } catch (e) { - throw new Error("Failed to shutdown VM, " + String(e)); - } + return this.withSpan( + "sandboxes.restart", + { "sandbox.id": sandboxId }, + async () => { + try { + await this.shutdown(sandboxId); + } catch (e) { + throw new Error("Failed to shutdown VM, " + String(e)); + } - try { - const startResponse = await this.api.startVm(sandboxId, opts); + try { + const startResponse = await this.api.startVm(sandboxId, opts); - return new Sandbox(sandboxId, this.api, startResponse); - } catch (e) { - throw new Error("Failed to start VM, " + String(e)); - } + return new Sandbox(sandboxId, this.api, startResponse); + } catch (e) { + throw new Error("Failed to start VM, " + String(e)); + } + } + ); } /** * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time * you resume the sandbox it will continue from the last state it was in. */ async hibernate(sandboxId: string): Promise { - await this.api.hibernate(sandboxId); + return this.withSpan( + "sandboxes.hibernate", + { "sandbox.id": sandboxId }, + async () => { + await this.api.hibernate(sandboxId); + } + ); } /** * Create a sandbox from a template. By default we will create a sandbox from the default universal template. */ async create(opts?: CreateSandboxOpts & StartSandboxOpts): Promise { - return this.createTemplateSandbox(opts); + return this.withSpan( + "sandboxes.create", + { + "template.id": opts?.id || this.defaultTemplateId, + "sandbox.privacy": opts?.privacy || "unlisted", + }, + async () => { + return this.createTemplateSandbox(opts); + } + ); } /** @@ -155,59 +232,70 @@ export class Sandboxes { pagination?: PaginationOpts; } = {} ): Promise { - const limit = opts.limit ?? 50; - let allSandboxes: SandboxInfo[] = []; - let currentPage = opts.pagination?.page ?? 1; - let pageSize = opts.pagination?.pageSize ?? limit; - let totalCount = 0; - let nextPage: number | null = null; + return this.withSpan( + "sandboxes.list", + { + "list.limit": opts.limit ?? 50, + "list.tags": opts.tags?.join(",") || "", + "list.orderBy": opts.orderBy || "inserted_at", + "list.direction": opts.direction || "desc", + }, + async () => { + const limit = opts.limit ?? 50; + let allSandboxes: SandboxInfo[] = []; + let currentPage = opts.pagination?.page ?? 1; + let pageSize = opts.pagination?.pageSize ?? limit; + let totalCount = 0; + let nextPage: number | null = null; - while (true) { - const info = await this.api.listSandboxes({ - tags: opts.tags?.join(","), - page: currentPage, - page_size: pageSize, - order_by: opts.orderBy, - direction: opts.direction, - status: opts.status, - }); - totalCount = info.pagination.total_records; - nextPage = info.pagination.next_page; + while (true) { + const info = await this.api.listSandboxes({ + tags: opts.tags?.join(","), + page: currentPage, + page_size: pageSize, + order_by: opts.orderBy, + direction: opts.direction, + status: opts.status, + }); + totalCount = info.pagination.total_records; + nextPage = info.pagination.next_page; - const sandboxes = info.sandboxes.map((sandbox) => ({ - id: sandbox.id, - createdAt: new Date(sandbox.created_at), - updatedAt: new Date(sandbox.updated_at), - title: sandbox.title ?? undefined, - description: sandbox.description ?? undefined, - privacy: privacyFromNumber(sandbox.privacy), - tags: sandbox.tags, - })); + const sandboxes = info.sandboxes.map((sandbox) => ({ + id: sandbox.id, + createdAt: new Date(sandbox.created_at), + updatedAt: new Date(sandbox.updated_at), + title: sandbox.title ?? undefined, + description: sandbox.description ?? undefined, + privacy: privacyFromNumber(sandbox.privacy), + tags: sandbox.tags, + })); - const newSandboxes = sandboxes.filter( - (sandbox) => - !allSandboxes.some((existing) => existing.id === sandbox.id) - ); - allSandboxes = [...allSandboxes, ...newSandboxes]; + const newSandboxes = sandboxes.filter( + (sandbox) => + !allSandboxes.some((existing) => existing.id === sandbox.id) + ); + allSandboxes = [...allSandboxes, ...newSandboxes]; - // Stop if we've hit the limit or there are no more pages - if (!nextPage || allSandboxes.length >= limit) { - break; - } + // Stop if we've hit the limit or there are no more pages + if (!nextPage || allSandboxes.length >= limit) { + break; + } - currentPage = nextPage; - } + currentPage = nextPage; + } - return { - sandboxes: allSandboxes, - hasMore: totalCount > allSandboxes.length, - totalCount, - pagination: { - currentPage, - nextPage: allSandboxes.length >= limit ? nextPage : null, - pageSize, - }, - }; + return { + sandboxes: allSandboxes, + hasMore: totalCount > allSandboxes.length, + totalCount, + pagination: { + currentPage, + nextPage: allSandboxes.length >= limit ? nextPage : null, + pageSize, + }, + }; + } + ); } /** diff --git a/src/index.ts b/src/index.ts index 9a45cbe..cc927e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ export class CodeSandbox { const apiKey = apiToken || getInferredApiKey(); const api = new API({ apiKey, config: opts }); - this.sandboxes = new Sandboxes(api); + this.sandboxes = new Sandboxes(api, opts.tracer); this.hosts = new HostTokens(api); } } diff --git a/src/types.ts b/src/types.ts index a5002f6..5a12345 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { VMTier } from "./VMTier"; import { HostToken } from "./HostTokens"; import { Config } from "@hey-api/client-fetch"; +import { Tracer } from "@opentelemetry/api"; export interface PitcherManagerResponse { bootupType: "RUNNING" | "CLEAN" | "RESUME" | "FORK"; @@ -80,6 +81,11 @@ export interface ClientOpts { * Additional headers to send with each request */ headers?: Record; + + /** + * Optional OpenTelemetry tracer for instrumenting SDK operations + */ + tracer?: Tracer; } export const DEFAULT_SUBSCRIPTIONS = { From 2f58d43ee44c98eeb7d0e917c8b423a80d202585 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 12:19:20 +0200 Subject: [PATCH 198/241] feat: add fetching single sandbox (#142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add efficient sandbox retrieval methods Add getSandbox() and getSandboxes() methods to retrieve specific sandboxes by ID without listing all sandboxes, solving performance issues for large organizations. - Add getSandbox(sandboxId) for single sandbox retrieval - Add getSandboxes(opts) for bulk retrieval with parallel requests - Support error handling with continueOnError option - Include comprehensive TypeScript types and JSDoc - Update README with usage examples This addresses the performance bottleneck when retrieving metadata for known sandbox IDs in organizations with thousands of sandboxes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Remove getSandboxes and rename getSandbox --------- Co-authored-by: Claude --- README.md | 14 ++++++++++++++ src/Sandboxes.ts | 31 +++++++++++++++++++++++++++++++ src/types.ts | 1 + 3 files changed, 46 insertions(+) diff --git a/README.md b/README.md index 6a354bd..ab84dd4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,20 @@ const output = await client.commands.run("echo 'Hello World'"); console.log(output); // Hello World ``` +## Efficient Sandbox Retrieval + +When you need to retrieve metadata for specific sandboxes by their IDs, you can use the efficient retrieval methods instead of listing and filtering all sandboxes: + +### Get Single Sandbox + +```javascript +// Efficient single sandbox retrieval +const sandbox = await sdk.sandboxes.get("sandbox-id"); +console.log(sandbox.title, sandbox.tags); +``` + +This method is significantly more efficient than using `list()` and filtering, especially for large organizations with thousands of sandboxes. + ## Configuration The SDK supports the following environment variables for configuration: diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index c56ed16..49ed541 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -323,6 +323,37 @@ export class Sandboxes { })), }; } + + /** + * Get a single sandbox by ID efficiently without listing all sandboxes. + * + * This method directly retrieves metadata for a specific sandbox ID, + * avoiding the performance overhead of the list-and-filter pattern. + * + * @param sandboxId The ID of the sandbox to retrieve + * @returns Promise The sandbox metadata + * @throws Error if the sandbox is not found or access is denied + * + * @example + * ```ts + * const sandbox = await client.sandboxes.get("sandbox-id"); + * console.log(sandbox.title, sandbox.tags); + * ``` + */ + async get(sandboxId: string): Promise { + const sandbox = await this.api.getSandbox(sandboxId); + + return { + id: sandbox.id, + createdAt: new Date(sandbox.created_at), + updatedAt: new Date(sandbox.updated_at), + title: sandbox.title ?? undefined, + description: sandbox.description ?? undefined, + privacy: privacyFromNumber(sandbox.privacy), + tags: sandbox.tags, + }; + } + } function parseTimestamp(timestamp: number): Date | undefined { diff --git a/src/types.ts b/src/types.ts index 5a12345..57c91bd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,7 @@ export type SandboxListOpts = { status?: "running"; }; + export interface SandboxListResponse { sandboxes: SandboxInfo[]; hasMore: boolean; From 6ef2bf51120068e35ff43607e3353a69d8fbf070 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 13:08:16 +0200 Subject: [PATCH 199/241] feat: add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors (#150) * feat: add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors * all sandbox client methods --- src/Sandbox.ts | 170 +++++++++++---- src/SandboxClient/commands.ts | 336 +++++++++++++++++++++--------- src/SandboxClient/filesystem.ts | 312 +++++++++++++++++++-------- src/SandboxClient/hosts.ts | 41 +++- src/SandboxClient/index.ts | 130 ++++++++---- src/SandboxClient/interpreters.ts | 88 ++++++-- src/SandboxClient/ports.ts | 119 +++++++---- src/SandboxClient/setup.ts | 215 ++++++++++++++----- src/SandboxClient/tasks.ts | 263 +++++++++++++++++------ src/SandboxClient/terminals.ts | 236 ++++++++++++++++----- src/Sandboxes.ts | 46 ++-- src/browser/index.ts | 5 +- src/node/index.ts | 5 +- 13 files changed, 1465 insertions(+), 501 deletions(-) diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 9a0c0bc..dba873c 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -6,10 +6,12 @@ import { import { VMTier } from "./VMTier"; import { API } from "./API"; import { SandboxClient } from "./SandboxClient"; -import { StartSandboxOpts } from "./types"; import { retryWithDelay } from "./utils/api"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export class Sandbox { + private tracer?: Tracer; + /** * How the Sandbox booted up: * - RUNNING: Already running @@ -41,8 +43,44 @@ export class Sandbox { constructor( public id: string, private api: API, - private pitcherManagerResponse: PitcherManagerResponse - ) {} + private pitcherManagerResponse: PitcherManagerResponse, + tracer?: Tracer + ) { + this.tracer = tracer; + } + + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } /** * Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the @@ -50,9 +88,18 @@ export class Sandbox { * than it can scale down to, it can become very slow. */ async updateTier(tier: VMTier): Promise { - await this.api.updateSpecs(this.id, { - tier: tier.name, - }); + return this.withSpan( + "sandbox.updateTier", + { + "sandbox.id": this.id, + "tier.name": tier.name + }, + async () => { + await this.api.updateSpecs(this.id, { + tier: tier.name, + }); + } + ); } /** @@ -60,17 +107,29 @@ export class Sandbox { * will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox. */ async updateHibernationTimeout(timeoutSeconds: number): Promise { - await this.api.updateHibernationTimeout(this.id, { - hibernation_timeout_seconds: timeoutSeconds, - }); + return this.withSpan( + "sandbox.updateHibernationTimeout", + { + "sandbox.id": this.id, + "hibernation.timeoutSeconds": timeoutSeconds + }, + async () => { + await this.api.updateHibernationTimeout(this.id, { + hibernation_timeout_seconds: timeoutSeconds, + }); + } + ); } private async initializeCustomSession( customSession: SessionCreateOptions, session: SandboxSession ) { - const client = await SandboxClient.create(session, async () => - this.getSession(await this.api.startVm(this.id), customSession) + const client = await SandboxClient.create( + session, + async () => this.getSession(await this.api.startVm(this.id), customSession), + undefined, + this.tracer ); if (customSession.env) { @@ -156,29 +215,42 @@ export class Sandbox { } async connect(customSession?: SessionCreateOptions) { - return await retryWithDelay( + return this.withSpan( + "sandbox.connect", + { + "sandbox.id": this.id, + "session.hasCustomSession": !!customSession, + "session.id": customSession?.id || "default" + }, async () => { - const session = await this.getSession( - this.pitcherManagerResponse, - customSession - ); + return await retryWithDelay( + async () => { + const session = await this.getSession( + this.pitcherManagerResponse, + customSession + ); - let client: SandboxClient | undefined; + let client: SandboxClient | undefined; - // We might create a client here if git or env is configured, we can reuse that - if (customSession) { - client = await this.initializeCustomSession(customSession, session); - } + // We might create a client here if git or env is configured, we can reuse that + if (customSession) { + client = await this.initializeCustomSession(customSession, session); + } - return ( - client || - SandboxClient.create(session, async () => - this.getSession(await this.api.startVm(this.id), customSession) - ) + return ( + client || + SandboxClient.create( + session, + async () => this.getSession(await this.api.startVm(this.id), customSession), + undefined, + this.tracer + ) + ); + }, + 3, + 100 ); - }, - 3, - 100 + } ); } @@ -192,20 +264,32 @@ export class Sandbox { async createSession( customSession?: SessionCreateOptions ): Promise { - if (customSession?.git || customSession?.env) { - const configureSession = await this.getSession( - this.pitcherManagerResponse, - customSession - ); - - const client = await this.initializeCustomSession( - customSession, - configureSession - ); - - client?.dispose(); - } + return this.withSpan( + "sandbox.createSession", + { + "sandbox.id": this.id, + "session.hasCustomSession": !!customSession, + "session.id": customSession?.id || "default", + "session.hasGit": !!customSession?.git, + "session.hasEnv": !!customSession?.env + }, + async () => { + if (customSession?.git || customSession?.env) { + const configureSession = await this.getSession( + this.pitcherManagerResponse, + customSession + ); + + const client = await this.initializeCustomSession( + customSession, + configureSession + ); - return this.getSession(this.pitcherManagerResponse, customSession); + client?.dispose(); + } + + return this.getSession(this.pitcherManagerResponse, customSession); + } + ); } } diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 98c1db6..110849a 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -3,6 +3,7 @@ import { Emitter } from "../utils/event"; import { IAgentClient } from "../AgentClient/agent-client-interface"; import * as protocol from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; type ShellSize = { cols: number; rows: number }; @@ -30,105 +31,171 @@ const DEFAULT_SHELL_SIZE = { cols: 128, rows: 24 }; // This can not be called Commands due to React Native export class SandboxCommands { private disposable = new Disposable(); + private tracer?: Tracer; + constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient + private agentClient: IAgentClient, + tracer?: Tracer ) { + this.tracer = tracer; sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * Create and run command in a new shell. Allows you to listen to the output and kill the command. */ async runBackground(command: string | string[], opts?: ShellRunOpts) { - const disposableStore = new DisposableStore(); - const onOutput = new Emitter(); - disposableStore.add(onOutput); - - command = Array.isArray(command) ? command.join(" && ") : command; - - const passedEnv = Object.assign(opts?.env ?? {}); - - const escapedCommand = command.replace(/'/g, "'\\''"); + const cmdString = Array.isArray(command) ? command.join(" && ") : command; + return this.withSpan( + "commands.runBackground", + { + "command.text": cmdString, + "command.cwd": opts?.cwd || "/project", + "command.asGlobalSession": opts?.asGlobalSession || false, + "command.name": opts?.name || "" + }, + async () => { + const disposableStore = new DisposableStore(); + const onOutput = new Emitter(); + disposableStore.add(onOutput); + + command = Array.isArray(command) ? command.join(" && ") : command; + + const passedEnv = Object.assign(opts?.env ?? {}); + + const escapedCommand = command.replace(/'/g, "'\\''"); + + // TODO: use a new shell API that natively supports cwd & env + let commandWithEnv = Object.keys(passedEnv).length + ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( + passedEnv + ) + .map(([key, value]) => `${key}=${value}`) + .join(" ")} bash -c '${escapedCommand}'` + : `source $HOME/.private/.env 2>/dev/null || true && bash -c '${escapedCommand}'`; + + if (opts?.cwd) { + commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; + } - // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(passedEnv).length - ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( - passedEnv - ) - .map(([key, value]) => `${key}=${value}`) - .join(" ")} bash -c '${escapedCommand}'` - : `source $HOME/.private/.env 2>/dev/null || true && bash -c '${escapedCommand}'`; + const shell = await this.agentClient.shells.create( + this.agentClient.workspacePath, + opts?.dimensions ?? DEFAULT_SHELL_SIZE, + commandWithEnv, + opts?.asGlobalSession ? "COMMAND" : "TERMINAL", + true + ); - if (opts?.cwd) { - commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; - } - - const shell = await this.agentClient.shells.create( - this.agentClient.workspacePath, - opts?.dimensions ?? DEFAULT_SHELL_SIZE, - commandWithEnv, - opts?.asGlobalSession ? "COMMAND" : "TERMINAL", - true - ); + if (shell.status === "ERROR" || shell.status === "KILLED") { + throw new Error(`Failed to create shell: ${shell.buffer.join("\n")}`); + } - if (shell.status === "ERROR" || shell.status === "KILLED") { - throw new Error(`Failed to create shell: ${shell.buffer.join("\n")}`); - } + const details = { + type: "command", + command, + name: opts?.name, + }; + + if (shell.status !== "FINISHED") { + // Only way for us to differentiate between a command and a terminal + this.agentClient.shells + .rename( + shell.shellId, + // We embed some details in the name to properly show the command that was run + // , the name and that it is an actual command + JSON.stringify(details) + ) + .catch(() => { + // It is already done + }); + } - const details = { - type: "command", - command, - name: opts?.name, - }; - - if (shell.status !== "FINISHED") { - // Only way for us to differentiate between a command and a terminal - this.agentClient.shells - .rename( - shell.shellId, - // We embed some details in the name to properly show the command that was run - // , the name and that it is an actual command - JSON.stringify(details) - ) - .catch(() => { - // It is already done - }); - } + const cmd = new Command( + this.agentClient, + shell as protocol.shell.CommandShellDTO, + details, + this.tracer + ); - const cmd = new Command( - this.agentClient, - shell as protocol.shell.CommandShellDTO, - details + return cmd; + } ); - - return cmd; } /** * Run a command in a new shell and wait for it to finish, returning its output. */ async run(command: string | string[], opts?: ShellRunOpts): Promise { - const cmd = await this.runBackground(command, opts); - - return cmd.waitUntilComplete(); + const cmdString = Array.isArray(command) ? command.join(" && ") : command; + return this.withSpan( + "commands.run", + { + "command.text": cmdString, + "command.cwd": opts?.cwd || "/project", + "command.asGlobalSession": opts?.asGlobalSession || false + }, + async () => { + const cmd = await this.runBackground(command, opts); + return cmd.waitUntilComplete(); + } + ); } /** * Get all running commands. */ async getAll(): Promise { - const shells = await this.agentClient.shells.getShells(); - - return shells - .filter( - (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) - ) - .map( - (shell) => new Command(this.agentClient, shell, JSON.parse(shell.name)) - ); + return this.withSpan( + "commands.getAll", + {}, + async () => { + const shells = await this.agentClient.shells.getShells(); + + return shells + .filter( + (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) + ) + .map( + (shell) => new Command(this.agentClient, shell, JSON.parse(shell.name), this.tracer) + ); + } + ); } } @@ -146,6 +213,7 @@ export function isCommandShell( export class Command { private disposable = new Disposable(); + private tracer?: Tracer; // TODO: differentiate between stdout and stderr, also send back bytes instead of // strings private onOutputEmitter = this.disposable.addDisposable( @@ -196,10 +264,12 @@ export class Command { constructor( private agentClient: IAgentClient, private shell: protocol.shell.ShellDTO & { buffer?: string[] }, - details: { command: string; name?: string } + details: { command: string; name?: string }, + tracer?: Tracer ) { this.command = details.command; this.name = details.name; + this.tracer = tracer; this.disposable.addDisposable( agentClient.shells.onShellExited(({ shellId, exitCode }) => { @@ -235,38 +305,92 @@ export class Command { ); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * Open the command and get its current output, subscribes to future output */ async open(dimensions = DEFAULT_SHELL_SIZE): Promise { - const shell = await this.agentClient.shells.open( - this.shell.shellId, - dimensions + return this.withSpan( + "command.open", + { + "command.shellId": this.shell.shellId, + "command.text": this.command, + "command.dimensions.cols": dimensions.cols, + "command.dimensions.rows": dimensions.rows + }, + async () => { + const shell = await this.agentClient.shells.open( + this.shell.shellId, + dimensions + ); + + this.output = shell.buffer; + + return this.output.join("\n"); + } ); - - this.output = shell.buffer; - - return this.output.join("\n"); } /** * Wait for the command to finish with its returned output */ async waitUntilComplete(): Promise { - await this.barrier.wait(); - - const cleaned = this.output - .join("\n") - .replace( - /Error: failed to exec in podman container: exit status 1[\s\S]*$/, - "" - ); - - if (this.status === "FINISHED") { - return cleaned; - } + return this.withSpan( + "command.waitUntilComplete", + { + "command.shellId": this.shell.shellId, + "command.text": this.command, + "command.status": this.status + }, + async () => { + await this.barrier.wait(); + + const cleaned = this.output + .join("\n") + .replace( + /Error: failed to exec in podman container: exit status 1[\s\S]*$/, + "" + ); + + if (this.status === "FINISHED") { + return cleaned; + } - throw new Error(`Command ERROR: ${cleaned}`); + throw new Error(`Command ERROR: ${cleaned}`); + } + ); } // TODO: allow for kill signals @@ -274,18 +398,38 @@ export class Command { * Kill the command and remove it from the session. */ async kill(): Promise { - this.disposable.dispose(); - await this.agentClient.shells.delete(this.shell.shellId); + return this.withSpan( + "command.kill", + { + "command.shellId": this.shell.shellId, + "command.text": this.command, + "command.status": this.status + }, + async () => { + this.disposable.dispose(); + await this.agentClient.shells.delete(this.shell.shellId); + } + ); } /** * Restart the command. */ async restart(): Promise { - if (this.status !== "RUNNING") { - throw new Error("Command is not running"); - } + return this.withSpan( + "command.restart", + { + "command.shellId": this.shell.shellId, + "command.text": this.command, + "command.status": this.status + }, + async () => { + if (this.status !== "RUNNING") { + throw new Error("Command is not running"); + } - await this.agentClient.shells.restart(this.shell.shellId); + await this.agentClient.shells.restart(this.shell.shellId); + } + ); } } diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index 0f8612b..b94df85 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -2,6 +2,7 @@ import { type IAgentClient } from "../AgentClient/agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type FSStatResult = { type: "file" | "directory"; @@ -40,16 +41,53 @@ export type Watcher = { export class FileSystem { private disposable = new Disposable(); + private tracer?: Tracer; + constructor( sessionDisposable: Disposable, private agentClient: IAgentClient, - private username?: string + private username?: string, + tracer?: Tracer ) { + this.tracer = tracer; sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * Write a file. */ @@ -58,71 +96,119 @@ export class FileSystem { content: Uint8Array, opts: WriteFileOpts = {} ): Promise { - const result = await this.agentClient.fs.writeFile( - path, - content, - opts.create ?? true, - opts.overwrite ?? true + return this.withSpan( + "fs.writeFile", + { + "fs.path": path, + "fs.size": content.length, + "fs.create": opts.create ?? true, + "fs.overwrite": opts.overwrite ?? true + }, + async () => { + const result = await this.agentClient.fs.writeFile( + path, + content, + opts.create ?? true, + opts.overwrite ?? true + ); + + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + } ); - - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } } /** * Write a file as a string. */ async writeTextFile(path: string, content: string, opts: WriteFileOpts = {}) { - return this.writeFile(path, new TextEncoder().encode(content), opts); + return this.withSpan( + "fs.writeTextFile", + { + "fs.path": path, + "fs.contentLength": content.length, + "fs.create": opts.create ?? true, + "fs.overwrite": opts.overwrite ?? true + }, + async () => { + return this.writeFile(path, new TextEncoder().encode(content), opts); + } + ); } /** * Create a directory. */ async mkdir(path: string, recursive = false): Promise { - const result = await this.agentClient.fs.mkdir(path, recursive); + return this.withSpan( + "fs.mkdir", + { + "fs.path": path, + "fs.recursive": recursive + }, + async () => { + const result = await this.agentClient.fs.mkdir(path, recursive); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + } + ); } /** * Read a directory. */ async readdir(path: string): Promise { - const result = await this.agentClient.fs.readdir(path); - - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } - - return result.result.entries.map((entry) => ({ - ...entry, - type: entry.type === 0 ? "file" : "directory", - })); + return this.withSpan( + "fs.readdir", + { "fs.path": path }, + async () => { + const result = await this.agentClient.fs.readdir(path); + + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + + return result.result.entries.map((entry) => ({ + ...entry, + type: entry.type === 0 ? "file" : "directory", + })); + } + ); } /** * Read a file */ async readFile(path: string): Promise { - const result = await this.agentClient.fs.readFile(path); + return this.withSpan( + "fs.readFile", + { "fs.path": path }, + async () => { + const result = await this.agentClient.fs.readFile(path); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } - return result.result.content; + return result.result.content; + } + ); } /** * Read a file as a string. */ async readTextFile(path: string): Promise { - return await this.readFile(path).then((content) => - new TextDecoder("utf-8").decode(content) + return this.withSpan( + "fs.readTextFile", + { "fs.path": path }, + async () => { + const content = await this.readFile(path); + return new TextDecoder("utf-8").decode(content); + } ); } @@ -130,17 +216,23 @@ export class FileSystem { * Get the stat of a file or directory. */ async stat(path: string): Promise { - const result = await this.agentClient.fs.stat(path); - - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } - - return { - ...result.result, - type: - result.result.type === 0 ? ("file" as const) : ("directory" as const), - }; + return this.withSpan( + "fs.stat", + { "fs.path": path }, + async () => { + const result = await this.agentClient.fs.stat(path); + + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + + return { + ...result.result, + type: + result.result.type === 0 ? ("file" as const) : ("directory" as const), + }; + } + ); } /** @@ -152,38 +244,68 @@ export class FileSystem { recursive = false, overwrite = false ): Promise { - const result = await this.agentClient.fs.copy( - from, - to, - recursive, - overwrite + return this.withSpan( + "fs.copy", + { + "fs.from": from, + "fs.to": to, + "fs.recursive": recursive, + "fs.overwrite": overwrite + }, + async () => { + const result = await this.agentClient.fs.copy( + from, + to, + recursive, + overwrite + ); + + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + } ); - - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } } /** * Rename a file or directory. */ async rename(from: string, to: string, overwrite = false): Promise { - const result = await this.agentClient.fs.rename(from, to, overwrite); + return this.withSpan( + "fs.rename", + { + "fs.from": from, + "fs.to": to, + "fs.overwrite": overwrite + }, + async () => { + const result = await this.agentClient.fs.rename(from, to, overwrite); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + } + ); } /** * Remove a file or directory. */ async remove(path: string, recursive = false): Promise { - const result = await this.agentClient.fs.remove(path, recursive); + return this.withSpan( + "fs.remove", + { + "fs.path": path, + "fs.recursive": recursive + }, + async () => { + const result = await this.agentClient.fs.remove(path, recursive); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } + } + ); } /** @@ -204,35 +326,45 @@ export class FileSystem { * @returns A watcher that can be disposed to stop the watch. */ async watch(path: string, options: WatchOpts = {}): Promise { - const emitter = new Emitter(); - - const result = await this.agentClient.fs.watch(path, options, (event) => { - if (this.username) { - emitter.fire({ - ...event, - paths: event.paths.map((path) => - path.replace(`home/${this.username}/workspace/`, "sandbox/") - ), + return this.withSpan( + "fs.watch", + { + "fs.path": path, + "fs.recursive": options.recursive ?? false, + "fs.excludeCount": options.excludes?.length ?? 0 + }, + async () => { + const emitter = new Emitter(); + + const result = await this.agentClient.fs.watch(path, options, (event) => { + if (this.username) { + emitter.fire({ + ...event, + paths: event.paths.map((path) => + path.replace(`home/${this.username}/workspace/`, "sandbox/") + ), + }); + } else { + emitter.fire(event); + } }); - } else { - emitter.fire(event); - } - }); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); + } - const watcher = { - dispose: () => { - result.dispose(); - emitter.dispose(); - }, - onEvent: emitter.event, - }; - this.disposable.addDisposable(watcher); + const watcher = { + dispose: () => { + result.dispose(); + emitter.dispose(); + }, + onEvent: emitter.event, + }; + this.disposable.addDisposable(watcher); - return watcher; + return watcher; + } + ); } /** @@ -240,8 +372,14 @@ export class FileSystem { * from within the workspace directory. A download URL that's valid for 5 minutes. */ async download(path: string): Promise<{ downloadUrl: string }> { - const result = await this.agentClient.fs.download(path); + return this.withSpan( + "fs.download", + { "fs.path": path }, + async () => { + const result = await this.agentClient.fs.download(path); - return result; + return result; + } + ); } } diff --git a/src/SandboxClient/hosts.ts b/src/SandboxClient/hosts.ts index be9e010..1a747bd 100644 --- a/src/SandboxClient/hosts.ts +++ b/src/SandboxClient/hosts.ts @@ -1,9 +1,48 @@ import type { HostToken } from "../HostTokens"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type { HostToken }; export class Hosts { - constructor(private sandboxId: string, private hostToken?: HostToken) {} + constructor( + private sandboxId: string, + private hostToken?: HostToken, + private tracer?: Tracer + ) {} + + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * If private Sandbox this will return a URL with a host token. */ diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 337ee13..fb54b5e 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -15,6 +15,7 @@ import { setup, system } from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; import { AgentClient } from "../AgentClient"; import { SandboxSession } from "../types"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export * from "./filesystem"; export * from "./ports"; @@ -28,13 +29,17 @@ export * from "./hosts"; type SandboxClientParams = { hostToken?: HostToken; username?: string; + tracer?: Tracer; }; export class SandboxClient { + private tracer?: Tracer; + static async create( session: SandboxSession, getSession: (id: string) => Promise, - initStatusCb?: (event: system.InitStatus) => void + initStatusCb?: (event: system.InitStatus) => void, + tracer?: Tracer ) { const { client: agentClient, joinResult } = await AgentClient.create({ session, @@ -50,6 +55,7 @@ export class SandboxClient { // FS events username: session.sessionId ? joinResult.client.username : undefined, hostToken: session.hostToken, + tracer, }; let setupProgress = await agentClient.setup.getProgress(); @@ -113,7 +119,7 @@ export class SandboxClient { /** * Namespace for managing ports on this Sandbox */ - public readonly ports = new Ports(this.disposable, this.agentClient); + public readonly ports = new Ports(this.disposable, this.agentClient, this.tracer); /** * Namespace for the setup that runs when the Sandbox starts from scratch. @@ -123,13 +129,14 @@ export class SandboxClient { /** * Namespace for tasks that are defined in the Sandbox. */ - public readonly tasks = new Tasks(this.disposable, this.agentClient); + public readonly tasks: Tasks; constructor( protected agentClient: IAgentClient, - { hostToken, username }: SandboxClientParams, + { hostToken, username, tracer }: SandboxClientParams, initialSetupProgress: setup.SetupProgress ) { + this.tracer = tracer; // TODO: Bring this back once metrics polling does not reset inactivity // const metricsDisposable = { // dispose: @@ -140,14 +147,16 @@ export class SandboxClient { this.setup = new Setup( this.disposable, this.agentClient, - initialSetupProgress + initialSetupProgress, + tracer ); - this.fs = new FileSystem(this.disposable, this.agentClient, username); - this.terminals = new Terminals(this.disposable, this.agentClient); - this.commands = new SandboxCommands(this.disposable, this.agentClient); + this.fs = new FileSystem(this.disposable, this.agentClient, username, tracer); + this.terminals = new Terminals(this.disposable, this.agentClient, tracer); + this.tasks = new Tasks(this.disposable, this.agentClient, tracer); + this.commands = new SandboxCommands(this.disposable, this.agentClient, tracer); - this.hosts = new Hosts(this.agentClient.sandboxId, hostToken); - this.interpreters = new Interpreters(this.disposable, this.commands); + this.hosts = new Hosts(this.agentClient.sandboxId, hostToken, tracer); + this.interpreters = new Interpreters(this.disposable, this.commands, tracer); this.disposable.onWillDispose(() => this.agentClient.dispose()); this.disposable.onWillDispose(() => { @@ -176,6 +185,39 @@ export class SandboxClient { }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * The current state of the Sandbox */ @@ -267,44 +309,62 @@ export class SandboxClient { * reconnect to the sandbox. */ public disconnect() { - this.isExplicitlyDisconnected = true; - if (this.keepAliveInterval) { - clearInterval(this.keepAliveInterval); - this.keepAliveInterval = null; - } + return this.withSpan( + "sandboxClient.disconnect", + { "sandbox.id": this.id }, + async () => { + this.isExplicitlyDisconnected = true; + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } - return this.agentClient.disconnect(); + return this.agentClient.disconnect(); + } + ); } /** * Explicitly reconnect to the sandbox. */ public reconnect() { - this.isExplicitlyDisconnected = false; - return this.agentClient.reconnect(); + return this.withSpan( + "sandboxClient.reconnect", + { "sandbox.id": this.id }, + async () => { + this.isExplicitlyDisconnected = false; + return this.agentClient.reconnect(); + } + ); } /** * Attempt automatic reconnection with retry logic */ private async attemptAutoReconnect() { - try { - await retryWithDelay( - async () => { - if (this.isExplicitlyDisconnected) { - throw new Error("Explicit disconnect - stopping auto-reconnect"); - } - await this.agentClient.reconnect(); - }, - 3, // retries - 2000 // delay in ms - ); - // Clear the disconnect flag on successful reconnection - this.isExplicitlyDisconnected = false; - } catch (error) { - // Auto-reconnect failed, but we don't throw to avoid unhandled rejections - console.warn("Auto-reconnect failed:", error); - } + return this.withSpan( + "sandboxClient.attemptAutoReconnect", + { "sandbox.id": this.id }, + async () => { + try { + await retryWithDelay( + async () => { + if (this.isExplicitlyDisconnected) { + throw new Error("Explicit disconnect - stopping auto-reconnect"); + } + await this.agentClient.reconnect(); + }, + 3, // retries + 2000 // delay in ms + ); + // Clear the disconnect flag on successful reconnection + this.isExplicitlyDisconnected = false; + } catch (error) { + // Auto-reconnect failed, but we don't throw to avoid unhandled rejections + console.warn("Auto-reconnect failed:", error); + } + } + ); } private keepAliveInterval: NodeJS.Timeout | null = null; diff --git a/src/SandboxClient/interpreters.ts b/src/SandboxClient/interpreters.ts index 06ea628..35ca949 100644 --- a/src/SandboxClient/interpreters.ts +++ b/src/SandboxClient/interpreters.ts @@ -1,17 +1,55 @@ import { Disposable } from "../utils/disposable"; import { SandboxCommands, ShellRunOpts } from "./commands"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export class Interpreters { private disposable = new Disposable(); + private tracer?: Tracer; + constructor( sessionDisposable: Disposable, - private commands: SandboxCommands + private commands: SandboxCommands, + tracer?: Tracer ) { + this.tracer = tracer; sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + private run(command: string, opts?: ShellRunOpts) { return this.commands.run(command, opts); } @@ -20,22 +58,32 @@ export class Interpreters { * Run a JavaScript code snippet in a new shell. */ javascript(code: string) { - return this.run( - `node -p "$(cat <<'EOF' + return this.withSpan( + "interpreters.javascript", + { + "interpreter.language": "javascript", + "interpreter.code": code, + "interpreter.codeLength": code.length + }, + async () => { + return this.run( + `node -p "$(cat <<'EOF' (() => {${code - .split("\n") - .map((line, index, lines) => { - return index === lines.length - 1 && !line.startsWith("return") - ? `return ${line}` - : line; - }) - .join("\n")}})() + .split("\n") + .map((line, index, lines) => { + return index === lines.length - 1 && !line.startsWith("return") + ? `return ${line}` + : line; + }) + .join("\n")}})() EOF )"`, - { - env: { - NO_COLOR: "true", - }, + { + env: { + NO_COLOR: "true", + }, + } + ); } ); } @@ -44,7 +92,15 @@ EOF * Run a Python code snippet in a new shell. */ python(code: string) { - return this.run(`python3 <<'PYCODE' + return this.withSpan( + "interpreters.python", + { + "interpreter.language": "python", + "interpreter.code": code, + "interpreter.codeLength": code.length + }, + async () => { + return this.run(`python3 <<'PYCODE' ${code .split("\n") .map((line, index, lines) => { @@ -55,5 +111,7 @@ ${code .join("\n")} PYCODE `); + } + ); } } diff --git a/src/SandboxClient/ports.ts b/src/SandboxClient/ports.ts index 403c01b..fe2cbbc 100644 --- a/src/SandboxClient/ports.ts +++ b/src/SandboxClient/ports.ts @@ -1,6 +1,7 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type Port = { host: string; @@ -26,7 +27,8 @@ export class Ports { constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient + private agentClient: IAgentClient, + private tracer?: Tracer ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); @@ -68,22 +70,60 @@ export class Ports { ); } + private withSpan( + operationName: string, + attributes: Record, + fn: () => Promise + ): Promise { + if (!this.tracer) { + return fn(); + } + return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException(error instanceof Error ? error : new Error(String(error))); + throw error; + } finally { + span.end(); + } + }); + } + /** * Get a port by number. */ async get(port: number) { - const ports = await this.getAll(); + return this.withSpan( + "ports.get", + { "port.number": port }, + async () => { + const ports = await this.getAll(); - return ports.find((p) => p.port === port); + return ports.find((p) => p.port === port); + } + ); } /** * Get all ports. */ async getAll(): Promise { - const ports = await this.agentClient.ports.getPorts(); + return this.withSpan( + "ports.getAll", + {}, + async () => { + const ports = await this.agentClient.ports.getPorts(); - return ports.map(({ port, url }) => ({ port, host: url })); + return ports.map(({ port, url }) => ({ port, host: url })); + } + ); } /** @@ -99,39 +139,48 @@ export class Ports { port: number, options?: { timeoutMs?: number } ): Promise { - return new Promise(async (resolve, reject) => { - // Check if port is already open - const portInfo = (await this.getAll()).find((p) => p.port === port); + return this.withSpan( + "ports.waitForPort", + { + "port.number": port, + "port.timeout.ms": options?.timeoutMs + }, + async () => { + return new Promise(async (resolve, reject) => { + // Check if port is already open + const portInfo = (await this.getAll()).find((p) => p.port === port); + + if (portInfo) { + resolve(portInfo); + return; + } - if (portInfo) { - resolve(portInfo); - return; - } + // Set up timeout if specified + let timeoutId: NodeJS.Timeout | undefined; + if (options?.timeoutMs !== undefined) { + timeoutId = setTimeout(() => { + reject( + new Error( + `Timeout of ${options.timeoutMs}ms exceeded waiting for port ${port} to open` + ) + ); + }, options.timeoutMs); + } - // Set up timeout if specified - let timeoutId: NodeJS.Timeout | undefined; - if (options?.timeoutMs !== undefined) { - timeoutId = setTimeout(() => { - reject( - new Error( - `Timeout of ${options.timeoutMs}ms exceeded waiting for port ${port} to open` - ) + // Listen for port open events + const disposable = this.disposable.addDisposable( + this.onDidPortOpen((portInfo) => { + if (portInfo.port === port) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + resolve(portInfo); + disposable.dispose(); + } + }) ); - }, options.timeoutMs); + }); } - - // Listen for port open events - const disposable = this.disposable.addDisposable( - this.onDidPortOpen((portInfo) => { - if (portInfo.port === port) { - if (timeoutId !== undefined) { - clearTimeout(timeoutId); - } - resolve(portInfo); - disposable.dispose(); - } - }) - ); - }); + ); } } diff --git a/src/SandboxClient/setup.ts b/src/SandboxClient/setup.ts index dbcb8cb..8bc70bf 100644 --- a/src/SandboxClient/setup.ts +++ b/src/SandboxClient/setup.ts @@ -3,6 +3,7 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export class Setup { private disposable = new Disposable(); @@ -21,44 +22,91 @@ export class Setup { constructor( sessionDisposable: Disposable, private agentClient: IAgentClient, - private setupProgress: protocol.setup.SetupProgress + private setupProgress: protocol.setup.SetupProgress, + private tracer?: Tracer ) { sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); this.steps = this.setupProgress.steps.map( - (step, index) => new Step(index, step, agentClient) + (step, index) => new Step(index, step, agentClient, tracer) ); } + private withSpan( + operationName: string, + attributes: Record, + fn: () => Promise + ): Promise { + if (!this.tracer) { + return fn(); + } + return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException(error instanceof Error ? error : new Error(String(error))); + throw error; + } finally { + span.end(); + } + }); + } + getSteps() { return this.steps; } async run(): Promise { - await this.agentClient.setup.init(); + return this.withSpan( + "setup.run", + { + "setup.state": this.setupProgress.state, + "setup.currentStepIndex": this.setupProgress.currentStepIndex, + "setup.totalSteps": this.setupProgress.steps.length, + }, + async () => { + await this.agentClient.setup.init(); + } + ); } async waitUntilComplete(): Promise { - if (this.setupProgress.state === "STOPPED") { - throw new Error("Setup Failed"); - } - - if (this.setupProgress.state === "FINISHED") { - return; - } + return this.withSpan( + "setup.waitUntilComplete", + { + "setup.state": this.setupProgress.state, + "setup.currentStepIndex": this.setupProgress.currentStepIndex, + "setup.totalSteps": this.setupProgress.steps.length, + }, + async () => { + if (this.setupProgress.state === "STOPPED") { + throw new Error("Setup Failed"); + } - return new Promise((resolve, reject) => { - const disposer = this.onSetupProgressChange(() => { if (this.setupProgress.state === "FINISHED") { - disposer.dispose(); - resolve(); - } else if (this.setupProgress.state === "STOPPED") { - disposer.dispose(); - reject(new Error("Setup Failed")); + return; } - }); - }); + + return new Promise((resolve, reject) => { + const disposer = this.onSetupProgressChange(() => { + if (this.setupProgress.state === "FINISHED") { + disposer.dispose(); + resolve(); + } else if (this.setupProgress.state === "STOPPED") { + disposer.dispose(); + reject(new Error("Setup Failed")); + } + }); + }); + } + ); } } @@ -89,9 +137,10 @@ export class Step { } constructor( - stepIndex: number, + private stepIndex: number, private step: protocol.setup.Step, - private agentClient: IAgentClient + private agentClient: IAgentClient, + private tracer?: Tracer ) { this.disposable.addDisposable( this.agentClient.setup.onSetupProgressUpdate((progress) => { @@ -123,51 +172,103 @@ export class Step { ); } - async open(dimensions = DEFAULT_SHELL_SIZE): Promise { - const open = async (shellId: protocol.shell.ShellId) => { - const shell = await this.agentClient.shells.open(shellId, dimensions); + private withSpan( + operationName: string, + attributes: Record, + fn: () => Promise + ): Promise { + if (!this.tracer) { + return fn(); + } + return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException(error instanceof Error ? error : new Error(String(error))); + throw error; + } finally { + span.end(); + } + }); + } - this.output = shell.buffer; + async open(dimensions = DEFAULT_SHELL_SIZE): Promise { + return this.withSpan( + "setup.stepOpen", + { + "step.index": this.stepIndex, + "step.name": this.step.name, + "step.command": this.step.command, + "step.status": this.step.finishStatus || "IDLE", + "step.shellId": this.step.shellId, + "dimensions.cols": dimensions.cols, + "dimensions.rows": dimensions.rows, + }, + async () => { + const open = async (shellId: protocol.shell.ShellId) => { + const shell = await this.agentClient.shells.open(shellId, dimensions); - return this.output.join("\n"); - }; + this.output = shell.buffer; - if (this.step.shellId) { - return open(this.step.shellId); - } + return this.output.join("\n"); + }; - return new Promise((resolve) => { - const disposable = this.onStatusChange(() => { if (this.step.shellId) { - disposable.dispose(); - resolve(open(this.step.shellId)); + return open(this.step.shellId); } - }); - }); + + return new Promise((resolve) => { + const disposable = this.onStatusChange(() => { + if (this.step.shellId) { + disposable.dispose(); + resolve(open(this.step.shellId)); + } + }); + }); + } + ); } async waitUntilComplete() { - if (this.step.finishStatus === "FAILED") { - throw new Error("Step Failed"); - } - - if ( - this.step.finishStatus === "SUCCEEDED" || - this.step.finishStatus === "SKIPPED" - ) { - return; - } + return this.withSpan( + "setup.stepWaitUntilComplete", + { + "step.index": this.stepIndex, + "step.name": this.step.name, + "step.command": this.step.command, + "step.status": this.step.finishStatus || "IDLE", + "step.shellId": this.step.shellId, + }, + async () => { + if (this.step.finishStatus === "FAILED") { + throw new Error("Step Failed"); + } - return new Promise((resolve, reject) => { - const disposable = this.onStatusChange((status) => { - if (status === "SUCCEEDED" || status === "SKIPPED") { - disposable.dispose(); - resolve(); - } else if (status === "FAILED") { - disposable.dispose(); - reject(new Error("Step Failed")); + if ( + this.step.finishStatus === "SUCCEEDED" || + this.step.finishStatus === "SKIPPED" + ) { + return; } - }); - }); + + return new Promise((resolve, reject) => { + const disposable = this.onStatusChange((status) => { + if (status === "SUCCEEDED" || status === "SKIPPED") { + disposable.dispose(); + resolve(); + } else if (status === "FAILED") { + disposable.dispose(); + reject(new Error("Step Failed")); + } + }); + }); + } + ); } -} +} \ No newline at end of file diff --git a/src/SandboxClient/tasks.ts b/src/SandboxClient/tasks.ts index a2a33af..9409caf 100644 --- a/src/SandboxClient/tasks.ts +++ b/src/SandboxClient/tasks.ts @@ -3,6 +3,7 @@ import { Disposable, IDisposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; import { IAgentClient } from "../AgentClient/agent-client-interface"; import { Emitter } from "../utils/event"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type TaskDefinition = { name: string; @@ -13,45 +14,95 @@ export type TaskDefinition = { export class Tasks { private disposable = new Disposable(); private cachedTasks?: Task[]; + private tracer?: Tracer; + constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient + private agentClient: IAgentClient, + tracer?: Tracer ) { + this.tracer = tracer; sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * Gets all tasks that are available in the current sandbox. */ async getAll(): Promise { - if (!this.cachedTasks) { - const [tasks, ports] = await Promise.all([ - this.agentClient.tasks.getTasks(), - this.agentClient.ports.getPorts(), - ]); - - this.cachedTasks = Object.values(tasks.tasks).map( - (task) => new Task(this.agentClient, task, ports) - ); - } + return this.withSpan( + "tasks.getAll", + { cached: !!this.cachedTasks }, + async () => { + if (!this.cachedTasks) { + const [tasks, ports] = await Promise.all([ + this.agentClient.tasks.getTasks(), + this.agentClient.ports.getPorts(), + ]); - return this.cachedTasks; + this.cachedTasks = Object.values(tasks.tasks).map( + (task) => new Task(this.agentClient, task, ports, this.tracer) + ); + } + + return this.cachedTasks; + } + ); } /** * Gets a task by its ID. */ async get(taskId: string): Promise { - const tasks = await this.getAll(); + return this.withSpan( + "tasks.get", + { taskId }, + async () => { + const tasks = await this.getAll(); - return tasks.find((task) => task.id === taskId); + return tasks.find((task) => task.id === taskId); + } + ); } } export class Task { private disposable = new Disposable(); + private tracer?: Tracer; private get shell() { return this.data.shell; } @@ -98,8 +149,10 @@ export class Task { constructor( private agentClient: IAgentClient, private data: protocol.task.TaskDTO, - private _ports: protocol.port.Port[] + private _ports: protocol.port.Port[], + tracer?: Tracer ) { + this.tracer = tracer; this.disposable.addDisposable( agentClient.ports.onPortsUpdated((ports) => { this._ports = ports; @@ -158,67 +211,155 @@ export class Task { }) ); } - async open(dimensions = DEFAULT_SHELL_SIZE) { - if (!this.shell) { - throw new Error("Task is not running"); + + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); } - const openedShell = await this.agentClient.shells.open( - this.shell.shellId, - dimensions + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } ); + } + + async open(dimensions = DEFAULT_SHELL_SIZE) { + return this.withSpan( + "task.open", + { + taskId: this.id, + taskName: this.name, + cols: dimensions.cols, + rows: dimensions.rows, + hasShell: !!this.shell + }, + async () => { + if (!this.shell) { + throw new Error("Task is not running"); + } + + const openedShell = await this.agentClient.shells.open( + this.shell.shellId, + dimensions + ); - this.openedShell = { - shellId: openedShell.shellId, - output: openedShell.buffer, - dimensions, - }; + this.openedShell = { + shellId: openedShell.shellId, + output: openedShell.buffer, + dimensions, + }; - return this.openedShell.output.join("\n"); + return this.openedShell.output.join("\n"); + } + ); } async waitForPort(timeout: number = 30_000) { - if (this.ports.length) { - return this.ports[0]; - } + return this.withSpan( + "task.waitForPort", + { + taskId: this.id, + taskName: this.name, + timeout, + existingPortsCount: this.ports.length + }, + async () => { + if (this.ports.length) { + return this.ports[0]; + } + + let disposer: IDisposable | undefined; - let disposer: IDisposable | undefined; - - const [port] = await Promise.all([ - new Promise((resolve) => { - disposer = this.agentClient.tasks.onTaskUpdate((task) => { - if (task.id !== this.id) { - return; - } - - if (task.ports.length) { - disposer?.dispose(); - resolve(task.ports[0]); - } - }); - this.disposable.addDisposable(disposer); - }), - new Promise((resolve, reject) => { - setTimeout(() => { - disposer?.dispose(); - reject(new Error("Timeout waiting for port")); - }, timeout); - }), - ]); - - return port; + const [port] = await Promise.all([ + new Promise((resolve) => { + disposer = this.agentClient.tasks.onTaskUpdate((task) => { + if (task.id !== this.id) { + return; + } + + if (task.ports.length) { + disposer?.dispose(); + resolve(task.ports[0]); + } + }); + this.disposable.addDisposable(disposer); + }), + new Promise((resolve, reject) => { + setTimeout(() => { + disposer?.dispose(); + reject(new Error("Timeout waiting for port")); + }, timeout); + }), + ]); + + return port; + } + ); } async run() { - await this.agentClient.tasks.runTask(this.id); + return this.withSpan( + "task.run", + { + taskId: this.id, + taskName: this.name, + command: this.command, + runAtStart: this.runAtStart + }, + async () => { + await this.agentClient.tasks.runTask(this.id); + } + ); } async restart() { - await this.run(); + return this.withSpan( + "task.restart", + { + taskId: this.id, + taskName: this.name, + command: this.command + }, + async () => { + await this.run(); + } + ); } async stop() { - if (this.shell) { - await this.agentClient.tasks.stopTask(this.id); - } + return this.withSpan( + "task.stop", + { + taskId: this.id, + taskName: this.name, + hasShell: !!this.shell + }, + async () => { + if (this.shell) { + await this.agentClient.tasks.stopTask(this.id); + } + } + ); } dispose() { this.disposable.dispose(); } -} +} \ No newline at end of file diff --git a/src/SandboxClient/terminals.ts b/src/SandboxClient/terminals.ts index 9c92ba1..c9c0391 100644 --- a/src/SandboxClient/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -3,6 +3,7 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type ShellSize = { cols: number; rows: number }; @@ -10,77 +11,139 @@ export const DEFAULT_SHELL_SIZE: ShellSize = { cols: 128, rows: 24 }; export class Terminals { private disposable = new Disposable(); + private tracer?: Tracer; + constructor( sessionDisposable: Disposable, - private agentClient: IAgentClient + private agentClient: IAgentClient, + tracer?: Tracer ) { + this.tracer = tracer; sessionDisposable.onWillDispose(() => { this.disposable.dispose(); }); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + async create( command: "bash" | "zsh" | "fish" | "ksh" | "dash" = "bash", opts?: ShellRunOpts ): Promise { - const allEnv = Object.assign(opts?.env ?? {}); - - // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(allEnv).length - ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( - allEnv - ) - .map(([key, value]) => `${key}=${value}`) - .join(" ")} ${command}` - : `source $HOME/.private/.env 2>/dev/null || true && ${command}`; - - if (opts?.cwd) { - commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; - } + return this.withSpan( + "terminals.create", + { + command, + cwd: opts?.cwd ?? "", + name: opts?.name ?? "", + envCount: Object.keys(opts?.env ?? {}).length, + hasDimensions: !!opts?.dimensions, + }, + async () => { + const allEnv = Object.assign(opts?.env ?? {}); - const shell = await this.agentClient.shells.create( - this.agentClient.workspacePath, - opts?.dimensions ?? DEFAULT_SHELL_SIZE, - commandWithEnv, - "TERMINAL", - true - ); + // TODO: use a new shell API that natively supports cwd & env + let commandWithEnv = Object.keys(allEnv).length + ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( + allEnv + ) + .map(([key, value]) => `${key}=${value}`) + .join(" ")} ${command}` + : `source $HOME/.private/.env 2>/dev/null || true && ${command}`; - if (opts?.name) { - this.agentClient.shells.rename(shell.shellId, opts.name); - } + if (opts?.cwd) { + commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; + } + + const shell = await this.agentClient.shells.create( + this.agentClient.workspacePath, + opts?.dimensions ?? DEFAULT_SHELL_SIZE, + commandWithEnv, + "TERMINAL", + true + ); - return new Terminal(shell, this.agentClient); + if (opts?.name) { + this.agentClient.shells.rename(shell.shellId, opts.name); + } + + return new Terminal(shell, this.agentClient, this.tracer); + } + ); } async get(shellId: string) { - const shells = await this.agentClient.shells.getShells(); + return this.withSpan( + "terminals.get", + { shellId }, + async () => { + const shells = await this.agentClient.shells.getShells(); - const shell = shells.find((shell) => shell.shellId === shellId); + const shell = shells.find((shell) => shell.shellId === shellId); - if (!shell) { - return; - } + if (!shell) { + return; + } - return new Terminal(shell, this.agentClient); + return new Terminal(shell, this.agentClient, this.tracer); + } + ); } /** * Gets all terminals running in the current sandbox */ async getAll() { - const shells = await this.agentClient.shells.getShells(); + return this.withSpan( + "terminals.getAll", + {}, + async () => { + const shells = await this.agentClient.shells.getShells(); - return shells - .filter( - (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) - ) - .map((shell) => new Terminal(shell, this.agentClient)); + return shells + .filter( + (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) + ) + .map((shell) => new Terminal(shell, this.agentClient, this.tracer)); + } + ); } } export class Terminal { private disposable = new Disposable(); + private tracer?: Tracer; // TODO: differentiate between stdout and stderr, also send back bytes instead of // strings private onOutputEmitter = this.disposable.addDisposable( @@ -105,8 +168,10 @@ export class Terminal { constructor( private shell: protocol.shell.ShellDTO & { buffer?: string[] }, - private agentClient: IAgentClient + private agentClient: IAgentClient, + tracer?: Tracer ) { + this.tracer = tracer; this.disposable.addDisposable( this.agentClient.shells.onShellOut(({ shellId, out }) => { if (shellId === this.shell.shellId) { @@ -121,31 +186,104 @@ export class Terminal { ); } + private async withSpan( + operationName: string, + attributes: Record = {}, + operation: () => Promise + ): Promise { + if (!this.tracer) { + return operation(); + } + + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await operation(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } + } + ); + } + /** * Open the terminal and get its current output, subscribes to future output */ async open(dimensions = DEFAULT_SHELL_SIZE): Promise { - const shell = await this.agentClient.shells.open( - this.shell.shellId, - dimensions - ); + return this.withSpan( + "terminal.open", + { + shellId: this.shell.shellId, + cols: dimensions.cols, + rows: dimensions.rows, + }, + async () => { + const shell = await this.agentClient.shells.open( + this.shell.shellId, + dimensions + ); - this.output = shell.buffer; + this.output = shell.buffer; - return this.output.join("\n"); + return this.output.join("\n"); + } + ); } async write(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - await this.agentClient.shells.send(this.shell.shellId, input, dimensions); + return this.withSpan( + "terminal.write", + { + shellId: this.shell.shellId, + inputLength: input.length, + cols: dimensions.cols, + rows: dimensions.rows, + }, + async () => { + await this.agentClient.shells.send(this.shell.shellId, input, dimensions); + } + ); } async run(input: string, dimensions = DEFAULT_SHELL_SIZE): Promise { - return this.write(input + "\n", dimensions); + return this.withSpan( + "terminal.run", + { + shellId: this.shell.shellId, + command: input, + cols: dimensions.cols, + rows: dimensions.rows, + }, + async () => { + return this.write(input + "\n", dimensions); + } + ); } // TODO: allow for kill signals async kill(): Promise { - this.disposable.dispose(); - await this.agentClient.shells.delete(this.shell.shellId); + return this.withSpan( + "terminal.kill", + { + shellId: this.shell.shellId, + }, + async () => { + this.disposable.dispose(); + await this.agentClient.shells.delete(this.shell.shellId); + } + ); } } diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 49ed541..b12768e 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -87,7 +87,7 @@ export class Sandboxes { getStartOptions(opts) ); - return new Sandbox(sandbox.id, this.api, startResponse); + return new Sandbox(sandbox.id, this.api, startResponse, this.tracer); } /** @@ -105,7 +105,7 @@ export class Sandboxes { { "sandbox.id": sandboxId }, async () => { const startResponse = await this.api.startVm(sandboxId); - return new Sandbox(sandboxId, this.api, startResponse); + return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } ); } @@ -160,7 +160,7 @@ export class Sandboxes { try { const startResponse = await this.api.startVm(sandboxId, opts); - return new Sandbox(sandboxId, this.api, startResponse); + return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } catch (e) { throw new Error("Failed to start VM, " + String(e)); } @@ -305,23 +305,29 @@ export class Sandboxes { * guaranteed to be perfectly up-to-date. */ async listRunning() { - const data = await this.api.listRunningVms(); - - return { - concurrentVmCount: data.concurrent_vm_count, - concurrentVmLimit: data.concurrent_vm_limit, - vms: data.vms.map(vm => ({ - id: vm.id, - creditBasis: vm.credit_basis, - lastActiveAt: vm.last_active_at ? parseTimestamp(vm.last_active_at) : undefined, - sessionStartedAt: vm.session_started_at ? parseTimestamp(vm.session_started_at) : undefined, - specs: vm.specs ? { - cpu: vm.specs.cpu, - memory: vm.specs.memory, - storage: vm.specs.storage, - } : undefined, - })), - }; + return this.withSpan( + "sandboxes.listRunning", + {}, + async () => { + const data = await this.api.listRunningVms(); + + return { + concurrentVmCount: data.concurrent_vm_count, + concurrentVmLimit: data.concurrent_vm_limit, + vms: data.vms.map(vm => ({ + id: vm.id, + creditBasis: vm.credit_basis, + lastActiveAt: vm.last_active_at ? parseTimestamp(vm.last_active_at) : undefined, + sessionStartedAt: vm.session_started_at ? parseTimestamp(vm.session_started_at) : undefined, + specs: vm.specs ? { + cpu: vm.specs.cpu, + memory: vm.specs.memory, + storage: vm.specs.storage, + } : undefined, + })), + }; + } + ); } /** diff --git a/src/browser/index.ts b/src/browser/index.ts index fab7497..eaafbab 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -1,6 +1,7 @@ import * as protocol from "../pitcher-protocol"; import { SandboxSession } from "../types"; import { SandboxClient } from "../SandboxClient"; +import { Tracer } from "@opentelemetry/api"; export * from "../SandboxClient"; @@ -11,6 +12,7 @@ type ConnectToSandboxOptions = { getSession: (id: string) => Promise; onFocusChange?: (cb: (isFocused: boolean) => void) => () => void; initStatusCb?: (event: protocol.system.InitStatus) => void; + tracer?: Tracer; }; /** @@ -29,8 +31,9 @@ export async function connectToSandbox({ }; }, initStatusCb = () => {}, + tracer, }: ConnectToSandboxOptions): Promise { - const client = await SandboxClient.create(session, getSession, initStatusCb); + const client = await SandboxClient.create(session, getSession, initStatusCb, tracer); onFocusChange((isFocused) => { // We immediately ping the connection when focusing, so that diff --git a/src/node/index.ts b/src/node/index.ts index 7b6ce81..1c6f1fe 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -1,17 +1,20 @@ import * as protocol from "../pitcher-protocol"; import { SandboxClient } from "../SandboxClient"; import { SandboxSession } from "../types"; +import { Tracer } from "@opentelemetry/api"; type ConnectToSandboxOptions = { session: SandboxSession; getSession: (id: string) => Promise; initStatusCb?: (event: protocol.system.InitStatus) => void; + tracer?: Tracer; }; export async function connectToSandbox({ session, getSession, initStatusCb = () => {}, + tracer, }: ConnectToSandboxOptions): Promise { - return SandboxClient.create(session, getSession, initStatusCb); + return SandboxClient.create(session, getSession, initStatusCb, tracer); } From 6309b9b30558c17d5b2f43997ff0329ac027a6c8 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 8 Aug 2025 19:43:19 +0200 Subject: [PATCH 200/241] add claude doc --- CLAUDE.md | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..33c255f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,116 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Build Commands +- `npm run build` - Full production build (clean, esbuild, generate types for both CJS and ESM) +- `npm run build:esbuild` - Run esbuild bundling only +- `npm run clean` - Remove dist directory +- `npm run dev:cli` - Development mode with file watching for CLI (watches src/bin) + +### Type Checking and Linting +- `npm run typecheck` - Run TypeScript type checking without emitting files +- `npm run format` - Format code using Prettier + +### CLI Development +- `npm run dev:cli` - Start development mode with file watching specifically for CLI changes +- The CLI binary is built to `dist/bin/codesandbox.mjs` and made executable +- CLI can be run with `./dist/bin/codesandbox.mjs` or just `csb` if installed globally + +### API Client Generation +- `npm run build-openapi` - Generate API clients from production OpenAPI spec +- `npm run build-openapi:staging` - Generate API clients from staging OpenAPI spec +- Generated clients are placed in `src/api-clients/` directory + +## Architecture + +### Core Components + +**SDK Entry Points:** +- `src/index.ts` - Main SDK export with `CodeSandbox` class +- `src/browser/index.ts` - Browser-specific SDK build +- `src/node/index.ts` - Node.js-specific SDK build + +**Key Classes:** +- `CodeSandbox` - Main SDK class providing `sandboxes` and `hosts` properties +- `Sandboxes` (src/Sandboxes.ts) - Manages sandbox operations (create, list, get, fork) +- `Sandbox` (src/Sandbox.ts) - Individual sandbox instance with connection and session management +- `SandboxClient` (src/SandboxClient/index.ts) - High-level client for sandbox interactions +- `API` (src/API.ts) - Low-level API wrapper for all CodeSandbox REST endpoints + +**Communication Layer:** +- `src/pitcher-protocol/` - WebSocket protocol definitions and message types for real-time sandbox communication +- `src/AgentClient/` - WebSocket client implementation for connecting to sandbox agents + +**CLI:** +- `src/bin/main.tsx` - CLI entry point using Ink (React for CLI) +- `src/bin/commands/` - Individual CLI commands (build, sandbox, previewHosts, hostTokens) +- `src/bin/ui/` - Ink-based UI components for the interactive CLI + +### Build System + +**Multi-Format Output:** +- ESM build: `dist/esm/` (primary format, "type": "module") +- CommonJS build: `dist/cjs/` (compatibility) +- Browser build: Separate bundles with browser polyfills +- CLI build: Standalone executable with shebang + +**Key Build Features:** +- esbuild for bundling with custom plugins for module replacement +- Separate TypeScript compilation for type definitions +- Browser polyfills for Node.js modules (os, path, crypto, etc.) +- External dependencies preserved except for CLI build which bundles React/Ink + +### Type System + +**Configuration:** +- Main tsconfig.json excludes `src/bin` and `src/api-clients` +- Separate build configs for CJS (`tsconfig.build-cjs.json`) and ESM (`tsconfig.build-esm.json`) +- Strict TypeScript configuration with comprehensive type checking + +**Generated Types:** +- `src/api-clients/` contains auto-generated API clients and types from OpenAPI specs +- Multiple API client modules for different sandbox services (fs, git, shell, etc.) + +### Authentication & Configuration + +**API Authentication:** +- Requires CodeSandbox API token (CSB_API_KEY environment variable) +- Token creation: https://codesandbox.io/t/api +- Automatic User-Agent header injection with SDK version + +**Environment Detection:** +- Automatic base URL inference based on API key format +- Support for different CodeSandbox environments (production, staging) + +### Testing + +The codebase includes a `test-template/` directory with a Vite + React + TypeScript template for SDK testing. + +## Documentation + +**SDK Documentation:** https://codesandbox.io/docs/sdk + +**Contributing to Documentation:** +- Documentation source is located at https://github.com/codesandbox/docs +- SDK-specific docs are in `packages/projects-docs/pages/sdk/` folder +- Update documentation there when making changes to SDK functionality + +## Key Patterns + +**Error Handling:** +- `handleResponse` utility for consistent API error handling +- Retry logic with exponential backoff for critical operations +- OpenTelemetry tracing integration for observability + +**Session Management:** +- Support for custom sessions with git credentials and environment variables +- Browser session creation for web-based sandbox interactions +- Automatic session cleanup and disposal + +**Resource Management:** +- VM tier scaling and hibernation timeout management +- Cluster and bootup type tracking +- Agent version management and update detection \ No newline at end of file From dce7cafd719e398b1f1421776bd55fa5601eccd0 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 11 Aug 2025 10:38:50 +0200 Subject: [PATCH 201/241] feat: private sandbox, public hosts with public-hosts privacy (#154) --- openapi.json | 20 +++++++++++++++++--- src/Sandboxes.ts | 26 ++++++++++++++++++-------- src/api-clients/client/types.gen.ts | 12 ++++++++++-- src/types.ts | 9 ++++++++- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/openapi.json b/openapi.json index e1898bd..5512a4e 100644 --- a/openapi.json +++ b/openapi.json @@ -317,8 +317,14 @@ "properties": { "credit_basis": { "type": "string" }, "id": { "type": "string" }, - "last_active_at": { "type": "integer" }, - "session_started_at": { "type": "integer" }, + "last_active_at": { + "format": "date-time", + "type": "string" + }, + "session_started_at": { + "format": "date-time", + "type": "string" + }, "specs": { "properties": { "cpu": { "type": "integer" }, @@ -406,6 +412,10 @@ "description": "Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox.", "type": "integer" }, + "private_preview": { + "description": "Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible", + "type": "boolean" + }, "start_options": { "description": "Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation.", "properties": { @@ -1042,6 +1052,10 @@ "minimum": 0, "type": "integer" }, + "private_preview": { + "description": "Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible", + "type": "boolean" + }, "runtime": { "default": "browser", "description": "Runtime to use for the sandbox. Defaults to `\"browser\"`.", @@ -2590,6 +2604,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], "tags": [] } diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index b12768e..fcf5091 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -74,12 +74,16 @@ export class Sandboxes { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy); + const sandbox = await this.api.forkSandbox(templateId, { - privacy: privacyToNumber(privacy), + privacy: mappedPrivacy, title: opts?.title, description: opts?.description, tags: tagsWithSdk, path, + private_preview: privatePreview, }); const startResponse = await this.api.startVm( @@ -362,30 +366,36 @@ export class Sandboxes { } -function parseTimestamp(timestamp: number): Date | undefined { +function parseTimestamp(timestamp: number | string): Date | undefined { if (!timestamp || timestamp === 0) { return undefined; } + // Convert string to number if needed + const numTimestamp = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp; + // Handle both seconds and milliseconds timestamps - const ts = timestamp < 10000000000 ? timestamp * 1000 : timestamp; + const ts = numTimestamp < 10000000000 ? numTimestamp * 1000 : numTimestamp; const date = new Date(ts); // Return undefined if the date is invalid return isNaN(date.getTime()) ? undefined : date; } -function privacyToNumber(privacy: SandboxPrivacy): number { +function mapPrivacyForApi(privacy: SandboxPrivacy): { mappedPrivacy: number; privatePreview?: boolean } { switch (privacy) { - case "public": - return 0; case "unlisted": - return 1; + return { mappedPrivacy: 1 }; // Keep as unlisted case "private": - return 2; + return { mappedPrivacy: 2 }; // Keep as private + case "public": + return { mappedPrivacy: 1 }; // Map to unlisted + case "public-hosts": + return { mappedPrivacy: 2, privatePreview: false }; // Map to private with public preview } } + function privacyFromNumber(privacy: number): SandboxPrivacy { switch (privacy) { case 0: diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 6017112..126232e 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -141,8 +141,8 @@ export type VmListRunningVmsResponse = { vms: Array<{ credit_basis?: string; id?: string; - last_active_at?: number; - session_started_at?: number; + last_active_at?: string; + session_started_at?: string; specs?: { cpu?: number; memory?: number; @@ -178,6 +178,10 @@ export type SandboxForkRequest = { * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. */ privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; /** * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. */ @@ -519,6 +523,10 @@ export type SandboxCreateRequest = { * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. */ privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; /** * Runtime to use for the sandbox. Defaults to `"browser"`. */ diff --git a/src/types.ts b/src/types.ts index 57c91bd..ac895ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,7 +33,14 @@ export interface SystemMetricsStatus { }; } -export type SandboxPrivacy = "public" | "unlisted" | "private"; +/** + * Sandbox privacy settings + */ +export type SandboxPrivacy = + | "public" + | "unlisted" /** @deprecated Use "public" or "public-hosts" instead */ + | "private" + | "public-hosts"; export type SandboxInfo = { id: string; From a6f9fe7c93450cfeded21048f62a6a2f0b842091 Mon Sep 17 00:00:00 2001 From: tristandubbeld Date: Wed, 13 Aug 2025 09:44:48 +0200 Subject: [PATCH 202/241] feat: enhance container setup logging in build command --- src/bin/commands/build.ts | 50 ++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index c9440e9..cdab648 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -145,18 +145,50 @@ export const buildCommand: yargs.CommandModule< const buffer: string[] = []; try { - spinner.start( - updateSpinnerMessage( - index, - `Running setup ${steps.indexOf(step) + 1} / ${steps.length} - ${ - step.name - }...` - ) - ); + // Check if this is a container setup step and handle it specially + const isContainerStep = + step.name.toLowerCase().includes("starting container"); + + if (isContainerStep) { + spinner.start( + updateSpinnerMessage( + index, + `Building and starting container...` + ) + ); + } else { + spinner.start( + updateSpinnerMessage( + index, + `Running setup ${steps.indexOf(step) + 1} / ${ + steps.length + } - ${step.name}...` + ) + ); + } step.onOutput((output) => { - buffer.push(stripAnsiCodes(output)); + const cleanOutput = stripAnsiCodes(output); + + buffer.push(cleanOutput); + + // For container steps, update spinner with current log line + if (isContainerStep && cleanOutput.trim()) { + const currentLogLine = cleanOutput.trim(); + const logPreview = + currentLogLine.length > 200 + ? currentLogLine.slice(0, 200) + "..." + : currentLogLine; + + spinner.start( + updateSpinnerMessage( + index, + `Building and starting container (${logPreview})...` + ) + ); + } }); + const output = await step.open(); buffer.push(...output.split("\n").map(stripAnsiCodes)); From ce3a2823e66198f453d257a68757226d50e3bf17 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 13 Aug 2025 12:54:07 +0200 Subject: [PATCH 203/241] fix: Add custom retry delay support for startVM API calls (#156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add custom retry delay support for startVM API calls - Added retryDelay parameter to startVm method with default 200ms - Sandbox creation: uses 200ms delay (preserves existing behavior) - Sandbox restart: uses 1000ms delay for better stability - Sandbox resume: uses 500ms delay for hibernated sandboxes - Connection fallbacks: use 200ms delay for reconnection scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * add explicit retry in build * use merged options object instead --------- Co-authored-by: Claude --- src/API.ts | 11 ++++++++--- src/Sandbox.ts | 4 ++-- src/Sandboxes.ts | 6 +++--- src/bin/commands/build.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/API.ts b/src/API.ts index ae0bda7..91c50bb 100644 --- a/src/API.ts +++ b/src/API.ts @@ -48,7 +48,7 @@ import type { VmCreateSessionData, VmShutdownData, VmUpdateSpecsData, - VmStartData, + VmStartRequest, VmUpdateSpecs2Data, PreviewHostListData, PreviewHostCreateData, @@ -111,6 +111,10 @@ export interface APIOptions { instrumentation?: (request: Request) => Promise; } +export interface StartVmOptions extends VmStartRequest { + retryDelay?: number; +} + export class API { private client: Client; @@ -353,7 +357,8 @@ export class API { return handleResponse(response, `Failed to update specs for VM ${id}`); } - async startVm(id: string, data?: VmStartData["body"]) { + async startVm(id: string, options?: StartVmOptions) { + const { retryDelay = 200, ...data } = options || {}; const response = await retryWithDelay( () => vmStart({ @@ -362,7 +367,7 @@ export class API { body: data, }), 3, - 200 + retryDelay ); const handledResponse = handleResponse( response, diff --git a/src/Sandbox.ts b/src/Sandbox.ts index dba873c..11f9764 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -127,7 +127,7 @@ export class Sandbox { ) { const client = await SandboxClient.create( session, - async () => this.getSession(await this.api.startVm(this.id), customSession), + async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession), undefined, this.tracer ); @@ -241,7 +241,7 @@ export class Sandbox { client || SandboxClient.create( session, - async () => this.getSession(await this.api.startVm(this.id), customSession), + async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession), undefined, this.tracer ) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index fcf5091..c5d6d1f 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -88,7 +88,7 @@ export class Sandboxes { const startResponse = await this.api.startVm( sandbox.id, - getStartOptions(opts) + { ...getStartOptions(opts), retryDelay: 200 } // Keep 200ms delay for creation ); return new Sandbox(sandbox.id, this.api, startResponse, this.tracer); @@ -108,7 +108,7 @@ export class Sandboxes { "sandboxes.resume", { "sandbox.id": sandboxId }, async () => { - const startResponse = await this.api.startVm(sandboxId); + const startResponse = await this.api.startVm(sandboxId, { retryDelay: 500 }); // Use 500ms delay for resume return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } ); @@ -162,7 +162,7 @@ export class Sandboxes { } try { - const startResponse = await this.api.startVm(sandboxId, opts); + const startResponse = await this.api.startVm(sandboxId, { ...opts, retryDelay: 1000 }); // Use 1000ms delay for restart return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } catch (e) { diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index c9440e9..006de68 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -187,7 +187,7 @@ export const buildCommand: yargs.CommandModule< spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); const startResponse = await withCustomError( - api.startVm(id), + api.startVm(id, { retryDelay: 200 }), "Failed to start sandbox at all" ); let sandboxVM = new Sandbox(id, api, startResponse); From 9a3099fc3984c6ff01fa901ac1616cbc7b883119 Mon Sep 17 00:00:00 2001 From: Tristan Date: Wed, 13 Aug 2025 12:57:33 +0200 Subject: [PATCH 204/241] fix: update log line length to be smaller --- src/bin/commands/build.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index cdab648..192bc14 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -176,8 +176,8 @@ export const buildCommand: yargs.CommandModule< if (isContainerStep && cleanOutput.trim()) { const currentLogLine = cleanOutput.trim(); const logPreview = - currentLogLine.length > 200 - ? currentLogLine.slice(0, 200) + "..." + currentLogLine.length > 100 + ? currentLogLine.slice(0, 100) + "..." : currentLogLine; spinner.start( From 0def4a31ae62fd24001ce83388de52884081c807 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 13 Aug 2025 15:52:36 +0200 Subject: [PATCH 205/241] Increase frequency of keep active (#159) --- src/SandboxClient/index.ts | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index fb54b5e..26c4108 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -119,7 +119,11 @@ export class SandboxClient { /** * Namespace for managing ports on this Sandbox */ - public readonly ports = new Ports(this.disposable, this.agentClient, this.tracer); + public readonly ports = new Ports( + this.disposable, + this.agentClient, + this.tracer + ); /** * Namespace for the setup that runs when the Sandbox starts from scratch. @@ -150,13 +154,26 @@ export class SandboxClient { initialSetupProgress, tracer ); - this.fs = new FileSystem(this.disposable, this.agentClient, username, tracer); + this.fs = new FileSystem( + this.disposable, + this.agentClient, + username, + tracer + ); this.terminals = new Terminals(this.disposable, this.agentClient, tracer); this.tasks = new Tasks(this.disposable, this.agentClient, tracer); - this.commands = new SandboxCommands(this.disposable, this.agentClient, tracer); + this.commands = new SandboxCommands( + this.disposable, + this.agentClient, + tracer + ); this.hosts = new Hosts(this.agentClient.sandboxId, hostToken, tracer); - this.interpreters = new Interpreters(this.disposable, this.commands, tracer); + this.interpreters = new Interpreters( + this.disposable, + this.commands, + tracer + ); this.disposable.onWillDispose(() => this.agentClient.dispose()); this.disposable.onWillDispose(() => { @@ -329,7 +346,7 @@ export class SandboxClient { */ public reconnect() { return this.withSpan( - "sandboxClient.reconnect", + "sandboxClient.reconnect", { "sandbox.id": this.id }, async () => { this.isExplicitlyDisconnected = false; @@ -350,7 +367,9 @@ export class SandboxClient { await retryWithDelay( async () => { if (this.isExplicitlyDisconnected) { - throw new Error("Explicit disconnect - stopping auto-reconnect"); + throw new Error( + "Explicit disconnect - stopping auto-reconnect" + ); } await this.agentClient.reconnect(); }, @@ -380,10 +399,10 @@ export class SandboxClient { if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { - this.agentClient.system.update().catch(() => { - // We do not care about errors here + this.agentClient.system.update().catch((error) => { + console.warn("Unable to keep active while connected", error); }); - }, 1000 * 30); + }, 1000 * 10); } } else { if (this.keepAliveInterval) { From f70903a593c51bdfcdbf49d86b7b7bdce7cfe4a4 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 14 Aug 2025 13:55:21 +0200 Subject: [PATCH 206/241] fix: include response handling in retries and dispose clients in build to avoid reconnecst (#162) --- src/API.ts | 87 +++++++----------------------- src/AgentClient/AgentConnection.ts | 4 +- src/bin/commands/build.ts | 22 ++++++++ 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/src/API.ts b/src/API.ts index 91c50bb..8dcead5 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1,6 +1,5 @@ import type { Client, Config } from "@hey-api/client-fetch"; -import { createClient, createConfig } from "@hey-api/client-fetch"; -import { handleResponse, retryWithDelay } from "./utils/api"; +import { handleResponse, retryWithDelay, createApiClient } from "./utils/api"; import { getInferredBaseUrl } from "./utils/constants"; import { metaInfo, @@ -56,54 +55,6 @@ import type { } from "./api-clients/client"; import { PitcherManagerResponse } from "./types"; -async function enhanceFetch( - request: Request, - instrumentation?: (request: Request) => Promise -) { - // Clone the request to modify headers - const headers = new Headers(request.headers); - const existingUserAgent = headers.get("User-Agent") || ""; - - // Extend User-Agent with SDK version - headers.set( - "User-Agent", - `${existingUserAgent ? `${existingUserAgent} ` : ""}codesandbox-sdk/${ - // @ts-expect-error - Replaced at build time - CSB_SDK_VERSION - }`.trim() - ); - - // Create new request with updated headers and optionally add instrumentation - return instrumentation - ? instrumentation( - new Request(request, { - headers, - }) - ) - : fetch( - new Request(request, { - headers, - }) - ); -} - -function createApiClient( - apiKey: string, - config: Config = {}, - instrumentation?: (request: Request) => Promise -) { - return createClient( - createConfig({ - baseUrl: config.baseUrl || getInferredBaseUrl(apiKey), - fetch: (request) => enhanceFetch(request, instrumentation), - ...config, - headers: { - Authorization: `Bearer ${apiKey}`, - ...config.headers, - }, - }) - ); -} export interface APIOptions { apiKey: string; @@ -297,17 +248,18 @@ export class API { } async hibernate(id: string, data?: VmHibernateData["body"]) { - const response = await retryWithDelay( - () => - vmHibernate({ + return retryWithDelay( + async () => { + const response = await vmHibernate({ client: this.client, path: { id }, body: data, - }), + }); + return handleResponse(response, `Failed to hibernate VM ${id}`); + }, 3, 200 ); - return handleResponse(response, `Failed to hibernate VM ${id}`); } async updateHibernationTimeout( @@ -335,17 +287,18 @@ export class API { } async shutdown(id: string, data?: VmShutdownData["body"]) { - const response = await retryWithDelay( - () => - vmShutdown({ + return retryWithDelay( + async () => { + const response = await vmShutdown({ client: this.client, path: { id }, body: data, - }), + }); + return handleResponse(response, `Failed to shutdown VM ${id}`); + }, 3, 200 ); - return handleResponse(response, `Failed to shutdown VM ${id}`); } async updateSpecs(id: string, data: VmUpdateSpecsData["body"]) { @@ -359,20 +312,18 @@ export class API { async startVm(id: string, options?: StartVmOptions) { const { retryDelay = 200, ...data } = options || {}; - const response = await retryWithDelay( - () => - vmStart({ + const handledResponse = await retryWithDelay( + async () => { + const response = await vmStart({ client: this.client, path: { id }, body: data, - }), + }); + return handleResponse(response, `Failed to start VM ${id}`); + }, 3, retryDelay ); - const handledResponse = handleResponse( - response, - `Failed to start VM ${id}` - ); return { bootupType: diff --git a/src/AgentClient/AgentConnection.ts b/src/AgentClient/AgentConnection.ts index 9541ffa..b8ac31d 100644 --- a/src/AgentClient/AgentConnection.ts +++ b/src/AgentClient/AgentConnection.ts @@ -258,11 +258,11 @@ export class AgentConnection { } dispose(): void { + this.errorEmitter.dispose(); + this.messageEmitter.dispose(); this.connection.dispose(); this.disposePendingMessages(); this.pendingMessages.clear(); this.notificationListeners = {}; - this.errorEmitter.dispose(); - this.messageEmitter.dispose(); } } diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index dcae967..9d8f996 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -215,6 +215,7 @@ export const buildCommand: yargs.CommandModule< }; const tasks = templateData.sandboxes.map(async ({ id }, index) => { + let currentSession: SandboxClient | null = null; try { spinner.start(updateSpinnerMessage(index, "Starting sandbox...")); @@ -225,6 +226,7 @@ export const buildCommand: yargs.CommandModule< let sandboxVM = new Sandbox(id, api, startResponse); let session = await sandboxVM.connect(); + currentSession = session; spinner.start( updateSpinnerMessage(index, "Writing files to sandbox...") @@ -251,6 +253,10 @@ export const buildCommand: yargs.CommandModule< throw new Error(`Failed to write files to sandbox: ${error}`); }); + // Dispose of the session after writing files to prevent reconnection + session.dispose(); + currentSession = null; + spinner.start(updateSpinnerMessage(index, "Building sandbox...")); sandboxVM = await withCustomError( @@ -264,9 +270,14 @@ export const buildCommand: yargs.CommandModule< sandboxVM.connect(), "Failed to connect to sandbox after building" ); + currentSession = session; await waitForSetup(session, index); + // Dispose of the session after setup to prevent reconnection + session.dispose(); + currentSession = null; + spinner.start( updateSpinnerMessage(index, "Optimizing initial state...") ); @@ -281,6 +292,7 @@ export const buildCommand: yargs.CommandModule< sandboxVM.connect(), "Failed to connect to sandbox after optimizing initial state" ); + currentSession = session; await waitForSetup(session, index); @@ -329,6 +341,10 @@ export const buildCommand: yargs.CommandModule< await new Promise((resolve) => setTimeout(resolve, 5000)); } + // Dispose of the session after port operations to prevent reconnection + session.dispose(); + currentSession = null; + spinner.start(updateSpinnerMessage(index, "Creating snapshot...")); await withCustomError( sdk.sandboxes.hibernate(id), @@ -338,6 +354,12 @@ export const buildCommand: yargs.CommandModule< return id; } catch (error) { + // Dispose of any active session to prevent reconnection attempts + if (currentSession) { + currentSession.dispose(); + currentSession = null; + } + spinner.start( updateSpinnerMessage( index, From 017b905f4dee911bd172cb1157ed748e6bc6a6cd Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 19 Aug 2025 15:02:38 +0200 Subject: [PATCH 207/241] handle isomorphic-ws building (#164) --- esbuild.cjs | 14 ++++++++++++-- package-lock.json | 47 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 8 +++++--- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/esbuild.cjs b/esbuild.cjs index 6eee9e1..e482de2 100644 --- a/esbuild.cjs +++ b/esbuild.cjs @@ -83,8 +83,13 @@ const nodeClientEsmBuild = esbuild.build({ bundle: true, format: "esm", outfile: "dist/esm/node.js", + // Handle dynamic requires (WS) + banner: { + js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`, + }, platform: "node", - external: externalModules, + // These has to be optional deps, due to being native and only working in certain envs + external: externalModules.concat("bufferutil", "utf-8-validate"), }); /** @@ -107,8 +112,13 @@ const sdkEsmBuild = esbuild.build({ format: "esm", define, platform: "node", + // Handle dynamic requires (WS) + banner: { + js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`, + }, outfile: "dist/esm/index.js", - external: externalModules, + // These has to be optional deps, due to being native and only working in certain envs + external: externalModules.concat("bufferutil", "utf-8-validate"), }); /** diff --git a/package-lock.json b/package-lock.json index 5e44705..f9487c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "cli-table3": "^0.6.3", "date-fns": "^4.1.0", "isbinaryfile": "^5.0.4", - "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", "readline": "^1.3.0", @@ -42,6 +41,7 @@ "esbuild": "^0.25.0", "ignore": "^6.0.2", "ink": "^6.1.0", + "isomorphic-ws": "^5.0.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -53,7 +53,9 @@ "why-is-node-running": "^2.3.0" }, "optionalDependencies": { - "@sentry/node": "^9.29.0" + "@sentry/node": "^9.29.0", + "bufferutil": "^4.0.0", + "utf-8-validate": "^6.0.0" } }, "node_modules/@alcalzone/ansi-tokenize": { @@ -2161,6 +2163,20 @@ "node": ">=0.2.0" } }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/c12": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", @@ -3840,6 +3856,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, "license": "MIT", "peerDependencies": { "ws": "*" @@ -4259,6 +4276,18 @@ "dev": true, "license": "MIT" }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/nopt": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", @@ -5468,6 +5497,20 @@ "devOptional": true, "license": "MIT" }, + "node_modules/utf-8-validate": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz", + "integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", diff --git a/package.json b/package.json index 7028620..91866bd 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,8 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "why-is-node-running": "^2.3.0" + "why-is-node-running": "^2.3.0", + "isomorphic-ws": "^5.0.0" }, "dependencies": { "@hey-api/client-fetch": "^0.7.3", @@ -102,7 +103,6 @@ "cli-table3": "^0.6.3", "date-fns": "^4.1.0", "isbinaryfile": "^5.0.4", - "isomorphic-ws": "^5.0.0", "ora": "^8.2.0", "path": "^0.12.7", "readline": "^1.3.0", @@ -110,6 +110,8 @@ "yargs": "^17.7.2" }, "optionalDependencies": { - "@sentry/node": "^9.29.0" + "@sentry/node": "^9.29.0", + "bufferutil": "^4.0.0", + "utf-8-validate": "^6.0.0" } } From 9af1cdd0657c1a74572dc473ac6e04f6e1a40cd5 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 19 Aug 2025 15:03:01 +0200 Subject: [PATCH 208/241] feat: Debug Sandboxes through CLI (#163) * wip * working version --- CHANGELOG.md | 115 +- CLAUDE.md | 28 +- package-lock.json | 28 +- package.json | 4 +- src/Sandbox.ts | 31 +- src/SandboxClient/commands.ts | 44 +- src/SandboxClient/filesystem.ts | 132 +- src/SandboxClient/interpreters.ts | 18 +- src/SandboxClient/ports.ts | 64 +- src/SandboxClient/setup.ts | 74 +- src/SandboxClient/tasks.ts | 34 +- src/SandboxClient/terminals.ts | 46 +- src/Sandboxes.ts | 89 +- .../client-rest-container/client.gen.ts | 4 +- .../client-rest-container/index.ts | 4 +- .../client-rest-container/sdk.gen.ts | 57 +- .../client-rest-container/types.gen.ts | 172 +- src/api-clients/client-rest-fs/client.gen.ts | 4 +- src/api-clients/client-rest-fs/index.ts | 4 +- src/api-clients/client-rest-fs/sdk.gen.ts | 508 +++-- src/api-clients/client-rest-fs/types.gen.ts | 1463 ++++++------ src/api-clients/client-rest-git/client.gen.ts | 4 +- src/api-clients/client-rest-git/index.ts | 4 +- src/api-clients/client-rest-git/sdk.gen.ts | 410 ++-- src/api-clients/client-rest-git/types.gen.ts | 1035 ++++----- .../client-rest-setup/client.gen.ts | 4 +- src/api-clients/client-rest-setup/index.ts | 4 +- src/api-clients/client-rest-setup/sdk.gen.ts | 219 +- .../client-rest-setup/types.gen.ts | 383 ++-- .../client-rest-shell/client.gen.ts | 4 +- src/api-clients/client-rest-shell/index.ts | 4 +- src/api-clients/client-rest-shell/sdk.gen.ts | 273 ++- .../client-rest-shell/types.gen.ts | 597 ++--- .../client-rest-system/client.gen.ts | 4 +- src/api-clients/client-rest-system/index.ts | 4 +- src/api-clients/client-rest-system/sdk.gen.ts | 111 +- .../client-rest-system/types.gen.ts | 294 +-- .../client-rest-task/client.gen.ts | 4 +- src/api-clients/client-rest-task/index.ts | 4 +- src/api-clients/client-rest-task/sdk.gen.ts | 273 ++- src/api-clients/client-rest-task/types.gen.ts | 742 +++--- src/api-clients/client/client.gen.ts | 4 +- src/api-clients/client/index.ts | 4 +- src/api-clients/client/sdk.gen.ts | 982 ++++---- src/api-clients/client/types.gen.ts | 1979 +++++++++-------- src/bin/commands/build.ts | 7 +- src/bin/ui/App.tsx | 11 +- src/bin/ui/components/Table.tsx | 2 +- src/bin/ui/components/TextInput.tsx | 2 +- src/bin/ui/components/VmTable.tsx | 175 +- src/bin/ui/hooks/useVmInput.ts | 19 +- src/bin/ui/sdkContext.tsx | 4 +- src/bin/ui/viewContext.tsx | 4 +- src/bin/ui/views/Dashboard.tsx | 103 +- src/bin/ui/views/Debug.tsx | 462 ++++ src/bin/ui/views/Open.tsx | 166 ++ src/bin/ui/views/Sandbox.tsx | 8 +- src/bin/utils/sentry.ts | 12 +- src/browser/index.ts | 7 +- src/pitcher-protocol/message.ts | 10 +- src/pitcher-protocol/protocol.ts | 2 +- src/types.ts | 7 +- 62 files changed, 6579 insertions(+), 4691 deletions(-) create mode 100644 src/bin/ui/views/Debug.tsx create mode 100644 src/bin/ui/views/Open.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 9812f00..0500fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,218 +2,187 @@ ## [2.0.7](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.6...v2.0.7) (2025-08-06) - ### Bug Fixes -* also retry resume ([#143](https://github.com/codesandbox/codesandbox-sdk/issues/143)) ([8b69036](https://github.com/codesandbox/codesandbox-sdk/commit/8b69036d0b586917db05f9c51046ae8b26660835)) +- also retry resume ([#143](https://github.com/codesandbox/codesandbox-sdk/issues/143)) ([8b69036](https://github.com/codesandbox/codesandbox-sdk/commit/8b69036d0b586917db05f9c51046ae8b26660835)) ## [2.0.6](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.5...v2.0.6) (2025-08-06) - ### Bug Fixes -* Add retries to all idempotent endpoints and added parallel file writing ([#140](https://github.com/codesandbox/codesandbox-sdk/issues/140)) ([db8aded](https://github.com/codesandbox/codesandbox-sdk/commit/db8aded97e1844cc31f70b08b6a294b458069656)) -* Fix broken authorization in preview hosts ([20a4e53](https://github.com/codesandbox/codesandbox-sdk/commit/20a4e538e3b473007783831c7592670cf99c8a96)) -* Fix broken authorization in preview hosts ([71b38b4](https://github.com/codesandbox/codesandbox-sdk/commit/71b38b4b12a9438864296fb599d20760c6b0a728)) -* Update to latest Ink and React 19 and bundle React and Ink into CLI ([#138](https://github.com/codesandbox/codesandbox-sdk/issues/138)) ([62da4fe](https://github.com/codesandbox/codesandbox-sdk/commit/62da4fef50a3497b84c71413b1c0e3337c73e59f)) +- Add retries to all idempotent endpoints and added parallel file writing ([#140](https://github.com/codesandbox/codesandbox-sdk/issues/140)) ([db8aded](https://github.com/codesandbox/codesandbox-sdk/commit/db8aded97e1844cc31f70b08b6a294b458069656)) +- Fix broken authorization in preview hosts ([20a4e53](https://github.com/codesandbox/codesandbox-sdk/commit/20a4e538e3b473007783831c7592670cf99c8a96)) +- Fix broken authorization in preview hosts ([71b38b4](https://github.com/codesandbox/codesandbox-sdk/commit/71b38b4b12a9438864296fb599d20760c6b0a728)) +- Update to latest Ink and React 19 and bundle React and Ink into CLI ([#138](https://github.com/codesandbox/codesandbox-sdk/issues/138)) ([62da4fe](https://github.com/codesandbox/codesandbox-sdk/commit/62da4fef50a3497b84c71413b1c0e3337c73e59f)) ## [2.0.5](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.4...v2.0.5) (2025-07-29) - ### Bug Fixes -* timeout errors on keepActiveWhileConnected ([a519bcf](https://github.com/codesandbox/codesandbox-sdk/commit/a519bcfe86abe2f978718169490a54d9977a9d88)) +- timeout errors on keepActiveWhileConnected ([a519bcf](https://github.com/codesandbox/codesandbox-sdk/commit/a519bcfe86abe2f978718169490a54d9977a9d88)) ## [2.0.4](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.3...v2.0.4) (2025-07-16) - ### Bug Fixes -* fix dependencies ([0211660](https://github.com/codesandbox/codesandbox-sdk/commit/02116601a6f33aa56c465eff63f3d44e2220ccf4)) -* move some devdependencies to dependencies ([77f74f1](https://github.com/codesandbox/codesandbox-sdk/commit/77f74f1c81f35c9f9ccd3d159b51857df0b3fc81)) +- fix dependencies ([0211660](https://github.com/codesandbox/codesandbox-sdk/commit/02116601a6f33aa56c465eff63f3d44e2220ccf4)) +- move some devdependencies to dependencies ([77f74f1](https://github.com/codesandbox/codesandbox-sdk/commit/77f74f1c81f35c9f9ccd3d159b51857df0b3fc81)) ## [2.0.3](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.2...v2.0.3) (2025-07-08) - ### Bug Fixes -* disable sentry by default, make it optional ([#126](https://github.com/codesandbox/codesandbox-sdk/issues/126)) ([09d9e8a](https://github.com/codesandbox/codesandbox-sdk/commit/09d9e8a9a678c35169f8ff98b6e283fb45e23ed1)) +- disable sentry by default, make it optional ([#126](https://github.com/codesandbox/codesandbox-sdk/issues/126)) ([09d9e8a](https://github.com/codesandbox/codesandbox-sdk/commit/09d9e8a9a678c35169f8ff98b6e283fb45e23ed1)) ## [2.0.2](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.1...v2.0.2) (2025-07-02) - ### Bug Fixes -* add api client to runningvms query fn ([0119379](https://github.com/codesandbox/codesandbox-sdk/commit/0119379482320dd1142366838ec0679380800909)) -* add apiClient to context and pass to query fn ([75f56da](https://github.com/codesandbox/codesandbox-sdk/commit/75f56dafb1fe93ad395d9727ebab39425875de5d)) -* Template resolve files fixes ([#121](https://github.com/codesandbox/codesandbox-sdk/issues/121)) ([6d455ad](https://github.com/codesandbox/codesandbox-sdk/commit/6d455ad7eefe69f70b8ff4f20efb18d929e57613)) +- add api client to runningvms query fn ([0119379](https://github.com/codesandbox/codesandbox-sdk/commit/0119379482320dd1142366838ec0679380800909)) +- add apiClient to context and pass to query fn ([75f56da](https://github.com/codesandbox/codesandbox-sdk/commit/75f56dafb1fe93ad395d9727ebab39425875de5d)) +- Template resolve files fixes ([#121](https://github.com/codesandbox/codesandbox-sdk/issues/121)) ([6d455ad](https://github.com/codesandbox/codesandbox-sdk/commit/6d455ad7eefe69f70b8ff4f20efb18d929e57613)) ## [0.12.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.2...v0.12.0) (2025-04-24) - ### Features -* add property to check if VM is up to date ([a44c842](https://github.com/codesandbox/codesandbox-sdk/commit/a44c8424dedee731b172ea2cdbbb6fe3ade0f2f5)) -* Rest client ([ec8f5eb](https://github.com/codesandbox/codesandbox-sdk/commit/ec8f5ebc8ab5d4e3540b1985a3a98ecfb64b0c7f)) +- add property to check if VM is up to date ([a44c842](https://github.com/codesandbox/codesandbox-sdk/commit/a44c8424dedee731b172ea2cdbbb6fe3ade0f2f5)) +- Rest client ([ec8f5eb](https://github.com/codesandbox/codesandbox-sdk/commit/ec8f5ebc8ab5d4e3540b1985a3a98ecfb64b0c7f)) ## [0.11.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.1...v0.11.2) (2025-04-15) - ### Bug Fixes -* **build:** consider port opened for faulty status codes ([#84](https://github.com/codesandbox/codesandbox-sdk/issues/84)) ([c5a2469](https://github.com/codesandbox/codesandbox-sdk/commit/c5a246997a07834fe86a73e15762c1e76d552d58)) +- **build:** consider port opened for faulty status codes ([#84](https://github.com/codesandbox/codesandbox-sdk/issues/84)) ([c5a2469](https://github.com/codesandbox/codesandbox-sdk/commit/c5a246997a07834fe86a73e15762c1e76d552d58)) ## [0.11.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.11.0...v0.11.1) (2025-03-13) - ### Bug Fixes -* support `keepActiveWhileConnected` for browser sessions ([#81](https://github.com/codesandbox/codesandbox-sdk/issues/81)) ([ca1f825](https://github.com/codesandbox/codesandbox-sdk/commit/ca1f82582d2f12b84cdf379902c596e6d5bfed52)) +- support `keepActiveWhileConnected` for browser sessions ([#81](https://github.com/codesandbox/codesandbox-sdk/issues/81)) ([ca1f825](https://github.com/codesandbox/codesandbox-sdk/commit/ca1f82582d2f12b84cdf379902c596e6d5bfed52)) ## [0.11.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.10.0...v0.11.0) (2025-03-11) - ### Features -* add support for configuring auto-wake behaviour ([#79](https://github.com/codesandbox/codesandbox-sdk/issues/79)) ([8c2ef89](https://github.com/codesandbox/codesandbox-sdk/commit/8c2ef897b03ce2d3f4865f6e68e2c2f07824852c)) +- add support for configuring auto-wake behaviour ([#79](https://github.com/codesandbox/codesandbox-sdk/issues/79)) ([8c2ef89](https://github.com/codesandbox/codesandbox-sdk/commit/8c2ef897b03ce2d3f4865f6e68e2c2f07824852c)) ## [0.10.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.9.0...v0.10.0) (2025-02-28) - ### Features -* allow defining timeout when waiting for port ([#70](https://github.com/codesandbox/codesandbox-sdk/issues/70)) ([27c559c](https://github.com/codesandbox/codesandbox-sdk/commit/27c559cb36839ba08b8a2518a45a5ede26b44f8b)) +- allow defining timeout when waiting for port ([#70](https://github.com/codesandbox/codesandbox-sdk/issues/70)) ([27c559c](https://github.com/codesandbox/codesandbox-sdk/commit/27c559cb36839ba08b8a2518a45a5ede26b44f8b)) ## [0.9.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.1...v0.9.0) (2025-02-26) - ### Features -* add support for `--since` when listing sandboxes ([#68](https://github.com/codesandbox/codesandbox-sdk/issues/68)) ([f054205](https://github.com/codesandbox/codesandbox-sdk/commit/f0542057345aa0d11251bba24b9420f1b7ae2574)) +- add support for `--since` when listing sandboxes ([#68](https://github.com/codesandbox/codesandbox-sdk/issues/68)) ([f054205](https://github.com/codesandbox/codesandbox-sdk/commit/f0542057345aa0d11251bba24b9420f1b7ae2574)) ## [0.8.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.8.0...v0.8.1) (2025-02-21) - ### Bug Fixes -* don't use env if not env vars are set ([#66](https://github.com/codesandbox/codesandbox-sdk/issues/66)) ([7b61dcc](https://github.com/codesandbox/codesandbox-sdk/commit/7b61dcc2ba6aa183fb2597674bccd264dbb8615c)) +- don't use env if not env vars are set ([#66](https://github.com/codesandbox/codesandbox-sdk/issues/66)) ([7b61dcc](https://github.com/codesandbox/codesandbox-sdk/commit/7b61dcc2ba6aa183fb2597674bccd264dbb8615c)) ## [0.8.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.7.0...v0.8.0) (2025-02-18) - ### Features -* support for private previews and preview tokens ([#63](https://github.com/codesandbox/codesandbox-sdk/issues/63)) ([993e509](https://github.com/codesandbox/codesandbox-sdk/commit/993e50981f907f3a2ccf7421a2fd8e4aba96e9cf)) +- support for private previews and preview tokens ([#63](https://github.com/codesandbox/codesandbox-sdk/issues/63)) ([993e509](https://github.com/codesandbox/codesandbox-sdk/commit/993e50981f907f3a2ccf7421a2fd8e4aba96e9cf)) ## [0.7.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.2...v0.7.0) (2025-02-15) - ### Features -* add support for passing name and path to cli ([#60](https://github.com/codesandbox/codesandbox-sdk/issues/60)) ([00e8c20](https://github.com/codesandbox/codesandbox-sdk/commit/00e8c201b4dcd55f9ce15fc3bd7db09b7c88a103)) +- add support for passing name and path to cli ([#60](https://github.com/codesandbox/codesandbox-sdk/issues/60)) ([00e8c20](https://github.com/codesandbox/codesandbox-sdk/commit/00e8c201b4dcd55f9ce15fc3bd7db09b7c88a103)) ## [0.6.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.1...v0.6.2) (2025-02-10) - ### Bug Fixes -* CommonJS build requires .cjs extension for ESM package ([#56](https://github.com/codesandbox/codesandbox-sdk/issues/56)) ([528022e](https://github.com/codesandbox/codesandbox-sdk/commit/528022e1e6beabd7e91ea755e544bffec02f265e)) +- CommonJS build requires .cjs extension for ESM package ([#56](https://github.com/codesandbox/codesandbox-sdk/issues/56)) ([528022e](https://github.com/codesandbox/codesandbox-sdk/commit/528022e1e6beabd7e91ea755e544bffec02f265e)) ## [0.6.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.6.0...v0.6.1) (2025-02-08) - ### Performance Improvements -* do hibernation and shutdown in parallel ([#54](https://github.com/codesandbox/codesandbox-sdk/issues/54)) ([d8e4c6f](https://github.com/codesandbox/codesandbox-sdk/commit/d8e4c6f54ac91aef526af7ac8207f62f084b159d)) +- do hibernation and shutdown in parallel ([#54](https://github.com/codesandbox/codesandbox-sdk/issues/54)) ([d8e4c6f](https://github.com/codesandbox/codesandbox-sdk/commit/d8e4c6f54ac91aef526af7ac8207f62f084b159d)) ## [0.6.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.3...v0.6.0) (2025-02-06) - ### Features -* allow sandbox to keep active while connected ([#53](https://github.com/codesandbox/codesandbox-sdk/issues/53)) ([7ad63d7](https://github.com/codesandbox/codesandbox-sdk/commit/7ad63d73926a13c7c1d4c23568901f7fd418c899)) -* support env and cwd in shell api ([#52](https://github.com/codesandbox/codesandbox-sdk/issues/52)) ([7bf1e35](https://github.com/codesandbox/codesandbox-sdk/commit/7bf1e35c79b347e1b85dc557236c601aa0f455a9)) - +- allow sandbox to keep active while connected ([#53](https://github.com/codesandbox/codesandbox-sdk/issues/53)) ([7ad63d7](https://github.com/codesandbox/codesandbox-sdk/commit/7ad63d73926a13c7c1d4c23568901f7fd418c899)) +- support env and cwd in shell api ([#52](https://github.com/codesandbox/codesandbox-sdk/issues/52)) ([7bf1e35](https://github.com/codesandbox/codesandbox-sdk/commit/7bf1e35c79b347e1b85dc557236c601aa0f455a9)) ### Bug Fixes -* **snapshot:** don't include ignorefiles when building snapshots ([#50](https://github.com/codesandbox/codesandbox-sdk/issues/50)) ([64abff8](https://github.com/codesandbox/codesandbox-sdk/commit/64abff88deec9bf2ccac0a857be48b7de1ee6205)) +- **snapshot:** don't include ignorefiles when building snapshots ([#50](https://github.com/codesandbox/codesandbox-sdk/issues/50)) ([64abff8](https://github.com/codesandbox/codesandbox-sdk/commit/64abff88deec9bf2ccac0a857be48b7de1ee6205)) ## [0.5.3](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.2...v0.5.3) (2025-01-30) - ### Bug Fixes -* use cjs for binary ([#47](https://github.com/codesandbox/codesandbox-sdk/issues/47)) ([7fc76a5](https://github.com/codesandbox/codesandbox-sdk/commit/7fc76a5648f1f2665dfda7c39d1432ca2ae2f768)) +- use cjs for binary ([#47](https://github.com/codesandbox/codesandbox-sdk/issues/47)) ([7fc76a5](https://github.com/codesandbox/codesandbox-sdk/commit/7fc76a5648f1f2665dfda7c39d1432ca2ae2f768)) ## [0.5.2](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.1...v0.5.2) (2025-01-30) - ### Bug Fixes -* esm node build ([#36](https://github.com/codesandbox/codesandbox-sdk/issues/36)) ([4691813](https://github.com/codesandbox/codesandbox-sdk/commit/46918131508bcf56bde7b0d3f2afb07ae710fec5)) +- esm node build ([#36](https://github.com/codesandbox/codesandbox-sdk/issues/36)) ([4691813](https://github.com/codesandbox/codesandbox-sdk/commit/46918131508bcf56bde7b0d3f2afb07ae710fec5)) ## [0.5.1](https://github.com/codesandbox/codesandbox-sdk/compare/v0.5.0...v0.5.1) (2025-01-30) - ### Bug Fixes -* uploads of big files ([#44](https://github.com/codesandbox/codesandbox-sdk/issues/44)) ([c8e6f46](https://github.com/codesandbox/codesandbox-sdk/commit/c8e6f46299f51f9f8ac3acc5934d34a16e7db051)) +- uploads of big files ([#44](https://github.com/codesandbox/codesandbox-sdk/issues/44)) ([c8e6f46](https://github.com/codesandbox/codesandbox-sdk/commit/c8e6f46299f51f9f8ac3acc5934d34a16e7db051)) ## [0.5.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.4.0...v0.5.0) (2025-01-28) - ### Features -* add more advanced pagination options ([#39](https://github.com/codesandbox/codesandbox-sdk/issues/39)) ([f086bb8](https://github.com/codesandbox/codesandbox-sdk/commit/f086bb88be2cb92848c8b8eae411408028978791)) -* list sandboxes by running status ([#37](https://github.com/codesandbox/codesandbox-sdk/issues/37)) ([1e9e19c](https://github.com/codesandbox/codesandbox-sdk/commit/1e9e19c7df09ea15025873e8557cbed031d2e9d0)) +- add more advanced pagination options ([#39](https://github.com/codesandbox/codesandbox-sdk/issues/39)) ([f086bb8](https://github.com/codesandbox/codesandbox-sdk/commit/f086bb88be2cb92848c8b8eae411408028978791)) +- list sandboxes by running status ([#37](https://github.com/codesandbox/codesandbox-sdk/issues/37)) ([1e9e19c](https://github.com/codesandbox/codesandbox-sdk/commit/1e9e19c7df09ea15025873e8557cbed031d2e9d0)) ## [0.4.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.3.0...v0.4.0) (2025-01-22) - ### Features -* support for creating sessions ([#32](https://github.com/codesandbox/codesandbox-sdk/issues/32)) ([491d13d](https://github.com/codesandbox/codesandbox-sdk/commit/491d13db77992df5e3ab3fefb5b9de7e8edbd1c9)) +- support for creating sessions ([#32](https://github.com/codesandbox/codesandbox-sdk/issues/32)) ([491d13d](https://github.com/codesandbox/codesandbox-sdk/commit/491d13db77992df5e3ab3fefb5b9de7e8edbd1c9)) ## [0.3.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.2.0...v0.3.0) (2025-01-14) - ### Features -* add support for listing sandboxes ([#33](https://github.com/codesandbox/codesandbox-sdk/issues/33)) ([6771396](https://github.com/codesandbox/codesandbox-sdk/commit/677139624f95431ec97e12ec985c3545bfe43678)) +- add support for listing sandboxes ([#33](https://github.com/codesandbox/codesandbox-sdk/issues/33)) ([6771396](https://github.com/codesandbox/codesandbox-sdk/commit/677139624f95431ec97e12ec985c3545bfe43678)) ## [0.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v0.1.0...v0.2.0) (2025-01-10) - ### Features -* add support for updating hibernation timeout ([#31](https://github.com/codesandbox/codesandbox-sdk/issues/31)) ([efd2051](https://github.com/codesandbox/codesandbox-sdk/commit/efd20510512cfeacc776d47cb0cf5d5563ba2914)) - +- add support for updating hibernation timeout ([#31](https://github.com/codesandbox/codesandbox-sdk/issues/31)) ([efd2051](https://github.com/codesandbox/codesandbox-sdk/commit/efd20510512cfeacc776d47cb0cf5d5563ba2914)) ### Performance Improvements -* **build:** precreate files inside sandbox ([#16](https://github.com/codesandbox/codesandbox-sdk/issues/16)) ([6cd070e](https://github.com/codesandbox/codesandbox-sdk/commit/6cd070ed1c0c8fc81eba21fcd990bf145489cf91)) +- **build:** precreate files inside sandbox ([#16](https://github.com/codesandbox/codesandbox-sdk/issues/16)) ([6cd070e](https://github.com/codesandbox/codesandbox-sdk/commit/6cd070ed1c0c8fc81eba21fcd990bf145489cf91)) ## 0.1.0 (2025-01-09) - ### Features -* add support for together-ai api token ([af3a023](https://github.com/codesandbox/codesandbox-sdk/commit/af3a0233f1ac8dfae0a0d7cab6b206b4fe1dea5c)) - +- add support for together-ai api token ([af3a023](https://github.com/codesandbox/codesandbox-sdk/commit/af3a0233f1ac8dfae0a0d7cab6b206b4fe1dea5c)) ### Bug Fixes -* don't keep VMs alive with polling ([#10](https://github.com/codesandbox/codesandbox-sdk/issues/10)) ([393d53c](https://github.com/codesandbox/codesandbox-sdk/commit/393d53c68bc4ea33983302fc2056727126124fc8)) -* include sandbox id in build command ([9a737ea](https://github.com/codesandbox/codesandbox-sdk/commit/9a737ea3f6b3a26d6dd7953f7742494bae9d68a7)) -* remove require banner for esm builds ([#15](https://github.com/codesandbox/codesandbox-sdk/issues/15)) ([6995957](https://github.com/codesandbox/codesandbox-sdk/commit/6995957220a3cc0da61f84d472a4e64d9bda0ebc)) - +- don't keep VMs alive with polling ([#10](https://github.com/codesandbox/codesandbox-sdk/issues/10)) ([393d53c](https://github.com/codesandbox/codesandbox-sdk/commit/393d53c68bc4ea33983302fc2056727126124fc8)) +- include sandbox id in build command ([9a737ea](https://github.com/codesandbox/codesandbox-sdk/commit/9a737ea3f6b3a26d6dd7953f7742494bae9d68a7)) +- remove require banner for esm builds ([#15](https://github.com/codesandbox/codesandbox-sdk/issues/15)) ([6995957](https://github.com/codesandbox/codesandbox-sdk/commit/6995957220a3cc0da61f84d472a4e64d9bda0ebc)) ### Performance Improvements -* start a vm while forking ([9b1774c](https://github.com/codesandbox/codesandbox-sdk/commit/9b1774c5d55ef802c069481a10e317e96d618f70)) - +- start a vm while forking ([9b1774c](https://github.com/codesandbox/codesandbox-sdk/commit/9b1774c5d55ef802c069481a10e317e96d618f70)) ### Miscellaneous Chores -* force release as 0.1.0 ([#28](https://github.com/codesandbox/codesandbox-sdk/issues/28)) ([352d049](https://github.com/codesandbox/codesandbox-sdk/commit/352d0492f78f508089d77750b7d5b4f773da797e)) +- force release as 0.1.0 ([#28](https://github.com/codesandbox/codesandbox-sdk/issues/28)) ([352d049](https://github.com/codesandbox/codesandbox-sdk/commit/352d0492f78f508089d77750b7d5b4f773da797e)) diff --git a/CLAUDE.md b/CLAUDE.md index 33c255f..e2137d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,21 +5,25 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands ### Build Commands + - `npm run build` - Full production build (clean, esbuild, generate types for both CJS and ESM) - `npm run build:esbuild` - Run esbuild bundling only - `npm run clean` - Remove dist directory - `npm run dev:cli` - Development mode with file watching for CLI (watches src/bin) ### Type Checking and Linting + - `npm run typecheck` - Run TypeScript type checking without emitting files - `npm run format` - Format code using Prettier ### CLI Development + - `npm run dev:cli` - Start development mode with file watching specifically for CLI changes - The CLI binary is built to `dist/bin/codesandbox.mjs` and made executable - CLI can be run with `./dist/bin/codesandbox.mjs` or just `csb` if installed globally ### API Client Generation + - `npm run build-openapi` - Generate API clients from production OpenAPI spec - `npm run build-openapi:staging` - Generate API clients from staging OpenAPI spec - Generated clients are placed in `src/api-clients/` directory @@ -29,22 +33,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Core Components **SDK Entry Points:** + - `src/index.ts` - Main SDK export with `CodeSandbox` class - `src/browser/index.ts` - Browser-specific SDK build - `src/node/index.ts` - Node.js-specific SDK build **Key Classes:** + - `CodeSandbox` - Main SDK class providing `sandboxes` and `hosts` properties -- `Sandboxes` (src/Sandboxes.ts) - Manages sandbox operations (create, list, get, fork) +- `Sandboxes` (src/Sandboxes.ts) - Manages sandbox operations (create, list, get, fork) - `Sandbox` (src/Sandbox.ts) - Individual sandbox instance with connection and session management - `SandboxClient` (src/SandboxClient/index.ts) - High-level client for sandbox interactions - `API` (src/API.ts) - Low-level API wrapper for all CodeSandbox REST endpoints **Communication Layer:** + - `src/pitcher-protocol/` - WebSocket protocol definitions and message types for real-time sandbox communication - `src/AgentClient/` - WebSocket client implementation for connecting to sandbox agents **CLI:** + - `src/bin/main.tsx` - CLI entry point using Ink (React for CLI) - `src/bin/commands/` - Individual CLI commands (build, sandbox, previewHosts, hostTokens) - `src/bin/ui/` - Ink-based UI components for the interactive CLI @@ -52,12 +60,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Build System **Multi-Format Output:** + - ESM build: `dist/esm/` (primary format, "type": "module") - CommonJS build: `dist/cjs/` (compatibility) - Browser build: Separate bundles with browser polyfills - CLI build: Standalone executable with shebang **Key Build Features:** + - esbuild for bundling with custom plugins for module replacement - Separate TypeScript compilation for type definitions - Browser polyfills for Node.js modules (os, path, crypto, etc.) @@ -66,22 +76,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Type System **Configuration:** -- Main tsconfig.json excludes `src/bin` and `src/api-clients` + +- Main tsconfig.json excludes `src/bin` and `src/api-clients` - Separate build configs for CJS (`tsconfig.build-cjs.json`) and ESM (`tsconfig.build-esm.json`) - Strict TypeScript configuration with comprehensive type checking **Generated Types:** + - `src/api-clients/` contains auto-generated API clients and types from OpenAPI specs - Multiple API client modules for different sandbox services (fs, git, shell, etc.) ### Authentication & Configuration **API Authentication:** + - Requires CodeSandbox API token (CSB_API_KEY environment variable) - Token creation: https://codesandbox.io/t/api - Automatic User-Agent header injection with SDK version **Environment Detection:** + - Automatic base URL inference based on API key format - Support for different CodeSandbox environments (production, staging) @@ -93,7 +107,8 @@ The codebase includes a `test-template/` directory with a Vite + React + TypeScr **SDK Documentation:** https://codesandbox.io/docs/sdk -**Contributing to Documentation:** +**Contributing to Documentation:** + - Documentation source is located at https://github.com/codesandbox/docs - SDK-specific docs are in `packages/projects-docs/pages/sdk/` folder - Update documentation there when making changes to SDK functionality @@ -101,16 +116,19 @@ The codebase includes a `test-template/` directory with a Vite + React + TypeScr ## Key Patterns **Error Handling:** + - `handleResponse` utility for consistent API error handling - Retry logic with exponential backoff for critical operations - OpenTelemetry tracing integration for observability **Session Management:** + - Support for custom sessions with git credentials and environment variables - Browser session creation for web-based sandbox interactions - Automatic session cleanup and disposal **Resource Management:** -- VM tier scaling and hibernation timeout management + +- VM tier scaling and hibernation timeout management - Cluster and bootup type tracking -- Agent version management and update detection \ No newline at end of file +- Agent version management and update detection diff --git a/package-lock.json b/package-lock.json index f9487c1..19c5746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,20 @@ { "name": "@codesandbox/sdk", - "version": "2.0.7", + "version": "2.1.0-rc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.0.7", + "version": "2.1.0-rc.4", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", "@opentelemetry/api": "^1.9.0", + "@xterm/addon-serialize": "^0.13.0", + "@xterm/headless": "^5.5.0", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "chalk": "^5.4.1", @@ -1811,6 +1813,28 @@ "dev": true, "license": "MIT" }, + "node_modules/@xterm/addon-serialize": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.13.0.tgz", + "integrity": "sha512-kGs8o6LWAmN1l2NpMp01/YkpxbmO4UrfWybeGu79Khw5K9+Krp7XhXbBTOTc3GJRRhd6EmILjpR8k5+odY39YQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/headless": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz", + "integrity": "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT", + "peer": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", diff --git a/package.json b/package.json index 91866bd..1161fda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.7", + "version": "2.1.0-rc.4", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", @@ -97,6 +97,8 @@ "@inkjs/ui": "^2.0.0", "@msgpack/msgpack": "^3.1.0", "@opentelemetry/api": "^1.9.0", + "@xterm/addon-serialize": "^0.13.0", + "@xterm/headless": "^5.5.0", "blessed": "^0.1.81", "blessed-contrib": "^4.11.0", "chalk": "^5.4.1", diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 11f9764..96084ae 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -90,9 +90,9 @@ export class Sandbox { async updateTier(tier: VMTier): Promise { return this.withSpan( "sandbox.updateTier", - { + { "sandbox.id": this.id, - "tier.name": tier.name + "tier.name": tier.name, }, async () => { await this.api.updateSpecs(this.id, { @@ -111,7 +111,7 @@ export class Sandbox { "sandbox.updateHibernationTimeout", { "sandbox.id": this.id, - "hibernation.timeoutSeconds": timeoutSeconds + "hibernation.timeoutSeconds": timeoutSeconds, }, async () => { await this.api.updateHibernationTimeout(this.id, { @@ -126,8 +126,12 @@ export class Sandbox { session: SandboxSession ) { const client = await SandboxClient.create( - session, - async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession), + session, + async () => + this.getSession( + await this.api.startVm(this.id, { retryDelay: 200 }), + customSession + ), undefined, this.tracer ); @@ -220,7 +224,7 @@ export class Sandbox { { "sandbox.id": this.id, "session.hasCustomSession": !!customSession, - "session.id": customSession?.id || "default" + "session.id": customSession?.id || "default", }, async () => { return await retryWithDelay( @@ -234,14 +238,21 @@ export class Sandbox { // We might create a client here if git or env is configured, we can reuse that if (customSession) { - client = await this.initializeCustomSession(customSession, session); + client = await this.initializeCustomSession( + customSession, + session + ); } return ( client || SandboxClient.create( - session, - async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession), + session, + async () => + this.getSession( + await this.api.startVm(this.id, { retryDelay: 200 }), + customSession + ), undefined, this.tracer ) @@ -271,7 +282,7 @@ export class Sandbox { "session.hasCustomSession": !!customSession, "session.id": customSession?.id || "default", "session.hasGit": !!customSession?.git, - "session.hasEnv": !!customSession?.env + "session.hasEnv": !!customSession?.env, }, async () => { if (customSession?.git || customSession?.env) { diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 110849a..c2a0c9b 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -88,7 +88,7 @@ export class SandboxCommands { "command.text": cmdString, "command.cwd": opts?.cwd || "/project", "command.asGlobalSession": opts?.asGlobalSession || false, - "command.name": opts?.name || "" + "command.name": opts?.name || "", }, async () => { const disposableStore = new DisposableStore(); @@ -168,7 +168,7 @@ export class SandboxCommands { { "command.text": cmdString, "command.cwd": opts?.cwd || "/project", - "command.asGlobalSession": opts?.asGlobalSession || false + "command.asGlobalSession": opts?.asGlobalSession || false, }, async () => { const cmd = await this.runBackground(command, opts); @@ -181,21 +181,23 @@ export class SandboxCommands { * Get all running commands. */ async getAll(): Promise { - return this.withSpan( - "commands.getAll", - {}, - async () => { - const shells = await this.agentClient.shells.getShells(); - - return shells - .filter( - (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) - ) - .map( - (shell) => new Command(this.agentClient, shell, JSON.parse(shell.name), this.tracer) - ); - } - ); + return this.withSpan("commands.getAll", {}, async () => { + const shells = await this.agentClient.shells.getShells(); + + return shells + .filter( + (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) + ) + .map( + (shell) => + new Command( + this.agentClient, + shell, + JSON.parse(shell.name), + this.tracer + ) + ); + }); } } @@ -348,7 +350,7 @@ export class Command { "command.shellId": this.shell.shellId, "command.text": this.command, "command.dimensions.cols": dimensions.cols, - "command.dimensions.rows": dimensions.rows + "command.dimensions.rows": dimensions.rows, }, async () => { const shell = await this.agentClient.shells.open( @@ -372,7 +374,7 @@ export class Command { { "command.shellId": this.shell.shellId, "command.text": this.command, - "command.status": this.status + "command.status": this.status, }, async () => { await this.barrier.wait(); @@ -403,7 +405,7 @@ export class Command { { "command.shellId": this.shell.shellId, "command.text": this.command, - "command.status": this.status + "command.status": this.status, }, async () => { this.disposable.dispose(); @@ -421,7 +423,7 @@ export class Command { { "command.shellId": this.shell.shellId, "command.text": this.command, - "command.status": this.status + "command.status": this.status, }, async () => { if (this.status !== "RUNNING") { diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index b94df85..ec039ef 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -102,7 +102,7 @@ export class FileSystem { "fs.path": path, "fs.size": content.length, "fs.create": opts.create ?? true, - "fs.overwrite": opts.overwrite ?? true + "fs.overwrite": opts.overwrite ?? true, }, async () => { const result = await this.agentClient.fs.writeFile( @@ -129,7 +129,7 @@ export class FileSystem { "fs.path": path, "fs.contentLength": content.length, "fs.create": opts.create ?? true, - "fs.overwrite": opts.overwrite ?? true + "fs.overwrite": opts.overwrite ?? true, }, async () => { return this.writeFile(path, new TextEncoder().encode(content), opts); @@ -145,7 +145,7 @@ export class FileSystem { "fs.mkdir", { "fs.path": path, - "fs.recursive": recursive + "fs.recursive": recursive, }, async () => { const result = await this.agentClient.fs.mkdir(path, recursive); @@ -161,78 +161,62 @@ export class FileSystem { * Read a directory. */ async readdir(path: string): Promise { - return this.withSpan( - "fs.readdir", - { "fs.path": path }, - async () => { - const result = await this.agentClient.fs.readdir(path); + return this.withSpan("fs.readdir", { "fs.path": path }, async () => { + const result = await this.agentClient.fs.readdir(path); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } - - return result.result.entries.map((entry) => ({ - ...entry, - type: entry.type === 0 ? "file" : "directory", - })); + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); } - ); + + return result.result.entries.map((entry) => ({ + ...entry, + type: entry.type === 0 ? "file" : "directory", + })); + }); } /** * Read a file */ async readFile(path: string): Promise { - return this.withSpan( - "fs.readFile", - { "fs.path": path }, - async () => { - const result = await this.agentClient.fs.readFile(path); - - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } + return this.withSpan("fs.readFile", { "fs.path": path }, async () => { + const result = await this.agentClient.fs.readFile(path); - return result.result.content; + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); } - ); + + return result.result.content; + }); } /** * Read a file as a string. */ async readTextFile(path: string): Promise { - return this.withSpan( - "fs.readTextFile", - { "fs.path": path }, - async () => { - const content = await this.readFile(path); - return new TextDecoder("utf-8").decode(content); - } - ); + return this.withSpan("fs.readTextFile", { "fs.path": path }, async () => { + const content = await this.readFile(path); + return new TextDecoder("utf-8").decode(content); + }); } /** * Get the stat of a file or directory. */ async stat(path: string): Promise { - return this.withSpan( - "fs.stat", - { "fs.path": path }, - async () => { - const result = await this.agentClient.fs.stat(path); + return this.withSpan("fs.stat", { "fs.path": path }, async () => { + const result = await this.agentClient.fs.stat(path); - if (result.type === "error") { - throw new Error(`${result.errno}: ${result.error}`); - } - - return { - ...result.result, - type: - result.result.type === 0 ? ("file" as const) : ("directory" as const), - }; + if (result.type === "error") { + throw new Error(`${result.errno}: ${result.error}`); } - ); + + return { + ...result.result, + type: + result.result.type === 0 ? ("file" as const) : ("directory" as const), + }; + }); } /** @@ -250,7 +234,7 @@ export class FileSystem { "fs.from": from, "fs.to": to, "fs.recursive": recursive, - "fs.overwrite": overwrite + "fs.overwrite": overwrite, }, async () => { const result = await this.agentClient.fs.copy( @@ -276,7 +260,7 @@ export class FileSystem { { "fs.from": from, "fs.to": to, - "fs.overwrite": overwrite + "fs.overwrite": overwrite, }, async () => { const result = await this.agentClient.fs.rename(from, to, overwrite); @@ -296,7 +280,7 @@ export class FileSystem { "fs.remove", { "fs.path": path, - "fs.recursive": recursive + "fs.recursive": recursive, }, async () => { const result = await this.agentClient.fs.remove(path, recursive); @@ -331,23 +315,27 @@ export class FileSystem { { "fs.path": path, "fs.recursive": options.recursive ?? false, - "fs.excludeCount": options.excludes?.length ?? 0 + "fs.excludeCount": options.excludes?.length ?? 0, }, async () => { const emitter = new Emitter(); - const result = await this.agentClient.fs.watch(path, options, (event) => { - if (this.username) { - emitter.fire({ - ...event, - paths: event.paths.map((path) => - path.replace(`home/${this.username}/workspace/`, "sandbox/") - ), - }); - } else { - emitter.fire(event); + const result = await this.agentClient.fs.watch( + path, + options, + (event) => { + if (this.username) { + emitter.fire({ + ...event, + paths: event.paths.map((path) => + path.replace(`home/${this.username}/workspace/`, "sandbox/") + ), + }); + } else { + emitter.fire(event); + } } - }); + ); if (result.type === "error") { throw new Error(`${result.errno}: ${result.error}`); @@ -372,14 +360,10 @@ export class FileSystem { * from within the workspace directory. A download URL that's valid for 5 minutes. */ async download(path: string): Promise<{ downloadUrl: string }> { - return this.withSpan( - "fs.download", - { "fs.path": path }, - async () => { - const result = await this.agentClient.fs.download(path); + return this.withSpan("fs.download", { "fs.path": path }, async () => { + const result = await this.agentClient.fs.download(path); - return result; - } - ); + return result; + }); } } diff --git a/src/SandboxClient/interpreters.ts b/src/SandboxClient/interpreters.ts index 35ca949..dbddaa8 100644 --- a/src/SandboxClient/interpreters.ts +++ b/src/SandboxClient/interpreters.ts @@ -63,19 +63,19 @@ export class Interpreters { { "interpreter.language": "javascript", "interpreter.code": code, - "interpreter.codeLength": code.length + "interpreter.codeLength": code.length, }, async () => { return this.run( `node -p "$(cat <<'EOF' (() => {${code - .split("\n") - .map((line, index, lines) => { - return index === lines.length - 1 && !line.startsWith("return") - ? `return ${line}` - : line; - }) - .join("\n")}})() + .split("\n") + .map((line, index, lines) => { + return index === lines.length - 1 && !line.startsWith("return") + ? `return ${line}` + : line; + }) + .join("\n")}})() EOF )"`, { @@ -97,7 +97,7 @@ EOF { "interpreter.language": "python", "interpreter.code": code, - "interpreter.codeLength": code.length + "interpreter.codeLength": code.length, }, async () => { return this.run(`python3 <<'PYCODE' diff --git a/src/SandboxClient/ports.ts b/src/SandboxClient/ports.ts index fe2cbbc..9666646 100644 --- a/src/SandboxClient/ports.ts +++ b/src/SandboxClient/ports.ts @@ -78,52 +78,50 @@ export class Ports { if (!this.tracer) { return fn(); } - return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { - try { - const result = await fn(); - span.setStatus({ code: SpanStatusCode.OK }); - return result; - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }); - span.recordException(error instanceof Error ? error : new Error(String(error))); - throw error; - } finally { - span.end(); + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } } - }); + ); } /** * Get a port by number. */ async get(port: number) { - return this.withSpan( - "ports.get", - { "port.number": port }, - async () => { - const ports = await this.getAll(); + return this.withSpan("ports.get", { "port.number": port }, async () => { + const ports = await this.getAll(); - return ports.find((p) => p.port === port); - } - ); + return ports.find((p) => p.port === port); + }); } /** * Get all ports. */ async getAll(): Promise { - return this.withSpan( - "ports.getAll", - {}, - async () => { - const ports = await this.agentClient.ports.getPorts(); + return this.withSpan("ports.getAll", {}, async () => { + const ports = await this.agentClient.ports.getPorts(); - return ports.map(({ port, url }) => ({ port, host: url })); - } - ); + return ports.map(({ port, url }) => ({ port, host: url })); + }); } /** @@ -141,9 +139,9 @@ export class Ports { ): Promise { return this.withSpan( "ports.waitForPort", - { + { "port.number": port, - "port.timeout.ms": options?.timeoutMs + "port.timeout.ms": options?.timeoutMs, }, async () => { return new Promise(async (resolve, reject) => { diff --git a/src/SandboxClient/setup.ts b/src/SandboxClient/setup.ts index 8bc70bf..d13a3bb 100644 --- a/src/SandboxClient/setup.ts +++ b/src/SandboxClient/setup.ts @@ -41,22 +41,28 @@ export class Setup { if (!this.tracer) { return fn(); } - return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { - try { - const result = await fn(); - span.setStatus({ code: SpanStatusCode.OK }); - return result; - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }); - span.recordException(error instanceof Error ? error : new Error(String(error))); - throw error; - } finally { - span.end(); + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } } - }); + ); } getSteps() { @@ -180,22 +186,28 @@ export class Step { if (!this.tracer) { return fn(); } - return this.tracer.startActiveSpan(operationName, { attributes }, async (span) => { - try { - const result = await fn(); - span.setStatus({ code: SpanStatusCode.OK }); - return result; - } catch (error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error instanceof Error ? error.message : String(error), - }); - span.recordException(error instanceof Error ? error : new Error(String(error))); - throw error; - } finally { - span.end(); + return this.tracer.startActiveSpan( + operationName, + { attributes }, + async (span) => { + try { + const result = await fn(); + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error instanceof Error ? error.message : String(error), + }); + span.recordException( + error instanceof Error ? error : new Error(String(error)) + ); + throw error; + } finally { + span.end(); + } } - }); + ); } async open(dimensions = DEFAULT_SHELL_SIZE): Promise { @@ -271,4 +283,4 @@ export class Step { } ); } -} \ No newline at end of file +} diff --git a/src/SandboxClient/tasks.ts b/src/SandboxClient/tasks.ts index 9409caf..8ca7aa3 100644 --- a/src/SandboxClient/tasks.ts +++ b/src/SandboxClient/tasks.ts @@ -88,15 +88,11 @@ export class Tasks { * Gets a task by its ID. */ async get(taskId: string): Promise { - return this.withSpan( - "tasks.get", - { taskId }, - async () => { - const tasks = await this.getAll(); + return this.withSpan("tasks.get", { taskId }, async () => { + const tasks = await this.getAll(); - return tasks.find((task) => task.id === taskId); - } - ); + return tasks.find((task) => task.id === taskId); + }); } } @@ -248,12 +244,12 @@ export class Task { async open(dimensions = DEFAULT_SHELL_SIZE) { return this.withSpan( "task.open", - { + { taskId: this.id, taskName: this.name, cols: dimensions.cols, rows: dimensions.rows, - hasShell: !!this.shell + hasShell: !!this.shell, }, async () => { if (!this.shell) { @@ -278,11 +274,11 @@ export class Task { async waitForPort(timeout: number = 30_000) { return this.withSpan( "task.waitForPort", - { + { taskId: this.id, taskName: this.name, timeout, - existingPortsCount: this.ports.length + existingPortsCount: this.ports.length, }, async () => { if (this.ports.length) { @@ -320,11 +316,11 @@ export class Task { async run() { return this.withSpan( "task.run", - { + { taskId: this.id, taskName: this.name, command: this.command, - runAtStart: this.runAtStart + runAtStart: this.runAtStart, }, async () => { await this.agentClient.tasks.runTask(this.id); @@ -334,10 +330,10 @@ export class Task { async restart() { return this.withSpan( "task.restart", - { + { taskId: this.id, taskName: this.name, - command: this.command + command: this.command, }, async () => { await this.run(); @@ -347,10 +343,10 @@ export class Task { async stop() { return this.withSpan( "task.stop", - { + { taskId: this.id, taskName: this.name, - hasShell: !!this.shell + hasShell: !!this.shell, }, async () => { if (this.shell) { @@ -362,4 +358,4 @@ export class Task { dispose() { this.disposable.dispose(); } -} \ No newline at end of file +} diff --git a/src/SandboxClient/terminals.ts b/src/SandboxClient/terminals.ts index c9c0391..ae544a8 100644 --- a/src/SandboxClient/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -104,40 +104,32 @@ export class Terminals { } async get(shellId: string) { - return this.withSpan( - "terminals.get", - { shellId }, - async () => { - const shells = await this.agentClient.shells.getShells(); + return this.withSpan("terminals.get", { shellId }, async () => { + const shells = await this.agentClient.shells.getShells(); - const shell = shells.find((shell) => shell.shellId === shellId); - - if (!shell) { - return; - } + const shell = shells.find((shell) => shell.shellId === shellId); - return new Terminal(shell, this.agentClient, this.tracer); + if (!shell) { + return; } - ); + + return new Terminal(shell, this.agentClient, this.tracer); + }); } /** * Gets all terminals running in the current sandbox */ async getAll() { - return this.withSpan( - "terminals.getAll", - {}, - async () => { - const shells = await this.agentClient.shells.getShells(); + return this.withSpan("terminals.getAll", {}, async () => { + const shells = await this.agentClient.shells.getShells(); - return shells - .filter( - (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) - ) - .map((shell) => new Terminal(shell, this.agentClient, this.tracer)); - } - ); + return shells + .filter( + (shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell) + ) + .map((shell) => new Terminal(shell, this.agentClient, this.tracer)); + }); } } @@ -253,7 +245,11 @@ export class Terminal { rows: dimensions.rows, }, async () => { - await this.agentClient.shells.send(this.shell.shellId, input, dimensions); + await this.agentClient.shells.send( + this.shell.shellId, + input, + dimensions + ); } ); } diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index c5d6d1f..669bff6 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -74,9 +74,9 @@ export class Sandboxes { // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - + const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy); - + const sandbox = await this.api.forkSandbox(templateId, { privacy: mappedPrivacy, title: opts?.title, @@ -108,7 +108,9 @@ export class Sandboxes { "sandboxes.resume", { "sandbox.id": sandboxId }, async () => { - const startResponse = await this.api.startVm(sandboxId, { retryDelay: 500 }); // Use 500ms delay for resume + const startResponse = await this.api.startVm(sandboxId, { + retryDelay: 500, + }); // Use 500ms delay for resume return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } ); @@ -162,7 +164,10 @@ export class Sandboxes { } try { - const startResponse = await this.api.startVm(sandboxId, { ...opts, retryDelay: 1000 }); // Use 1000ms delay for restart + const startResponse = await this.api.startVm(sandboxId, { + ...opts, + retryDelay: 1000, + }); // Use 1000ms delay for restart return new Sandbox(sandboxId, this.api, startResponse, this.tracer); } catch (e) { @@ -304,46 +309,48 @@ export class Sandboxes { /** * List information about currently running VMs. - * - * This information is updated roughly every 30 seconds, so this data is not + * + * This information is updated roughly every 30 seconds, so this data is not * guaranteed to be perfectly up-to-date. */ async listRunning() { - return this.withSpan( - "sandboxes.listRunning", - {}, - async () => { - const data = await this.api.listRunningVms(); - - return { - concurrentVmCount: data.concurrent_vm_count, - concurrentVmLimit: data.concurrent_vm_limit, - vms: data.vms.map(vm => ({ - id: vm.id, - creditBasis: vm.credit_basis, - lastActiveAt: vm.last_active_at ? parseTimestamp(vm.last_active_at) : undefined, - sessionStartedAt: vm.session_started_at ? parseTimestamp(vm.session_started_at) : undefined, - specs: vm.specs ? { - cpu: vm.specs.cpu, - memory: vm.specs.memory, - storage: vm.specs.storage, - } : undefined, - })), - }; - } - ); + return this.withSpan("sandboxes.listRunning", {}, async () => { + const data = await this.api.listRunningVms(); + + return { + concurrentVmCount: data.concurrent_vm_count, + concurrentVmLimit: data.concurrent_vm_limit, + vms: data.vms.map((vm) => ({ + id: vm.id, + creditBasis: vm.credit_basis, + lastActiveAt: vm.last_active_at + ? parseTimestamp(vm.last_active_at) + : undefined, + sessionStartedAt: vm.session_started_at + ? parseTimestamp(vm.session_started_at) + : undefined, + specs: vm.specs + ? { + cpu: vm.specs.cpu, + memory: vm.specs.memory, + storage: vm.specs.storage, + } + : undefined, + })), + }; + }); } /** * Get a single sandbox by ID efficiently without listing all sandboxes. - * + * * This method directly retrieves metadata for a specific sandbox ID, * avoiding the performance overhead of the list-and-filter pattern. - * + * * @param sandboxId The ID of the sandbox to retrieve * @returns Promise The sandbox metadata * @throws Error if the sandbox is not found or access is denied - * + * * @example * ```ts * const sandbox = await client.sandboxes.get("sandbox-id"); @@ -352,7 +359,7 @@ export class Sandboxes { */ async get(sandboxId: string): Promise { const sandbox = await this.api.getSandbox(sandboxId); - + return { id: sandbox.id, createdAt: new Date(sandbox.created_at), @@ -363,26 +370,29 @@ export class Sandboxes { tags: sandbox.tags, }; } - } function parseTimestamp(timestamp: number | string): Date | undefined { if (!timestamp || timestamp === 0) { return undefined; } - + // Convert string to number if needed - const numTimestamp = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp; - + const numTimestamp = + typeof timestamp === "string" ? parseInt(timestamp, 10) : timestamp; + // Handle both seconds and milliseconds timestamps const ts = numTimestamp < 10000000000 ? numTimestamp * 1000 : numTimestamp; const date = new Date(ts); - + // Return undefined if the date is invalid return isNaN(date.getTime()) ? undefined : date; } -function mapPrivacyForApi(privacy: SandboxPrivacy): { mappedPrivacy: number; privatePreview?: boolean } { +function mapPrivacyForApi(privacy: SandboxPrivacy): { + mappedPrivacy: number; + privatePreview?: boolean; +} { switch (privacy) { case "unlisted": return { mappedPrivacy: 1 }; // Keep as unlisted @@ -395,7 +405,6 @@ function mapPrivacyForApi(privacy: SandboxPrivacy): { mappedPrivacy: number; pri } } - function privacyFromNumber(privacy: number): SandboxPrivacy { switch (privacy) { case 0: diff --git a/src/api-clients/client-rest-container/client.gen.ts b/src/api-clients/client-rest-container/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-container/client.gen.ts +++ b/src/api-clients/client-rest-container/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-container/index.ts b/src/api-clients/client-rest-container/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-container/index.ts +++ b/src/api-clients/client-rest-container/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-container/sdk.gen.ts b/src/api-clients/client-rest-container/sdk.gen.ts index bfa7256..e63b97a 100644 --- a/src/api-clients/client-rest-container/sdk.gen.ts +++ b/src/api-clients/client-rest-container/sdk.gen.ts @@ -1,29 +1,46 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { ContainerSetupData, ContainerSetupResponse, ContainerSetupError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + ContainerSetupData, + ContainerSetupResponse, + ContainerSetupError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Setup container * Set up a new container based on a template */ -export const containerSetup = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/container/setup', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const containerSetup = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ContainerSetupResponse, + ContainerSetupError, + ThrowOnError + >({ + url: "/container/setup", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-container/types.gen.ts b/src/api-clients/client-rest-container/types.gen.ts index b2fa4a4..3814bef 100644 --- a/src/api-clients/client-rest-container/types.gen.ts +++ b/src/api-clients/client-rest-container/types.gen.ts @@ -1,111 +1,113 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type ProtocolError = { - /** - * Error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; + /** + * Error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; }; export type TaskDto = { + /** + * Task identifier + */ + id: string; + /** + * Task status + */ + status: string; + /** + * Task progress (0-100) + */ + progress: number; +}; + +export type ContainerSetupData = { + body: { /** - * Task identifier - */ - id: string; - /** - * Task status + * Identifier of the template to use */ - status: string; + templateId: string; /** - * Task progress (0-100) + * Arguments for the template */ - progress: number; -}; - -export type ContainerSetupData = { - body: { - /** - * Identifier of the template to use - */ - templateId: string; - /** - * Arguments for the template - */ - templateArgs: { - [key: string]: string; - }; - features?: Array<{ - /** - * Feature identifier - */ - id: string; - /** - * Options for the feature - */ - options: { - [key: string]: string; - }; - }> | null; + templateArgs: { + [key: string]: string; }; - path?: never; - query?: never; - url: '/container/setup'; + features?: Array<{ + /** + * Feature identifier + */ + id: string; + /** + * Options for the feature + */ + options: { + [key: string]: string; + }; + }> | null; + }; + path?: never; + query?: never; + url: "/container/setup"; }; export type ContainerSetupErrors = { - /** - * Error setting up container - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error setting up container + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; -export type ContainerSetupError = ContainerSetupErrors[keyof ContainerSetupErrors]; +export type ContainerSetupError = + ContainerSetupErrors[keyof ContainerSetupErrors]; export type ContainerSetupResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type ContainerSetupResponse = ContainerSetupResponses[keyof ContainerSetupResponses]; \ No newline at end of file +export type ContainerSetupResponse = + ContainerSetupResponses[keyof ContainerSetupResponses]; diff --git a/src/api-clients/client-rest-fs/client.gen.ts b/src/api-clients/client-rest-fs/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-fs/client.gen.ts +++ b/src/api-clients/client-rest-fs/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-fs/index.ts b/src/api-clients/client-rest-fs/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-fs/index.ts +++ b/src/api-clients/client-rest-fs/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-fs/sdk.gen.ts b/src/api-clients/client-rest-fs/sdk.gen.ts index 83a4cab..e9fc1ca 100644 --- a/src/api-clients/client-rest-fs/sdk.gen.ts +++ b/src/api-clients/client-rest-fs/sdk.gen.ts @@ -1,280 +1,450 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { WriteFileData, WriteFileResponse, WriteFileError, FsReadData, FsReadResponse, FsReadError, FsOperationData, FsOperationResponse, FsOperationError, FsSearchData, FsSearchResponse, FsSearchError, FsStreamingSearchData, FsStreamingSearchResponse, FsStreamingSearchError, FsCancelStreamingSearchData, FsCancelStreamingSearchResponse, FsCancelStreamingSearchError, FsPathSearchData, FsPathSearchResponse, FsPathSearchError, FsUploadData, FsUploadResponse, FsUploadError, FsDownloadData, FsDownloadResponse, FsDownloadError, FsReadFileData, FsReadFileResponse, FsReadFileError, FsReadDirData, FsReadDirResponse, FsReadDirError, FsStatData, FsStatResponse, FsStatError, FsCopyData, FsCopyResponse, FsCopyError, FsRenameData, FsRenameResponse, FsRenameError, FsRemoveData, FsRemoveResponse, FsRemoveError, FsMkdirData, FsMkdirResponse, FsMkdirError, FsWatchData, FsWatchResponse, FsWatchError, FsUnwatchData, FsUnwatchResponse, FsUnwatchError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + WriteFileData, + WriteFileResponse, + WriteFileError, + FsReadData, + FsReadResponse, + FsReadError, + FsOperationData, + FsOperationResponse, + FsOperationError, + FsSearchData, + FsSearchResponse, + FsSearchError, + FsStreamingSearchData, + FsStreamingSearchResponse, + FsStreamingSearchError, + FsCancelStreamingSearchData, + FsCancelStreamingSearchResponse, + FsCancelStreamingSearchError, + FsPathSearchData, + FsPathSearchResponse, + FsPathSearchError, + FsUploadData, + FsUploadResponse, + FsUploadError, + FsDownloadData, + FsDownloadResponse, + FsDownloadError, + FsReadFileData, + FsReadFileResponse, + FsReadFileError, + FsReadDirData, + FsReadDirResponse, + FsReadDirError, + FsStatData, + FsStatResponse, + FsStatError, + FsCopyData, + FsCopyResponse, + FsCopyError, + FsRenameData, + FsRenameResponse, + FsRenameError, + FsRemoveData, + FsRemoveResponse, + FsRemoveError, + FsMkdirData, + FsMkdirResponse, + FsMkdirError, + FsWatchData, + FsWatchResponse, + FsWatchError, + FsUnwatchData, + FsUnwatchResponse, + FsUnwatchError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Write to a file * Write content to a file at the specified path */ -export const writeFile = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/writeFile', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const writeFile = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + WriteFileResponse, + WriteFileError, + ThrowOnError + >({ + url: "/fs/writeFile", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Read file system * Retrieve the latest snapshot of the server's MemoryFS file and children list */ -export const fsRead = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - url: '/fs/read', - ...options - }); +export const fsRead = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + FsReadResponse, + FsReadError, + ThrowOnError + >({ + url: "/fs/read", + ...options, + }); }; /** * Perform file system operation * Send a tree operation reflecting filesystem operations */ -export const fsOperation = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/operation', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsOperation = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsOperationResponse, + FsOperationError, + ThrowOnError + >({ + url: "/fs/operation", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Search files * Search for content in files */ -export const fsSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/search', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsSearch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsSearchResponse, + FsSearchError, + ThrowOnError + >({ + url: "/fs/search", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Start streaming search * Start a streaming search for content in files */ -export const fsStreamingSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/streamingSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsStreamingSearch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsStreamingSearchResponse, + FsStreamingSearchError, + ThrowOnError + >({ + url: "/fs/streamingSearch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Cancel streaming search * Cancel an ongoing streaming search */ -export const fsCancelStreamingSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/cancelStreamingSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsCancelStreamingSearch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsCancelStreamingSearchResponse, + FsCancelStreamingSearchError, + ThrowOnError + >({ + url: "/fs/cancelStreamingSearch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Search file paths * Search for file paths matching a pattern */ -export const fsPathSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/pathSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsPathSearch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsPathSearchResponse, + FsPathSearchError, + ThrowOnError + >({ + url: "/fs/pathSearch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Upload file * Upload a file to the specified parent directory */ -export const fsUpload = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/upload', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsUpload = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsUploadResponse, + FsUploadError, + ThrowOnError + >({ + url: "/fs/upload", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Download files * Download files at a specified path as a zip */ -export const fsDownload = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/download', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsDownload = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsDownloadResponse, + FsDownloadError, + ThrowOnError + >({ + url: "/fs/download", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Read file content * Read the content of a file at the specified path */ -export const fsReadFile = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/readFile', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsReadFile = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsReadFileResponse, + FsReadFileError, + ThrowOnError + >({ + url: "/fs/readFile", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Read directory contents * List the contents of a directory at the specified path */ -export const fsReadDir = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/readdir', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsReadDir = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsReadDirResponse, + FsReadDirError, + ThrowOnError + >({ + url: "/fs/readdir", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get file/directory stats * Get stats for a file or directory at the specified path */ -export const fsStat = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/stat', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsStat = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsStatResponse, + FsStatError, + ThrowOnError + >({ + url: "/fs/stat", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Copy file/directory * Copy a file or directory from one location to another */ -export const fsCopy = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/copy', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsCopy = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsCopyResponse, + FsCopyError, + ThrowOnError + >({ + url: "/fs/copy", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Rename file/directory * Rename a file or directory (move from one location to another) */ -export const fsRename = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/rename', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsRename = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsRenameResponse, + FsRenameError, + ThrowOnError + >({ + url: "/fs/rename", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Remove file/directory * Delete a file or directory at the specified path */ -export const fsRemove = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/remove', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsRemove = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsRemoveResponse, + FsRemoveError, + ThrowOnError + >({ + url: "/fs/remove", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Create directory * Create a new directory at the specified path */ -export const fsMkdir = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/mkdir', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsMkdir = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsMkdirResponse, + FsMkdirError, + ThrowOnError + >({ + url: "/fs/mkdir", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Watch file/directory * Watch a file or directory for changes */ -export const fsWatch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/watch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const fsWatch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsWatchResponse, + FsWatchError, + ThrowOnError + >({ + url: "/fs/watch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Stop watching file/directory * Stop watching a file or directory for changes */ -export const fsUnwatch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/unwatch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const fsUnwatch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + FsUnwatchResponse, + FsUnwatchError, + ThrowOnError + >({ + url: "/fs/unwatch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-fs/types.gen.ts b/src/api-clients/client-rest-fs/types.gen.ts index 624cd45..8aa230c 100644 --- a/src/api-clients/client-rest-fs/types.gen.ts +++ b/src/api-clients/client-rest-fs/types.gen.ts @@ -1,1058 +1,1123 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - error: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); + /** + * Status code for error operations + */ + status: 1; + error: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; + } & RawFsError); }; export type DefaultError = { - code: PitcherErrorCode; - /** - * Additional error details - */ - data?: { - [key: string]: unknown; - } | null; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; + code: PitcherErrorCode; + /** + * Additional error details + */ + data?: { + [key: string]: unknown; + } | null; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; }; export type RawFsError = { + /** + * RAWFS_ERROR code + */ + code: 102; + data: { /** - * RAWFS_ERROR code - */ - code: 102; - data: { - /** - * File system error number, or null if not available - */ - errno: unknown; - }; - /** - * Human-readable error message that can be displayed to users + * File system error number, or null if not available */ - publicMessage?: string | null; + errno: unknown; + }; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; }; /** * Enumeration of error codes */ -export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; +export type PitcherErrorCode = + | 0 + | 1 + | 2 + | 3 + | 100 + | 101 + | 102 + | 200 + | 201 + | 204 + | 300 + | 400 + | 404 + | 410 + | 420 + | 430 + | 440 + | 450 + | 460 + | 470 + | 500 + | 600 + | 601 + | 602 + | 704 + | 800 + | 801 + | 802 + | 803 + | 814; export type WriteFileRequest = { - /** - * File path to write to - */ - path: string; - /** - * File content as binary data (Uint8Array) - */ - content: Blob | File; - /** - * Whether to create the file if it doesn't exist - */ - create?: boolean; - /** - * Whether to overwrite the file if it exists - */ - overwrite?: boolean; + /** + * File path to write to + */ + path: string; + /** + * File content as binary data (Uint8Array) + */ + content: Blob | File; + /** + * Whether to create the file if it doesn't exist + */ + create?: boolean; + /** + * Whether to overwrite the file if it exists + */ + overwrite?: boolean; }; export type FsReadResult = { - treeNodes: Array<{ - [key: string]: unknown; - }>; - /** - * Current clock value for the file system - */ - clock: number; + treeNodes: Array<{ + [key: string]: unknown; + }>; + /** + * Current clock value for the file system + */ + clock: number; }; export type FsOperationRequest = { - operation: FsOperation; + operation: FsOperation; }; -export type FsOperation = ({ - type?: 'FSCreateOperation'; -} & FsCreateOperation) | ({ - type?: 'FSDeleteOperation'; -} & FsDeleteOperation) | ({ - type?: 'FSMoveOperation'; -} & FsMoveOperation); +export type FsOperation = + | ({ + type?: "FSCreateOperation"; + } & FsCreateOperation) + | ({ + type?: "FSDeleteOperation"; + } & FsDeleteOperation) + | ({ + type?: "FSMoveOperation"; + } & FsMoveOperation); export type FsCreateOperation = { - type: 'create'; + type: "create"; + /** + * ID of the parent directory + */ + parentId: string; + newEntry: { /** - * ID of the parent directory - */ - parentId: string; - newEntry: { - /** - * ID of the new entry - */ - id: string; - /** - * Type of the node - */ - type: 'directory' | 'file'; - /** - * Name of the new entry - */ - name: string; - }; -}; - -export type FsDeleteOperation = { - type: 'delete'; - /** - * ID of the entry to delete - */ - id: string; -}; - -export type FsMoveOperation = { - type: 'move'; - /** - * ID of the entry to move + * ID of the new entry */ id: string; /** - * ID of the new parent directory + * Type of the node */ - parentId?: string | null; + type: "directory" | "file"; /** - * New name for the entry + * Name of the new entry */ - name?: string | null; + name: string; + }; }; -export type FsOperationResult = { - /** - * Success code - */ - code: 0; - /** - * Current clock value - */ - clock: number; -} | { - /** - * Ignored code - */ - code: 1; +export type FsDeleteOperation = { + type: "delete"; + /** + * ID of the entry to delete + */ + id: string; }; +export type FsMoveOperation = { + type: "move"; + /** + * ID of the entry to move + */ + id: string; + /** + * ID of the new parent directory + */ + parentId?: string | null; + /** + * New name for the entry + */ + name?: string | null; +}; + +export type FsOperationResult = + | { + /** + * Success code + */ + code: 0; + /** + * Current clock value + */ + clock: number; + } + | { + /** + * Ignored code + */ + code: 1; + }; + export type FsSearchParams = { - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: 'smart' | 'enabled' | 'disabled'; + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: "smart" | "enabled" | "disabled"; }; export type SearchResult = { + /** + * ID of the file containing the match + */ + fileId: string; + lines: { /** - * ID of the file containing the match + * Text of the line containing the match */ - fileId: string; - lines: { - /** - * Text of the line containing the match - */ - text: string; - }; - /** - * Line number of the match - */ - lineNumber: number; - /** - * Absolute offset of the match in the file - */ - absoluteOffset: number; - submatches: Array; + text: string; + }; + /** + * Line number of the match + */ + lineNumber: number; + /** + * Absolute offset of the match in the file + */ + absoluteOffset: number; + submatches: Array; }; export type SearchSubMatch = { - match: { - /** - * Matched text - */ - text: string; - }; - /** - * Start position of the match - */ - start: number; + match: { /** - * End position of the match + * Matched text */ - end: number; + text: string; + }; + /** + * Start position of the match + */ + start: number; + /** + * End position of the match + */ + end: number; }; export type FsStreamingSearchParams = { - /** - * ID for the search operation - */ - searchId: string; - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: 'smart' | 'enabled' | 'disabled'; - /** - * Maximum number of results to return (default: 10,000) - */ - maxResults?: number | null; + /** + * ID for the search operation + */ + searchId: string; + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: "smart" | "enabled" | "disabled"; + /** + * Maximum number of results to return (default: 10,000) + */ + maxResults?: number | null; }; export type PathSearchParams = { - /** - * Text to search for in file paths - */ - text: string; + /** + * Text to search for in file paths + */ + text: string; }; export type PathSearchResult = { - matches: Array; + matches: Array; }; export type PathSearchMatch = { - /** - * Path that matched the search - */ - path: string; - submatches: Array; + /** + * Path that matched the search + */ + path: string; + submatches: Array; }; export type InvalidIdError = { - /** - * INVALID_ID error code - */ - code: 100; + /** + * INVALID_ID error code + */ + code: 100; }; export type FsReadFileParams = { - /** - * Path to the file to read - */ - path: string; + /** + * Path to the file to read + */ + path: string; }; export type FsReadFileResult = { - /** - * File content as binary data - */ - content: Blob | File; + /** + * File content as binary data + */ + content: Blob | File; }; export type FsReadDirParams = { - /** - * Path to the directory to read - */ - path: string; + /** + * Path to the directory to read + */ + path: string; }; export type FsReadDirResult = { - entries: Array<{ - /** - * Name of the entry - */ - name: string; - /** - * Type of the entry - */ - type: 'directory' | 'file'; - /** - * Whether the entry is a symlink - */ - isSymlink: boolean; - }>; -}; - -export type FsStatParams = { + entries: Array<{ /** - * Path to the file or directory to stat + * Name of the entry */ - path: string; -}; - -export type FsStatResult = { + name: string; /** * Type of the entry */ - type: 'directory' | 'file'; + type: "directory" | "file"; /** * Whether the entry is a symlink */ isSymlink: boolean; - /** - * Size of the file in bytes - */ - size: number; - /** - * Last modified time - */ - mtime: number; - /** - * Creation time - */ - ctime: number; - /** - * Last accessed time - */ - atime: number; + }>; +}; + +export type FsStatParams = { + /** + * Path to the file or directory to stat + */ + path: string; +}; + +export type FsStatResult = { + /** + * Type of the entry + */ + type: "directory" | "file"; + /** + * Whether the entry is a symlink + */ + isSymlink: boolean; + /** + * Size of the file in bytes + */ + size: number; + /** + * Last modified time + */ + mtime: number; + /** + * Creation time + */ + ctime: number; + /** + * Last accessed time + */ + atime: number; }; export type FsCopyParams = { - /** - * Path to copy from - */ - from: string; - /** - * Path to copy to - */ - to: string; - /** - * Whether to copy directories recursively - */ - recursive?: boolean | null; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; + /** + * Path to copy from + */ + from: string; + /** + * Path to copy to + */ + to: string; + /** + * Whether to copy directories recursively + */ + recursive?: boolean | null; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; }; export type FsRenameParams = { - /** - * Path to rename from - */ - from: string; - /** - * Path to rename to - */ - to: string; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; + /** + * Path to rename from + */ + from: string; + /** + * Path to rename to + */ + to: string; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; }; export type FsRemoveParams = { - /** - * Path to remove - */ - path: string; - /** - * Whether to remove directories recursively - */ - recursive?: boolean | null; + /** + * Path to remove + */ + path: string; + /** + * Whether to remove directories recursively + */ + recursive?: boolean | null; }; export type FsMkdirParams = { - /** - * Path to create directory at - */ - path: string; - /** - * Whether to create parent directories if they don't exist - */ - recursive?: boolean | null; + /** + * Path to create directory at + */ + path: string; + /** + * Whether to create parent directories if they don't exist + */ + recursive?: boolean | null; }; export type FsWatchParams = { - /** - * Path to watch - */ - path: string; - /** - * Whether to watch directories recursively - */ - recursive?: boolean | null; - /** - * Glob patterns to exclude from watching - */ - excludes?: Array | null; + /** + * Path to watch + */ + path: string; + /** + * Whether to watch directories recursively + */ + recursive?: boolean | null; + /** + * Glob patterns to exclude from watching + */ + excludes?: Array | null; }; export type FsWatchResult = { - /** - * ID of the watch - */ - watchId: string; + /** + * ID of the watch + */ + watchId: string; }; export type FsUnwatchParams = { - /** - * ID of the watch to stop - */ - watchId: string; + /** + * ID of the watch to stop + */ + watchId: string; }; export type WriteFileData = { - body: WriteFileRequest; - path?: never; - query?: never; - url: '/fs/writeFile'; + body: WriteFileRequest; + path?: never; + query?: never; + url: "/fs/writeFile"; }; export type WriteFileErrors = { - /** - * Error writing file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error writing file + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; export type WriteFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; export type FsReadData = { - body?: never; - path?: never; - query?: never; - url: '/fs/read'; + body?: never; + path?: never; + query?: never; + url: "/fs/read"; }; export type FsReadErrors = { - /** - * Error reading file system - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error reading file system + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsReadError = FsReadErrors[keyof FsReadErrors]; export type FsReadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadResult; + }; }; export type FsReadResponse = FsReadResponses[keyof FsReadResponses]; export type FsOperationData = { - body: FsOperationRequest; - path?: never; - query?: never; - url: '/fs/operation'; + body: FsOperationRequest; + path?: never; + query?: never; + url: "/fs/operation"; }; export type FsOperationErrors = { - /** - * Error performing operation - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error performing operation + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsOperationError = FsOperationErrors[keyof FsOperationErrors]; export type FsOperationResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsOperationResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsOperationResult; + }; }; -export type FsOperationResponse = FsOperationResponses[keyof FsOperationResponses]; +export type FsOperationResponse = + FsOperationResponses[keyof FsOperationResponses]; export type FsSearchData = { - body: FsSearchParams; - path?: never; - query?: never; - url: '/fs/search'; + body: FsSearchParams; + path?: never; + query?: never; + url: "/fs/search"; }; export type FsSearchErrors = { - /** - * Error searching files - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error searching files + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsSearchError = FsSearchErrors[keyof FsSearchErrors]; export type FsSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array; + }; }; export type FsSearchResponse = FsSearchResponses[keyof FsSearchResponses]; export type FsStreamingSearchData = { - body: FsStreamingSearchParams; - path?: never; - query?: never; - url: '/fs/streamingSearch'; + body: FsStreamingSearchParams; + path?: never; + query?: never; + url: "/fs/streamingSearch"; }; export type FsStreamingSearchErrors = { - /** - * Error starting streaming search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error starting streaming search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; -export type FsStreamingSearchError = FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; +export type FsStreamingSearchError = + FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; export type FsStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the search operation - */ - searchId?: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the search operation + */ + searchId?: string; }; + }; }; -export type FsStreamingSearchResponse = FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; +export type FsStreamingSearchResponse = + FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; export type FsCancelStreamingSearchData = { - body: { - /** - * ID of the search to cancel - */ - searchId: string; - }; - path?: never; - query?: never; - url: '/fs/cancelStreamingSearch'; + body: { + /** + * ID of the search to cancel + */ + searchId: string; + }; + path?: never; + query?: never; + url: "/fs/cancelStreamingSearch"; }; export type FsCancelStreamingSearchErrors = { - /** - * Error cancelling search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error cancelling search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; -export type FsCancelStreamingSearchError = FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; +export type FsCancelStreamingSearchError = + FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; export type FsCancelStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the cancelled search - */ - searchId?: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the cancelled search + */ + searchId?: string; }; + }; }; -export type FsCancelStreamingSearchResponse = FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; +export type FsCancelStreamingSearchResponse = + FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; export type FsPathSearchData = { - body: PathSearchParams; - path?: never; - query?: never; - url: '/fs/pathSearch'; + body: PathSearchParams; + path?: never; + query?: never; + url: "/fs/pathSearch"; }; export type FsPathSearchErrors = { - /** - * Error searching paths - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error searching paths + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsPathSearchError = FsPathSearchErrors[keyof FsPathSearchErrors]; export type FsPathSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: PathSearchResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: PathSearchResult; + }; }; -export type FsPathSearchResponse = FsPathSearchResponses[keyof FsPathSearchResponses]; +export type FsPathSearchResponse = + FsPathSearchResponses[keyof FsPathSearchResponses]; export type FsUploadData = { - body: { - /** - * ID of the parent directory - */ - parentId: string; - /** - * Name of the file to create - */ - filename: string; - /** - * File content as binary data - */ - content: Blob | File; - }; - path?: never; - query?: never; - url: '/fs/upload'; + body: { + /** + * ID of the parent directory + */ + parentId: string; + /** + * Name of the file to create + */ + filename: string; + /** + * File content as binary data + */ + content: Blob | File; + }; + path?: never; + query?: never; + url: "/fs/upload"; }; export type FsUploadErrors = { - /** - * Error uploading file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'InvalidIdError'; + /** + * Error uploading file + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "InvalidIdError"; } & InvalidIdError); - }; + }; }; export type FsUploadError = FsUploadErrors[keyof FsUploadErrors]; export type FsUploadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the created file - */ - fileId?: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the created file + */ + fileId?: string; }; + }; }; export type FsUploadResponse = FsUploadResponses[keyof FsUploadResponses]; export type FsDownloadData = { - body: { - /** - * Path to download - */ - path: string; - /** - * Glob patterns of files/folders to exclude from the download - */ - excludes?: Array; - }; - path?: never; - query?: never; - url: '/fs/download'; + body: { + /** + * Path to download + */ + path: string; + /** + * Glob patterns of files/folders to exclude from the download + */ + excludes?: Array; + }; + path?: never; + query?: never; + url: "/fs/download"; }; export type FsDownloadErrors = { - /** - * Error creating download - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error creating download + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsDownloadError = FsDownloadErrors[keyof FsDownloadErrors]; export type FsDownloadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * URL to download the files from - */ - downloadUrl?: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * URL to download the files from + */ + downloadUrl?: string; }; + }; }; export type FsDownloadResponse = FsDownloadResponses[keyof FsDownloadResponses]; export type FsReadFileData = { - body: FsReadFileParams; - path?: never; - query?: never; - url: '/fs/readFile'; + body: FsReadFileParams; + path?: never; + query?: never; + url: "/fs/readFile"; }; export type FsReadFileErrors = { - /** - * Error reading file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error reading file + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsReadFileError = FsReadFileErrors[keyof FsReadFileErrors]; export type FsReadFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadFileResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadFileResult; + }; }; export type FsReadFileResponse = FsReadFileResponses[keyof FsReadFileResponses]; export type FsReadDirData = { - body: FsReadDirParams; - path?: never; - query?: never; - url: '/fs/readdir'; + body: FsReadDirParams; + path?: never; + query?: never; + url: "/fs/readdir"; }; export type FsReadDirErrors = { - /** - * Error reading directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error reading directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsReadDirError = FsReadDirErrors[keyof FsReadDirErrors]; export type FsReadDirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadDirResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadDirResult; + }; }; export type FsReadDirResponse = FsReadDirResponses[keyof FsReadDirResponses]; export type FsStatData = { - body: FsStatParams; - path?: never; - query?: never; - url: '/fs/stat'; + body: FsStatParams; + path?: never; + query?: never; + url: "/fs/stat"; }; export type FsStatErrors = { - /** - * Error getting stats - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error getting stats + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsStatError = FsStatErrors[keyof FsStatErrors]; export type FsStatResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsStatResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsStatResult; + }; }; export type FsStatResponse = FsStatResponses[keyof FsStatResponses]; export type FsCopyData = { - body: FsCopyParams; - path?: never; - query?: never; - url: '/fs/copy'; + body: FsCopyParams; + path?: never; + query?: never; + url: "/fs/copy"; }; export type FsCopyErrors = { - /** - * Error copying file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error copying file/directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsCopyError = FsCopyErrors[keyof FsCopyErrors]; export type FsCopyResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; export type FsCopyResponse = FsCopyResponses[keyof FsCopyResponses]; export type FsRenameData = { - body: FsRenameParams; - path?: never; - query?: never; - url: '/fs/rename'; + body: FsRenameParams; + path?: never; + query?: never; + url: "/fs/rename"; }; export type FsRenameErrors = { - /** - * Error renaming file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error renaming file/directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsRenameError = FsRenameErrors[keyof FsRenameErrors]; export type FsRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; export type FsRenameResponse = FsRenameResponses[keyof FsRenameResponses]; export type FsRemoveData = { - body: FsRemoveParams; - path?: never; - query?: never; - url: '/fs/remove'; + body: FsRemoveParams; + path?: never; + query?: never; + url: "/fs/remove"; }; export type FsRemoveErrors = { - /** - * Error removing file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error removing file/directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsRemoveError = FsRemoveErrors[keyof FsRemoveErrors]; export type FsRemoveResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; export type FsRemoveResponse = FsRemoveResponses[keyof FsRemoveResponses]; export type FsMkdirData = { - body: FsMkdirParams; - path?: never; - query?: never; - url: '/fs/mkdir'; + body: FsMkdirParams; + path?: never; + query?: never; + url: "/fs/mkdir"; }; export type FsMkdirErrors = { - /** - * Error creating directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error creating directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsMkdirError = FsMkdirErrors[keyof FsMkdirErrors]; export type FsMkdirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; export type FsMkdirResponse = FsMkdirResponses[keyof FsMkdirResponses]; export type FsWatchData = { - body: FsWatchParams; - path?: never; - query?: never; - url: '/fs/watch'; + body: FsWatchParams; + path?: never; + query?: never; + url: "/fs/watch"; }; export type FsWatchErrors = { - /** - * Error watching file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error watching file/directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsWatchError = FsWatchErrors[keyof FsWatchErrors]; export type FsWatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsWatchResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsWatchResult; + }; }; export type FsWatchResponse = FsWatchResponses[keyof FsWatchResponses]; export type FsUnwatchData = { - body: FsUnwatchParams; - path?: never; - query?: never; - url: '/fs/unwatch'; + body: FsUnwatchParams; + path?: never; + query?: never; + url: "/fs/unwatch"; }; export type FsUnwatchErrors = { - /** - * Error unwatching file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; + /** + * Error unwatching file/directory + */ + 400: ErrorResponse & { + error?: + | ({ + code?: "DefaultError"; + } & DefaultError) + | ({ + code?: "RawFsError"; } & RawFsError); - }; + }; }; export type FsUnwatchError = FsUnwatchErrors[keyof FsUnwatchErrors]; export type FsUnwatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; -export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; \ No newline at end of file +export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; diff --git a/src/api-clients/client-rest-git/client.gen.ts b/src/api-clients/client-rest-git/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-git/client.gen.ts +++ b/src/api-clients/client-rest-git/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-git/index.ts b/src/api-clients/client-rest-git/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-git/index.ts +++ b/src/api-clients/client-rest-git/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-git/sdk.gen.ts b/src/api-clients/client-rest-git/sdk.gen.ts index 0e3b272..6f7d801 100644 --- a/src/api-clients/client-rest-git/sdk.gen.ts +++ b/src/api-clients/client-rest-git/sdk.gen.ts @@ -1,224 +1,358 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { GitStatusData, GitStatusResponse, GitStatusError, GitRemotesData, GitRemotesResponse, GitRemotesError, GitTargetDiffData, GitTargetDiffResponse, GitTargetDiffError, GitPullData, GitPullResponse, GitPullError, GitDiscardData, GitDiscardResponse, GitDiscardError, GitCommitData, GitCommitResponse, GitCommitError, GitPushData, GitPushResponse, GitPushError, GitPushToRemoteData, GitPushToRemoteResponse, GitPushToRemoteError, GitRenameBranchData, GitRenameBranchResponse, GitRenameBranchError, GitRemoteContentData, GitRemoteContentResponse, GitRemoteContentError, GitDiffStatusData, GitDiffStatusResponse, GitDiffStatusError, GitResetLocalWithRemoteData, GitResetLocalWithRemoteResponse, GitResetLocalWithRemoteError, GitCheckoutInitialBranchData, GitCheckoutInitialBranchResponse, GitCheckoutInitialBranchError, GitTransposeLinesData, GitTransposeLinesResponse, GitTransposeLinesError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + GitStatusData, + GitStatusResponse, + GitStatusError, + GitRemotesData, + GitRemotesResponse, + GitRemotesError, + GitTargetDiffData, + GitTargetDiffResponse, + GitTargetDiffError, + GitPullData, + GitPullResponse, + GitPullError, + GitDiscardData, + GitDiscardResponse, + GitDiscardError, + GitCommitData, + GitCommitResponse, + GitCommitError, + GitPushData, + GitPushResponse, + GitPushError, + GitPushToRemoteData, + GitPushToRemoteResponse, + GitPushToRemoteError, + GitRenameBranchData, + GitRenameBranchResponse, + GitRenameBranchError, + GitRemoteContentData, + GitRemoteContentResponse, + GitRemoteContentError, + GitDiffStatusData, + GitDiffStatusResponse, + GitDiffStatusError, + GitResetLocalWithRemoteData, + GitResetLocalWithRemoteResponse, + GitResetLocalWithRemoteError, + GitCheckoutInitialBranchData, + GitCheckoutInitialBranchResponse, + GitCheckoutInitialBranchError, + GitTransposeLinesData, + GitTransposeLinesResponse, + GitTransposeLinesError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Get git status * Retrieve current git status including changed files, branch information, and commits */ -export const gitStatus = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/status', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitStatus = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitStatusResponse, + GitStatusError, + ThrowOnError + >({ + url: "/git/status", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get git remotes * Retrieve git remote information */ -export const gitRemotes = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/remotes', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitRemotes = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitRemotesResponse, + GitRemotesError, + ThrowOnError + >({ + url: "/git/remotes", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get git target diff * Retrieve diff between current branch and target branch */ -export const gitTargetDiff = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/targetDiff', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitTargetDiff = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitTargetDiffResponse, + GitTargetDiffError, + ThrowOnError + >({ + url: "/git/targetDiff", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Pull from remote * Pull changes from remote repository */ -export const gitPull = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/pull', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitPull = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitPullResponse, + GitPullError, + ThrowOnError + >({ + url: "/git/pull", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Discard changes * Discard local changes for specified paths */ -export const gitDiscard = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/discard', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitDiscard = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitDiscardResponse, + GitDiscardError, + ThrowOnError + >({ + url: "/git/discard", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Commit changes * Commit changes to the repository */ -export const gitCommit = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/commit', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitCommit = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitCommitResponse, + GitCommitError, + ThrowOnError + >({ + url: "/git/commit", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Push changes * Push local commits to remote repository */ -export const gitPush = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/push', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitPush = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitPushResponse, + GitPushError, + ThrowOnError + >({ + url: "/git/push", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Push to remote * Push to a specific remote repository */ -export const gitPushToRemote = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/pushToRemote', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitPushToRemote = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitPushToRemoteResponse, + GitPushToRemoteError, + ThrowOnError + >({ + url: "/git/pushToRemote", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Rename branch * Rename a git branch */ -export const gitRenameBranch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/renameBranch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitRenameBranch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitRenameBranchResponse, + GitRenameBranchError, + ThrowOnError + >({ + url: "/git/renameBranch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get remote content * Retrieve content from a remote repository */ -export const gitRemoteContent = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/remoteContent', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitRemoteContent = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitRemoteContentResponse, + GitRemoteContentError, + ThrowOnError + >({ + url: "/git/remoteContent", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get diff status * Retrieve diff status between two git references */ -export const gitDiffStatus = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/diffStatus', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitDiffStatus = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitDiffStatusResponse, + GitDiffStatusError, + ThrowOnError + >({ + url: "/git/diffStatus", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Reset local with remote * Reset local repository to match the remote state */ -export const gitResetLocalWithRemote = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/resetLocalWithRemote', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitResetLocalWithRemote = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitResetLocalWithRemoteResponse, + GitResetLocalWithRemoteError, + ThrowOnError + >({ + url: "/git/resetLocalWithRemote", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Checkout initial branch * Checkout the initial branch of the repository */ -export const gitCheckoutInitialBranch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/checkoutInitialBranch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const gitCheckoutInitialBranch = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitCheckoutInitialBranchResponse, + GitCheckoutInitialBranchError, + ThrowOnError + >({ + url: "/git/checkoutInitialBranch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Transpose lines * Transpose line numbers from one git reference to another */ -export const gitTransposeLines = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/transposeLines', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const gitTransposeLines = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + GitTransposeLinesResponse, + GitTransposeLinesError, + ThrowOnError + >({ + url: "/git/transposeLines", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-git/types.gen.ts b/src/api-clients/client-rest-git/types.gen.ts index dce87ba..26ac8e7 100644 --- a/src/api-clients/client-rest-git/types.gen.ts +++ b/src/api-clients/client-rest-git/types.gen.ts @@ -1,725 +1,744 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; +}; + +export type CommonError = + | { + /** + * Error code + */ + code: "GIT_OPERATION_IN_PROGRESS" | "GIT_REMOTE_FILE_NOT_FOUND"; + /** + * Error message + */ + message: string; + } + | { + /** + * Protocol error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { [key: string]: unknown; + }; }; -}; - -export type CommonError = { - /** - * Error code - */ - code: 'GIT_OPERATION_IN_PROGRESS' | 'GIT_REMOTE_FILE_NOT_FOUND'; - /** - * Error message - */ - message: string; -} | { - /** - * Protocol error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - }; -}; /** * Git status short format codes */ -export type GitStatusShortFormat = '' | 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'; +export type GitStatusShortFormat = "" | "M" | "A" | "D" | "R" | "C" | "U" | "?"; export type GitItem = { - /** - * File path - */ - path: string; - index: GitStatusShortFormat; - workingTree: GitStatusShortFormat; - /** - * Whether the file is staged - */ - isStaged: boolean; - /** - * Whether the file has conflicts - */ - isConflicted: boolean; - /** - * Unique identifier for the file - */ - fileId?: string; + /** + * File path + */ + path: string; + index: GitStatusShortFormat; + workingTree: GitStatusShortFormat; + /** + * Whether the file is staged + */ + isStaged: boolean; + /** + * Whether the file has conflicts + */ + isConflicted: boolean; + /** + * Unique identifier for the file + */ + fileId?: string; }; /** * Map of file IDs to Git items */ export type GitChangedFiles = { - [key: string]: GitItem; + [key: string]: GitItem; }; export type GitBranchProperties = { - /** - * Current HEAD reference - */ - head?: unknown; - /** - * Current branch name - */ - branch?: unknown; - /** - * Number of commits ahead of the remote - */ - ahead: number; - /** - * Number of commits behind the remote - */ - behind: number; - /** - * Whether the branch is safe to operate on - */ - safe: boolean; + /** + * Current HEAD reference + */ + head?: unknown; + /** + * Current branch name + */ + branch?: unknown; + /** + * Number of commits ahead of the remote + */ + ahead: number; + /** + * Number of commits behind the remote + */ + behind: number; + /** + * Whether the branch is safe to operate on + */ + safe: boolean; }; export type GitCommit = { - /** - * Commit hash - */ - hash: string; - /** - * Commit date - */ - date: string; - /** - * Commit message - */ - message: string; - /** - * Commit author - */ - author: string; + /** + * Commit hash + */ + hash: string; + /** + * Commit date + */ + date: string; + /** + * Commit message + */ + message: string; + /** + * Commit author + */ + author: string; }; export type GitStatus = { - changedFiles: GitChangedFiles; - deletedFiles: Array; - /** - * Whether there are remote conflicts - */ - conflicts: boolean; - /** - * Whether there are local changes - */ - localChanges: boolean; - remote: GitBranchProperties; - target: GitBranchProperties; - /** - * Current HEAD reference - */ - head?: string; - commits: Array; - /** - * Current branch name - */ - branch: unknown; - /** - * Whether a merge is in progress - */ - isMerging: boolean; + changedFiles: GitChangedFiles; + deletedFiles: Array; + /** + * Whether there are remote conflicts + */ + conflicts: boolean; + /** + * Whether there are local changes + */ + localChanges: boolean; + remote: GitBranchProperties; + target: GitBranchProperties; + /** + * Current HEAD reference + */ + head?: string; + commits: Array; + /** + * Current branch name + */ + branch: unknown; + /** + * Whether a merge is in progress + */ + isMerging: boolean; }; export type GitTargetDiff = { - /** - * Number of commits ahead of the target - */ - ahead: number; - /** - * Number of commits behind the target - */ - behind: number; - commits: Array; + /** + * Number of commits ahead of the target + */ + ahead: number; + /** + * Number of commits behind the target + */ + behind: number; + commits: Array; }; export type GitRemotes = { - /** - * Origin remote URL - */ - origin: string; - /** - * Upstream remote URL - */ - upstream: string; + /** + * Origin remote URL + */ + origin: string; + /** + * Upstream remote URL + */ + upstream: string; }; export type GitRemoteParams = { - /** - * Branch or commit hash - */ - reference: string; - /** - * Path to the file - */ - path: string; + /** + * Branch or commit hash + */ + reference: string; + /** + * Path to the file + */ + path: string; }; export type GitDiffStatusParams = { - /** - * Base reference used for diffing - */ - base: string; - /** - * Head reference used for diffing - */ - head: string; + /** + * Base reference used for diffing + */ + base: string; + /** + * Head reference used for diffing + */ + head: string; }; export type GitDiffStatusItem = { - status: GitStatusShortFormat; - /** - * Path to the file - */ - path: string; - /** - * Original path for renamed files - */ - oldPath?: string; - hunks: Array<{ - original: { - start: number; - end: number; - }; - modified: { - start: number; - end: number; - }; - }>; + status: GitStatusShortFormat; + /** + * Path to the file + */ + path: string; + /** + * Original path for renamed files + */ + oldPath?: string; + hunks: Array<{ + original: { + start: number; + end: number; + }; + modified: { + start: number; + end: number; + }; + }>; }; export type GitDiffStatusResult = { - files: Array; + files: Array; }; export type GitStatusData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/status'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/git/status"; }; export type GitStatusErrors = { - /** - * Error retrieving git status - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git status + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitStatusError = GitStatusErrors[keyof GitStatusErrors]; export type GitStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitStatus; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitStatus; + }; }; export type GitStatusResponse = GitStatusResponses[keyof GitStatusResponses]; export type GitRemotesData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/remotes'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/git/remotes"; }; export type GitRemotesErrors = { - /** - * Error retrieving git remotes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git remotes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitRemotesError = GitRemotesErrors[keyof GitRemotesErrors]; export type GitRemotesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitRemotes; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitRemotes; + }; }; export type GitRemotesResponse = GitRemotesResponses[keyof GitRemotesResponses]; export type GitTargetDiffData = { - body: { - /** - * Branch to compare against - */ - branch: string; - }; - path?: never; - query?: never; - url: '/git/targetDiff'; + body: { + /** + * Branch to compare against + */ + branch: string; + }; + path?: never; + query?: never; + url: "/git/targetDiff"; }; export type GitTargetDiffErrors = { - /** - * Error retrieving git target diff - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git target diff + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitTargetDiffError = GitTargetDiffErrors[keyof GitTargetDiffErrors]; export type GitTargetDiffResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitTargetDiff; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitTargetDiff; + }; }; -export type GitTargetDiffResponse = GitTargetDiffResponses[keyof GitTargetDiffResponses]; +export type GitTargetDiffResponse = + GitTargetDiffResponses[keyof GitTargetDiffResponses]; export type GitPullData = { - body: { - /** - * Branch to pull from - */ - branch?: string; - /** - * Force pull even if there are conflicts - */ - force?: boolean; - }; - path?: never; - query?: never; - url: '/git/pull'; + body: { + /** + * Branch to pull from + */ + branch?: string; + /** + * Force pull even if there are conflicts + */ + force?: boolean; + }; + path?: never; + query?: never; + url: "/git/pull"; }; export type GitPullErrors = { - /** - * Error pulling from remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pulling from remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitPullError = GitPullErrors[keyof GitPullErrors]; export type GitPullResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type GitPullResponse = GitPullResponses[keyof GitPullResponses]; export type GitDiscardData = { - body: { - /** - * Paths of files to discard changes - */ - paths?: Array; - }; - path?: never; - query?: never; - url: '/git/discard'; + body: { + /** + * Paths of files to discard changes + */ + paths?: Array; + }; + path?: never; + query?: never; + url: "/git/discard"; }; export type GitDiscardErrors = { - /** - * Error discarding changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error discarding changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitDiscardError = GitDiscardErrors[keyof GitDiscardErrors]; export type GitDiscardResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - paths?: Array; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + paths?: Array; }; + }; }; export type GitDiscardResponse = GitDiscardResponses[keyof GitDiscardResponses]; export type GitCommitData = { - body: { - /** - * Paths of files to commit - */ - paths?: Array; - /** - * Commit message - */ - message: string; - /** - * Whether to push the commit immediately - */ - push?: boolean; - }; - path?: never; - query?: never; - url: '/git/commit'; + body: { + /** + * Paths of files to commit + */ + paths?: Array; + /** + * Commit message + */ + message: string; + /** + * Whether to push the commit immediately + */ + push?: boolean; + }; + path?: never; + query?: never; + url: "/git/commit"; }; export type GitCommitErrors = { - /** - * Error committing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error committing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitCommitError = GitCommitErrors[keyof GitCommitErrors]; export type GitCommitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the shell process - */ - shellId: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the shell process + */ + shellId: string; }; + }; }; export type GitCommitResponse = GitCommitResponses[keyof GitCommitResponses]; export type GitPushData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/push'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/git/push"; }; export type GitPushErrors = { - /** - * Error pushing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pushing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitPushError = GitPushErrors[keyof GitPushErrors]; export type GitPushResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type GitPushResponse = GitPushResponses[keyof GitPushResponses]; export type GitPushToRemoteData = { - body: { - /** - * URL of the remote repository - */ - url: string; - /** - * Branch to push to - */ - branch: string; - /** - * Whether to squash all commits into one - */ - squashAllCommits?: boolean; - }; - path?: never; - query?: never; - url: '/git/pushToRemote'; + body: { + /** + * URL of the remote repository + */ + url: string; + /** + * Branch to push to + */ + branch: string; + /** + * Whether to squash all commits into one + */ + squashAllCommits?: boolean; + }; + path?: never; + query?: never; + url: "/git/pushToRemote"; }; export type GitPushToRemoteErrors = { - /** - * Error pushing to remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pushing to remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitPushToRemoteError = GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; +export type GitPushToRemoteError = + GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; export type GitPushToRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitPushToRemoteResponse = GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; +export type GitPushToRemoteResponse = + GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; export type GitRenameBranchData = { - body: { - /** - * Current branch name - */ - oldBranch: string; - /** - * New branch name - */ - newBranch: string; - }; - path?: never; - query?: never; - url: '/git/renameBranch'; + body: { + /** + * Current branch name + */ + oldBranch: string; + /** + * New branch name + */ + newBranch: string; + }; + path?: never; + query?: never; + url: "/git/renameBranch"; }; export type GitRenameBranchErrors = { - /** - * Error renaming branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error renaming branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitRenameBranchError = GitRenameBranchErrors[keyof GitRenameBranchErrors]; +export type GitRenameBranchError = + GitRenameBranchErrors[keyof GitRenameBranchErrors]; export type GitRenameBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitRenameBranchResponse = GitRenameBranchResponses[keyof GitRenameBranchResponses]; +export type GitRenameBranchResponse = + GitRenameBranchResponses[keyof GitRenameBranchResponses]; export type GitRemoteContentData = { - body: GitRemoteParams; - path?: never; - query?: never; - url: '/git/remoteContent'; + body: GitRemoteParams; + path?: never; + query?: never; + url: "/git/remoteContent"; }; export type GitRemoteContentErrors = { - /** - * Error retrieving remote content - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving remote content + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitRemoteContentError = GitRemoteContentErrors[keyof GitRemoteContentErrors]; +export type GitRemoteContentError = + GitRemoteContentErrors[keyof GitRemoteContentErrors]; export type GitRemoteContentResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * Content of the file - */ - content: string; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * Content of the file + */ + content: string; }; + }; }; -export type GitRemoteContentResponse = GitRemoteContentResponses[keyof GitRemoteContentResponses]; +export type GitRemoteContentResponse = + GitRemoteContentResponses[keyof GitRemoteContentResponses]; export type GitDiffStatusData = { - body: GitDiffStatusParams; - path?: never; - query?: never; - url: '/git/diffStatus'; + body: GitDiffStatusParams; + path?: never; + query?: never; + url: "/git/diffStatus"; }; export type GitDiffStatusErrors = { - /** - * Error retrieving diff status - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving diff status + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitDiffStatusError = GitDiffStatusErrors[keyof GitDiffStatusErrors]; export type GitDiffStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitDiffStatusResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitDiffStatusResult; + }; }; -export type GitDiffStatusResponse = GitDiffStatusResponses[keyof GitDiffStatusResponses]; +export type GitDiffStatusResponse = + GitDiffStatusResponses[keyof GitDiffStatusResponses]; export type GitResetLocalWithRemoteData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/resetLocalWithRemote'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/git/resetLocalWithRemote"; }; export type GitResetLocalWithRemoteErrors = { - /** - * Error resetting local with remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error resetting local with remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitResetLocalWithRemoteError = GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; +export type GitResetLocalWithRemoteError = + GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; export type GitResetLocalWithRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitResetLocalWithRemoteResponse = GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; +export type GitResetLocalWithRemoteResponse = + GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; export type GitCheckoutInitialBranchData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/checkoutInitialBranch'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/git/checkoutInitialBranch"; }; export type GitCheckoutInitialBranchErrors = { - /** - * Error checking out initial branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error checking out initial branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitCheckoutInitialBranchError = GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; +export type GitCheckoutInitialBranchError = + GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; export type GitCheckoutInitialBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitCheckoutInitialBranchResponse = GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; +export type GitCheckoutInitialBranchResponse = + GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; export type GitTransposeLinesData = { - body: Array<{ - /** - * Git commit SHA - */ - sha: string; - /** - * Path to the file - */ - path: string; - /** - * Line number to transpose - */ - line: number; - }>; - path?: never; - query?: never; - url: '/git/transposeLines'; + body: Array<{ + /** + * Git commit SHA + */ + sha: string; + /** + * Path to the file + */ + path: string; + /** + * Line number to transpose + */ + line: number; + }>; + path?: never; + query?: never; + url: "/git/transposeLines"; }; export type GitTransposeLinesErrors = { - /** - * Error transposing lines - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error transposing lines + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitTransposeLinesError = GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; +export type GitTransposeLinesError = + GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; export type GitTransposeLinesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array<{ - path: string; - line: number; - } | unknown>; - }; -}; - -export type GitTransposeLinesResponse = GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; \ No newline at end of file + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array< + | { + path: string; + line: number; + } + | unknown + >; + }; +}; + +export type GitTransposeLinesResponse = + GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; diff --git a/src/api-clients/client-rest-setup/client.gen.ts b/src/api-clients/client-rest-setup/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-setup/client.gen.ts +++ b/src/api-clients/client-rest-setup/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-setup/index.ts b/src/api-clients/client-rest-setup/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-setup/index.ts +++ b/src/api-clients/client-rest-setup/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-setup/sdk.gen.ts b/src/api-clients/client-rest-setup/sdk.gen.ts index 1a3ae61..3855e23 100644 --- a/src/api-clients/client-rest-setup/sdk.gen.ts +++ b/src/api-clients/client-rest-setup/sdk.gen.ts @@ -1,119 +1,190 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { SetupGetData, SetupGetResponse, SetupGetError, SetupSkipStepData, SetupSkipStepResponse, SetupSkipStepError, SetupSkipAllData, SetupSkipAllResponse, SetupSkipAllError, SetupDisableData, SetupDisableResponse, SetupDisableError, SetupEnableData, SetupEnableResponse, SetupEnableError, SetupInitData, SetupInitResponse, SetupInitError, SetupSetStepData, SetupSetStepResponse, SetupSetStepError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + SetupGetData, + SetupGetResponse, + SetupGetError, + SetupSkipStepData, + SetupSkipStepResponse, + SetupSkipStepError, + SetupSkipAllData, + SetupSkipAllResponse, + SetupSkipAllError, + SetupDisableData, + SetupDisableResponse, + SetupDisableError, + SetupEnableData, + SetupEnableResponse, + SetupEnableError, + SetupInitData, + SetupInitResponse, + SetupInitError, + SetupSetStepData, + SetupSetStepResponse, + SetupSetStepError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Get setup progress * Retrieve the current setup progress status */ -export const setupGet = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/get', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupGet = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupGetResponse, + SetupGetError, + ThrowOnError + >({ + url: "/setup/get", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Skip setup step * Skip a specific step in the setup process */ -export const setupSkipStep = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/skip', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupSkipStep = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupSkipStepResponse, + SetupSkipStepError, + ThrowOnError + >({ + url: "/setup/skip", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Skip all setup steps * Skip all remaining steps in the setup process */ -export const setupSkipAll = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/skipAll', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupSkipAll = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupSkipAllResponse, + SetupSkipAllError, + ThrowOnError + >({ + url: "/setup/skipAll", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Disable setup * Disable the setup process */ -export const setupDisable = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/disable', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupDisable = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupDisableResponse, + SetupDisableError, + ThrowOnError + >({ + url: "/setup/disable", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Enable setup * Enable the setup process */ -export const setupEnable = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/enable', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupEnable = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupEnableResponse, + SetupEnableError, + ThrowOnError + >({ + url: "/setup/enable", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Initialize setup * Initialize the setup process */ -export const setupInit = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/init', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const setupInit = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupInitResponse, + SetupInitError, + ThrowOnError + >({ + url: "/setup/init", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Set current setup step * Set the current step in the setup process (used for restarting) */ -export const setupSetStep = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/setStep', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const setupSetStep = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SetupSetStepResponse, + SetupSetStepError, + ThrowOnError + >({ + url: "/setup/setStep", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-setup/types.gen.ts b/src/api-clients/client-rest-setup/types.gen.ts index 43425e7..8cfaef0 100644 --- a/src/api-clients/client-rest-setup/types.gen.ts +++ b/src/api-clients/client-rest-setup/types.gen.ts @@ -1,295 +1,300 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type ProtocolError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; + /** + * Error code + */ + code: number; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; }; /** * Status of a setup shell step */ -export type SetupShellStatus = 'SUCCEEDED' | 'FAILED' | 'SKIPPED'; +export type SetupShellStatus = "SUCCEEDED" | "FAILED" | "SKIPPED"; export type Step = { - /** - * Name of the setup step - */ - name: string; - /** - * Command to execute for this step - */ - command: string; - /** - * ID of the shell executing the command - */ - shellId: string | null; - finishStatus: SetupShellStatus; + /** + * Name of the setup step + */ + name: string; + /** + * Command to execute for this step + */ + command: string; + /** + * ID of the shell executing the command + */ + shellId: string | null; + finishStatus: SetupShellStatus; }; export type SetupProgress = { - /** - * Current state of the setup process - */ - state: 'IDLE' | 'IN_PROGRESS' | 'FINISHED' | 'STOPPED'; - /** - * List of setup steps - */ - steps: Array; - /** - * Index of the current step being executed - */ - currentStepIndex: number; + /** + * Current state of the setup process + */ + state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; + /** + * List of setup steps + */ + steps: Array; + /** + * Index of the current step being executed + */ + currentStepIndex: number; }; export type SetupGetData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/setup/get'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/setup/get"; }; export type SetupGetErrors = { - /** - * Error retrieving setup progress - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error retrieving setup progress + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupGetError = SetupGetErrors[keyof SetupGetErrors]; export type SetupGetResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; export type SetupGetResponse = SetupGetResponses[keyof SetupGetResponses]; export type SetupSkipStepData = { - body: { - /** - * Index of the step to skip - */ - stepIndexToSkip: number; - }; - path?: never; - query?: never; - url: '/setup/skip'; + body: { + /** + * Index of the step to skip + */ + stepIndexToSkip: number; + }; + path?: never; + query?: never; + url: "/setup/skip"; }; export type SetupSkipStepErrors = { - /** - * Error skipping step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error skipping step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSkipStepError = SetupSkipStepErrors[keyof SetupSkipStepErrors]; export type SetupSkipStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSkipStepResponse = SetupSkipStepResponses[keyof SetupSkipStepResponses]; +export type SetupSkipStepResponse = + SetupSkipStepResponses[keyof SetupSkipStepResponses]; export type SetupSkipAllData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/skipAll'; + body: unknown; + path?: never; + query?: never; + url: "/setup/skipAll"; }; export type SetupSkipAllErrors = { - /** - * Error skipping all steps - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error skipping all steps + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSkipAllError = SetupSkipAllErrors[keyof SetupSkipAllErrors]; export type SetupSkipAllResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSkipAllResponse = SetupSkipAllResponses[keyof SetupSkipAllResponses]; +export type SetupSkipAllResponse = + SetupSkipAllResponses[keyof SetupSkipAllResponses]; export type SetupDisableData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/disable'; + body: unknown; + path?: never; + query?: never; + url: "/setup/disable"; }; export type SetupDisableErrors = { - /** - * Error disabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error disabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupDisableError = SetupDisableErrors[keyof SetupDisableErrors]; export type SetupDisableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupDisableResponse = SetupDisableResponses[keyof SetupDisableResponses]; +export type SetupDisableResponse = + SetupDisableResponses[keyof SetupDisableResponses]; export type SetupEnableData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/enable'; + body: unknown; + path?: never; + query?: never; + url: "/setup/enable"; }; export type SetupEnableErrors = { - /** - * Error enabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error enabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupEnableError = SetupEnableErrors[keyof SetupEnableErrors]; export type SetupEnableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupEnableResponse = SetupEnableResponses[keyof SetupEnableResponses]; +export type SetupEnableResponse = + SetupEnableResponses[keyof SetupEnableResponses]; export type SetupInitData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/init'; + body: unknown; + path?: never; + query?: never; + url: "/setup/init"; }; export type SetupInitErrors = { - /** - * Error initializing setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error initializing setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupInitError = SetupInitErrors[keyof SetupInitErrors]; export type SetupInitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; export type SetupInitResponse = SetupInitResponses[keyof SetupInitResponses]; export type SetupSetStepData = { - body: { - /** - * Index of the step to set as current - */ - stepIndex: number; - }; - path?: never; - query?: never; - url: '/setup/setStep'; + body: { + /** + * Index of the step to set as current + */ + stepIndex: number; + }; + path?: never; + query?: never; + url: "/setup/setStep"; }; export type SetupSetStepErrors = { - /** - * Error setting current step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error setting current step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSetStepError = SetupSetStepErrors[keyof SetupSetStepErrors]; export type SetupSetStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSetStepResponse = SetupSetStepResponses[keyof SetupSetStepResponses]; \ No newline at end of file +export type SetupSetStepResponse = + SetupSetStepResponses[keyof SetupSetStepResponses]; diff --git a/src/api-clients/client-rest-shell/client.gen.ts b/src/api-clients/client-rest-shell/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-shell/client.gen.ts +++ b/src/api-clients/client-rest-shell/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-shell/index.ts b/src/api-clients/client-rest-shell/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-shell/index.ts +++ b/src/api-clients/client-rest-shell/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-shell/sdk.gen.ts b/src/api-clients/client-rest-shell/sdk.gen.ts index 319634a..b721e6f 100644 --- a/src/api-clients/client-rest-shell/sdk.gen.ts +++ b/src/api-clients/client-rest-shell/sdk.gen.ts @@ -1,149 +1,238 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { ShellCreateData, ShellCreateResponse, ShellCreateError, ShellInData, ShellInResponse, ShellInError, ShellListData, ShellListResponse, ShellListError, ShellOpenData, ShellOpenResponse, ShellOpenError, ShellCloseData, ShellCloseResponse, ShellCloseError, ShellRestartData, ShellRestartResponse, ShellRestartError, ShellTerminateData, ShellTerminateResponse, ShellTerminateError, ShellResizeData, ShellResizeResponse, ShellResizeError, ShellRenameData, ShellRenameResponse, ShellRenameError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + ShellCreateData, + ShellCreateResponse, + ShellCreateError, + ShellInData, + ShellInResponse, + ShellInError, + ShellListData, + ShellListResponse, + ShellListError, + ShellOpenData, + ShellOpenResponse, + ShellOpenError, + ShellCloseData, + ShellCloseResponse, + ShellCloseError, + ShellRestartData, + ShellRestartResponse, + ShellRestartError, + ShellTerminateData, + ShellTerminateResponse, + ShellTerminateError, + ShellResizeData, + ShellResizeResponse, + ShellResizeError, + ShellRenameData, + ShellRenameResponse, + ShellRenameError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Create a new shell * Creates a new terminal or command shell */ -export const shellCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/create', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellCreate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellCreateResponse, + ShellCreateError, + ThrowOnError + >({ + url: "/shell/create", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Send input to shell * Sends user input to an active shell */ -export const shellIn = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/in', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellIn = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellInResponse, + ShellInError, + ThrowOnError + >({ + url: "/shell/in", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * List all shells * Retrieves a list of all available shells */ -export const shellList = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/list', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellList = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellListResponse, + ShellListError, + ThrowOnError + >({ + url: "/shell/list", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Open an existing shell * Opens an existing shell and retrieves its buffer */ -export const shellOpen = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/open', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellOpen = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellOpenResponse, + ShellOpenError, + ThrowOnError + >({ + url: "/shell/open", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Close a shell * Closes a shell without terminating the underlying process */ -export const shellClose = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/close', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellClose = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellCloseResponse, + ShellCloseError, + ThrowOnError + >({ + url: "/shell/close", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Restart a shell * Restarts an existing shell process */ -export const shellRestart = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/restart', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellRestart = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellRestartResponse, + ShellRestartError, + ThrowOnError + >({ + url: "/shell/restart", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Terminate a shell * Terminates a shell and its underlying process */ -export const shellTerminate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/terminate', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellTerminate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellTerminateResponse, + ShellTerminateError, + ThrowOnError + >({ + url: "/shell/terminate", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Resize a shell * Updates the dimensions of a shell */ -export const shellResize = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/resize', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const shellResize = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellResizeResponse, + ShellResizeError, + ThrowOnError + >({ + url: "/shell/resize", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Rename a shell * Updates the name of a shell */ -export const shellRename = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/rename', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const shellRename = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + ShellRenameResponse, + ShellRenameError, + ThrowOnError + >({ + url: "/shell/rename", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-shell/types.gen.ts b/src/api-clients/client-rest-shell/types.gen.ts index 45aa947..efc243b 100644 --- a/src/api-clients/client-rest-shell/types.gen.ts +++ b/src/api-clients/client-rest-shell/types.gen.ts @@ -1,29 +1,29 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; /** @@ -32,412 +32,429 @@ export type ErrorResponse = { export type ShellId = string; export type ShellSize = { - /** - * Number of columns in the terminal - */ - cols: number; - /** - * Number of rows in the terminal - */ - rows: number; + /** + * Number of columns in the terminal + */ + cols: number; + /** + * Number of rows in the terminal + */ + rows: number; }; /** * Type of shell process */ -export type ShellProcessType = 'TERMINAL' | 'COMMAND'; +export type ShellProcessType = "TERMINAL" | "COMMAND"; /** * Current status of the shell process */ -export type ShellProcessStatus = 'RUNNING' | 'FINISHED' | 'ERROR' | 'KILLED' | 'RESTARTING'; +export type ShellProcessStatus = + | "RUNNING" + | "FINISHED" + | "ERROR" + | "KILLED" + | "RESTARTING"; export type BaseShellDto = { - shellId: ShellId; - /** - * Display name of the shell - */ - name: string; - status: ShellProcessStatus; - /** - * Exit code of the process if it has finished - */ - exitCode?: number | null; + shellId: ShellId; + /** + * Display name of the shell + */ + name: string; + status: ShellProcessStatus; + /** + * Exit code of the process if it has finished + */ + exitCode?: number | null; }; export type CommandShellDto = BaseShellDto & { - /** - * Indicates this is a command shell - */ - shellType: 'COMMAND'; - /** - * The command that was executed to start this shell - */ - startCommand: string; + /** + * Indicates this is a command shell + */ + shellType: "COMMAND"; + /** + * The command that was executed to start this shell + */ + startCommand: string; }; export type TerminalShellDto = BaseShellDto & { - /** - * Indicates this is a terminal shell - */ - shellType: 'TERMINAL'; - /** - * Username of the shell owner - */ - ownerUsername: string; - /** - * Whether this is a system shell - */ - isSystemShell: boolean; -}; - -export type ShellDto = ({ - shellType?: 'COMMAND'; -} & CommandShellDto) | ({ - shellType?: 'TERMINAL'; -} & TerminalShellDto); + /** + * Indicates this is a terminal shell + */ + shellType: "TERMINAL"; + /** + * Username of the shell owner + */ + ownerUsername: string; + /** + * Whether this is a system shell + */ + isSystemShell: boolean; +}; + +export type ShellDto = + | ({ + shellType?: "COMMAND"; + } & CommandShellDto) + | ({ + shellType?: "TERMINAL"; + } & TerminalShellDto); export type OpenCommandShellDto = CommandShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; + /** + * Content buffer of the shell + */ + buffer: Array; }; export type OpenTerminalShellDto = TerminalShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; -}; - -export type OpenShellDto = ({ - shellType?: 'COMMAND'; -} & OpenCommandShellDto) | ({ - shellType?: 'TERMINAL'; -} & OpenTerminalShellDto); + /** + * Content buffer of the shell + */ + buffer: Array; +}; + +export type OpenShellDto = + | ({ + shellType?: "COMMAND"; + } & OpenCommandShellDto) + | ({ + shellType?: "TERMINAL"; + } & OpenTerminalShellDto); + +export type CommonError = + | { + /** + * Error code indicating the shell is not accessible + */ + code: "SHELL_NOT_ACCESSIBLE"; + /** + * Error message + */ + message: string; + } + | { + /** + * Protocol error code + */ + code: string; + /** + * Error message + */ + message: string; + }; -export type CommonError = { - /** - * Error code indicating the shell is not accessible - */ - code: 'SHELL_NOT_ACCESSIBLE'; +export type ShellCreateData = { + body: { /** - * Error message + * Command to execute in the shell */ - message: string; -} | { + command?: string; /** - * Protocol error code + * Working directory for the shell */ - code: string; + cwd?: string; + size?: ShellSize; + type?: ShellProcessType; /** - * Error message + * Whether this shell is started by the editor itself to run a specific process */ - message: string; -}; - -export type ShellCreateData = { - body: { - /** - * Command to execute in the shell - */ - command?: string; - /** - * Working directory for the shell - */ - cwd?: string; - size?: ShellSize; - type?: ShellProcessType; - /** - * Whether this shell is started by the editor itself to run a specific process - */ - isSystemShell?: boolean; - }; - path?: never; - query?: never; - url: '/shell/create'; + isSystemShell?: boolean; + }; + path?: never; + query?: never; + url: "/shell/create"; }; export type ShellCreateErrors = { - /** - * Error creating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error creating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellCreateError = ShellCreateErrors[keyof ShellCreateErrors]; export type ShellCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; }; -export type ShellCreateResponse = ShellCreateResponses[keyof ShellCreateResponses]; +export type ShellCreateResponse = + ShellCreateResponses[keyof ShellCreateResponses]; export type ShellInData = { - body: { - shellId: ShellId; - /** - * Input to send to the shell - */ - input: string; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/in'; + body: { + shellId: ShellId; + /** + * Input to send to the shell + */ + input: string; + size: ShellSize; + }; + path?: never; + query?: never; + url: "/shell/in"; }; export type ShellInErrors = { - /** - * Error sending input to shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error sending input to shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellInError = ShellInErrors[keyof ShellInErrors]; export type ShellInResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type ShellInResponse = ShellInResponses[keyof ShellInResponses]; export type ShellListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/shell/list'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/shell/list"; }; export type ShellListErrors = { - /** - * Error listing shells - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error listing shells + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellListError = ShellListErrors[keyof ShellListErrors]; export type ShellListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - shells: Array; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + shells: Array; }; + }; }; export type ShellListResponse = ShellListResponses[keyof ShellListResponses]; export type ShellOpenData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/open'; + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: "/shell/open"; }; export type ShellOpenErrors = { - /** - * Error opening shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error opening shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellOpenError = ShellOpenErrors[keyof ShellOpenErrors]; export type ShellOpenResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; }; export type ShellOpenResponse = ShellOpenResponses[keyof ShellOpenResponses]; export type ShellCloseData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/close'; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: "/shell/close"; }; export type ShellCloseErrors = { - /** - * Error closing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error closing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellCloseError = ShellCloseErrors[keyof ShellCloseErrors]; export type ShellCloseResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type ShellCloseResponse = ShellCloseResponses[keyof ShellCloseResponses]; export type ShellRestartData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/restart'; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: "/shell/restart"; }; export type ShellRestartErrors = { - /** - * Error restarting shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error restarting shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellRestartError = ShellRestartErrors[keyof ShellRestartErrors]; export type ShellRestartResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellRestartResponse = ShellRestartResponses[keyof ShellRestartResponses]; +export type ShellRestartResponse = + ShellRestartResponses[keyof ShellRestartResponses]; export type ShellTerminateData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/terminate'; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: "/shell/terminate"; }; export type ShellTerminateErrors = { - /** - * Error terminating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error terminating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type ShellTerminateError = ShellTerminateErrors[keyof ShellTerminateErrors]; +export type ShellTerminateError = + ShellTerminateErrors[keyof ShellTerminateErrors]; export type ShellTerminateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: ShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: ShellDto; + }; }; -export type ShellTerminateResponse = ShellTerminateResponses[keyof ShellTerminateResponses]; +export type ShellTerminateResponse = + ShellTerminateResponses[keyof ShellTerminateResponses]; export type ShellResizeData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/resize'; + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: "/shell/resize"; }; export type ShellResizeErrors = { - /** - * Error resizing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error resizing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellResizeError = ShellResizeErrors[keyof ShellResizeErrors]; export type ShellResizeResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellResizeResponse = ShellResizeResponses[keyof ShellResizeResponses]; +export type ShellResizeResponse = + ShellResizeResponses[keyof ShellResizeResponses]; export type ShellRenameData = { - body: { - shellId: ShellId; - /** - * New name for the shell - */ - name: string; - }; - path?: never; - query?: never; - url: '/shell/rename'; + body: { + shellId: ShellId; + /** + * New name for the shell + */ + name: string; + }; + path?: never; + query?: never; + url: "/shell/rename"; }; export type ShellRenameErrors = { - /** - * Error renaming shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error renaming shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellRenameError = ShellRenameErrors[keyof ShellRenameErrors]; export type ShellRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellRenameResponse = ShellRenameResponses[keyof ShellRenameResponses]; \ No newline at end of file +export type ShellRenameResponse = + ShellRenameResponses[keyof ShellRenameResponses]; diff --git a/src/api-clients/client-rest-system/client.gen.ts b/src/api-clients/client-rest-system/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-system/client.gen.ts +++ b/src/api-clients/client-rest-system/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-system/index.ts b/src/api-clients/client-rest-system/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-system/index.ts +++ b/src/api-clients/client-rest-system/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-system/sdk.gen.ts b/src/api-clients/client-rest-system/sdk.gen.ts index d1f450e..41b133b 100644 --- a/src/api-clients/client-rest-system/sdk.gen.ts +++ b/src/api-clients/client-rest-system/sdk.gen.ts @@ -1,59 +1,94 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { SystemUpdateData, SystemUpdateResponse, SystemUpdateError, SystemHibernateData, SystemHibernateResponse, SystemHibernateError, SystemMetricsData, SystemMetricsResponse, SystemMetricsError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + SystemUpdateData, + SystemUpdateResponse, + SystemUpdateError, + SystemHibernateData, + SystemHibernateResponse, + SystemHibernateError, + SystemMetricsData, + SystemMetricsResponse, + SystemMetricsError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Update system * Update the sandbox system */ -export const systemUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/update', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const systemUpdate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SystemUpdateResponse, + SystemUpdateError, + ThrowOnError + >({ + url: "/system/update", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Hibernate system * Put the sandbox system into hibernation mode */ -export const systemHibernate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/hibernate', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const systemHibernate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SystemHibernateResponse, + SystemHibernateError, + ThrowOnError + >({ + url: "/system/hibernate", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Get system metrics * Retrieve current system metrics including CPU, memory and storage usage */ -export const systemMetrics = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/metrics', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const systemMetrics = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SystemMetricsResponse, + SystemMetricsError, + ThrowOnError + >({ + url: "/system/metrics", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-system/types.gen.ts b/src/api-clients/client-rest-system/types.gen.ts index f23b7f1..6aed933 100644 --- a/src/api-clients/client-rest-system/types.gen.ts +++ b/src/api-clients/client-rest-system/types.gen.ts @@ -1,207 +1,211 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type SystemError = { + /** + * Error code + */ + code: number; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type SystemMetricsStatus = { + cpu: { /** - * Error code + * Number of CPU cores */ - code: number; + cores: number; /** - * Error message + * Used CPU resources */ - message: string; + used: number; /** - * Additional error data + * Configured CPU resources */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type SystemMetricsStatus = { - cpu: { - /** - * Number of CPU cores - */ - cores: number; - /** - * Used CPU resources - */ - used: number; - /** - * Configured CPU resources - */ - configured: number; - }; - memory: { - /** - * Used memory in bytes - */ - used: number; - /** - * Total available memory in bytes - */ - total: number; - /** - * Configured memory limit in bytes - */ - configured: number; - }; - storage: { - /** - * Used storage in bytes - */ - used: number; - /** - * Total available storage in bytes - */ - total: number; - /** - * Configured storage limit in bytes - */ - configured: number; - }; -}; - -export type InitStatus = { + configured: number; + }; + memory: { + /** + * Used memory in bytes + */ + used: number; /** - * Status message + * Total available memory in bytes */ - message: string; + total: number; /** - * Whether the status represents an error + * Configured memory limit in bytes */ - isError?: boolean | null; + configured: number; + }; + storage: { /** - * Current progress (0-100) + * Used storage in bytes */ - progress: number; + used: number; /** - * Next progress target (0-100) + * Total available storage in bytes */ - nextProgress: number; + total: number; /** - * Standard output from the initialization process + * Configured storage limit in bytes */ - stdout?: string | null; + configured: number; + }; +}; + +export type InitStatus = { + /** + * Status message + */ + message: string; + /** + * Whether the status represents an error + */ + isError?: boolean | null; + /** + * Current progress (0-100) + */ + progress: number; + /** + * Next progress target (0-100) + */ + nextProgress: number; + /** + * Standard output from the initialization process + */ + stdout?: string | null; }; export type SystemUpdateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/update'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/system/update"; }; export type SystemUpdateErrors = { - /** - * Error updating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error updating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; export type SystemUpdateError = SystemUpdateErrors[keyof SystemUpdateErrors]; export type SystemUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; }; + }; }; -export type SystemUpdateResponse = SystemUpdateResponses[keyof SystemUpdateResponses]; +export type SystemUpdateResponse = + SystemUpdateResponses[keyof SystemUpdateResponses]; export type SystemHibernateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/hibernate'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/system/hibernate"; }; export type SystemHibernateErrors = { - /** - * Error hibernating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error hibernating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; -export type SystemHibernateError = SystemHibernateErrors[keyof SystemHibernateErrors]; +export type SystemHibernateError = + SystemHibernateErrors[keyof SystemHibernateErrors]; export type SystemHibernateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type SystemHibernateResponse = SystemHibernateResponses[keyof SystemHibernateResponses]; +export type SystemHibernateResponse = + SystemHibernateResponses[keyof SystemHibernateResponses]; export type SystemMetricsData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/metrics'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/system/metrics"; }; export type SystemMetricsErrors = { - /** - * Error retrieving system metrics - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error retrieving system metrics + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; export type SystemMetricsError = SystemMetricsErrors[keyof SystemMetricsErrors]; export type SystemMetricsResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SystemMetricsStatus; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SystemMetricsStatus; + }; }; -export type SystemMetricsResponse = SystemMetricsResponses[keyof SystemMetricsResponses]; \ No newline at end of file +export type SystemMetricsResponse = + SystemMetricsResponses[keyof SystemMetricsResponses]; diff --git a/src/api-clients/client-rest-task/client.gen.ts b/src/api-clients/client-rest-task/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client-rest-task/client.gen.ts +++ b/src/api-clients/client-rest-task/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client-rest-task/index.ts b/src/api-clients/client-rest-task/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client-rest-task/index.ts +++ b/src/api-clients/client-rest-task/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client-rest-task/sdk.gen.ts b/src/api-clients/client-rest-task/sdk.gen.ts index 133e8f8..d21f21f 100644 --- a/src/api-clients/client-rest-task/sdk.gen.ts +++ b/src/api-clients/client-rest-task/sdk.gen.ts @@ -1,149 +1,238 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { TaskListData, TaskListResponse, TaskListError, TaskRunData, TaskRunResponse, TaskRunError, TaskRunCommandData, TaskRunCommandResponse, TaskRunCommandError, TaskStopData, TaskStopResponse, TaskStopError, TaskCreateData, TaskCreateResponse, TaskCreateError, TaskUpdateData, TaskUpdateResponse, TaskUpdateError, TaskSaveToConfigData, TaskSaveToConfigResponse, TaskSaveToConfigError, TaskGenerateConfigData, TaskGenerateConfigResponse, TaskGenerateConfigError, TaskCreateSetupTasksData, TaskCreateSetupTasksResponse, TaskCreateSetupTasksError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + TaskListData, + TaskListResponse, + TaskListError, + TaskRunData, + TaskRunResponse, + TaskRunError, + TaskRunCommandData, + TaskRunCommandResponse, + TaskRunCommandError, + TaskStopData, + TaskStopResponse, + TaskStopError, + TaskCreateData, + TaskCreateResponse, + TaskCreateError, + TaskUpdateData, + TaskUpdateResponse, + TaskUpdateError, + TaskSaveToConfigData, + TaskSaveToConfigResponse, + TaskSaveToConfigError, + TaskGenerateConfigData, + TaskGenerateConfigResponse, + TaskGenerateConfigError, + TaskCreateSetupTasksData, + TaskCreateSetupTasksResponse, + TaskCreateSetupTasksError, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * List tasks * Retrieve a list of all configured tasks */ -export const taskList = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/list', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskList = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskListResponse, + TaskListError, + ThrowOnError + >({ + url: "/task/list", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Run task * Start execution of a task by ID */ -export const taskRun = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/run', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskRun = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskRunResponse, + TaskRunError, + ThrowOnError + >({ + url: "/task/run", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Run command * Run a shell command directly, optionally saving it as a task */ -export const taskRunCommand = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/runCommand', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskRunCommand = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskRunCommandResponse, + TaskRunCommandError, + ThrowOnError + >({ + url: "/task/runCommand", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Stop task * Stop execution of a running task */ -export const taskStop = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/stop', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskStop = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskStopResponse, + TaskStopError, + ThrowOnError + >({ + url: "/task/stop", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Create task * Create a new task configuration */ -export const taskCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/create', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskCreate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskCreateResponse, + TaskCreateError, + ThrowOnError + >({ + url: "/task/create", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Update task * Update an existing task configuration */ -export const taskUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/update', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskUpdate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskUpdateResponse, + TaskUpdateError, + ThrowOnError + >({ + url: "/task/update", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Save task to config * Save a runtime task to the configuration file */ -export const taskSaveToConfig = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/saveToConfig', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskSaveToConfig = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskSaveToConfigResponse, + TaskSaveToConfigError, + ThrowOnError + >({ + url: "/task/saveToConfig", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Generate task config * Generate a configuration file from current tasks */ -export const taskGenerateConfig = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/generateConfig', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const taskGenerateConfig = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskGenerateConfigResponse, + TaskGenerateConfigError, + ThrowOnError + >({ + url: "/task/generateConfig", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** * Create setup tasks * Create tasks that run during sandbox setup */ -export const taskCreateSetupTasks = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/createSetupTasks', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const taskCreateSetupTasks = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TaskCreateSetupTasksResponse, + TaskCreateSetupTasksError, + ThrowOnError + >({ + url: "/task/createSetupTasks", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client-rest-task/types.gen.ts b/src/api-clients/client-rest-task/types.gen.ts index 012bd28..984f610 100644 --- a/src/api-clients/client-rest-task/types.gen.ts +++ b/src/api-clients/client-rest-task/types.gen.ts @@ -1,507 +1,519 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type CommonError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message?: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type TaskError = { - /** - * CONFIG_FILE_ALREADY_EXISTS error code - */ - code: 600; - /** - * Error message - */ - message: string; -} | { - /** - * TASK_NOT_FOUND error code - */ - code: 601; - /** - * Error message - */ - message: string; -} | { - /** - * COMMAND_ALREADY_CONFIGURED error code - */ - code: 602; - /** - * Error message - */ - message: string; -} | ({ - code?: 'CommonError'; -} & CommonError); + /** + * Error code + */ + code: number; + /** + * Error message + */ + message?: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type TaskError = + | { + /** + * CONFIG_FILE_ALREADY_EXISTS error code + */ + code: 600; + /** + * Error message + */ + message: string; + } + | { + /** + * TASK_NOT_FOUND error code + */ + code: 601; + /** + * Error message + */ + message: string; + } + | { + /** + * COMMAND_ALREADY_CONFIGURED error code + */ + code: 602; + /** + * Error message + */ + message: string; + } + | ({ + code?: "CommonError"; + } & CommonError); export type TaskDefinitionDto = { + /** + * Name of the task + */ + name: string; + /** + * Command to run for the task + */ + command: string; + /** + * Whether the task should run when the sandbox starts + */ + runAtStart?: boolean | null; + preview?: { /** - * Name of the task + * Port to preview from this task */ - name: string; + port?: number | null; /** - * Command to run for the task + * Type of PR link to use */ - command: string; - /** - * Whether the task should run when the sandbox starts - */ - runAtStart?: boolean | null; - preview?: { - /** - * Port to preview from this task - */ - port?: number | null; - /** - * Type of PR link to use - */ - 'pr-link'?: 'direct' | 'redirect' | 'devtool'; - } | null; + "pr-link"?: "direct" | "redirect" | "devtool"; + } | null; }; export type CommandShellDto = { - /** - * ID of the shell command - */ - id: string; - /** - * Command being executed - */ - command: string; - /** - * Current status of the shell command - */ - status: 'initializing' | 'running' | 'stopped' | 'error'; - /** - * Current output of the command - */ - output: string; + /** + * ID of the shell command + */ + id: string; + /** + * Command being executed + */ + command: string; + /** + * Current status of the shell command + */ + status: "initializing" | "running" | "stopped" | "error"; + /** + * Current output of the command + */ + output: string; }; export type Port = { - /** - * Port number - */ - port: number; - /** - * Hostname the port is bound to - */ - hostname: string; - /** - * Current status of the port - */ - status: 'open' | 'closed'; - /** - * ID of the task that opened this port - */ - taskId?: string | null; + /** + * Port number + */ + port: number; + /** + * Hostname the port is bound to + */ + hostname: string; + /** + * Current status of the port + */ + status: "open" | "closed"; + /** + * ID of the task that opened this port + */ + taskId?: string | null; }; export type TaskDto = TaskDefinitionDto & { - /** - * Unique ID of the task - */ - id: string; - /** - * Whether this task is unconfigured (not saved in config) - */ - unconfigured?: boolean | null; - shell: CommandShellDto | null; - /** - * Ports opened by this task - */ - ports: Array; + /** + * Unique ID of the task + */ + id: string; + /** + * Whether this task is unconfigured (not saved in config) + */ + unconfigured?: boolean | null; + shell: CommandShellDto | null; + /** + * Ports opened by this task + */ + ports: Array; }; export type TaskListDto = { - /** - * Map of task IDs to task objects - */ - tasks: { - [key: string]: TaskDto; - }; - /** - * Tasks that run during sandbox setup - */ - setupTasks: Array; - /** - * Validation errors in the task configuration - */ - validationErrors: Array; + /** + * Map of task IDs to task objects + */ + tasks: { + [key: string]: TaskDto; + }; + /** + * Tasks that run during sandbox setup + */ + setupTasks: Array; + /** + * Validation errors in the task configuration + */ + validationErrors: Array; }; export type TaskListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/task/list'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/task/list"; }; export type TaskListErrors = { - /** - * Error retrieving task list - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving task list + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type TaskListError = TaskListErrors[keyof TaskListErrors]; export type TaskListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; }; export type TaskListResponse = TaskListResponses[keyof TaskListResponses]; export type TaskRunData = { - body: { - /** - * ID of the task to run - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/run'; + body: { + /** + * ID of the task to run + */ + taskId: string; + }; + path?: never; + query?: never; + url: "/task/run"; }; export type TaskRunErrors = { - /** - * Error running task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error running task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskRunError = TaskRunErrors[keyof TaskRunErrors]; export type TaskRunResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; export type TaskRunResponse = TaskRunResponses[keyof TaskRunResponses]; export type TaskRunCommandData = { - body: { - /** - * Command to run - */ - command: string; - /** - * Optional name for the task - */ - name?: string | null; - /** - * Whether to save this command as a task in the config - */ - saveToConfig?: boolean | null; - }; - path?: never; - query?: never; - url: '/task/runCommand'; + body: { + /** + * Command to run + */ + command: string; + /** + * Optional name for the task + */ + name?: string | null; + /** + * Whether to save this command as a task in the config + */ + saveToConfig?: boolean | null; + }; + path?: never; + query?: never; + url: "/task/runCommand"; }; export type TaskRunCommandErrors = { - /** - * Error running command - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error running command + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskRunCommandError = TaskRunCommandErrors[keyof TaskRunCommandErrors]; +export type TaskRunCommandError = + TaskRunCommandErrors[keyof TaskRunCommandErrors]; export type TaskRunCommandResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type TaskRunCommandResponse = TaskRunCommandResponses[keyof TaskRunCommandResponses]; +export type TaskRunCommandResponse = + TaskRunCommandResponses[keyof TaskRunCommandResponses]; export type TaskStopData = { - body: { - /** - * ID of the task to stop - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/stop'; + body: { + /** + * ID of the task to stop + */ + taskId: string; + }; + path?: never; + query?: never; + url: "/task/stop"; }; export type TaskStopErrors = { - /** - * Error stopping task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error stopping task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskStopError = TaskStopErrors[keyof TaskStopErrors]; export type TaskStopResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto | unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto | unknown; + }; }; export type TaskStopResponse = TaskStopResponses[keyof TaskStopResponses]; export type TaskCreateData = { - body: { - taskFields: TaskDefinitionDto; - /** - * Whether to start the task immediately after creation - */ - startTask?: boolean | null; - }; - path?: never; - query?: never; - url: '/task/create'; + body: { + taskFields: TaskDefinitionDto; + /** + * Whether to start the task immediately after creation + */ + startTask?: boolean | null; + }; + path?: never; + query?: never; + url: "/task/create"; }; export type TaskCreateErrors = { - /** - * Error creating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error creating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskCreateError = TaskCreateErrors[keyof TaskCreateErrors]; export type TaskCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; }; export type TaskCreateResponse = TaskCreateResponses[keyof TaskCreateResponses]; export type TaskUpdateData = { - body: { + body: { + /** + * ID of the task to update + */ + taskId: string; + /** + * Fields to update in the task + */ + taskFields: { + /** + * Name of the task + */ + name?: string | null; + /** + * Command to run + */ + command?: string | null; + /** + * Whether to run the task at sandbox start + */ + runAtStart?: boolean | null; + preview?: { /** - * ID of the task to update + * Port to use for previewing the task */ - taskId: string; + port?: number | null; /** - * Fields to update in the task + * Type of PR link to use */ - taskFields: { - /** - * Name of the task - */ - name?: string | null; - /** - * Command to run - */ - command?: string | null; - /** - * Whether to run the task at sandbox start - */ - runAtStart?: boolean | null; - preview?: { - /** - * Port to use for previewing the task - */ - port?: number | null; - /** - * Type of PR link to use - */ - 'pr-link'?: 'direct' | 'redirect' | 'devtool'; - } | null; - }; + "pr-link"?: "direct" | "redirect" | "devtool"; + } | null; }; - path?: never; - query?: never; - url: '/task/update'; + }; + path?: never; + query?: never; + url: "/task/update"; }; export type TaskUpdateErrors = { - /** - * Error updating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error updating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskUpdateError = TaskUpdateErrors[keyof TaskUpdateErrors]; export type TaskUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; export type TaskUpdateResponse = TaskUpdateResponses[keyof TaskUpdateResponses]; export type TaskSaveToConfigData = { - body: { - /** - * ID of the task to save to config - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/saveToConfig'; + body: { + /** + * ID of the task to save to config + */ + taskId: string; + }; + path?: never; + query?: never; + url: "/task/saveToConfig"; }; export type TaskSaveToConfigErrors = { - /** - * Error saving task to config - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error saving task to config + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskSaveToConfigError = TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; +export type TaskSaveToConfigError = + TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; export type TaskSaveToConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type TaskSaveToConfigResponse = TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; +export type TaskSaveToConfigResponse = + TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; export type TaskGenerateConfigData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/task/generateConfig'; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: "/task/generateConfig"; }; export type TaskGenerateConfigErrors = { - /** - * Error generating config - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error generating config + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskGenerateConfigError = TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; +export type TaskGenerateConfigError = + TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; export type TaskGenerateConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type TaskGenerateConfigResponse = TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; +export type TaskGenerateConfigResponse = + TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; export type TaskCreateSetupTasksData = { - body: { - /** - * Setup tasks to create - */ - tasks: Array; - }; - path?: never; - query?: never; - url: '/task/createSetupTasks'; + body: { + /** + * Setup tasks to create + */ + tasks: Array; + }; + path?: never; + query?: never; + url: "/task/createSetupTasks"; }; export type TaskCreateSetupTasksErrors = { - /** - * Error creating setup tasks - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error creating setup tasks + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskCreateSetupTasksError = TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; +export type TaskCreateSetupTasksError = + TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; export type TaskCreateSetupTasksResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type TaskCreateSetupTasksResponse = TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; \ No newline at end of file +export type TaskCreateSetupTasksResponse = + TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; diff --git a/src/api-clients/client/client.gen.ts b/src/api-clients/client/client.gen.ts index 1822a95..853e4ff 100644 --- a/src/api-clients/client/client.gen.ts +++ b/src/api-clients/client/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { createClient, createConfig } from "@hey-api/client-fetch"; -export const client = createClient(createConfig()); \ No newline at end of file +export const client = createClient(createConfig()); diff --git a/src/api-clients/client/index.ts b/src/api-clients/client/index.ts index e64537d..da87079 100644 --- a/src/api-clients/client/index.ts +++ b/src/api-clients/client/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file +export * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 195250e..8d31f86 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,26 +1,94 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, TemplatesCreateData, TemplatesCreateResponse, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { + Options as ClientOptions, + TDataShape, + Client, +} from "@hey-api/client-fetch"; +import type { + MetaInfoData, + MetaInfoResponse, + WorkspaceCreateData, + WorkspaceCreateResponse2, + TokenCreateData, + TokenCreateResponse2, + TokenUpdateData, + TokenUpdateResponse2, + SandboxListData, + SandboxListResponse2, + SandboxCreateData, + SandboxCreateResponse2, + SandboxGetData, + SandboxGetResponse2, + SandboxForkData, + SandboxForkResponse2, + PreviewTokenRevokeAllData, + PreviewTokenRevokeAllResponse2, + PreviewTokenListData, + PreviewTokenListResponse2, + PreviewTokenCreateData, + PreviewTokenCreateResponse2, + PreviewTokenUpdateData, + PreviewTokenUpdateResponse2, + TemplatesCreateData, + TemplatesCreateResponse, + VmAssignTagAliasData, + VmAssignTagAliasResponse2, + VmListClustersData, + VmListClustersResponse2, + VmListRunningVmsData, + VmListRunningVmsResponse2, + VmCreateTagData, + VmCreateTagResponse2, + VmHibernateData, + VmHibernateResponse2, + VmUpdateHibernationTimeoutData, + VmUpdateHibernationTimeoutResponse2, + VmCreateSessionData, + VmCreateSessionResponse2, + VmShutdownData, + VmShutdownResponse2, + VmUpdateSpecsData, + VmUpdateSpecsResponse2, + VmStartData, + VmStartResponse2, + VmUpdateSpecs2Data, + VmUpdateSpecs2Response, + PreviewHostListData, + PreviewHostListResponse2, + PreviewHostCreateData, + PreviewHostCreateResponse, + PreviewHostUpdateData, + PreviewHostUpdateResponse, +} from "./types.gen"; +import { client as _heyApiClient } from "./client.gen"; -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Metadata about the API */ -export const metaInfo = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ - url: '/meta/info', - ...options - }); +export const metaInfo = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + MetaInfoResponse, + unknown, + ThrowOnError + >({ + url: "/meta/info", + ...options, + }); }; /** @@ -28,21 +96,27 @@ export const metaInfo = (options?: Options * Create a new, empty, workspace in the current organization * */ -export const workspaceCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/org/workspace', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const workspaceCreate = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + WorkspaceCreateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/org/workspace", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -50,21 +124,27 @@ export const workspaceCreate = (options?: * Create a new API token for a workspace that is part of the current organization. * */ -export const tokenCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/org/workspace/{team_id}/tokens', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const tokenCreate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + TokenCreateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/org/workspace/{team_id}/tokens", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -72,21 +152,27 @@ export const tokenCreate = (options: Optio * Update an API token for a workspace that is part of the current organization. * */ -export const tokenUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).patch({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/org/workspace/{team_id}/tokens/{token_id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const tokenUpdate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).patch< + TokenUpdateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/org/workspace/{team_id}/tokens/{token_id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -95,17 +181,23 @@ export const tokenUpdate = (options: Optio * Results are limited to a maximum of 50 sandboxes per request. * */ -export const sandboxList = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox', - ...options - }); +export const sandboxList = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + SandboxListResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox", + ...options, + }); }; /** @@ -113,21 +205,27 @@ export const sandboxList = (options?: Opti * Create a new sandbox in the current workspace with file contents * */ -export const sandboxCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const sandboxCreate = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + SandboxCreateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -135,17 +233,23 @@ export const sandboxCreate = (options?: Op * Retrieve a sandbox by its ID * */ -export const sandboxGet = (options: Options) => { - return (options.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}', - ...options - }); +export const sandboxGet = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + SandboxGetResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}", + ...options, + }); }; /** @@ -153,21 +257,27 @@ export const sandboxGet = (options: Option * Fork an existing sandbox to the current workspace * */ -export const sandboxFork = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}/fork', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const sandboxFork = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + SandboxForkResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}/fork", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -175,17 +285,23 @@ export const sandboxFork = (options: Optio * Immediately expires all active preview tokens associated with this sandbox * */ -export const previewTokenRevokeAll = (options: Options) => { - return (options.client ?? _heyApiClient).delete({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}/tokens', - ...options - }); +export const previewTokenRevokeAll = ( + options: Options +) => { + return (options.client ?? _heyApiClient).delete< + PreviewTokenRevokeAllResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}/tokens", + ...options, + }); }; /** @@ -193,17 +309,23 @@ export const previewTokenRevokeAll = (opti * List information about the preview tokens associated with the current sandbox * */ -export const previewTokenList = (options: Options) => { - return (options.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}/tokens', - ...options - }); +export const previewTokenList = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + PreviewTokenListResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}/tokens", + ...options, + }); }; /** @@ -211,21 +333,27 @@ export const previewTokenList = (options: * Create a new Preview token that allow access to a private sandbox * */ -export const previewTokenCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}/tokens', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const previewTokenCreate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + PreviewTokenCreateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}/tokens", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -233,21 +361,27 @@ export const previewTokenCreate = (options * Update a Preview token that allow access to a private sandbox * */ -export const previewTokenUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).patch({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/sandbox/{id}/tokens/{token_id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const previewTokenUpdate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).patch< + PreviewTokenUpdateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/sandbox/{id}/tokens/{token_id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -255,21 +389,27 @@ export const previewTokenUpdate = (options * Create a new template in the current workspace (creates 3 sandboxes and tags them) * */ -export const templatesCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/templates', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const templatesCreate = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + TemplatesCreateResponse, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/templates", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -277,21 +417,27 @@ export const templatesCreate = (options?: * Assign a tag alias to a VM tag. * */ -export const vmAssignTagAlias = (options: Options) => { - return (options.client ?? _heyApiClient).put({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/alias/{namespace}/{alias}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmAssignTagAlias = ( + options: Options +) => { + return (options.client ?? _heyApiClient).put< + VmAssignTagAliasResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/alias/{namespace}/{alias}", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -299,17 +445,23 @@ export const vmAssignTagAlias = (options: * List all available clusters. * */ -export const vmListClusters = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/clusters', - ...options - }); +export const vmListClusters = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + VmListClustersResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/clusters", + ...options, + }); }; /** @@ -317,17 +469,23 @@ export const vmListClusters = (options?: O * List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date. * */ -export const vmListRunningVms = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/running', - ...options - }); +export const vmListRunningVms = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + VmListRunningVmsResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/running", + ...options, + }); }; /** @@ -335,21 +493,27 @@ export const vmListRunningVms = (options?: * Creates a new tag for a VM. * */ -export const vmCreateTag = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/tag', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmCreateTag = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + VmCreateTagResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/tag", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -363,21 +527,27 @@ export const vmCreateTag = (options?: Opti * minimal latency. * */ -export const vmHibernate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/hibernate', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmHibernate = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + VmHibernateResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/hibernate", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -388,21 +558,29 @@ export const vmHibernate = (options: Optio * The new timeout must be greater than 0 and less than or equal to 86400 seconds (24 hours). * */ -export const vmUpdateHibernationTimeout = (options: Options) => { - return (options.client ?? _heyApiClient).put({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/hibernation_timeout', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmUpdateHibernationTimeout = < + ThrowOnError extends boolean = false +>( + options: Options +) => { + return (options.client ?? _heyApiClient).put< + VmUpdateHibernationTimeoutResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/hibernation_timeout", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -416,21 +594,27 @@ export const vmUpdateHibernationTimeout = * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. * */ -export const vmCreateSession = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/sessions', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmCreateSession = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + VmCreateSessionResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/sessions", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -443,21 +627,27 @@ export const vmCreateSession = (options: O * Shutdown VMs require additional time to start up. * */ -export const vmShutdown = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/shutdown', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmShutdown = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + VmShutdownResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/shutdown", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -468,21 +658,27 @@ export const vmShutdown = (options: Option * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs = (options: Options) => { - return (options.client ?? _heyApiClient).put({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/specs', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmUpdateSpecs = ( + options: Options +) => { + return (options.client ?? _heyApiClient).put< + VmUpdateSpecsResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/specs", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -497,21 +693,27 @@ export const vmUpdateSpecs = (options: Opt * This endpoint is subject to special rate limits related to concurrent VM usage. * */ -export const vmStart = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/start', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmStart = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + VmStartResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/start", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -522,21 +724,27 @@ export const vmStart = (options: Options(options: Options) => { - return (options.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/vm/{id}/update_specs', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const vmUpdateSpecs2 = ( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + VmUpdateSpecs2Response, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/vm/{id}/update_specs", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -544,17 +752,23 @@ export const vmUpdateSpecs2 = (options: Op * List all trusted preview hosts for the current team * */ -export const previewHostList = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/workspace/preview_hosts', - ...options - }); +export const previewHostList = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + PreviewHostListResponse2, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/workspace/preview_hosts", + ...options, + }); }; /** @@ -562,21 +776,27 @@ export const previewHostList = (options?: * Add one or more trusted domains that are allowed to access sandbox previews for this workspace. * */ -export const previewHostCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/workspace/preview_hosts', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); +export const previewHostCreate = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).post< + PreviewHostCreateResponse, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/workspace/preview_hosts", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); }; /** @@ -584,19 +804,25 @@ export const previewHostCreate = (options? * Replace the list of trusted domains that are allowed to access sandbox previews for this workspace. * */ -export const previewHostUpdate = (options?: Options) => { - return (options?.client ?? _heyApiClient).put({ - security: [ - { - scheme: 'bearer', - type: 'http' - } - ], - url: '/workspace/preview_hosts', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file +export const previewHostUpdate = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).put< + PreviewHostUpdateResponse, + unknown, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/workspace/preview_hosts", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); +}; diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 126232e..88e914f 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -1,1362 +1,1465 @@ // This file is auto-generated by @hey-api/openapi-ts export type Response = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; }; export type PreviewTokenListResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - sandbox_id: string; - tokens: Array; - }; + data?: { + sandbox_id: string; + tokens: Array; + }; }; export type VmUpdateHibernationTimeoutRequest = { - /** - * The new hibernation timeout in seconds. - * - * Must be greater than 0 and less than or equal to 86400 (24 hours). - * - */ - hibernation_timeout_seconds: number; + /** + * The new hibernation timeout in seconds. + * + * Must be greater than 0 and less than or equal to 86400 (24 hours). + * + */ + hibernation_timeout_seconds: number; }; export type VmAssignTagAliasResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - alias: string; - namespace: string; - tag_alias_id: string; - tag_id: string; - team_id: string; - }; + data?: { + alias: string; + namespace: string; + tag_alias_id: string; + tag_id: string; + team_id: string; + }; }; export type TemplateCreateRequest = { - /** - * Template description. Maximum 255 characters. Defaults to description of original sandbox. - */ - description?: string; - /** - * Short ID of the sandbox to fork. - */ - forkOf: string; - /** - * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. - */ - tags?: Array; - /** - * Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked). - */ - title?: string; + /** + * Template description. Maximum 255 characters. Defaults to description of original sandbox. + */ + description?: string; + /** + * Short ID of the sandbox to fork. + */ + forkOf: string; + /** + * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. + */ + tags?: Array; + /** + * Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + */ + title?: string; }; export type PreviewToken = { - expires_at: string | null; - last_used_at: string | null; - token_id: string; - token_prefix: string; + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; }; export type VmShutdownResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type PreviewTokenRevokeAllResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type Sandbox = { - created_at: string; - description?: string | null; - id: string; - is_frozen: boolean; - privacy: number; - tags: Array; - title?: string | null; - updated_at: string; -}; - -export type _Error = string | { - [key: string]: unknown; -}; + created_at: string; + description?: string | null; + id: string; + is_frozen: boolean; + privacy: number; + tags: Array; + title?: string | null; + updated_at: string; +}; + +export type _Error = + | string + | { + [key: string]: unknown; + }; export type VmHibernateRequest = { - [key: string]: unknown; + [key: string]: unknown; }; export type PreviewTokenCreateRequest = { - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; }; export type VmCreateTagResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - tag_id: string; - }; + data?: { + tag_id: string; + }; }; export type VmListRunningVmsResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - concurrent_vm_count: number; - concurrent_vm_limit: number; - vms: Array<{ - credit_basis?: string; - id?: string; - last_active_at?: string; - session_started_at?: string; - specs?: { - cpu?: number; - memory?: number; - storage?: number; - }; - }>; - }; + data?: { + concurrent_vm_count: number; + concurrent_vm_limit: number; + vms: Array<{ + credit_basis?: string; + id?: string; + last_active_at?: string; + session_started_at?: string; + specs?: { + cpu?: number; + memory?: number; + storage?: number; + }; + }>; + }; }; export type SandboxGetResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: Sandbox; + data?: Sandbox; }; export type SandboxForkRequest = { + /** + * Sandbox description. Maximum 255 characters. Defaults to description of original sandbox. + */ + description?: string; + /** + * Sandbox frozen status. When true, edits to the sandbox are restricted. Defaults to frozen status of the original sandbox. + */ + is_frozen?: boolean; + /** + * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. + */ + path?: string; + /** + * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. + */ + privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; + /** + * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. + */ + start_options?: { /** - * Sandbox description. Maximum 255 characters. Defaults to description of original sandbox. - */ - description?: string; - /** - * Sandbox frozen status. When true, edits to the sandbox are restricted. Defaults to frozen status of the original sandbox. - */ - is_frozen?: boolean; - /** - * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. - */ - path?: string; - /** - * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. - */ - privacy?: number; - /** - * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + * Configuration for when the VM should automatically wake up from hibernation */ - private_preview?: boolean; + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; /** - * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * */ - start_options?: { - /** - * Configuration for when the VM should automatically wake up from hibernation - */ - automatic_wakeup_config?: { - /** - * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) - */ - http?: boolean; - /** - * Whether the VM should automatically wake up on WebSocket connections - */ - websocket?: boolean; - }; - /** - * The time in seconds after which the VM will hibernate due to inactivity. - * Must be a positive integer between 1 and 86400 (24 hours). - * Defaults to 300 seconds (5 minutes) if not specified. - * - */ - hibernation_timeout_seconds?: number; - /** - * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). - */ - ipcountry?: string; - /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; - }; + hibernation_timeout_seconds?: number; /** - * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). */ - tags?: Array; + ipcountry?: string; /** - * Sandbox title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * */ - title?: string; + tier?: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; + }; + /** + * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. + */ + tags?: Array; + /** + * Sandbox title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + */ + title?: string; }; export type SandboxListResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - pagination: { - current_page: number; - /** - * The number of the next page, if any. If `null`, the current page is the last page of records. - */ - next_page: number | null; - total_records: number; - }; - sandboxes: Array; + data?: { + pagination: { + current_page: number; + /** + * The number of the next page, if any. If `null`, the current page is the last page of records. + */ + next_page: number | null; + total_records: number; }; + sandboxes: Array; + }; }; export type MetaInformation = { - /** - * Meta information about the CodeSandbox API - */ - api: { - latest_version: string; - name: string; + /** + * Meta information about the CodeSandbox API + */ + api: { + latest_version: string; + name: string; + }; + /** + * Meta information about the current authentication context + */ + auth?: { + scopes: Array; + team: string | null; + version: string; + }; + /** + * Current workspace rate limits + */ + rate_limits?: { + concurrent_vms: { + limit?: number; + remaining?: number; }; - /** - * Meta information about the current authentication context - */ - auth?: { - scopes: Array; - team: string | null; - version: string; + requests_hourly: { + limit?: number; + remaining?: number; + reset?: number; }; - /** - * Current workspace rate limits - */ - rate_limits?: { - concurrent_vms: { - limit?: number; - remaining?: number; - }; - requests_hourly: { - limit?: number; - remaining?: number; - reset?: number; - }; - sandboxes_hourly: { - limit?: number; - remaining?: number; - reset?: number; - }; + sandboxes_hourly: { + limit?: number; + remaining?: number; + reset?: number; }; + }; }; /** * Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value. */ export type TokenUpdateRequest = { - /** - * API Version to use, formatted as YYYY-MM-DD - */ - default_version?: string | null; - /** - * Description of this token, for instance where it will be used. - */ - description?: string | null; - /** - * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. - */ - expires_at?: string | null; - /** - * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. - */ - scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; + /** + * API Version to use, formatted as YYYY-MM-DD + */ + default_version?: string | null; + /** + * Description of this token, for instance where it will be used. + */ + description?: string | null; + /** + * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. + */ + expires_at?: string | null; + /** + * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + */ + scopes?: Array< + "sandbox_create" | "sandbox_edit_code" | "sandbox_read" | "vm_manage" + >; }; /** * Assign a tag alias to a VM */ export type VmAssignTagAliasRequest = { - tag_id: string; + tag_id: string; }; export type VmHibernateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type PreviewTokenUpdateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - sandbox_id: string; - token: PreviewToken; - }; + data?: { + sandbox_id: string; + token: PreviewToken; + }; }; export type SandboxCreateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - alias: string; - id: string; - title: string | null; - }; + data?: { + alias: string; + id: string; + title: string | null; + }; }; export type VmStartRequest = { - /** - * Configuration for when the VM should automatically wake up from hibernation - */ - automatic_wakeup_config?: { - /** - * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) - */ - http?: boolean; - /** - * Whether the VM should automatically wake up on WebSocket connections - */ - websocket?: boolean; - }; - /** - * The time in seconds after which the VM will hibernate due to inactivity. - * Must be a positive integer between 1 and 86400 (24 hours). - * Defaults to 300 seconds (5 minutes) if not specified. - * - */ - hibernation_timeout_seconds?: number; - /** - * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). - */ - ipcountry?: string; - /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; + /** + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * + */ + hibernation_timeout_seconds?: number; + /** + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + */ + ipcountry?: string; + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier?: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; }; export type PreviewTokenUpdateRequest = { - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; }; export type PreviewHostListResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - preview_hosts: Array<{ - host: string; - inserted_at: string; - }>; - }; + data?: { + preview_hosts: Array<{ + host: string; + inserted_at: string; + }>; + }; }; export type VmShutdownRequest = { - [key: string]: unknown; + [key: string]: unknown; }; export type VmUpdateSpecsRequest = { - /** - * Determines which specs to update the VM with. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; + /** + * Determines which specs to update the VM with. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; }; export type WorkspaceCreateRequest = { - /** - * Name for the new workspace. Maximum length 64 characters. - */ - name: string; + /** + * Name for the new workspace. Maximum length 64 characters. + */ + name: string; }; /** * Create a tag for a list of VM IDs */ export type VmCreateTagRequest = { - vm_ids: Array; + vm_ids: Array; }; export type PreviewHostRequest = { - hosts: Array; + hosts: Array; }; export type VmStartResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - bootup_type: string; - cluster: string; - id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; - }; + data?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + }; }; export type VmUpdateSpecsResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - id: string; - tier: string; - }; + data?: { + id: string; + tier: string; + }; }; export type SandboxCreateRequest = { - /** - * Optional text description of the sandbox. Defaults to no description. - */ - description?: string; - /** - * Filename of the entrypoint of the sandbox. - */ - entry?: string; - /** - * Array of strings with external resources to load. - */ - external_resources?: Array; - /** - * Map of `path => file` where each file is a map. - */ - files: { - [key: string]: { - /** - * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. - */ - binary_content?: string; - /** - * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. - */ - code?: string; - /** - * Whether the file contains binary contents. - */ - is_binary?: boolean; - }; + /** + * Optional text description of the sandbox. Defaults to no description. + */ + description?: string; + /** + * Filename of the entrypoint of the sandbox. + */ + entry?: string; + /** + * Array of strings with external resources to load. + */ + external_resources?: Array; + /** + * Map of `path => file` where each file is a map. + */ + files: { + [key: string]: { + /** + * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. + */ + binary_content?: string; + /** + * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. + */ + code?: string; + /** + * Whether the file contains binary contents. + */ + is_binary?: boolean; }; - /** - * Whether changes to the sandbox are disallowed. Defaults to `false`. - */ - is_frozen?: boolean; - /** - * Map of dependencies and their version specifications. - */ - npm_dependencies?: { - [key: string]: string; - }; - /** - * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. - */ - path?: string; - /** - * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. - */ - privacy?: number; - /** - * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible - */ - private_preview?: boolean; - /** - * Runtime to use for the sandbox. Defaults to `"browser"`. - */ - runtime?: 'browser' | 'vm'; - /** - * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. - */ - tags?: Array; - /** - * Name of the template from which the sandbox is derived (for example, `"static"`). - */ - template?: string; - /** - * Sandbox title. Maximum 255 characters. Defaults to no title. - */ - title?: string; + }; + /** + * Whether changes to the sandbox are disallowed. Defaults to `false`. + */ + is_frozen?: boolean; + /** + * Map of dependencies and their version specifications. + */ + npm_dependencies?: { + [key: string]: string; + }; + /** + * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. + */ + path?: string; + /** + * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. + */ + privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; + /** + * Runtime to use for the sandbox. Defaults to `"browser"`. + */ + runtime?: "browser" | "vm"; + /** + * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. + */ + tags?: Array; + /** + * Name of the template from which the sandbox is derived (for example, `"static"`). + */ + template?: string; + /** + * Sandbox title. Maximum 255 characters. Defaults to no title. + */ + title?: string; }; export type VmListClustersResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - clusters: Array<{ - host: string; - slug: string; - }>; - }; + data?: { + clusters: Array<{ + host: string; + slug: string; + }>; + }; }; export type TokenUpdateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - description: string | null; - expires_at?: string | null; - scopes: Array; - team_id: string; - token_id: string; - }; + data?: { + description: string | null; + expires_at?: string | null; + scopes: Array; + team_id: string; + token_id: string; + }; }; export type TokenCreateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - description: string | null; - expires_at: string | null; - scopes: Array; - team_id: string; - token: string; - token_id: string; - }; + data?: { + description: string | null; + expires_at: string | null; + scopes: Array; + team_id: string; + token: string; + token_id: string; + }; }; export type TemplateCreateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - sandboxes: Array<{ - cluster: string; - id: string; - }>; - tag: string; - }; + data?: { + sandboxes: Array<{ + cluster: string; + id: string; + }>; + tag: string; + }; }; export type TokenCreateRequest = { - /** - * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. - */ - default_version?: string | null; - /** - * Description of this token, for instance where it will be used. - */ - description?: string; - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; - /** - * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. - */ - scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; + /** + * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. + */ + default_version?: string | null; + /** + * Description of this token, for instance where it will be used. + */ + description?: string; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; + /** + * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + */ + scopes?: Array< + "sandbox_create" | "sandbox_edit_code" | "sandbox_read" | "vm_manage" + >; }; export type VmCreateSessionRequest = { + /** + * GitHub token for the session + */ + git_access_token?: string; + /** + * Git user email to configure for this session + */ + git_user_email?: string; + /** + * Git user name to configure for this session + */ + git_user_name?: string; + /** + * Permission level for the session + */ + permission: "read" | "write"; + /** + * Unique identifier for the session + */ + session_id: string; +}; + +export type VmCreateSessionResponse = { + errors?: Array< + | string + | { + [key: string]: unknown; + } + >; + success?: boolean; +} & { + data?: { /** - * GitHub token for the session + * List of capabilities that Pitcher has */ - git_access_token?: string; + capabilities: Array; /** - * Git user email to configure for this session + * The permissions of the current session */ - git_user_email?: string; + permissions: { + [key: string]: unknown; + }; /** - * Git user name to configure for this session + * Token to authenticate with Pitcher (the agent running inside the VM) */ - git_user_name?: string; + pitcher_token: string; /** - * Permission level for the session + * WebSocket URL to connect to Pitcher */ - permission: 'read' | 'write'; + pitcher_url: string; /** - * Unique identifier for the session + * Path to the user's workspace in the VM */ - session_id: string; -}; - -export type VmCreateSessionResponse = { - errors?: Array; - success?: boolean; -} & { - data?: { - /** - * List of capabilities that Pitcher has - */ - capabilities: Array; - /** - * The permissions of the current session - */ - permissions: { - [key: string]: unknown; - }; - /** - * Token to authenticate with Pitcher (the agent running inside the VM) - */ - pitcher_token: string; - /** - * WebSocket URL to connect to Pitcher - */ - pitcher_url: string; - /** - * Path to the user's workspace in the VM - */ - user_workspace_path: string; - /** - * The Linux username created for this session - */ - username: string; - }; + user_workspace_path: string; + /** + * The Linux username created for this session + */ + username: string; + }; }; export type WorkspaceCreateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - id: string; - name: string; - }; + data?: { + id: string; + name: string; + }; }; export type VmUpdateHibernationTimeoutResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - hibernation_timeout_seconds: number; - id: string; - }; + data?: { + hibernation_timeout_seconds: number; + id: string; + }; }; export type SandboxForkResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - alias: string; - id: string; - /** - * VM start response. Only present when start_options were provided in the request. - */ - start_response?: { - bootup_type: string; - cluster: string; - id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; - } | null; - title: string | null; - }; + data?: { + alias: string; + id: string; + /** + * VM start response. Only present when start_options were provided in the request. + */ + start_response?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + } | null; + title: string | null; + }; }; export type PreviewTokenCreateResponse = { - errors?: Array; - success?: boolean; + } + >; + success?: boolean; } & { - data?: { - sandbox_id: string; - token: { - expires_at: string | null; - last_used_at: string | null; - token_id: string; - token_prefix: string; - } & { - token: string; - }; + data?: { + sandbox_id: string; + token: { + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; + } & { + token: string; }; + }; }; export type MetaInfoData = { - body?: never; - path?: never; - query?: never; - url: '/meta/info'; + body?: never; + path?: never; + query?: never; + url: "/meta/info"; }; export type MetaInfoResponses = { - /** - * Meta Info Response - */ - 200: MetaInformation; + /** + * Meta Info Response + */ + 200: MetaInformation; }; export type MetaInfoResponse = MetaInfoResponses[keyof MetaInfoResponses]; export type WorkspaceCreateData = { - /** - * Workspace Create Request - */ - body?: WorkspaceCreateRequest; - path?: never; - query?: never; - url: '/org/workspace'; + /** + * Workspace Create Request + */ + body?: WorkspaceCreateRequest; + path?: never; + query?: never; + url: "/org/workspace"; }; export type WorkspaceCreateResponses = { - /** - * Workspace Create Response - */ - 201: WorkspaceCreateResponse; + /** + * Workspace Create Response + */ + 201: WorkspaceCreateResponse; }; -export type WorkspaceCreateResponse2 = WorkspaceCreateResponses[keyof WorkspaceCreateResponses]; +export type WorkspaceCreateResponse2 = + WorkspaceCreateResponses[keyof WorkspaceCreateResponses]; export type TokenCreateData = { + /** + * Token Create Request + */ + body?: TokenCreateRequest; + path: { /** - * Token Create Request + * ID of the workspace to create the token in */ - body?: TokenCreateRequest; - path: { - /** - * ID of the workspace to create the token in - */ - team_id: string; - }; - query?: never; - url: '/org/workspace/{team_id}/tokens'; + team_id: string; + }; + query?: never; + url: "/org/workspace/{team_id}/tokens"; }; export type TokenCreateResponses = { - /** - * Token Create Response - */ - 201: TokenCreateResponse; + /** + * Token Create Response + */ + 201: TokenCreateResponse; }; -export type TokenCreateResponse2 = TokenCreateResponses[keyof TokenCreateResponses]; +export type TokenCreateResponse2 = + TokenCreateResponses[keyof TokenCreateResponses]; export type TokenUpdateData = { + /** + * Token Update Request + */ + body?: TokenUpdateRequest; + path: { /** - * Token Update Request + * ID of the workspace the token belongs to */ - body?: TokenUpdateRequest; - path: { - /** - * ID of the workspace the token belongs to - */ - team_id: string; - /** - * ID of token to update - */ - token_id: string; - }; - query?: never; - url: '/org/workspace/{team_id}/tokens/{token_id}'; + team_id: string; + /** + * ID of token to update + */ + token_id: string; + }; + query?: never; + url: "/org/workspace/{team_id}/tokens/{token_id}"; }; export type TokenUpdateResponses = { - /** - * Token Update Response - */ - 201: TokenUpdateResponse; + /** + * Token Update Response + */ + 201: TokenUpdateResponse; }; -export type TokenUpdateResponse2 = TokenUpdateResponses[keyof TokenUpdateResponses]; +export type TokenUpdateResponse2 = + TokenUpdateResponses[keyof TokenUpdateResponses]; export type SandboxListData = { - body?: never; - path?: never; - query?: { - /** - * Comma-separated list of tags to filter by - */ - tags?: string; - /** - * Field to order results by - */ - order_by?: 'inserted_at' | 'updated_at'; - /** - * Order direction - */ - direction?: 'asc' | 'desc'; - /** - * Maximum number of sandboxes to return in a single response - */ - page_size?: number; - /** - * Page number to return - */ - page?: number; - /** - * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. - */ - status?: 'running'; - }; - url: '/sandbox'; + body?: never; + path?: never; + query?: { + /** + * Comma-separated list of tags to filter by + */ + tags?: string; + /** + * Field to order results by + */ + order_by?: "inserted_at" | "updated_at"; + /** + * Order direction + */ + direction?: "asc" | "desc"; + /** + * Maximum number of sandboxes to return in a single response + */ + page_size?: number; + /** + * Page number to return + */ + page?: number; + /** + * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. + */ + status?: "running"; + }; + url: "/sandbox"; }; export type SandboxListResponses = { - /** - * Sandbox List Response - */ - 200: SandboxListResponse; + /** + * Sandbox List Response + */ + 200: SandboxListResponse; }; -export type SandboxListResponse2 = SandboxListResponses[keyof SandboxListResponses]; +export type SandboxListResponse2 = + SandboxListResponses[keyof SandboxListResponses]; export type SandboxCreateData = { - /** - * Sandbox Create Request - */ - body?: SandboxCreateRequest; - path?: never; - query?: never; - url: '/sandbox'; + /** + * Sandbox Create Request + */ + body?: SandboxCreateRequest; + path?: never; + query?: never; + url: "/sandbox"; }; export type SandboxCreateResponses = { - /** - * Sandbox Create Response - */ - 201: SandboxCreateResponse; + /** + * Sandbox Create Response + */ + 201: SandboxCreateResponse; }; -export type SandboxCreateResponse2 = SandboxCreateResponses[keyof SandboxCreateResponses]; +export type SandboxCreateResponse2 = + SandboxCreateResponses[keyof SandboxCreateResponses]; export type SandboxGetData = { - body?: never; - path: { - /** - * Short ID of the sandbox to retrieve - */ - id: string; - }; - query?: never; - url: '/sandbox/{id}'; + body?: never; + path: { + /** + * Short ID of the sandbox to retrieve + */ + id: string; + }; + query?: never; + url: "/sandbox/{id}"; }; export type SandboxGetResponses = { - /** - * Sandbox Get Response - */ - 200: SandboxGetResponse; + /** + * Sandbox Get Response + */ + 200: SandboxGetResponse; }; -export type SandboxGetResponse2 = SandboxGetResponses[keyof SandboxGetResponses]; +export type SandboxGetResponse2 = + SandboxGetResponses[keyof SandboxGetResponses]; export type SandboxForkData = { + /** + * Sandbox Fork Request + */ + body?: SandboxForkRequest; + path: { /** - * Sandbox Fork Request + * Short ID of the sandbox to fork */ - body?: SandboxForkRequest; - path: { - /** - * Short ID of the sandbox to fork - */ - id: string; - }; - query?: never; - url: '/sandbox/{id}/fork'; + id: string; + }; + query?: never; + url: "/sandbox/{id}/fork"; }; export type SandboxForkResponses = { - /** - * Sandbox Fork Response - */ - 201: SandboxForkResponse; + /** + * Sandbox Fork Response + */ + 201: SandboxForkResponse; }; -export type SandboxForkResponse2 = SandboxForkResponses[keyof SandboxForkResponses]; +export type SandboxForkResponse2 = + SandboxForkResponses[keyof SandboxForkResponses]; export type PreviewTokenRevokeAllData = { - body?: never; - path: { - /** - * Shortid of the sandbox to revoke tokens for - */ - id: string; - }; - query?: never; - url: '/sandbox/{id}/tokens'; + body?: never; + path: { + /** + * Shortid of the sandbox to revoke tokens for + */ + id: string; + }; + query?: never; + url: "/sandbox/{id}/tokens"; }; export type PreviewTokenRevokeAllResponses = { - /** - * RevokeAllPreviewTokensResponse - */ - 200: PreviewTokenRevokeAllResponse; + /** + * RevokeAllPreviewTokensResponse + */ + 200: PreviewTokenRevokeAllResponse; }; -export type PreviewTokenRevokeAllResponse2 = PreviewTokenRevokeAllResponses[keyof PreviewTokenRevokeAllResponses]; +export type PreviewTokenRevokeAllResponse2 = + PreviewTokenRevokeAllResponses[keyof PreviewTokenRevokeAllResponses]; export type PreviewTokenListData = { - body?: never; - path: { - /** - * Shortid of the sandbox to list the tokens for - */ - id: string; - }; - query?: never; - url: '/sandbox/{id}/tokens'; + body?: never; + path: { + /** + * Shortid of the sandbox to list the tokens for + */ + id: string; + }; + query?: never; + url: "/sandbox/{id}/tokens"; }; export type PreviewTokenListResponses = { - /** - * Token List Response - */ - 201: PreviewTokenListResponse; + /** + * Token List Response + */ + 201: PreviewTokenListResponse; }; -export type PreviewTokenListResponse2 = PreviewTokenListResponses[keyof PreviewTokenListResponses]; +export type PreviewTokenListResponse2 = + PreviewTokenListResponses[keyof PreviewTokenListResponses]; export type PreviewTokenCreateData = { + /** + * Token Create Request + */ + body?: PreviewTokenCreateRequest; + path: { /** - * Token Create Request + * Shortid of the sandbox to create the token for */ - body?: PreviewTokenCreateRequest; - path: { - /** - * Shortid of the sandbox to create the token for - */ - id: string; - }; - query?: never; - url: '/sandbox/{id}/tokens'; + id: string; + }; + query?: never; + url: "/sandbox/{id}/tokens"; }; export type PreviewTokenCreateResponses = { - /** - * Token Create Response - */ - 201: PreviewTokenCreateResponse; + /** + * Token Create Response + */ + 201: PreviewTokenCreateResponse; }; -export type PreviewTokenCreateResponse2 = PreviewTokenCreateResponses[keyof PreviewTokenCreateResponses]; +export type PreviewTokenCreateResponse2 = + PreviewTokenCreateResponses[keyof PreviewTokenCreateResponses]; export type PreviewTokenUpdateData = { + /** + * Token Update Request + */ + body?: PreviewTokenUpdateRequest; + path: { /** - * Token Update Request + * Shortid of the sandbox to create the token for */ - body?: PreviewTokenUpdateRequest; - path: { - /** - * Shortid of the sandbox to create the token for - */ - id: string; - /** - * ID of the token to update. Does not accept the token itself. - */ - token_id: string; - }; - query?: never; - url: '/sandbox/{id}/tokens/{token_id}'; + id: string; + /** + * ID of the token to update. Does not accept the token itself. + */ + token_id: string; + }; + query?: never; + url: "/sandbox/{id}/tokens/{token_id}"; }; export type PreviewTokenUpdateResponses = { - /** - * Token Update Response - */ - 201: PreviewTokenUpdateResponse; + /** + * Token Update Response + */ + 201: PreviewTokenUpdateResponse; }; -export type PreviewTokenUpdateResponse2 = PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; +export type PreviewTokenUpdateResponse2 = + PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; export type TemplatesCreateData = { - /** - * Template Create Request - */ - body?: TemplateCreateRequest; - path?: never; - query?: never; - url: '/templates'; + /** + * Template Create Request + */ + body?: TemplateCreateRequest; + path?: never; + query?: never; + url: "/templates"; }; export type TemplatesCreateResponses = { - /** - * Template Create Response - */ - 201: TemplateCreateResponse; + /** + * Template Create Response + */ + 201: TemplateCreateResponse; }; -export type TemplatesCreateResponse = TemplatesCreateResponses[keyof TemplatesCreateResponses]; +export type TemplatesCreateResponse = + TemplatesCreateResponses[keyof TemplatesCreateResponses]; export type VmAssignTagAliasData = { + /** + * VM Assign Tag Alias Request + */ + body?: VmAssignTagAliasRequest; + path: { /** - * VM Assign Tag Alias Request + * Tag alias namespace */ - body?: VmAssignTagAliasRequest; - path: { - /** - * Tag alias namespace - */ - namespace: string; - /** - * Tag alias - */ - alias: string; - }; - query?: never; - url: '/vm/alias/{namespace}/{alias}'; + namespace: string; + /** + * Tag alias + */ + alias: string; + }; + query?: never; + url: "/vm/alias/{namespace}/{alias}"; }; export type VmAssignTagAliasResponses = { - /** - * VM Assign Tag Alias Response - */ - 200: VmAssignTagAliasResponse; + /** + * VM Assign Tag Alias Response + */ + 200: VmAssignTagAliasResponse; }; -export type VmAssignTagAliasResponse2 = VmAssignTagAliasResponses[keyof VmAssignTagAliasResponses]; +export type VmAssignTagAliasResponse2 = + VmAssignTagAliasResponses[keyof VmAssignTagAliasResponses]; export type VmListClustersData = { - body?: never; - path?: never; - query?: never; - url: '/vm/clusters'; + body?: never; + path?: never; + query?: never; + url: "/vm/clusters"; }; export type VmListClustersResponses = { - /** - * VM List Clusters Response - */ - 200: VmListClustersResponse; + /** + * VM List Clusters Response + */ + 200: VmListClustersResponse; }; -export type VmListClustersResponse2 = VmListClustersResponses[keyof VmListClustersResponses]; +export type VmListClustersResponse2 = + VmListClustersResponses[keyof VmListClustersResponses]; export type VmListRunningVmsData = { - body?: never; - path?: never; - query?: never; - url: '/vm/running'; + body?: never; + path?: never; + query?: never; + url: "/vm/running"; }; export type VmListRunningVmsResponses = { - /** - * VM List Running VMs Response - */ - 200: VmListRunningVmsResponse; + /** + * VM List Running VMs Response + */ + 200: VmListRunningVmsResponse; }; -export type VmListRunningVmsResponse2 = VmListRunningVmsResponses[keyof VmListRunningVmsResponses]; +export type VmListRunningVmsResponse2 = + VmListRunningVmsResponses[keyof VmListRunningVmsResponses]; export type VmCreateTagData = { - /** - * VM Create Tag Request - */ - body?: VmCreateTagRequest; - path?: never; - query?: never; - url: '/vm/tag'; + /** + * VM Create Tag Request + */ + body?: VmCreateTagRequest; + path?: never; + query?: never; + url: "/vm/tag"; }; export type VmCreateTagResponses = { - /** - * VM Create Tag Response - */ - 200: VmCreateTagResponse; + /** + * VM Create Tag Response + */ + 200: VmCreateTagResponse; }; -export type VmCreateTagResponse2 = VmCreateTagResponses[keyof VmCreateTagResponses]; +export type VmCreateTagResponse2 = + VmCreateTagResponses[keyof VmCreateTagResponses]; export type VmHibernateData = { + /** + * VM Hibernate Request + */ + body?: VmHibernateRequest; + path: { /** - * VM Hibernate Request + * Sandbox ID */ - body?: VmHibernateRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/hibernate'; + id: string; + }; + query?: never; + url: "/vm/{id}/hibernate"; }; export type VmHibernateResponses = { - /** - * VM Hibernate Response - */ - 200: VmHibernateResponse; + /** + * VM Hibernate Response + */ + 200: VmHibernateResponse; }; -export type VmHibernateResponse2 = VmHibernateResponses[keyof VmHibernateResponses]; +export type VmHibernateResponse2 = + VmHibernateResponses[keyof VmHibernateResponses]; export type VmUpdateHibernationTimeoutData = { + /** + * VM Update Hibernation Timeout Request + */ + body?: VmUpdateHibernationTimeoutRequest; + path: { /** - * VM Update Hibernation Timeout Request + * Sandbox ID */ - body?: VmUpdateHibernationTimeoutRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/hibernation_timeout'; + id: string; + }; + query?: never; + url: "/vm/{id}/hibernation_timeout"; }; export type VmUpdateHibernationTimeoutResponses = { - /** - * VM Update Hibernation Timeout Response - */ - 200: VmUpdateHibernationTimeoutResponse; + /** + * VM Update Hibernation Timeout Response + */ + 200: VmUpdateHibernationTimeoutResponse; }; -export type VmUpdateHibernationTimeoutResponse2 = VmUpdateHibernationTimeoutResponses[keyof VmUpdateHibernationTimeoutResponses]; +export type VmUpdateHibernationTimeoutResponse2 = + VmUpdateHibernationTimeoutResponses[keyof VmUpdateHibernationTimeoutResponses]; export type VmCreateSessionData = { + /** + * VM Create Session Request + */ + body?: VmCreateSessionRequest; + path: { /** - * VM Create Session Request + * Sandbox ID */ - body?: VmCreateSessionRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/sessions'; + id: string; + }; + query?: never; + url: "/vm/{id}/sessions"; }; export type VmCreateSessionResponses = { - /** - * VM Create Session Response - */ - 200: VmCreateSessionResponse; + /** + * VM Create Session Response + */ + 200: VmCreateSessionResponse; }; -export type VmCreateSessionResponse2 = VmCreateSessionResponses[keyof VmCreateSessionResponses]; +export type VmCreateSessionResponse2 = + VmCreateSessionResponses[keyof VmCreateSessionResponses]; export type VmShutdownData = { + /** + * VM Shutdown Request + */ + body?: VmShutdownRequest; + path: { /** - * VM Shutdown Request + * Sandbox ID */ - body?: VmShutdownRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/shutdown'; + id: string; + }; + query?: never; + url: "/vm/{id}/shutdown"; }; export type VmShutdownResponses = { - /** - * VM Shutdown Response - */ - 200: VmShutdownResponse; + /** + * VM Shutdown Response + */ + 200: VmShutdownResponse; }; -export type VmShutdownResponse2 = VmShutdownResponses[keyof VmShutdownResponses]; +export type VmShutdownResponse2 = + VmShutdownResponses[keyof VmShutdownResponses]; export type VmUpdateSpecsData = { + /** + * VM Update Specs Request + */ + body?: VmUpdateSpecsRequest; + path: { /** - * VM Update Specs Request + * Sandbox ID */ - body?: VmUpdateSpecsRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/specs'; + id: string; + }; + query?: never; + url: "/vm/{id}/specs"; }; export type VmUpdateSpecsResponses = { - /** - * VM Update Specs Response - */ - 200: VmUpdateSpecsResponse; + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; }; -export type VmUpdateSpecsResponse2 = VmUpdateSpecsResponses[keyof VmUpdateSpecsResponses]; +export type VmUpdateSpecsResponse2 = + VmUpdateSpecsResponses[keyof VmUpdateSpecsResponses]; export type VmStartData = { + /** + * VM Start Request + */ + body?: VmStartRequest; + path: { /** - * VM Start Request + * Sandbox ID */ - body?: VmStartRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/start'; + id: string; + }; + query?: never; + url: "/vm/{id}/start"; }; export type VmStartResponses = { - /** - * VM Start Response - */ - 200: VmStartResponse; + /** + * VM Start Response + */ + 200: VmStartResponse; }; export type VmStartResponse2 = VmStartResponses[keyof VmStartResponses]; export type VmUpdateSpecs2Data = { + /** + * VM Update Specs Request + */ + body?: VmUpdateSpecsRequest; + path: { /** - * VM Update Specs Request + * Sandbox ID */ - body?: VmUpdateSpecsRequest; - path: { - /** - * Sandbox ID - */ - id: string; - }; - query?: never; - url: '/vm/{id}/update_specs'; + id: string; + }; + query?: never; + url: "/vm/{id}/update_specs"; }; export type VmUpdateSpecs2Responses = { - /** - * VM Update Specs Response - */ - 200: VmUpdateSpecsResponse; + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; }; -export type VmUpdateSpecs2Response = VmUpdateSpecs2Responses[keyof VmUpdateSpecs2Responses]; +export type VmUpdateSpecs2Response = + VmUpdateSpecs2Responses[keyof VmUpdateSpecs2Responses]; export type PreviewHostListData = { - body?: never; - path?: never; - query?: never; - url: '/workspace/preview_hosts'; + body?: never; + path?: never; + query?: never; + url: "/workspace/preview_hosts"; }; export type PreviewHostListResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostListResponse2 = PreviewHostListResponses[keyof PreviewHostListResponses]; +export type PreviewHostListResponse2 = + PreviewHostListResponses[keyof PreviewHostListResponses]; export type PreviewHostCreateData = { - /** - * Preview Host Create Request - */ - body?: PreviewHostRequest; - path?: never; - query?: never; - url: '/workspace/preview_hosts'; + /** + * Preview Host Create Request + */ + body?: PreviewHostRequest; + path?: never; + query?: never; + url: "/workspace/preview_hosts"; }; export type PreviewHostCreateResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostCreateResponse = PreviewHostCreateResponses[keyof PreviewHostCreateResponses]; +export type PreviewHostCreateResponse = + PreviewHostCreateResponses[keyof PreviewHostCreateResponses]; export type PreviewHostUpdateData = { - /** - * Preview Host Update Request - */ - body?: PreviewHostRequest; - path?: never; - query?: never; - url: '/workspace/preview_hosts'; + /** + * Preview Host Update Request + */ + body?: PreviewHostRequest; + path?: never; + query?: never; + url: "/workspace/preview_hosts"; }; export type PreviewHostUpdateResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostUpdateResponse = PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; \ No newline at end of file +export type PreviewHostUpdateResponse = + PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 9d8f996..35e733a 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -146,8 +146,9 @@ export const buildCommand: yargs.CommandModule< try { // Check if this is a container setup step and handle it specially - const isContainerStep = - step.name.toLowerCase().includes("starting container"); + const isContainerStep = step.name + .toLowerCase() + .includes("starting container"); if (isContainerStep) { spinner.start( @@ -188,7 +189,7 @@ export const buildCommand: yargs.CommandModule< ); } }); - + const output = await step.open(); buffer.push(...output.split("\n").map(stripAnsiCodes)); diff --git a/src/bin/ui/App.tsx b/src/bin/ui/App.tsx index fb0a43e..e11192b 100644 --- a/src/bin/ui/App.tsx +++ b/src/bin/ui/App.tsx @@ -3,6 +3,8 @@ import { Box, Text } from "ink"; import { Dashboard } from "./views/Dashboard"; import { useView } from "./viewContext"; import { Sandbox } from "./views/Sandbox"; +import { Debug } from "./views/Debug"; +import { Open } from "./views/Open"; import { useTerminalSize } from "./hooks/useTerminalSize"; export function App() { @@ -10,12 +12,19 @@ export function App() { const { view } = useView(); return ( - + □ CodeSandbox SDK {view.name === "dashboard" && } {view.name === "sandbox" && } + {view.name === "debug" && } + {view.name === "open" && } ); } diff --git a/src/bin/ui/components/Table.tsx b/src/bin/ui/components/Table.tsx index 0a0788f..774340d 100644 --- a/src/bin/ui/components/Table.tsx +++ b/src/bin/ui/components/Table.tsx @@ -13,7 +13,7 @@ export const Table = ({ renderHeader, renderBody }: TableProps) => { let totalWidth = 0; const headerElement = renderHeader(); - + if ( React.isValidElement(headerElement) && headerElement.type === TableHeader diff --git a/src/bin/ui/components/TextInput.tsx b/src/bin/ui/components/TextInput.tsx index 7da9777..17c9bee 100644 --- a/src/bin/ui/components/TextInput.tsx +++ b/src/bin/ui/components/TextInput.tsx @@ -61,7 +61,7 @@ export const TextInput: React.FC = ({ value.slice(0, cursorPosition - 1) + value.slice(cursorPosition); const newCursorPosition = cursorPosition - 1; isInternalChange.current = true; - + onChange(newValue); setCursorPosition(newCursorPosition); } diff --git a/src/bin/ui/components/VmTable.tsx b/src/bin/ui/components/VmTable.tsx index 6ff3f4e..a9f626c 100644 --- a/src/bin/ui/components/VmTable.tsx +++ b/src/bin/ui/components/VmTable.tsx @@ -1,39 +1,42 @@ import React from "react"; import { Box, Text } from "ink"; -import { Table, TableHeader, TableBody, TableRow, TableColumn } from "./Table"; import { format, parseISO } from "date-fns"; +import { useTerminalSize } from "../hooks/useTerminalSize"; const formatDate = (dateString: string | undefined): string => { if (!dateString) return "N/A"; - + try { const date = parseISO(dateString); - return format(date, "d MMMM yyyy 'at' HH:mm 'UTC'"); + return format(date, "MMM d HH:mm"); } catch (error) { - return "Invalid date"; + return "Invalid"; } }; -const calculateRuntime = (startedAt: string | undefined, lastActiveAt: string | undefined): string => { +const calculateRuntime = ( + startedAt: string | undefined, + lastActiveAt: string | undefined +): string => { if (!startedAt || !lastActiveAt) { - return "N/A" - }; - + return "N/A"; + } + try { const startDate = parseISO(startedAt); const lastActiveDate = parseISO(lastActiveAt); - + // Calculate difference in milliseconds const diffMs = lastActiveDate.getTime() - startDate.getTime(); - + if (diffMs < 0) return "N/A"; - + // Convert to seconds, minutes, hours const totalSeconds = Math.floor(diffMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; - + // Format output let result = ""; @@ -42,19 +45,37 @@ const calculateRuntime = (startedAt: string | undefined, lastActiveAt: string | } if (minutes > 0) { - result += `${minutes}m `; - } - - if (seconds > 0 || result === "") { + result += `${minutes}m`; + } else if (hours === 0) { + // Only show seconds if less than 1 minute total result += `${seconds}s`; } - + return result.trim(); } catch (error) { return "N/A"; } }; +const calculateRuntimeMs = ( + startedAt: string | undefined, + lastActiveAt: string | undefined +): number => { + if (!startedAt || !lastActiveAt) { + return 0; + } + + try { + const startDate = parseISO(startedAt); + const lastActiveDate = parseISO(lastActiveAt); + + const diffMs = lastActiveDate.getTime() - startDate.getTime(); + return diffMs > 0 ? diffMs : 0; + } catch (error) { + return 0; + } +}; + interface VmData { id?: string; credit_basis?: string; @@ -71,53 +92,99 @@ interface VmTableProps { vms: VmData[]; selectedIndex: number; onSelect: (index: number, vmId: string) => void; + scrollOffset?: number; + maxVisibleRows: number; } -export const VmTable = ({ vms, selectedIndex, onSelect }: VmTableProps) => { - const columnWidths = { - id: 20, - lastActive: 28, - startedAt: 28, - runtime: 14, - creditBasis: 20, - }; +export const VmTable = ({ + vms, + selectedIndex, + onSelect, + scrollOffset = 0, + maxVisibleRows, +}: VmTableProps) => { + const [terminalWidth, terminalHeight] = useTerminalSize(); + + // VMs are already sorted when passed in + const vmsSorted = vms; + + // Calculate scroll window + const startIndex = scrollOffset; + const endIndex = Math.min(startIndex + maxVisibleRows, vmsSorted.length); + const visibleVms = vmsSorted.slice(startIndex, endIndex); if (vms.length === 0) { return ( - - Running VMs + + Running VMs (0) No running VMs found. ); } + // Create title with scroll position + const titleText = + vmsSorted.length <= maxVisibleRows + ? `Running VMs (${vmsSorted.length})` + : `Running VMs (${startIndex + 1}-${endIndex} / ${vmsSorted.length})`; + + // Pad or truncate strings to fixed widths + const padString = (str: string, width: number) => { + return str.padEnd(width).substring(0, width); + }; + return ( - - Running VMs -
( - - VM ID - Last Active - Started At - Runtime - Credit Basis - - )} - renderBody={(totalWidth) => ( - - {vms.map((vm, index) => ( - - {vm.id || "N/A"} - {formatDate(vm.last_active_at)} - {formatDate(vm.session_started_at)} - {calculateRuntime(vm.session_started_at, vm.last_active_at)} - {vm.credit_basis || "N/A"} credits / hour - - ))} - - )} - /> + + {/* Header */} + + + {padString("VM ID", 14)} {padString("Last Active", 14)} {padString("Started At", 14)} {padString("Runtime", 10)} Credits + + + + {/* Separator */} + + {"─".repeat(Math.min(terminalWidth - 2, 60))} + + + {/* Data rows */} + {visibleVms.map((vm, visibleIndex) => { + const actualIndex = startIndex + visibleIndex; + const isSelected = selectedIndex === actualIndex; + + // Safely get VM ID and handle edge cases + const vmId = (vm?.id && typeof vm.id === 'string') ? vm.id : "N/A"; + + // Skip rendering if VM is completely invalid + if (!vm) { + return ( + + INVALID_VM[{actualIndex}] + + ); + } + + return ( + + + {padString(vmId, 14)} {padString(formatDate(vm.last_active_at), 14)} {padString(formatDate(vm.session_started_at), 14)} {padString(calculateRuntime(vm.session_started_at, vm.last_active_at), 10)} {vm.credit_basis || "N/A"} cr/hr + + + ); + })} + + {/* VM count and range info */} + + + {vmsSorted.length <= maxVisibleRows + ? `${vmsSorted.length} VMs total` + : `Showing ${startIndex + 1}-${endIndex} of ${vmsSorted.length} VMs` + } + + ); -}; \ No newline at end of file +}; diff --git a/src/bin/ui/hooks/useVmInput.ts b/src/bin/ui/hooks/useVmInput.ts index 0a24981..4582f20 100644 --- a/src/bin/ui/hooks/useVmInput.ts +++ b/src/bin/ui/hooks/useVmInput.ts @@ -15,7 +15,7 @@ export const useVmInput = ({ vms, onSubmit }: UseVmInputOptions) => { const [sandboxId, setSandboxId] = useState(""); const [selectedVm, setSelectedVm] = useState(null); const [selectedVmIndex, setSelectedVmIndex] = useState(-1); - + const handleInputChange = (value: string) => { setSandboxId(value); @@ -43,17 +43,20 @@ export const useVmInput = ({ vms, onSubmit }: UseVmInputOptions) => { if (key.upArrow || key.downArrow) { if (vms && vms.length > 0) { let newIndex = selectedVmIndex; - + if (key.upArrow) { - newIndex = selectedVmIndex <= 0 ? vms.length - 1 : selectedVmIndex - 1; + newIndex = + selectedVmIndex <= 0 ? vms.length - 1 : selectedVmIndex - 1; } else if (key.downArrow) { - newIndex = selectedVmIndex >= vms.length - 1 ? 0 : selectedVmIndex + 1; + newIndex = + selectedVmIndex >= vms.length - 1 ? 0 : selectedVmIndex + 1; } - + setSelectedVmIndex(newIndex); - const vmId = vms[newIndex]?.id || null; + const vm = vms[newIndex]; + const vmId = (vm?.id && typeof vm.id === 'string') ? vm.id : null; setSelectedVm(vmId); - + // Set the selected VM ID in the text input if (vmId) { setSandboxId(vmId); @@ -70,4 +73,4 @@ export const useVmInput = ({ vms, onSubmit }: UseVmInputOptions) => { handleInputSubmit, handleVmSelect, }; -}; \ No newline at end of file +}; diff --git a/src/bin/ui/sdkContext.tsx b/src/bin/ui/sdkContext.tsx index 50a390e..38d0676 100644 --- a/src/bin/ui/sdkContext.tsx +++ b/src/bin/ui/sdkContext.tsx @@ -16,9 +16,7 @@ export const SDKContext = createContext<{ sdk: CodeSandbox; api: API }>({ export const SDKProvider = ({ children }: { children: React.ReactNode }) => { return ( - - {children} - + {children} ); }; diff --git a/src/bin/ui/viewContext.tsx b/src/bin/ui/viewContext.tsx index 7283766..1670e25 100644 --- a/src/bin/ui/viewContext.tsx +++ b/src/bin/ui/viewContext.tsx @@ -2,7 +2,9 @@ import React, { createContext, useContext, useState } from "react"; type ViewState = | { name: "dashboard" } - | { name: "sandbox"; params: { id: string } }; + | { name: "sandbox"; params: { id: string } } + | { name: "debug"; params: { id: string } } + | { name: "open"; params: { id: string } }; export const ViewContext = createContext<{ view: ViewState; diff --git a/src/bin/ui/views/Dashboard.tsx b/src/bin/ui/views/Dashboard.tsx index d19bb00..77ab059 100644 --- a/src/bin/ui/views/Dashboard.tsx +++ b/src/bin/ui/views/Dashboard.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { Box, Text } from "ink"; import { useView } from "../viewContext"; import { useQuery } from "@tanstack/react-query"; @@ -6,17 +6,76 @@ import { useSDK } from "../sdkContext"; import { TextInput } from "../components/TextInput"; import { VmTable } from "../components/VmTable"; import { useVmInput } from "../hooks/useVmInput"; +import { useTerminalSize } from "../hooks/useTerminalSize"; + +const calculateRuntimeMs = ( + startedAt: string | undefined, + lastActiveAt: string | undefined +): number => { + if (!startedAt || !lastActiveAt) { + return 0; + } + + try { + const startDate = new Date(startedAt); + const lastActiveDate = new Date(lastActiveAt); + + const diffMs = lastActiveDate.getTime() - startDate.getTime(); + return diffMs > 0 ? diffMs : 0; + } catch (error) { + return 0; + } +}; export const Dashboard = () => { const { api } = useSDK(); + const [terminalWidth, terminalHeight] = useTerminalSize(); + const [scrollOffset, setScrollOffset] = useState(0); - const { data, isLoading } = useQuery({ + const { data, isLoading, refetch } = useQuery({ queryKey: ["runningVms"], queryFn: () => api.listRunningVms(), }); + // Poll getRunningVms API every 2 seconds + useEffect(() => { + const interval = setInterval(() => { + refetch(); + }, 2000); + + return () => { + clearInterval(interval); + }; + }, [refetch]); + const { setView } = useView(); + // Sort VMs by started_at (oldest to newest) + const sortedVms = data?.vms + ? [...data.vms].sort((a, b) => { + if (!a.session_started_at && !b.session_started_at) return 0; + if (!a.session_started_at) return 1; + if (!b.session_started_at) return -1; + + const dateA = new Date(a.session_started_at); + const dateB = new Date(b.session_started_at); + + // Check for invalid dates + if (isNaN(dateA.getTime()) && isNaN(dateB.getTime())) return 0; + if (isNaN(dateA.getTime())) return 1; + if (isNaN(dateB.getTime())) return -1; + + return dateA.getTime() - dateB.getTime(); + }) + : []; + + + // Calculate visible rows dynamically based on terminal size + // Account for UI elements: instructions (1), sandbox label (1), input field (1), + // table title (1), table header (1), separator (1), bottom margin (2) + const uiElementRows = 8; + const maxVisibleRows = Math.max(1, Math.floor((terminalHeight - uiElementRows) * 0.7) - 3); + const { sandboxId, selectedVm, @@ -25,12 +84,40 @@ export const Dashboard = () => { handleInputSubmit, handleVmSelect, } = useVmInput({ - vms: data?.vms, + vms: sortedVms, onSubmit: (id: string) => { setView({ name: "sandbox", params: { id } }); }, }); + // Update scroll offset when selection changes + useEffect(() => { + if (selectedVmIndex < 0) { + // Reset scroll offset when no VM is selected + setScrollOffset(0); + return; + } + + if (sortedVms.length <= maxVisibleRows) { + // No need to scroll if all items fit + setScrollOffset(0); + return; + } + + // Calculate current visible range + const currentVisibleStart = scrollOffset; + const currentVisibleEnd = scrollOffset + maxVisibleRows - 1; + + // If selected item is outside visible area, scroll to include it + if (selectedVmIndex < currentVisibleStart) { + // Scroll up to show selected item at top + setScrollOffset(selectedVmIndex); + } else if (selectedVmIndex > currentVisibleEnd) { + // Scroll down to show selected item at bottom + setScrollOffset(selectedVmIndex - maxVisibleRows + 1); + } + }, [selectedVmIndex, maxVisibleRows, sortedVms.length]); + // Cursor is shown when no VM is selected (user typed manually) const showCursor = selectedVm === null; @@ -46,13 +133,15 @@ export const Dashboard = () => { ); } - if (data?.vms && data.vms.length > 0) { + if (sortedVms && sortedVms.length > 0) { // TODO: Fix type mismatch - API types are being updated return ( ); } @@ -68,14 +157,14 @@ export const Dashboard = () => { }; return ( - + Start typing to input an ID or use ↑/↓ arrows to select from running VMs. Press ENTER to view VM details. - + Sandbox ID diff --git a/src/bin/ui/views/Debug.tsx b/src/bin/ui/views/Debug.tsx new file mode 100644 index 0000000..c3c53ef --- /dev/null +++ b/src/bin/ui/views/Debug.tsx @@ -0,0 +1,462 @@ +import React, { useEffect, useRef, useState, useCallback } from "react"; +import { Box, Text, useInput } from "ink"; +import { stripVTControlCharacters } from "node:util"; +import { useView } from "../viewContext"; +import { useSDK } from "../sdkContext"; +import { useTerminalSize } from "../hooks/useTerminalSize"; +import { Terminal } from "../../../SandboxClient/terminals"; +import xtermPkg from "@xterm/headless"; +import serializePkg from "@xterm/addon-serialize"; + +const { Terminal: XTerm } = xtermPkg; +const { SerializeAddon } = serializePkg; + +function useAnimationFrame(callback: () => void, fps = 60) { + useEffect(() => { + let t: NodeJS.Timeout | null = null; + const frame = () => { + callback(); + t = setTimeout(frame, 1000 / fps); + }; + frame(); + return () => { + if (t) clearTimeout(t); + }; + }, [callback, fps]); +} + +export const Debug = () => { + const { view, setView } = useView<"debug">(); + const { sdk } = useSDK(); + const [terminalWidth, terminalHeight] = useTerminalSize(); + + const [sandboxTerminal, setSandboxTerminal] = useState(null); + const [isConnecting, setIsConnecting] = useState(false); + const [error, setError] = useState(null); + + // XTerm refs + const xtermRef = useRef(null); + const serializeRef = useRef(null); + + // Calculate terminal dimensions based on available space + const terminalCols = 150; + // Calculate rows based on terminal height, reserving space for UI elements: + // - Title (1 row) + // - Error message (1 row when present) + // - "Connecting..." message (1 row when present) + // - Instructions at bottom (2 rows with margin) + // Total reserved: ~5-6 rows for UI, remaining for terminal content + const reservedRows = 6; // Minimal UI for terminal-only view + const availableRows = Math.max(5, terminalHeight - reservedRows); // Minimum 5 rows + const terminalRows = availableRows; + + // Initialize sandbox connection and terminal + useEffect(() => { + let mounted = true; + let client: any = null; + + const initializeDebug = async () => { + try { + setIsConnecting(true); + setError(null); + + // Connect to sandbox with a global user session + const sandbox = await sdk.sandboxes.resume(view.params.id); + client = await sandbox.connect({ + id: "debug-session", + permission: "write", + }); + + if (!mounted) return; + + // Create or get existing terminal + let debugTerminal: Terminal; + const existingTerminals = await client.terminals.getAll(); + const debugTerminalExists = existingTerminals.find( + (t) => t.name === "debug-xterm" + ); + + if (debugTerminalExists) { + debugTerminal = debugTerminalExists; + } else { + debugTerminal = await client.terminals.create("bash", { + name: "debug-xterm", + dimensions: { + cols: terminalCols, + rows: terminalRows, + }, + }); + } + + if (!mounted) return; + + setSandboxTerminal(debugTerminal); + + // Initialize XTerm + const xterm = new XTerm({ + cols: terminalCols, + rows: terminalRows, + allowProposedApi: true, + }); + const serialize = new SerializeAddon(); + xterm.loadAddon(serialize); + xtermRef.current = xterm; + serializeRef.current = serialize; + + // Open sandbox terminal and get initial output + const initialOutput = await debugTerminal.open({ + cols: terminalCols, + rows: terminalRows, + }); + + // Write initial output to xterm + if (initialOutput) { + xterm.write(initialOutput); + } + + // Listen for terminal output and pipe to xterm + const outputDisposable = debugTerminal.onOutput((output) => { + if (xtermRef.current) { + xtermRef.current.write(output); + } + }); + + setIsConnecting(false); + + // Cleanup function + return () => { + outputDisposable?.dispose?.(); + // Kill the terminal session + if (debugTerminal) { + debugTerminal.kill().catch(() => { + // Ignore cleanup errors + }); + } + client?.dispose?.(); + xterm?.dispose?.(); + }; + } catch (err) { + if (!mounted) return; + setError(err instanceof Error ? err.message : String(err)); + setIsConnecting(false); + } + }; + + initializeDebug().then((cleanup) => { + if (!mounted && cleanup) { + cleanup(); + } + }); + + return () => { + mounted = false; + // Kill terminal on component unmount + if (sandboxTerminal) { + sandboxTerminal.kill().catch(() => { + // Ignore cleanup errors + }); + } + client?.dispose?.(); + if (xtermRef.current) { + xtermRef.current.dispose(); + xtermRef.current = null; + } + }; + }, [view.params.id, sdk.sandboxes, terminalCols, terminalRows]); + + // Handle terminal resizing + useEffect(() => { + if (xtermRef.current && sandboxTerminal) { + xtermRef.current.resize(terminalCols, terminalRows); + // Note: We don't resize the sandbox terminal as it might disrupt the session + } + }, [terminalCols, terminalRows, sandboxTerminal]); + + // Handle keyboard input + useInput((input, key) => { + if (key.escape) { + // Kill the terminal before exiting + if (sandboxTerminal) { + sandboxTerminal.kill().catch((error) => { + console.warn("Failed to kill terminal:", error); + }); + } + setView({ name: "sandbox", params: { id: view.params.id } }); + } else if (key.shift && (key.upArrow || key.downArrow)) { + // Handle scrolling with SHIFT + arrow keys + const buffer = xtermRef.current?.buffer.active; + if (buffer) { + const maxScroll = Math.max(0, buffer.length - terminalRows); + if (key.upArrow) { + setScrollOffset((prev) => { + const newOffset = Math.min(prev + 1, maxScroll); + // Force terminal content update by clearing the buffer hash + setLastBufferHash(""); + return newOffset; + }); + } else if (key.downArrow) { + setScrollOffset((prev) => { + const newOffset = Math.max(prev - 1, 0); + // Force terminal content update by clearing the buffer hash + setLastBufferHash(""); + return newOffset; + }); + } + } + } else if (sandboxTerminal && xtermRef.current && !isConnecting) { + // Handle special keys + if (key.return) { + sandboxTerminal.write("\r", { cols: terminalCols, rows: terminalRows }); + // Don't echo to xterm - let the response come back naturally + } else if (key.backspace || key.delete) { + // Try both common backspace codes + const backspaceCode = "\x08"; // Backspace (Ctrl+H) + sandboxTerminal.write(backspaceCode, { + cols: terminalCols, + rows: terminalRows, + }); + // Don't echo to xterm - let the response come back naturally + } else if (key.leftArrow) { + sandboxTerminal.write("\x1b[D", { + cols: terminalCols, + rows: terminalRows, + }); + } else if (key.rightArrow) { + sandboxTerminal.write("\x1b[C", { + cols: terminalCols, + rows: terminalRows, + }); + } else if (key.upArrow) { + sandboxTerminal.write("\x1b[A", { + cols: terminalCols, + rows: terminalRows, + }); + } else if (key.downArrow) { + sandboxTerminal.write("\x1b[B", { + cols: terminalCols, + rows: terminalRows, + }); + } else if (key.ctrl && input === "c") { + sandboxTerminal.write("\x03", { + cols: terminalCols, + rows: terminalRows, + }); + } else if (key.tab) { + sandboxTerminal.write("\t", { cols: terminalCols, rows: terminalRows }); + } else if (input) { + sandboxTerminal.write(input, { + cols: terminalCols, + rows: terminalRows, + }); + // Don't echo regular input to xterm as it will come back via the output handler + } + } + }); + + // Simple terminal content - single text block + const [terminalContent, setTerminalContent] = useState(""); + const [lastBufferHash, setLastBufferHash] = useState(""); + const [scrollOffset, setScrollOffset] = useState(0); + + // Simple terminal content update - no line splitting needed + const updateTerminalContent = useCallback(() => { + const xterm = xtermRef.current; + const serialize = serializeRef.current; + if (!xterm || !serialize || isConnecting) return; + + try { + const buffer = xterm.buffer.active; + + // Create a hash to detect changes + const bufferHash = `${buffer.length}-${buffer.baseY}-${buffer.viewportY}-${buffer.cursorY}-${buffer.cursorX}`; + if (bufferHash === lastBufferHash) { + return; // No changes, skip update + } + setLastBufferHash(bufferHash); + + // Don't scroll - let the terminal frame extend naturally + + // Use serialize addon with proper cleaning to prevent accumulation + try { + const buffer = xterm.buffer.active; + const cursorX = buffer.cursorX; + const cursorY = buffer.cursorY; + + // Get full terminal content first + const fullContent = + serializeRef.current?.serialize({ + scrollback: buffer.length, // Get all content + excludeAltBuffer: false, + onlySelection: false, + }) || ""; + + // Split into lines and apply scroll offset to get consistent window + const allLines = fullContent.split("\n"); + const totalLines = allLines.length; + + // Calculate which lines to show based on scroll offset + const startLine = Math.max(0, totalLines - terminalRows - scrollOffset); + const endLine = Math.min(totalLines, startLine + terminalRows); + const visibleLines = allLines.slice(startLine, endLine); + + // Pad with empty lines if needed to maintain consistent height + while (visibleLines.length < terminalRows) { + visibleLines.push(""); + } + + const serializedContent = visibleLines.join("\n"); + + // Calculate cursor position relative to visible window + const cursorLineInBuffer = cursorY + (buffer.length - terminalRows); + const adjustedCursorY = cursorLineInBuffer - startLine; + + // Aggressively clean any existing cursor codes to prevent accumulation + const cleanContent = serializedContent + .replace(/\x1b\[7m/g, "") // Remove inverse video start + .replace(/\x1b\[27m/g, "") // Remove inverse video end + .replace(/\x1b\[39m/g, "") // Remove reset foreground + .replace(/\x1b\[49m/g, "") // Remove reset background + .replace(/\x1b\[7m \x1b\[27m\x1b\[39m\x1b\[49m/g, ""); // Remove complete cursor sequence + + // Calculate cursor position in clean content using viewport-relative coordinates + const cleanTextContent = stripVTControlCharacters(cleanContent); + const cleanLines = cleanTextContent.split("\n"); + let targetPosition = 0; + + // Use the adjusted cursor position + const finalCursorY = adjustedCursorY; + + for (let i = 0; i < finalCursorY && i < cleanLines.length; i++) { + targetPosition += cleanLines[i].length + 1; + } + + if (finalCursorY < cleanLines.length) { + const currentLine = cleanLines[finalCursorY] || ""; + targetPosition += Math.min(cursorX, currentLine.length); + } + + // Map to position in original content with ANSI codes + let actualPosition = 0; + let cleanPosition = 0; + + while ( + cleanPosition < targetPosition && + actualPosition < cleanContent.length + ) { + const char = cleanContent.charAt(actualPosition); + + if ( + char === "\x1b" && + cleanContent.charAt(actualPosition + 1) === "[" + ) { + let seqEnd = actualPosition + 2; + while (seqEnd < cleanContent.length) { + const seqChar = cleanContent.charAt(seqEnd); + seqEnd++; + if ( + (seqChar >= "A" && seqChar <= "Z") || + (seqChar >= "a" && seqChar <= "z") + ) { + break; + } + } + actualPosition = seqEnd; + } else { + actualPosition++; + cleanPosition++; + } + } + + // Try to split content at cursor position and render with React components + const beforeCursor = cleanContent.substring(0, actualPosition); + const charAtCursor = cleanContent.charAt(actualPosition); + const afterCursor = cleanContent.substring(actualPosition + 1); + + // Check if cursor is at the very end of the buffer (no character to highlight) + const isAtEnd = !charAtCursor; + + // Check if cursor is on an ANSI escape sequence + const isOnEscapeSequence = + charAtCursor === "\x1b" || + (beforeCursor.endsWith("\x1b[") && charAtCursor === "?"); + + if (isAtEnd) { + // Cursor is at the end - just show clean content without appending cursor + setTerminalContent(cleanContent); + } else if (!isOnEscapeSequence) { + // Cursor is on an actual character (not escape sequence) - highlight it + setTerminalContent( + JSON.stringify({ + before: beforeCursor, + cursor: charAtCursor, + after: afterCursor, + }) + ); + } else { + // Cursor is on escape sequence - don't render a cursor to avoid breaking ANSI + setTerminalContent(cleanContent); + } + } catch (error) { + console.warn("Terminal serialization error:", error); + } + } catch (error) { + console.warn("Terminal buffer access error:", error); + } + }, [isConnecting, lastBufferHash, scrollOffset]); + + useAnimationFrame(updateTerminalContent, 15); // Responsive scrolling for prompt visibility + + if (!view.params.id) { + return No sandbox ID provided. Press escape to go back.; + } + + return ( + + Terminal - {view.params.id} + + {error && ( + + Error: {error} + + )} + + {isConnecting && ( + + Connecting to sandbox... + + )} + + {/* Terminal Section - Allow content to flow naturally */} + {!isConnecting && ( + + {terminalContent ? ( + (() => { + try { + const parsed = JSON.parse(terminalContent); + return ( + + {parsed.before} + + {parsed.cursor} + + {parsed.after} + + ); + } catch { + return {terminalContent}; + } + })() + ) : ( + Terminal starting... + )} + + )} + + + + Type commands normally. SHIFT + ↑/↓ to scroll. Press ESC to go back. + + + + ); +}; diff --git a/src/bin/ui/views/Open.tsx b/src/bin/ui/views/Open.tsx new file mode 100644 index 0000000..1306d3f --- /dev/null +++ b/src/bin/ui/views/Open.tsx @@ -0,0 +1,166 @@ +import React, { useEffect, useState } from "react"; +import { Box, Text, useInput } from "ink"; +import { useView } from "../viewContext"; +import { useSDK } from "../sdkContext"; +import { Port } from "../../../SandboxClient/ports"; +import { Task } from "../../../SandboxClient/tasks"; + +export const Open = () => { + const { view, setView } = useView<"open">(); + const { sdk } = useSDK(); + + const [ports, setPorts] = useState([]); + const [tasks, setTasks] = useState([]); + const [isConnecting, setIsConnecting] = useState(false); + const [error, setError] = useState(null); + + // Initialize sandbox connection + useEffect(() => { + let mounted = true; + let client: any = null; + + const initializeOpen = async () => { + try { + setIsConnecting(true); + setError(null); + + // Connect to sandbox with a global user session + const sandbox = await sdk.sandboxes.resume(view.params.id); + client = await sandbox.connect({ + id: "open-session", + permission: "write", + }); + + if (!mounted) return; + + // Get current ports and tasks + const [currentPorts, currentTasks] = await Promise.all([ + client.ports.getAll(), + client.tasks.getAll(), + ]); + setPorts(currentPorts); + setTasks(currentTasks); + + // Listen for port changes + const portOpenDisposable = client.ports.onDidPortOpen((port: Port) => { + setPorts((prev) => { + const exists = prev.some((p) => p.port === port.port); + return exists ? prev : [...prev, port]; + }); + }); + + const portCloseDisposable = client.ports.onDidPortClose( + (portNumber: number) => { + setPorts((prev) => prev.filter((p) => p.port !== portNumber)); + } + ); + + setIsConnecting(false); + + // Cleanup function + return () => { + portOpenDisposable?.dispose?.(); + portCloseDisposable?.dispose?.(); + client?.dispose?.(); + }; + } catch (err) { + if (!mounted) return; + setError(err instanceof Error ? err.message : String(err)); + setIsConnecting(false); + } + }; + + initializeOpen().then((cleanup) => { + if (!mounted && cleanup) { + cleanup(); + } + }); + + return () => { + mounted = false; + client?.dispose?.(); + }; + }, [view.params.id, sdk.sandboxes]); + + // Handle keyboard input + useInput((input, key) => { + if (key.escape) { + setView({ name: "sandbox", params: { id: view.params.id } }); + } + }); + + if (!view.params.id) { + return No sandbox ID provided. Press escape to go back.; + } + + return ( + + Open - {view.params.id} + + {error && ( + + Error: {error} + + )} + + {isConnecting ? ( + + Connecting to sandbox... + + ) : ( + <> + {/* Ports Section */} + + Active Ports: + {ports.length === 0 ? ( + No ports currently open + ) : ( + ports.map((port) => ( + + Port {port.port}: https://{port.host} + + )) + )} + + + {/* Tasks Section */} + + Tasks: + {tasks.length === 0 ? ( + No tasks defined + ) : ( + tasks.map((task) => { + const status = task.status; + const statusColor = + status === "RUNNING" + ? "green" + : status === "FINISHED" + ? "blue" + : status === "ERROR" + ? "red" + : status === "KILLED" + ? "red" + : status === "RESTARTING" + ? "yellow" + : status === "IDLE" + ? "gray" + : "gray"; + + return ( + + {task.name}: + {status} + + ); + }) + )} + + + )} + + + Press ESC to go back to sandbox view. + + + ); +}; diff --git a/src/bin/ui/views/Sandbox.tsx b/src/bin/ui/views/Sandbox.tsx index 31baeb1..a057d43 100644 --- a/src/bin/ui/views/Sandbox.tsx +++ b/src/bin/ui/views/Sandbox.tsx @@ -61,7 +61,7 @@ export const Sandbox = () => { const getMenuOptions = () => { switch (sandboxState) { case "RUNNING": - return ["Hibernate", "Shutdown", "Restart"]; + return ["Open", "Terminal", "Hibernate", "Shutdown", "Restart"]; case "IDLE": return ["Start"]; default: @@ -74,6 +74,12 @@ export const Sandbox = () => { // Handle menu options const handleAction = async (action: string) => { switch (action) { + case "Terminal": + setView({ name: "debug", params: { id: view.params.id } }); + break; + case "Open": + setView({ name: "open", params: { id: view.params.id } }); + break; case "Hibernate": case "Shutdown": setSandboxState("PENDING"); diff --git a/src/bin/utils/sentry.ts b/src/bin/utils/sentry.ts index 30a9a80..678fa82 100644 --- a/src/bin/utils/sentry.ts +++ b/src/bin/utils/sentry.ts @@ -4,13 +4,13 @@ let sentryInitialized = false; // Function to initialize Sentry if conditions are met async function initializeSentry() { if (sentryInitialized) return; - + // Only initialize Sentry if the CODESANDBOX_SENTRY_ENABLED environment variable is set to "true" // and the @sentry/node package is available if (process.env.CODESANDBOX_SENTRY_ENABLED === "true") { try { Sentry = await import("@sentry/node"); - + // This can happen when the CLI uses Sentry for its own requests, but also the SDK for other requests if (!Sentry.isInitialized()) { Sentry.init({ @@ -20,17 +20,19 @@ async function initializeSentry() { } } catch (error) { // Sentry is not available, continue without it - console.warn("Sentry error reporting is enabled but @sentry/node is not available. Install it as a dependency to enable error reporting."); + console.warn( + "Sentry error reporting is enabled but @sentry/node is not available. Install it as a dependency to enable error reporting." + ); } } - + sentryInitialized = true; } export async function instrumentedFetch(request: Request) { // Initialize Sentry if needed await initializeSentry(); - + // We are cloning the request to be able to read its body on errors const res = await fetch(request.clone()); diff --git a/src/browser/index.ts b/src/browser/index.ts index eaafbab..8f72cee 100644 --- a/src/browser/index.ts +++ b/src/browser/index.ts @@ -33,7 +33,12 @@ export async function connectToSandbox({ initStatusCb = () => {}, tracer, }: ConnectToSandboxOptions): Promise { - const client = await SandboxClient.create(session, getSession, initStatusCb, tracer); + const client = await SandboxClient.create( + session, + getSession, + initStatusCb, + tracer + ); onFocusChange((isFocused) => { // We immediately ping the connection when focusing, so that diff --git a/src/pitcher-protocol/message.ts b/src/pitcher-protocol/message.ts index b2080ce..feb62c9 100644 --- a/src/pitcher-protocol/message.ts +++ b/src/pitcher-protocol/message.ts @@ -41,7 +41,7 @@ export function decodeMessage(blob: Uint8Array): any { } export function isNotificationPayload( - payload: any, + payload: any ): payload is PitcherNotificationPayload { return !("id" in payload) && "params" in payload; } @@ -51,25 +51,25 @@ export function isErrorPayload(payload: any): payload is PitcherErrorPayload { } export function isResultPayload( - payload: any, + payload: any ): payload is PitcherResponsePayload { return "result" in payload; } export function createNotificationPayload( - payload: PitcherNotificationPayload, + payload: PitcherNotificationPayload ): Uint8Array { return encodeMessage(payload); } export function createRequestPayload( - payload: PitcherRequestPayload, + payload: PitcherRequestPayload ): Uint8Array { return encodeMessage(payload); } export function createResponsePayload( - payload: PitcherResponsePayload | PitcherErrorPayload, + payload: PitcherResponsePayload | PitcherErrorPayload ): Uint8Array { return encodeMessage(payload); } diff --git a/src/pitcher-protocol/protocol.ts b/src/pitcher-protocol/protocol.ts index d5b8b8b..5e2c4d1 100644 --- a/src/pitcher-protocol/protocol.ts +++ b/src/pitcher-protocol/protocol.ts @@ -31,7 +31,7 @@ export type TMessage< data?: unknown; publicMessage?: string; }; - }, + } > = { request: { method: Method; diff --git a/src/types.ts b/src/types.ts index ac895ac..3aca37d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,10 +36,10 @@ export interface SystemMetricsStatus { /** * Sandbox privacy settings */ -export type SandboxPrivacy = - | "public" +export type SandboxPrivacy = + | "public" | "unlisted" /** @deprecated Use "public" or "public-hosts" instead */ - | "private" + | "private" | "public-hosts"; export type SandboxInfo = { @@ -59,7 +59,6 @@ export type SandboxListOpts = { status?: "running"; }; - export interface SandboxListResponse { sandboxes: SandboxInfo[]; hasMore: boolean; From cb2f330897c5e4c26637bc39a85d0e28dc4331d6 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 20 Aug 2025 11:45:45 +0200 Subject: [PATCH 209/241] fix: Stabilize websocket connection (#166) --- package.json | 4 ++-- src/AgentClient/AgentConnection.ts | 9 ++++++++- src/AgentClient/WebSocketClient.ts | 3 +++ src/AgentClient/index.ts | 3 ++- src/SandboxClient/index.ts | 32 +++++++++++++++++++++++++++--- src/utils/api.ts | 9 +++++++++ 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1161fda..c1c04dc 100644 --- a/package.json +++ b/package.json @@ -90,12 +90,12 @@ "tslib": "^2.1.0", "typescript": "^5.7.2", "why-is-node-running": "^2.3.0", - "isomorphic-ws": "^5.0.0" + "isomorphic-ws": "^5.0.0", + "@msgpack/msgpack": "^2.7.1" }, "dependencies": { "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", - "@msgpack/msgpack": "^3.1.0", "@opentelemetry/api": "^1.9.0", "@xterm/addon-serialize": "^0.13.0", "@xterm/headless": "^5.5.0", diff --git a/src/AgentClient/AgentConnection.ts b/src/AgentClient/AgentConnection.ts index b8ac31d..69019df 100644 --- a/src/AgentClient/AgentConnection.ts +++ b/src/AgentClient/AgentConnection.ts @@ -93,8 +93,15 @@ export class AgentConnection { }); connection.onMissingHeartbeat(() => { + // Be more conservative about disconnection - only disconnect if we have no activity + // and no pending messages, indicating a truly dead connection if (this.pendingMessages.size === 0) { - this.state = "DISCONNECTED"; + // Add a small delay to allow for network recovery before declaring disconnection + setTimeout(() => { + if (this.pendingMessages.size === 0 && this.state === "CONNECTED") { + this.state = "DISCONNECTED"; + } + }, 1000); } }); } diff --git a/src/AgentClient/WebSocketClient.ts b/src/AgentClient/WebSocketClient.ts index acda7bb..fc6c76c 100644 --- a/src/AgentClient/WebSocketClient.ts +++ b/src/AgentClient/WebSocketClient.ts @@ -235,6 +235,9 @@ export class WebSocketClient extends Disposable { ); } + // Update lastActivity on send to prevent heartbeat suppression + this.lastActivity = Date.now(); + // This is an async operation in Node, but to avoid wrapping every send in a promise, we // rely on the error listener to deal with any errors. Any unsent messages will be timed out // by our PendingMessage logic diff --git a/src/AgentClient/index.ts b/src/AgentClient/index.ts index e195fb5..c6915b6 100644 --- a/src/AgentClient/index.ts +++ b/src/AgentClient/index.ts @@ -25,7 +25,8 @@ import { SandboxClient } from "../SandboxClient"; import { InitStatus } from "../pitcher-protocol/messages/system"; // Timeout for detecting a pong response, leading to a forced disconnect -let PONG_DETECTION_TIMEOUT = 15_000; +// Increased from 15s to 30s to be more tolerant of network latency +let PONG_DETECTION_TIMEOUT = 30_000; // When focusing the app we do a lower timeout to more quickly detect a potential disconnect const FOCUS_PONG_DETECTION_TIMEOUT = 5_000; diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 26c4108..9496150 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -195,6 +195,8 @@ export class SandboxClient { this.attemptAutoReconnect(); } } else if (state === "CONNECTED") { + // Reset keep-alive failures on successful connection + this.keepAliveFailures = 0; if (this.shouldKeepAlive) { this.keepActiveWhileConnected(true); } @@ -389,6 +391,8 @@ export class SandboxClient { private keepAliveInterval: NodeJS.Timeout | null = null; private shouldKeepAlive = false; private isExplicitlyDisconnected = false; + private keepAliveFailures = 0; + private maxKeepAliveFailures = 3; /** * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ @@ -399,9 +403,31 @@ export class SandboxClient { if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { - this.agentClient.system.update().catch((error) => { - console.warn("Unable to keep active while connected", error); - }); + this.agentClient.system.update() + .then(() => { + // Reset failure count on success + this.keepAliveFailures = 0; + }) + .catch((error) => { + this.keepAliveFailures++; + console.warn(`Keep-alive failed (${this.keepAliveFailures}/${this.maxKeepAliveFailures}):`, error); + + // If we've hit max failures, stop aggressive keep-alive to prevent connection thrashing + if (this.keepAliveFailures >= this.maxKeepAliveFailures) { + console.warn("Max keep-alive failures reached, reducing frequency to prevent connection issues"); + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + // Restart with longer interval after failures + setTimeout(() => { + if (this.shouldKeepAlive && !this.keepAliveInterval) { + this.keepActiveWhileConnected(true); + this.keepAliveFailures = 0; // Reset for retry + } + }, 60000); // Wait 1 minute before retrying + } + }); }, 1000 * 10); } } else { diff --git a/src/utils/api.ts b/src/utils/api.ts index ef06a49..8fd9dc1 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -68,6 +68,15 @@ export type HandledResponse = { export function getStartOptions(opts: StartSandboxOpts | undefined) { if (!opts) return {}; + // Warn about hibernation timeouts that are too short and may cause connection issues + if (opts.hibernationTimeoutSeconds !== undefined && opts.hibernationTimeoutSeconds < 60) { + console.warn( + `Warning: hibernationTimeoutSeconds (${opts.hibernationTimeoutSeconds}s) is less than 60 seconds. ` + + `This may cause connection instability and frequent disconnections. ` + + `Consider using at least 60 seconds for stable websocket connections.` + ); + } + return { ipcountry: opts.ipcountry, tier: opts.vmTier?.name, From 6b50aaa651bfdaeefb645126bc844300b2b0a243 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 21 Aug 2025 13:58:04 +0200 Subject: [PATCH 210/241] chore: add basic integration tests (#168) * chore: add basic integration tests * check counds --- .gitignore | 1 - package-lock.json | 1144 +++++++++++++++++++++++++- package.json | 10 +- tests/sandbox-creation.test.ts | 80 ++ tests/sandbox-lifecycle.test.ts | 135 +++ tests/sandbox-retry-behavior.test.ts | 168 ++++ tests/test-utils.ts | 99 +++ vitest.config.ts | 11 + 8 files changed, 1633 insertions(+), 15 deletions(-) create mode 100644 tests/sandbox-creation.test.ts create mode 100644 tests/sandbox-lifecycle.test.ts create mode 100644 tests/sandbox-retry-behavior.test.ts create mode 100644 tests/test-utils.ts create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 308ba6d..68e2089 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ # Generated stuff dist -tests test.ts test-template diff --git a/package-lock.json b/package-lock.json index 19c5746..fcacaf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@hey-api/client-fetch": "^0.7.3", "@inkjs/ui": "^2.0.0", - "@msgpack/msgpack": "^3.1.0", "@opentelemetry/api": "^1.9.0", "@xterm/addon-serialize": "^0.13.0", "@xterm/headless": "^5.5.0", @@ -32,9 +31,11 @@ }, "devDependencies": { "@hey-api/openapi-ts": "^0.63.2", + "@msgpack/msgpack": "^2.7.1", "@parcel/watcher": "^2.5.1", "@tanstack/react-query": "^5.76.1", "@types/blessed": "^0.1.25", + "@types/nock": "^10.0.3", "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", @@ -44,6 +45,7 @@ "ignore": "^6.0.2", "ink": "^6.1.0", "isomorphic-ws": "^5.0.0", + "nock": "^14.0.10", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -52,6 +54,7 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", + "vitest": "^3.2.4", "why-is-node-running": "^2.3.0" }, "optionalDependencies": { @@ -657,6 +660,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -665,14 +675,58 @@ "license": "MIT" }, "node_modules/@msgpack/msgpack": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.1.tgz", - "integrity": "sha512-DnBpqkMOUGayNVKyTLlkM6ILmU/m/+VUxGkuQlPQVAcvreLz5jn1OlQnWd8uHKL/ZSiljpM12rjRhr51VtvJUQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", + "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", + "dev": true, "license": "ISC", "engines": { - "node": ">= 18" + "node": ">= 10" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.6.tgz", + "integrity": "sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -1579,6 +1633,286 @@ "@opentelemetry/api": "^1.8" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.0.tgz", + "integrity": "sha512-Weap5hVbZs/yIvUZcFpAmIso8rLmwkO1LesddNjeX28tIhQkAKjRuVgAJ2xpj8wXTny7IZro9aBIgGov0qsL4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.0.tgz", + "integrity": "sha512-XcnlqvG5riTJByKX7bZ1ehe48GiF+eNkdnzV0ziLp85XyJ6tLPfhkXHv3e0h3cpZESTQa8IB+ZHhV/r02+8qKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.0.tgz", + "integrity": "sha512-kZzTIzmzAUOKteh688kN88HNaL7wxwTz9XB5dDK94AQdf9nD+lxm/H5uPKQaawUFS+klBEowqPMUPjBRKGbo/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.0.tgz", + "integrity": "sha512-WaMrgHRbFspYjvycbsbqheBmlsQBLwfZVWv/KFsT212Yz/RjEQ/9KEp1/p0Ef3ZNwbWsylmgf69St66D9NQNHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.0.tgz", + "integrity": "sha512-umfYslurvSmAK5MEyOcOGooQ6EBB2pYePQaTVlrOkIfG6uuwu9egYOlxr35lwsp6XG0NzmXW0/5o150LUioMkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.0.tgz", + "integrity": "sha512-EFXhIykAl8//4ihOjGNirF89HEUbOB8ev2aiw8ST8wFGwDdIPARh3enDlbp8aFnScl4CDK4DZLQYXaM6qpxzZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.0.tgz", + "integrity": "sha512-EwkC5N61ptruQ9wNkYfLgUWEGh+F3JZSGHkUWhaK2ISAK0d0xmiMKF0trFhRqPQFov5d9DmFiFIhWB5IC79OUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.0.tgz", + "integrity": "sha512-Iz/g1X94vIjppA4H9hN3VEedw4ObC+u+aua2J/VPJnENEJ0GeCAPBN15nJc5pS5M8JPlUhOd3oqhOWX6Un4RHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.0.tgz", + "integrity": "sha512-eYEYHYjFo/vb6k1l5uq5+Af9yuo9WaST/z+/8T5gkee+A0Sfx1NIPZtKMEQOLjm/oaeHFGpWaAO97gTPhouIfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.0.tgz", + "integrity": "sha512-LX2x0/RszFEmDfjzL6kG/vihD5CkpJ+0K6lcbqX0jAopkkXeY2ZjStngdFMFW+BK7pyrqryJgy6Jt3+oyDxrSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.0.tgz", + "integrity": "sha512-0U+56rJmJvqBCwlPFz/BcxkvdiRdNPamBfuFHrOGQtGajSMJ2OqzlvOgwj5vReRQnSA6XMKw/JL1DaBhceil+g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.0.tgz", + "integrity": "sha512-2VKOsnNyvS05HFPKtmAWtef+nZyKCot/V3Jh/A5sYMhUvtthNjp6CjakYTtc5xZ8J8Fp5FKrUWGxptVtZ2OzEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.0.tgz", + "integrity": "sha512-uY5UP7YZM4DMQiiP9Fl4/7O3UbT2p3uI0qvqLXZSGWBfyYuqi2DYQ48ExylgBN3T8AJork+b+mLGq6VXsxBfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.0.tgz", + "integrity": "sha512-qpcN2+/ivq3TcrXtZoHrS9WZplV3Nieh0gvnGb+SFZg7h/YkWsOXINJnjJRWHp9tEur7T8lMnMeQMPS7s9MjUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.0.tgz", + "integrity": "sha512-XfuI+o7a2/KA2tBeP+J1CT3siyIQyjpGEL6fFvtUdoHJK1k5iVI3qeGT2i5y6Bb+xQu08AHKBsUGJ2GsOZzXbQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.0.tgz", + "integrity": "sha512-ylkLO6G7oUiN28mork3caDmgXHqRuopAxjYDaOqs4CoU9pkfR0R/pGQb2V1x2Zg3tlFj4b/DvxZroxC3xALX6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.0.tgz", + "integrity": "sha512-1L72a+ice8xKqJ2afsAVW9EfECOhNMAOC1jH65TgghLaHSFwNzyEdeye+1vRFDNy52OGKip/vajj0ONtX7VpAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.0.tgz", + "integrity": "sha512-wluhdd1uNLk/S+ex2Yj62WFw3un2cZo2ZKXy9cOuoti5IhaPXSDSvxT3os+SJ1cjNorE1PwAOfiJU7QUH6n3Zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.0.tgz", + "integrity": "sha512-0SMTA6AeG7u2rfwdkKSo6aZD/obmA7oyhR+4ePwLzlwxNE8sfSI9zmjZXtchvBAZmtkVQNt/lZ6RxSl9wBj4pw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.0.tgz", + "integrity": "sha512-mw1/7kAGxLcfzoG7DIKFHvKr2ZUQasKOPCgT2ubkNZPgIDZOJPymqThtRWEeAlXBoipehP4BUFpBAZIrPhFg8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sentry/core": { "version": "9.29.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz", @@ -1710,6 +2044,16 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1720,6 +2064,20 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1737,6 +2095,16 @@ "@types/node": "*" } }, + "node_modules/@types/nock": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz", + "integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", @@ -1813,6 +2181,135 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@xterm/addon-serialize": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.13.0.tgz", @@ -1935,6 +2432,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -2230,6 +2737,16 @@ } } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2290,6 +2807,23 @@ "cdl": "bin/cdl.js" } }, + "node_modules/chai": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.1.tgz", + "integrity": "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", @@ -2308,6 +2842,16 @@ "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", "license": "MIT/X11" }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2743,11 +3287,11 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "ms": "^2.1.3" }, @@ -2760,6 +3304,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -2954,6 +3508,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3048,6 +3609,16 @@ "node": ">=4" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/event-stream": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-0.9.8.tgz", @@ -3091,6 +3662,16 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -3184,6 +3765,21 @@ "node": ">=8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3795,6 +4391,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3931,6 +4534,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3977,6 +4587,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", @@ -3987,6 +4604,16 @@ "node": "20 || >=22" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/map-canvas": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/map-canvas/-/map-canvas-0.1.5.tgz", @@ -4268,15 +4895,49 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "optional": true + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "license": "MIT" + "license": "MIT" + }, + "node_modules/nock": { + "version": "14.0.10", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.10.tgz", + "integrity": "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.39.5", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } }, "node_modules/node-addon-api": { "version": "7.1.1", @@ -4445,6 +5106,13 @@ "dev": true, "license": "MIT" }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4552,6 +5220,16 @@ "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -4610,6 +5288,13 @@ "node": ">=4" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4676,6 +5361,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -4751,6 +5465,16 @@ "dev": true, "license": "MIT" }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -4979,6 +5703,46 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.0.tgz", + "integrity": "sha512-jZVxJwlAptA83ftdZK1kjLZfi0f6o+vVX7ub3HaRzkehLO3l4VB4vYpMHyunhBt1sawv9fiRWPA8Qi/sbg9Kcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.47.0", + "@rollup/rollup-android-arm64": "4.47.0", + "@rollup/rollup-darwin-arm64": "4.47.0", + "@rollup/rollup-darwin-x64": "4.47.0", + "@rollup/rollup-freebsd-arm64": "4.47.0", + "@rollup/rollup-freebsd-x64": "4.47.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.0", + "@rollup/rollup-linux-arm-musleabihf": "4.47.0", + "@rollup/rollup-linux-arm64-gnu": "4.47.0", + "@rollup/rollup-linux-arm64-musl": "4.47.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.0", + "@rollup/rollup-linux-ppc64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-musl": "4.47.0", + "@rollup/rollup-linux-s390x-gnu": "4.47.0", + "@rollup/rollup-linux-x64-gnu": "4.47.0", + "@rollup/rollup-linux-x64-musl": "4.47.0", + "@rollup/rollup-win32-arm64-msvc": "4.47.0", + "@rollup/rollup-win32-ia32-msvc": "4.47.0", + "@rollup/rollup-win32-x64-msvc": "4.47.0", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5175,6 +5939,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparkline": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/sparkline/-/sparkline-0.1.2.tgz", @@ -5218,6 +5992,13 @@ "dev": true, "license": "MIT" }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -5230,6 +6011,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5372,6 +6160,26 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -5452,6 +6260,13 @@ "resolved": "https://registry.npmjs.org/term-canvas/-/term-canvas-0.0.5.tgz", "integrity": "sha512-eZ3rIWi5yLnKiUcsW8P79fKyooaLmyLWAGqBhFspqMxRNUiB4GmHHk5AzQ4LxvFbJILaXqQZLwbbATLOhCFwkw==" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -5459,6 +6274,84 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5555,6 +6448,235 @@ "dev": true, "license": "MIT" }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c1c04dc..a7a05c7 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "build-openapi-rest-system": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-system.json -o src/api-clients/client-rest-system -c @hey-api/client-fetch", "build-openapi-rest-task": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-task.json -o src/api-clients/client-rest-task -c @hey-api/client-fetch", "clean": "rimraf ./dist", + "test": "vitest", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", "postbuild": "rimraf {lib,es}/**/__tests__ {lib,es}/**/*.{spec,test}.{js,d.ts,js.map}", @@ -70,9 +71,11 @@ ], "devDependencies": { "@hey-api/openapi-ts": "^0.63.2", + "@msgpack/msgpack": "^2.7.1", "@parcel/watcher": "^2.5.1", "@tanstack/react-query": "^5.76.1", "@types/blessed": "^0.1.25", + "@types/nock": "^10.0.3", "@types/node": "^22.15.30", "@types/react": "^19.1.5", "@types/yargs": "^17.0.33", @@ -81,6 +84,8 @@ "esbuild": "^0.25.0", "ignore": "^6.0.2", "ink": "^6.1.0", + "isomorphic-ws": "^5.0.0", + "nock": "^14.0.10", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.2.1", @@ -89,9 +94,8 @@ "semver": "^6.3.0", "tslib": "^2.1.0", "typescript": "^5.7.2", - "why-is-node-running": "^2.3.0", - "isomorphic-ws": "^5.0.0", - "@msgpack/msgpack": "^2.7.1" + "vitest": "^3.2.4", + "why-is-node-running": "^2.3.0" }, "dependencies": { "@hey-api/client-fetch": "^0.7.3", diff --git a/tests/sandbox-creation.test.ts b/tests/sandbox-creation.test.ts new file mode 100644 index 0000000..e93444b --- /dev/null +++ b/tests/sandbox-creation.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import nock from 'nock' +import { CodeSandbox } from '../src/index' +import { + mockForkSandboxSuccess, + mockStartVMSuccess, + setupTestEnvironment, + cleanupTestEnvironment +} from './test-utils' + +describe('Sandbox Creation', () => { + beforeEach(() => { + setupTestEnvironment() + }) + + afterEach(() => { + cleanupTestEnvironment() + }) + + it('should successfully create and start a sandbox', async () => { + // Mock the fork sandbox API call (pcz35m is the default template) + const forkScope = mockForkSandboxSuccess('test-sandbox-123', { + title: 'Test Sandbox', + description: 'Integration test sandbox', + privacy: 1, + tags: ['integration-test', 'sdk'] + }) + + // Mock the start VM API call - use regex to match any ID + const startScope = mockStartVMSuccess('test-sandbox-123') + + // Initialize SDK + const sdk = new CodeSandbox() + + // Create sandbox + const sandbox = await sdk.sandboxes.create({ + title: 'Test Sandbox', + description: 'Integration test sandbox', + privacy: 'unlisted', + tags: ['integration-test'] + }) + + // Verify sandbox was created successfully + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe('test-sandbox-123') + + // Verify all API calls were made + expect(forkScope.isDone()).toBe(true) + expect(startScope.isDone()).toBe(true) + }, 10000) // 10 second timeout for integration test + + it('should use default template when no id is provided', async () => { + // Mock default template call - pcz35m is the default template + const forkScope = mockForkSandboxSuccess('default-sandbox-456') + + const startScope = mockStartVMSuccess('default-sandbox-456') + + const sdk = new CodeSandbox() + + // Create sandbox without specifying template id + const sandbox = await sdk.sandboxes.create() + + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe('default-sandbox-456') + expect(forkScope.isDone()).toBe(true) + expect(startScope.isDone()).toBe(true) + }) + + it('should handle API errors gracefully', async () => { + // Mock fork sandbox failure + nock('https://api.codesandbox.io') + .post('/sandbox/pcz35m/fork') + .reply(500, { message: 'Internal server error' }) + + const sdk = new CodeSandbox() + + // Expect the creation to throw an error + await expect(sdk.sandboxes.create()).rejects.toThrow() + }) +}) \ No newline at end of file diff --git a/tests/sandbox-lifecycle.test.ts b/tests/sandbox-lifecycle.test.ts new file mode 100644 index 0000000..41d4fa1 --- /dev/null +++ b/tests/sandbox-lifecycle.test.ts @@ -0,0 +1,135 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { CodeSandbox } from '../src/index' +import { + mockHibernateSuccess, + mockHibernateFailure, + mockStartVMSuccess, + mockShutdownSuccess, + mockShutdownFailure, + mockStartVMFailure, + setupTestEnvironment, + cleanupTestEnvironment +} from './test-utils' + +describe('Sandbox lifecycle operations', () => { + beforeEach(() => { + setupTestEnvironment() + }) + + afterEach(() => { + cleanupTestEnvironment() + }) + + it('should successfully hibernate a sandbox', async () => { + const sandboxId = 'test-sandbox-hibernate' + + // Mock hibernate API call + const hibernateScope = mockHibernateSuccess(sandboxId) + + const sdk = new CodeSandbox() + + // Test hibernate operation + await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow() + + // Verify API call was made + expect(hibernateScope.isDone()).toBe(true) + }) + + it('should successfully resume a sandbox', async () => { + const sandboxId = 'test-sandbox-resume' + + // Mock resume (startVm) API call + const resumeScope = mockStartVMSuccess(sandboxId, 'RESUME') + + const sdk = new CodeSandbox() + + // Test resume operation + const sandbox = await sdk.sandboxes.resume(sandboxId) + + // Verify sandbox was resumed successfully + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe(sandboxId) + expect(sandbox.bootupType).toBe('RESUME') + expect(resumeScope.isDone()).toBe(true) + }) + + it('should successfully restart a sandbox', async () => { + const sandboxId = 'test-sandbox-restart' + + // Mock shutdown API call + const shutdownScope = mockShutdownSuccess(sandboxId) + + // Mock start VM API call (after shutdown) + const startScope = mockStartVMSuccess(sandboxId, 'CLEAN') + + const sdk = new CodeSandbox() + + // Test restart operation + const sandbox = await sdk.sandboxes.restart(sandboxId) + + // Verify sandbox was restarted successfully + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe(sandboxId) + expect(sandbox.bootupType).toBe('CLEAN') + expect(shutdownScope.isDone()).toBe(true) + expect(startScope.isDone()).toBe(true) + }) + + it('should retry API calls on failure and eventually succeed', async () => { + const sandboxId = 'test-sandbox-retry-success' + + // Mock hibernate API to fail twice, then succeed on 3rd attempt + mockHibernateFailure(sandboxId, 2, 'Server error') + const hibernateScope = mockHibernateSuccess(sandboxId) + + const sdk = new CodeSandbox() + + // Test should succeed after retries + await expect(sdk.sandboxes.hibernate(sandboxId)).resolves.not.toThrow() + + // Verify all retry attempts were made + expect(hibernateScope.isDone()).toBe(true) + }, 10000) // Longer timeout for retry test + + it('should fail after exhausting all retry attempts', async () => { + const sandboxId = 'test-sandbox-retry-fail' + + // Mock hibernate API to fail 3 times (exhaust retries) + mockHibernateFailure(sandboxId, 3, 'Persistent server error') + + const sdk = new CodeSandbox() + + // Test should fail after all retries are exhausted + await expect(sdk.sandboxes.hibernate(sandboxId)).rejects.toThrow() + }, 10000) // Longer timeout for retry test + + it('should handle restart failure during shutdown phase', async () => { + const sandboxId = 'test-sandbox-restart-fail' + + // Mock shutdown to fail + mockShutdownFailure(sandboxId, 3, 'Shutdown failed') + + const sdk = new CodeSandbox() + + // Test should fail during shutdown phase + await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow('Failed to shutdown VM') + }) + + it('should handle restart failure during start phase', async () => { + const sandboxId = 'test-sandbox-restart-start-fail' + + // Mock successful shutdown + const shutdownScope = mockShutdownSuccess(sandboxId) + + // Mock start VM to fail + mockStartVMFailure(3, 'Start failed') + + const sdk = new CodeSandbox() + + // Test should fail during start phase + await expect(sdk.sandboxes.restart(sandboxId)).rejects.toThrow('Failed to start VM') + + // Verify both phases were attempted + expect(shutdownScope.isDone()).toBe(true) + }) +}) \ No newline at end of file diff --git a/tests/sandbox-retry-behavior.test.ts b/tests/sandbox-retry-behavior.test.ts new file mode 100644 index 0000000..4e03c80 --- /dev/null +++ b/tests/sandbox-retry-behavior.test.ts @@ -0,0 +1,168 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import nock from 'nock' +import { CodeSandbox } from '../src/index' +import { + mockForkSandboxSuccess, + mockStartVMSuccess, + mockStartVMFailure, + setupTestEnvironment, + cleanupTestEnvironment +} from './test-utils' + +describe('Create operation retry behavior', () => { + beforeEach(() => { + setupTestEnvironment() + }) + + afterEach(() => { + cleanupTestEnvironment() + }) + + it('should fail immediately on fork API error (no retry for fork)', async () => { + let forkRequestCount = 0 + + // Mock fork to fail once - should fail immediately since fork doesn't retry + const forkScope = nock('https://api.codesandbox.io') + .post('/sandbox/pcz35m/fork') + .reply(500, () => { + forkRequestCount++ + return { error: { errors: ['Fork failed'] } } + }) + + const sdk = new CodeSandbox() + + // Should fail immediately without retries + const startTime = Date.now() + await expect(sdk.sandboxes.create()).rejects.toThrow() + const duration = Date.now() - startTime + + // Should fail quickly (no retry delays) + expect(duration).toBeLessThan(1000) + expect(forkScope.isDone()).toBe(true) + expect(forkRequestCount).toBe(1) // Should only make 1 request (no retries) + }) + + it('should retry start VM failures and eventually succeed', async () => { + let startVMRequestCount = 0 + + // Mock successful fork + const forkScope = mockForkSandboxSuccess('test-sandbox-start-retry') + + // Mock start VM to fail twice + const failureScope = nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .times(2) + .reply(500, () => { + startVMRequestCount++ + return { error: { errors: ['Start failed'] } } + }) + + // Then succeed on 3rd attempt + const startScope = nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .reply(200, () => { + startVMRequestCount++ + return { + data: { + bootup_type: 'CLEAN', + cluster: 'test-cluster', + pitcher_url: `wss://pitcher.codesandbox.io/test-sandbox-start-retry`, + workspace_path: '/project/sandbox', + user_workspace_path: '/project/sandbox', + pitcher_manager_version: '1.0.0', + pitcher_version: '1.0.0', + latest_pitcher_version: '1.0.0', + pitcher_token: `pitcher-token-retry` + } + } + }) + + const sdk = new CodeSandbox() + + // Should succeed after start VM retries + const sandbox = await sdk.sandboxes.create() + + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe('test-sandbox-start-retry') + expect(forkScope.isDone()).toBe(true) + expect(failureScope.isDone()).toBe(true) + expect(startScope.isDone()).toBe(true) + expect(startVMRequestCount).toBe(3) // Should make 3 requests (2 failures + 1 success) + }, 10000) // Longer timeout for retries + + it('should fail create after start VM exhausts all retries', async () => { + let startVMRequestCount = 0 + + // Mock successful fork + const forkScope = mockForkSandboxSuccess('test-sandbox-start-fail') + + // Mock start VM to fail all 3 retry attempts + const failureScope = nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .times(3) + .reply(500, () => { + startVMRequestCount++ + return { error: { errors: ['Persistent start failure'] } } + }) + + const sdk = new CodeSandbox() + + // Should fail after exhausting start VM retries + await expect(sdk.sandboxes.create()).rejects.toThrow() + + expect(forkScope.isDone()).toBe(true) + expect(failureScope.isDone()).toBe(true) + expect(startVMRequestCount).toBe(3) // Should make exactly 3 retry attempts + }, 10000) // Longer timeout for retries + + it('should validate retry timing for start VM failures', async () => { + let startVMRequestCount = 0 + + // Mock successful fork + const forkScope = mockForkSandboxSuccess('test-sandbox-timing') + + // Mock start VM to fail twice + const failureScope = nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .times(2) + .reply(500, () => { + startVMRequestCount++ + return { error: { errors: ['Start failed'] } } + }) + + // Then succeed on 3rd attempt + const startScope = nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .reply(200, () => { + startVMRequestCount++ + return { + data: { + bootup_type: 'CLEAN', + cluster: 'test-cluster', + pitcher_url: `wss://pitcher.codesandbox.io/test-sandbox-timing`, + workspace_path: '/project/sandbox', + user_workspace_path: '/project/sandbox', + pitcher_manager_version: '1.0.0', + pitcher_version: '1.0.0', + latest_pitcher_version: '1.0.0', + pitcher_token: `pitcher-token-timing` + } + } + }) + + const sdk = new CodeSandbox() + + const startTime = Date.now() + const sandbox = await sdk.sandboxes.create() + const duration = Date.now() - startTime + + // Should take at least 400ms (2 retries × 200ms delay each) + expect(duration).toBeGreaterThanOrEqual(300) // Allow some tolerance + expect(sandbox).toBeDefined() + expect(sandbox.id).toBe('test-sandbox-timing') + expect(forkScope.isDone()).toBe(true) + expect(failureScope.isDone()).toBe(true) + expect(startScope.isDone()).toBe(true) + expect(startVMRequestCount).toBe(3) // Should make 3 requests (2 failures + 1 success) + }, 10000) // Longer timeout for retries +}) \ No newline at end of file diff --git a/tests/test-utils.ts b/tests/test-utils.ts new file mode 100644 index 0000000..6ad1cf8 --- /dev/null +++ b/tests/test-utils.ts @@ -0,0 +1,99 @@ +import nock from 'nock' + +export const mockForkSandboxSuccess = (sandboxId: string, options?: { + title?: string + description?: string + privacy?: number + tags?: string[] +}) => { + return nock('https://api.codesandbox.io') + .post('/sandbox/pcz35m/fork', { + privacy: options?.privacy ?? 1, + ...(options?.title && { title: options.title }), + ...(options?.description && { description: options.description }), + tags: options?.tags ?? ['sdk'], + path: '/SDK' + }) + .reply(200, { + data: { + id: sandboxId, + title: options?.title ?? 'Test Sandbox', + description: options?.description, + privacy: options?.privacy ?? 1, + tags: options?.tags ?? ['sdk'], + created_at: '2025-01-21T12:00:00Z', + updated_at: '2025-01-21T12:00:00Z' + } + }) +} + +export const mockStartVMSuccess = (sandboxId: string, bootupType: 'CLEAN' | 'RESUME' = 'CLEAN') => { + return nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .reply(200, { + data: { + bootup_type: bootupType, + cluster: 'test-cluster', + pitcher_url: `wss://pitcher.codesandbox.io/${sandboxId}`, + workspace_path: '/project/sandbox', + user_workspace_path: '/project/sandbox', + pitcher_manager_version: '1.0.0', + pitcher_version: '1.0.0', + latest_pitcher_version: '1.0.0', + pitcher_token: `pitcher-token-${sandboxId.split('-').pop()}` + } + }) +} + +export const mockStartVMFailure = (times: number = 1, errorMessage: string = 'Start failed') => { + return nock('https://api.codesandbox.io') + .post(/\/vm\/.*\/start/) + .times(times) + .reply(500, { error: { errors: [errorMessage] } }) +} + +export const mockHibernateSuccess = (sandboxId: string) => { + return nock('https://api.codesandbox.io') + .post(`/vm/${sandboxId}/hibernate`) + .reply(200, { + data: { + success: true + } + }) +} + +export const mockHibernateFailure = (sandboxId: string, times: number = 1, errorMessage: string = 'Server error') => { + return nock('https://api.codesandbox.io') + .post(`/vm/${sandboxId}/hibernate`) + .times(times) + .reply(500, { error: { errors: [errorMessage] } }) +} + +export const mockShutdownSuccess = (sandboxId: string) => { + return nock('https://api.codesandbox.io') + .post(`/vm/${sandboxId}/shutdown`) + .reply(200, { + data: { + success: true + } + }) +} + +export const mockShutdownFailure = (sandboxId: string, times: number = 1, errorMessage: string = 'Shutdown failed') => { + return nock('https://api.codesandbox.io') + .post(`/vm/${sandboxId}/shutdown`) + .times(times) + .reply(500, { error: { errors: [errorMessage] } }) +} + +export const setupTestEnvironment = () => { + process.env.CSB_API_KEY = 'csb_test_key_123' + nock.cleanAll() +} + +export const cleanupTestEnvironment = () => { + if (!nock.isDone()) { + console.error('Unused nock interceptors:', nock.pendingMocks()) + } + nock.cleanAll() +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..cba2eed --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, + define: { + CSB_SDK_VERSION: JSON.stringify('2.1.0-rc.4'), + }, +}) \ No newline at end of file From 029e3a554010fe278eaeb6632f9df45264cbdc29 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 22 Aug 2025 12:42:11 +0200 Subject: [PATCH 211/241] fix: properly dispose and prevent wakeups (#170) --- src/AgentClient/AgentConnection.ts | 6 +++ src/SandboxClient/index.ts | 64 ++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/AgentClient/AgentConnection.ts b/src/AgentClient/AgentConnection.ts index 69019df..36f4a42 100644 --- a/src/AgentClient/AgentConnection.ts +++ b/src/AgentClient/AgentConnection.ts @@ -38,6 +38,7 @@ export class AgentConnection { } private _state: IAgentClientState = "CONNECTED"; + private _isDisposed = false; get state() { return this._state; @@ -172,6 +173,10 @@ export class AgentConnection { pitcherRequest: T, options: IRequestOptions = {} ) { + if (this._isDisposed) { + throw new Error("Cannot perform operation: SandboxClient has been disposed"); + } + const { timeoutMs } = options; const request = this.createRequest(pitcherRequest, timeoutMs); @@ -265,6 +270,7 @@ export class AgentConnection { } dispose(): void { + this._isDisposed = true; this.errorEmitter.dispose(); this.messageEmitter.dispose(); this.connection.dispose(); diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index 9496150..a8b0fc6 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -190,8 +190,12 @@ export class SandboxClient { } this.keepAliveInterval = null; - // Only attempt auto-reconnect on DISCONNECTED, not HIBERNATED - if (state === "DISCONNECTED" && !this.isExplicitlyDisconnected) { + // Only attempt auto-reconnect on DISCONNECTED, not HIBERNATED, and not if disposed + if ( + state === "DISCONNECTED" && + !this.isExplicitlyDisconnected && + !this.disposable.isDisposed + ) { this.attemptAutoReconnect(); } } else if (state === "CONNECTED") { @@ -332,6 +336,10 @@ export class SandboxClient { "sandboxClient.disconnect", { "sandbox.id": this.id }, async () => { + if (this.disposable.isDisposed) { + throw new Error("Cannot disconnect: SandboxClient has been disposed"); + } + this.isExplicitlyDisconnected = true; if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); @@ -351,6 +359,10 @@ export class SandboxClient { "sandboxClient.reconnect", { "sandbox.id": this.id }, async () => { + if (this.disposable.isDisposed) { + throw new Error("Cannot reconnect: SandboxClient has been disposed"); + } + this.isExplicitlyDisconnected = false; return this.agentClient.reconnect(); } @@ -368,6 +380,9 @@ export class SandboxClient { try { await retryWithDelay( async () => { + if (this.disposable.isDisposed) { + throw new Error("Client disposed - stopping auto-reconnect"); + } if (this.isExplicitlyDisconnected) { throw new Error( "Explicit disconnect - stopping auto-reconnect" @@ -397,31 +412,53 @@ export class SandboxClient { * If enabled, we will keep the sandbox from hibernating as long as the SDK is connected to it. */ public keepActiveWhileConnected(enabled: boolean) { + if (this.disposable.isDisposed) { + throw new Error("Client disposed"); + } + // Used to manage the interval when disconnects happen this.shouldKeepAlive = enabled; if (enabled) { if (!this.keepAliveInterval) { this.keepAliveInterval = setInterval(() => { - this.agentClient.system.update() + if (this.disposable.isDisposed) { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + return; + } + + this.agentClient.system + .update() .then(() => { // Reset failure count on success this.keepAliveFailures = 0; }) .catch((error) => { this.keepAliveFailures++; - console.warn(`Keep-alive failed (${this.keepAliveFailures}/${this.maxKeepAliveFailures}):`, error); - + console.warn( + `Keep-alive failed (${this.keepAliveFailures}/${this.maxKeepAliveFailures}):`, + error + ); + // If we've hit max failures, stop aggressive keep-alive to prevent connection thrashing if (this.keepAliveFailures >= this.maxKeepAliveFailures) { - console.warn("Max keep-alive failures reached, reducing frequency to prevent connection issues"); + console.warn( + "Max keep-alive failures reached, reducing frequency to prevent connection issues" + ); if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; } - // Restart with longer interval after failures + // Restart with longer interval after failures, but only if not disposed setTimeout(() => { - if (this.shouldKeepAlive && !this.keepAliveInterval) { + if ( + this.shouldKeepAlive && + !this.keepAliveInterval && + !this.disposable.isDisposed + ) { this.keepActiveWhileConnected(true); this.keepAliveFailures = 0; // Reset for retry } @@ -441,6 +478,17 @@ export class SandboxClient { * Dispose the session, this will disconnect from the sandbox and dispose all resources. If you want to do a clean disconnect, await "disconnect" method first. */ dispose() { + // Prevent any future reconnection attempts + this.isExplicitlyDisconnected = true; + + // Clear keep-alive settings to prevent restart + this.shouldKeepAlive = false; + + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval); + this.keepAliveInterval = null; + } + this.disposable.dispose(); } } From f9987b1a0b625bd580b94b6035c17d09d561cbbc Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 22 Aug 2025 12:42:23 +0200 Subject: [PATCH 212/241] fix: friendly 503 error for overloaded Sandbox (#172) --- src/utils/api.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/api.ts b/src/utils/api.ts index 8fd9dc1..94a6c28 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -148,6 +148,10 @@ export function handleResponse( throw new Error(errorPrefix + ": Bad gateway"); } + if (result.response.status === 503) { + throw new Error(errorPrefix + ": The sandbox is currently overloaded. Please review your logic to reduce the number of concurrent requests or try again in a moment."); + } + if ("error" in result) { const error = (result.error as { errors: string[] }).errors[0]; throw new Error(errorPrefix + ": " + error); From e50a7230be7a329e9fe6cc0a94c7d0d28e6f4e9b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 22 Aug 2025 12:52:39 +0200 Subject: [PATCH 213/241] chore: fix version number (#173) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7a05c7..2ae9582 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0-rc.4", + "version": "2.0.7", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 73e3e5c1ba2d9a763df7ed8b1643bf48ec382335 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Fri, 22 Aug 2025 15:38:27 +0200 Subject: [PATCH 214/241] chore(main): release 2.1.0 (#149) Co-authored-by: Christian Alfoni --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0500fdc..5176470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [2.1.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.7...v2.1.0) (2025-08-22) + + +### Features + +* add fetching single sandbox ([#142](https://github.com/codesandbox/codesandbox-sdk/issues/142)) ([2f58d43](https://github.com/codesandbox/codesandbox-sdk/commit/2f58d43ee44c98eeb7d0e917c8b423a80d202585)) +* add listRunning method to sandboxes namespace ([#145](https://github.com/codesandbox/codesandbox-sdk/issues/145)) ([6050dbd](https://github.com/codesandbox/codesandbox-sdk/commit/6050dbd289058c782e634a7ce9e25d9cf5921276)) +* add open telemetry for sandboxes methods ([#147](https://github.com/codesandbox/codesandbox-sdk/issues/147)) ([b331315](https://github.com/codesandbox/codesandbox-sdk/commit/b3313153b357dfda0d666a6a56ea79f6aa3dbbdf)) +* add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors ([#150](https://github.com/codesandbox/codesandbox-sdk/issues/150)) ([6ef2bf5](https://github.com/codesandbox/codesandbox-sdk/commit/6ef2bf51120068e35ff43607e3353a69d8fbf070)) +* Debug Sandboxes through CLI ([#163](https://github.com/codesandbox/codesandbox-sdk/issues/163)) ([9af1cdd](https://github.com/codesandbox/codesandbox-sdk/commit/9af1cdd0657c1a74572dc473ac6e04f6e1a40cd5)) +* enhance container setup logging in build command ([836a7a6](https://github.com/codesandbox/codesandbox-sdk/commit/836a7a6ed1dc7c73d737e04475083faf8d6d8fc4)) +* enhance container setup logging in build command ([a6f9fe7](https://github.com/codesandbox/codesandbox-sdk/commit/a6f9fe7c93450cfeded21048f62a6a2f0b842091)) +* private sandbox, public hosts with public-hosts privacy ([#154](https://github.com/codesandbox/codesandbox-sdk/issues/154)) ([dce7caf](https://github.com/codesandbox/codesandbox-sdk/commit/dce7cafd719e398b1f1421776bd55fa5601eccd0)) + + +### Bug Fixes + +* Add custom retry delay support for startVM API calls ([#156](https://github.com/codesandbox/codesandbox-sdk/issues/156)) ([ce3a282](https://github.com/codesandbox/codesandbox-sdk/commit/ce3a2823e66198f453d257a68757226d50e3bf17)) +* Decouple pitcher-client ([#148](https://github.com/codesandbox/codesandbox-sdk/issues/148)) ([3a6f9ea](https://github.com/codesandbox/codesandbox-sdk/commit/3a6f9ea213d978dc5a896bfc4d275deae6608abe)) +* friendly 503 error for overloaded Sandbox ([#172](https://github.com/codesandbox/codesandbox-sdk/issues/172)) ([f9987b1](https://github.com/codesandbox/codesandbox-sdk/commit/f9987b1a0b625bd580b94b6035c17d09d561cbbc)) +* include response handling in retries and dispose clients in build to avoid reconnecst ([#162](https://github.com/codesandbox/codesandbox-sdk/issues/162)) ([f70903a](https://github.com/codesandbox/codesandbox-sdk/commit/f70903a593c51bdfcdbf49d86b7b7bdce7cfe4a4)) +* properly dispose and prevent wakeups ([#170](https://github.com/codesandbox/codesandbox-sdk/issues/170)) ([029e3a5](https://github.com/codesandbox/codesandbox-sdk/commit/029e3a554010fe278eaeb6632f9df45264cbdc29)) +* Stabilize websocket connection ([#166](https://github.com/codesandbox/codesandbox-sdk/issues/166)) ([cb2f330](https://github.com/codesandbox/codesandbox-sdk/commit/cb2f330897c5e4c26637bc39a85d0e28dc4331d6)) +* update log line length to be smaller ([9a3099f](https://github.com/codesandbox/codesandbox-sdk/commit/9a3099fc3984c6ff01fa901ac1616cbc7b883119)) + ## [2.0.7](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.6...v2.0.7) (2025-08-06) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index fcacaf2..e34bc34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0-rc.4", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.1.0-rc.4", + "version": "2.1.0", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.7.3", diff --git a/package.json b/package.json index 2ae9582..725a378 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.0.7", + "version": "2.1.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From c5a8ffdf4bcba321c4d3c9581f752c5e72a3bc8f Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 29 Aug 2025 16:41:07 +0200 Subject: [PATCH 215/241] fix: Queue messages on lost connection and reconnect also on hibernation (#176) * fix: Queue messages on lost connection and reconnect also on hibernation * revert hibernated ignore as it would be breaking --- src/AgentClient/AgentConnection.ts | 51 +++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/AgentClient/AgentConnection.ts b/src/AgentClient/AgentConnection.ts index 36f4a42..8627ee5 100644 --- a/src/AgentClient/AgentConnection.ts +++ b/src/AgentClient/AgentConnection.ts @@ -57,6 +57,7 @@ export class AgentConnection { private nextMessageId = 0; private pendingMessages = new Map>(); + private messageQueue: PendingPitcherMessage[] = []; private notificationListeners: Record< string, SliceList<(params: any) => void> @@ -174,18 +175,29 @@ export class AgentConnection { options: IRequestOptions = {} ) { if (this._isDisposed) { - throw new Error("Cannot perform operation: SandboxClient has been disposed"); + throw new Error( + "Cannot perform operation: SandboxClient has been disposed" + ); } - + const { timeoutMs } = options; const request = this.createRequest(pitcherRequest, timeoutMs); + // If not connected, queue the message for later + if (this.state !== "CONNECTED") { + this.messageQueue.push(request); + return request.unwrap(); + } + try { // This will throw if we are not in the right connection state this.connection.send(request.message); return request.unwrap(); } catch (error) { + // If send fails, queue the message for retry on reconnect + this.messageQueue.push(request); + this.errorEmitter.fire({ message: (error as Error).message, extras: { @@ -195,9 +207,8 @@ export class AgentConnection { }, }); - // We always want to return a promise from the method so it does not matter if the error is related to disconnect - // or Pitcher giving an error. It all ends up in the `catch` of the unwrapped promise - return Promise.reject(error); + // Return the queued message's promise instead of rejecting + return request.unwrap(); } } @@ -267,6 +278,28 @@ export class AgentConnection { }); this.state = "CONNECTED"; + + // Flush the message queue after successful reconnection + await this.flushMessageQueue(); + } + + private async flushMessageQueue() { + const queuedMessages = [...this.messageQueue]; + this.messageQueue = []; + + for (const message of queuedMessages) { + if (message.isDisposed) { + // Skip messages that have already been disposed (timed out) + continue; + } + + try { + this.connection.send(message.message); + } catch (error) { + // If send still fails, reject the message + message.reject(error as Error); + } + } } dispose(): void { @@ -275,7 +308,15 @@ export class AgentConnection { this.messageEmitter.dispose(); this.connection.dispose(); this.disposePendingMessages(); + this.disposeQueuedMessages(); this.pendingMessages.clear(); + this.messageQueue = []; this.notificationListeners = {}; } + + private disposeQueuedMessages() { + this.messageQueue.forEach((queuedMessage) => { + queuedMessage.dispose("Client disposed"); + }); + } } From a9ec1a78c2c83a53dfd0649dfa1764589b9fa671 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 29 Aug 2025 16:42:18 +0200 Subject: [PATCH 216/241] fix: prevent api config overrides (#177) * fix: prevent api config overrides * do not override baseUrl or fetch options, would be breaking --- src/utils/api.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 94a6c28..67ee65e 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -50,8 +50,8 @@ export function createApiClient( fetch: (request) => enhanceFetch(request, instrumentation), ...config, headers: { - Authorization: `Bearer ${apiKey}`, ...config.headers, + Authorization: `Bearer ${apiKey}`, }, }) ); @@ -69,11 +69,14 @@ export function getStartOptions(opts: StartSandboxOpts | undefined) { if (!opts) return {}; // Warn about hibernation timeouts that are too short and may cause connection issues - if (opts.hibernationTimeoutSeconds !== undefined && opts.hibernationTimeoutSeconds < 60) { + if ( + opts.hibernationTimeoutSeconds !== undefined && + opts.hibernationTimeoutSeconds < 60 + ) { console.warn( `Warning: hibernationTimeoutSeconds (${opts.hibernationTimeoutSeconds}s) is less than 60 seconds. ` + - `This may cause connection instability and frequent disconnections. ` + - `Consider using at least 60 seconds for stable websocket connections.` + `This may cause connection instability and frequent disconnections. ` + + `Consider using at least 60 seconds for stable websocket connections.` ); } @@ -149,7 +152,10 @@ export function handleResponse( } if (result.response.status === 503) { - throw new Error(errorPrefix + ": The sandbox is currently overloaded. Please review your logic to reduce the number of concurrent requests or try again in a moment."); + throw new Error( + errorPrefix + + ": The sandbox is currently overloaded. Please review your logic to reduce the number of concurrent requests or try again in a moment." + ); } if ("error" in result) { From 04381a071fb54aa385ad40ed7ff6d489945565ef Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 1 Sep 2025 12:44:55 +0200 Subject: [PATCH 217/241] fix: ensure private preview on private sandbox (#179) --- src/Sandboxes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 669bff6..8f032ec 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -397,7 +397,7 @@ function mapPrivacyForApi(privacy: SandboxPrivacy): { case "unlisted": return { mappedPrivacy: 1 }; // Keep as unlisted case "private": - return { mappedPrivacy: 2 }; // Keep as private + return { mappedPrivacy: 2, privatePreview: true }; // Keep as private case "public": return { mappedPrivacy: 1 }; // Map to unlisted case "public-hosts": From 493c5d52d1eaa527d099b0270a5b0e78a694abbd Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 2 Sep 2025 09:44:07 +0200 Subject: [PATCH 218/241] feat: batch writes (#175) --- package-lock.json | 7 +++ package.json | 1 + src/SandboxClient/filesystem.ts | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/package-lock.json b/package-lock.json index e34bc34..7b9f89d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "chalk": "^5.4.1", "cli-table3": "^0.6.3", "date-fns": "^4.1.0", + "fflate": "^0.8.2", "isbinaryfile": "^5.0.4", "ora": "^8.2.0", "path": "^0.12.7", @@ -3672,6 +3673,12 @@ "node": ">=12.0.0" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", diff --git a/package.json b/package.json index 725a378..6da016f 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "chalk": "^5.4.1", "cli-table3": "^0.6.3", "date-fns": "^4.1.0", + "fflate": "^0.8.2", "isbinaryfile": "^5.0.4", "ora": "^8.2.0", "path": "^0.12.7", diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index ec039ef..3e1ac32 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -3,6 +3,7 @@ import { type IAgentClient } from "../AgentClient/agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; +import { zip } from "fflate"; export type FSStatResult = { type: "file" | "directory"; @@ -39,6 +40,11 @@ export type Watcher = { onEvent: Event; }; +export type BatchWriteFile = { + path: string; + content: string | Uint8Array; +}; + export class FileSystem { private disposable = new Disposable(); private tracer?: Tracer; @@ -137,6 +143,97 @@ export class FileSystem { ); } + /** + * Batch write multiple files by zipping them, uploading the zip, and extracting it on the sandbox. + * This is more efficient than writing many files individually. + * Files will be created/overwritten as needed. + */ + async batchWrite(files: BatchWriteFile[]): Promise { + return this.withSpan( + "fs.batchWrite", + { + "fs.fileCount": files.length, + }, + async () => { + if (files.length === 0) { + return; + } + + // Create a zip file containing all the files + const zipData: Record = {}; + + for (const file of files) { + const content = + typeof file.content === "string" + ? new TextEncoder().encode(file.content) + : file.content; + zipData[file.path] = content; + } + + // Create the zip using fflate + const zipBytes = await new Promise((resolve, reject) => { + zip(zipData, (err, data) => { + if (err) reject(err); + else resolve(data); + }); + }); + + // Write the zip file to a temporary location + const tempZipPath = `/tmp/batch_write_${Date.now()}.zip`; + await this.writeFile(tempZipPath, zipBytes); + + try { + // Extract the zip file using unzip command + const result = await this.agentClient.shells.create( + this.agentClient.workspacePath, + { cols: 128, rows: 24 }, + `cd ${this.agentClient.workspacePath} && unzip -o ${tempZipPath}`, + "COMMAND", + true + ); + + if (result.status === "ERROR" || result.status === "KILLED") { + throw new Error( + `Failed to extract batch files: ${ + result.buffer?.join("\n") || "Unknown error" + }` + ); + } + + // Wait for the command to complete if it's still running + if (result.status === "RUNNING") { + // Wait for shell exit event + await new Promise((resolve, reject) => { + const disposable = this.agentClient.shells.onShellExited( + ({ shellId, exitCode }) => { + if (shellId === result.shellId) { + disposable.dispose(); + if (exitCode === 0) { + resolve(); + } else { + reject( + new Error( + `Unzip command failed with exit code ${exitCode}` + ) + ); + } + } + } + ); + }); + } + } finally { + // Always clean up the temporary zip file, regardless of success or failure + try { + await this.remove(tempZipPath); + } catch { + // Ignore cleanup errors - file might already be deleted or not exist + } + } + } + ); + } + /** * Create a directory. */ From b6f484665de0bf0533127e098ff0ef1aa641a84b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 2 Sep 2025 11:12:07 +0200 Subject: [PATCH 219/241] feat: Add traceparent for all requests to API (#180) --- src/utils/api.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/utils/api.ts b/src/utils/api.ts index 67ee65e..72dd452 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -25,6 +25,9 @@ async function enhanceFetch( }`.trim() ); + // Add trace parent header for distributed tracing + headers.set("traceparent", generateTraceParent()); + // Create new request with updated headers and optionally add instrumentation return instrumentation ? instrumentation( @@ -169,3 +172,18 @@ export function handleResponse( return result.data.data; } + +export function generateTraceParent(): string { + // Generate W3C Trace Context traceparent header + // Format: version-trace-id-span-id-trace-flags + const version = "00"; // Current version is 00 + const traceId = Array.from({ length: 32 }, () => + Math.floor(Math.random() * 16).toString(16) + ).join(""); // 128-bit (32 hex chars) + const spanId = Array.from({ length: 16 }, () => + Math.floor(Math.random() * 16).toString(16) + ).join(""); // 64-bit (16 hex chars) + const traceFlags = "01"; // Sampled flag set to 1 + + return `${version}-${traceId}-${spanId}-${traceFlags}`; +} From ca0e02aaa0161575d4038f0e6e906b9168d3f4f9 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 2 Sep 2025 14:39:34 +0200 Subject: [PATCH 220/241] chore(main): release 2.2.0 (#178) --- CHANGELOG.md | 15 +++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5176470..f48820a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [2.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.1.0...v2.2.0) (2025-09-02) + + +### Features + +* Add traceparent for all requests to API ([#180](https://github.com/codesandbox/codesandbox-sdk/issues/180)) ([b6f4846](https://github.com/codesandbox/codesandbox-sdk/commit/b6f484665de0bf0533127e098ff0ef1aa641a84b)) +* batch writes ([#175](https://github.com/codesandbox/codesandbox-sdk/issues/175)) ([493c5d5](https://github.com/codesandbox/codesandbox-sdk/commit/493c5d52d1eaa527d099b0270a5b0e78a694abbd)) + + +### Bug Fixes + +* ensure private preview on private sandbox ([#179](https://github.com/codesandbox/codesandbox-sdk/issues/179)) ([04381a0](https://github.com/codesandbox/codesandbox-sdk/commit/04381a071fb54aa385ad40ed7ff6d489945565ef)) +* prevent api config overrides ([#177](https://github.com/codesandbox/codesandbox-sdk/issues/177)) ([a9ec1a7](https://github.com/codesandbox/codesandbox-sdk/commit/a9ec1a78c2c83a53dfd0649dfa1764589b9fa671)) +* Queue messages on lost connection and reconnect also on hibernation ([#176](https://github.com/codesandbox/codesandbox-sdk/issues/176)) ([c5a8ffd](https://github.com/codesandbox/codesandbox-sdk/commit/c5a8ffdf4bcba321c4d3c9581f752c5e72a3bc8f)) + ## [2.1.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.0.7...v2.1.0) (2025-08-22) diff --git a/package-lock.json b/package-lock.json index 7b9f89d..423029c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.2.0", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.7.3", diff --git a/package.json b/package.json index 6da016f..22728f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.1.0", + "version": "2.2.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From d0bdf3128787cdea22ea7e18c5af63594c303402 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 16 Sep 2025 14:27:13 +0200 Subject: [PATCH 221/241] fix: Throw error when invalid port is used (#188) * fix: Throw error when invalid port is used * fix: show actual wrong input value --- src/bin/commands/build.ts | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index 35e733a..bb75785 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -98,9 +98,49 @@ export const buildCommand: yargs.CommandModule< describe: "Path to the project that we'll create a snapshot from", type: "string", demandOption: "Path to the project is required", + }) + .check((argv) => { + // Validate ports parameter - ensure all values are valid numbers + if (argv.ports && argv.ports.length > 0) { + const invalidPortsWithOriginal: string[] = []; + + // Get the original arguments to show what the user actually typed + const originalArgs = process.argv; + const portArgIndices: number[] = []; + + // Find all --ports arguments in the original command + originalArgs.forEach((arg, i) => { + if (arg === '--ports' && i + 1 < originalArgs.length) { + portArgIndices.push(i + 1); + } + }); + + argv.ports.forEach((port, i) => { + const isInvalid = !Number.isInteger(port) || + port <= 0 || + port > 65535 || + !Number.isFinite(port); + + if (isInvalid) { + // Try to get the original input, fallback to the parsed value + const originalInput = portArgIndices[i] ? originalArgs[portArgIndices[i]] : String(port); + invalidPortsWithOriginal.push(originalInput); + } + }); + + if (invalidPortsWithOriginal.length > 0) { + throw new Error( + `Invalid port value(s): ${invalidPortsWithOriginal.join( + ", " + )}. Ports must be integers between 1 and 65535.` + ); + } + } + return true; }), handler: async (argv) => { + const apiKey = getInferredApiKey(); const api = new API({ apiKey, instrumentation: instrumentedFetch }); const sdk = new CodeSandbox(apiKey); From 7f6f7b4508bcf539e1469ec06de485a66c26c785 Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Tue, 16 Sep 2025 08:30:42 -0400 Subject: [PATCH 222/241] chore(main): release 2.2.1 (#189) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f48820a..672806e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.2.1](https://github.com/codesandbox/codesandbox-sdk/compare/v2.2.0...v2.2.1) (2025-09-16) + + +### Bug Fixes + +* Throw error when invalid port is used ([#188](https://github.com/codesandbox/codesandbox-sdk/issues/188)) ([d0bdf31](https://github.com/codesandbox/codesandbox-sdk/commit/d0bdf3128787cdea22ea7e18c5af63594c303402)) + ## [2.2.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.1.0...v2.2.0) (2025-09-02) diff --git a/package-lock.json b/package-lock.json index 423029c..e63ba55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.2.0", + "version": "2.2.1", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.7.3", diff --git a/package.json b/package.json index 22728f7..74f9564 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.2.0", + "version": "2.2.1", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 3f14843c2ea354cb9ba0c1b9137ca9ca92c2ee60 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 26 Sep 2025 13:15:42 +0200 Subject: [PATCH 223/241] fix: allow --help without api key (#190) --- src/bin/commands/previewHosts.ts | 10 ++++++++-- src/bin/ui/sdkContext.tsx | 15 +++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/bin/commands/previewHosts.ts b/src/bin/commands/previewHosts.ts index 91a1b94..4c6144f 100644 --- a/src/bin/commands/previewHosts.ts +++ b/src/bin/commands/previewHosts.ts @@ -3,8 +3,10 @@ import type * as yargs from "yargs"; import { API } from "../../API"; import { getInferredApiKey } from "../../utils/constants"; -const apiKey = getInferredApiKey(); -const api = new API({ apiKey }); +function getAPI() { + const apiKey = getInferredApiKey(); + return new API({ apiKey }); +} export const previewHostsCommand: yargs.CommandModule = { command: "preview-hosts", @@ -16,6 +18,7 @@ export const previewHostsCommand: yargs.CommandModule = { command: "list", describe: "List current preview hosts", handler: async () => { + const api = getAPI(); const response = await api.listPreviewHosts(); const hosts = response.preview_hosts.map(({ host }) => host); if (hosts.length) { @@ -35,6 +38,7 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { + const api = getAPI(); const response = await api.listPreviewHosts(); let hosts = response.preview_hosts.map(({ host }) => host); const hostToAdd = (argv.host as string).trim(); @@ -57,6 +61,7 @@ export const previewHostsCommand: yargs.CommandModule = { demandOption: true, }), handler: async (argv) => { + const api = getAPI(); const response = await api.listPreviewHosts(); let hosts = response.preview_hosts.map(({ host }) => host); const hostToRemove = (argv.host as string).trim(); @@ -73,6 +78,7 @@ export const previewHostsCommand: yargs.CommandModule = { command: "clear", describe: "Clear all preview hosts", handler: async () => { + const api = getAPI(); const response = await api.listPreviewHosts(); const hosts = response.preview_hosts.map(({ host }) => host); if (hosts.length === 0) { diff --git a/src/bin/ui/sdkContext.tsx b/src/bin/ui/sdkContext.tsx index 38d0676..a9ace58 100644 --- a/src/bin/ui/sdkContext.tsx +++ b/src/bin/ui/sdkContext.tsx @@ -5,16 +5,15 @@ import { API } from "../../API"; import { getInferredApiKey } from "../../utils/constants"; import { instrumentedFetch } from "../utils/sentry"; -const apiKey = getInferredApiKey(); -const sdk = new CodeSandbox(apiKey); -const api = new API({ apiKey, instrumentation: instrumentedFetch }); - -export const SDKContext = createContext<{ sdk: CodeSandbox; api: API }>({ - sdk, - api, -}); +export const SDKContext = createContext( + null as unknown as { sdk: CodeSandbox; api: API } +); export const SDKProvider = ({ children }: { children: React.ReactNode }) => { + const apiKey = getInferredApiKey(); + const sdk = new CodeSandbox(apiKey); + const api = new API({ apiKey, instrumentation: instrumentedFetch }); + return ( {children} ); From ce5773624c03455c8fba4aaf7023e9872ad10e3b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 26 Sep 2025 14:55:32 +0200 Subject: [PATCH 224/241] New best practices release (#191) * fix: Default to public-hosts privacy. Unlisted is a security concern. Will not break default behavior as end users should never open Sandboxes in workspace of SDK user * fix: Warn about creating sandboxes from sandboxes * chore: deprecate automatic lifecycle management related config * fix: make user (session) creation optional and deprecate it * fix: properly handle optional id when getting session * fix: ignore files should be part of template * fix: deprecate hibernation timeout * feat: delete method to delete Sandboxes * chore: remove deprecation indications, we'll rely on docs for now --- openapi.json | 69 +- src/API.ts | 10 + src/Sandbox.ts | 3 +- src/Sandboxes.ts | 80 +- .../client-rest-container/client.gen.ts | 4 +- .../client-rest-container/index.ts | 4 +- .../client-rest-container/sdk.gen.ts | 57 +- .../client-rest-container/types.gen.ts | 172 +- src/api-clients/client-rest-fs/client.gen.ts | 4 +- src/api-clients/client-rest-fs/index.ts | 4 +- src/api-clients/client-rest-fs/sdk.gen.ts | 508 ++--- src/api-clients/client-rest-fs/types.gen.ts | 1463 ++++++------ src/api-clients/client-rest-git/client.gen.ts | 4 +- src/api-clients/client-rest-git/index.ts | 4 +- src/api-clients/client-rest-git/sdk.gen.ts | 410 ++-- src/api-clients/client-rest-git/types.gen.ts | 1035 +++++---- .../client-rest-setup/client.gen.ts | 4 +- src/api-clients/client-rest-setup/index.ts | 4 +- src/api-clients/client-rest-setup/sdk.gen.ts | 219 +- .../client-rest-setup/types.gen.ts | 383 ++-- .../client-rest-shell/client.gen.ts | 4 +- src/api-clients/client-rest-shell/index.ts | 4 +- src/api-clients/client-rest-shell/sdk.gen.ts | 273 +-- .../client-rest-shell/types.gen.ts | 597 +++-- .../client-rest-system/client.gen.ts | 4 +- src/api-clients/client-rest-system/index.ts | 4 +- src/api-clients/client-rest-system/sdk.gen.ts | 111 +- .../client-rest-system/types.gen.ts | 294 ++- .../client-rest-task/client.gen.ts | 4 +- src/api-clients/client-rest-task/index.ts | 4 +- src/api-clients/client-rest-task/sdk.gen.ts | 273 +-- src/api-clients/client-rest-task/types.gen.ts | 742 +++--- src/api-clients/client/client.gen.ts | 4 +- src/api-clients/client/index.ts | 4 +- src/api-clients/client/sdk.gen.ts | 1003 ++++---- src/api-clients/client/types.gen.ts | 2020 ++++++++--------- src/bin/utils/files.ts | 1 - src/types.ts | 2 +- 38 files changed, 4439 insertions(+), 5350 deletions(-) diff --git a/openapi.json b/openapi.json index 5512a4e..5890be8 100644 --- a/openapi.json +++ b/openapi.json @@ -1062,6 +1062,16 @@ "enum": ["browser", "vm"], "type": "string" }, + "settings": { + "description": "Sandbox settings.", + "properties": { + "use_pint": { + "description": "Whether to use Pint for the sandbox.", + "type": "boolean" + } + }, + "type": "object" + }, "tags": { "default": [], "description": "List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags.", @@ -1217,6 +1227,33 @@ "title": "TokenCreateResponse", "type": "object" }, + "VMDeleteResponse": { + "allOf": [ + { + "properties": { + "errors": { + "items": { + "oneOf": [ + { "type": "string" }, + { "additionalProperties": true, "type": "object" } + ], + "title": "Error" + }, + "type": "array" + }, + "success": { "type": "boolean" } + }, + "title": "Response", + "type": "object" + }, + { + "properties": { "data": { "properties": {}, "type": "object" } }, + "type": "object" + } + ], + "title": "VMDeleteResponse", + "type": "object" + }, "TemplateCreateResponse": { "allOf": [ { @@ -2234,6 +2271,36 @@ "tags": ["vm"] } }, + "/vm/{id}": { + "delete": { + "callbacks": {}, + "description": "Deletes a VM, permanently removing it from the system.\n\nThis endpoint can only be used on VMs that belong to your team's workspace.\nDeleting a VM is irreversible and will permanently delete all data associated with it.\n", + "operationId": "vm/delete", + "parameters": [ + { + "description": "Sandbox ID", + "example": "new", + "in": "path", + "name": "id", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VMDeleteResponse" } + } + }, + "description": "VM Delete Response" + } + }, + "security": [{ "authorization": ["vm:manage"] }], + "summary": "Delete a VM", + "tags": ["vm"] + } + }, "/vm/{id}/hibernate": { "post": { "callbacks": {}, @@ -2604,6 +2671,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], "tags": [] } diff --git a/src/API.ts b/src/API.ts index 8dcead5..42898a9 100644 --- a/src/API.ts +++ b/src/API.ts @@ -19,6 +19,7 @@ import { vmListClusters, vmListRunningVms, vmCreateTag, + vmDelete, vmHibernate, vmUpdateHibernationTimeout, vmCreateSession, @@ -42,6 +43,7 @@ import type { TemplatesCreateData, VmAssignTagAliasData, VmCreateTagData, + VmDeleteData, VmHibernateData, VmUpdateHibernationTimeoutData, VmCreateSessionData, @@ -247,6 +249,14 @@ export class API { return handleResponse(response, "Failed to create VM tag"); } + async deleteVm(id: string) { + const response = await vmDelete({ + client: this.client, + path: { id }, + }); + return handleResponse(response, `Failed to delete VM ${id}`); + } + async hibernate(id: string, data?: VmHibernateData["body"]) { return retryWithDelay( async () => { diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 96084ae..76e9287 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -178,10 +178,11 @@ export class Sandbox { pitcherManagerResponse: PitcherManagerResponse, customSession?: SessionCreateOptions ): Promise { - if (!customSession) { + if (!customSession || !customSession.id) { return { sandboxId: this.id, bootupType: this.bootupType, + hostToken: customSession?.hostToken, cluster: this.cluster, latestPitcherVersion: pitcherManagerResponse.latestPitcherVersion, pitcherManagerVersion: pitcherManagerResponse.pitcherManagerVersion, diff --git a/src/Sandboxes.ts b/src/Sandboxes.ts index 8f032ec..c4c57ec 100644 --- a/src/Sandboxes.ts +++ b/src/Sandboxes.ts @@ -60,40 +60,6 @@ export class Sandboxes { ); } - private async createTemplateSandbox( - opts?: CreateSandboxOpts & StartSandboxOpts - ) { - const templateId = opts?.id || this.defaultTemplateId; - const privacy = opts?.privacy || "unlisted"; - const tags = opts?.tags || ["sdk"]; - let path = opts?.path || "/SDK"; - - if (!path.startsWith("/")) { - path = "/" + path; - } - - // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. - const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; - - const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy); - - const sandbox = await this.api.forkSandbox(templateId, { - privacy: mappedPrivacy, - title: opts?.title, - description: opts?.description, - tags: tagsWithSdk, - path, - private_preview: privatePreview, - }); - - const startResponse = await this.api.startVm( - sandbox.id, - { ...getStartOptions(opts), retryDelay: 200 } // Keep 200ms delay for creation - ); - - return new Sandbox(sandbox.id, this.api, startResponse, this.tracer); - } - /** * Resume a sandbox. * @@ -129,6 +95,20 @@ export class Sandboxes { ); } + /** + * Permanently deletes a sandbox. This action is irreversible and will delete all data associated with the sandbox. + * The sandbox must belong to your team's workspace to be deleted. + */ + async delete(sandboxId: string): Promise { + return this.withSpan( + "sandboxes.delete", + { "sandbox.id": sandboxId }, + async () => { + await this.api.deleteVm(sandboxId); + } + ); + } + /** * Forks a sandbox. This will create a new sandbox from the given sandbox. * @deprecated This will be removed shortly to avoid having multiple ways of doing the same thing @@ -198,10 +178,38 @@ export class Sandboxes { "sandboxes.create", { "template.id": opts?.id || this.defaultTemplateId, - "sandbox.privacy": opts?.privacy || "unlisted", + "sandbox.privacy": opts?.privacy || "public-hosts", }, async () => { - return this.createTemplateSandbox(opts); + const templateId = opts?.id || this.defaultTemplateId; + const privacy = opts?.privacy || "public-hosts"; + const tags = opts?.tags || ["sdk"]; + let path = opts?.path || "/SDK"; + + if (!path.startsWith("/")) { + path = "/" + path; + } + + // Always add the "sdk" tag to the sandbox, this is used to identify sandboxes created by the SDK. + const tagsWithSdk = tags.includes("sdk") ? tags : [...tags, "sdk"]; + + const { mappedPrivacy, privatePreview } = mapPrivacyForApi(privacy); + + const sandbox = await this.api.forkSandbox(templateId, { + privacy: mappedPrivacy, + title: opts?.title, + description: opts?.description, + tags: tagsWithSdk, + path, + private_preview: privatePreview, + }); + + const startResponse = await this.api.startVm( + sandbox.id, + { ...getStartOptions(opts), retryDelay: 200 } // Keep 200ms delay for creation + ); + + return new Sandbox(sandbox.id, this.api, startResponse, this.tracer); } ); } diff --git a/src/api-clients/client-rest-container/client.gen.ts b/src/api-clients/client-rest-container/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-container/client.gen.ts +++ b/src/api-clients/client-rest-container/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-container/index.ts b/src/api-clients/client-rest-container/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-container/index.ts +++ b/src/api-clients/client-rest-container/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-container/sdk.gen.ts b/src/api-clients/client-rest-container/sdk.gen.ts index e63b97a..bfa7256 100644 --- a/src/api-clients/client-rest-container/sdk.gen.ts +++ b/src/api-clients/client-rest-container/sdk.gen.ts @@ -1,46 +1,29 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - ContainerSetupData, - ContainerSetupResponse, - ContainerSetupError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { ContainerSetupData, ContainerSetupResponse, ContainerSetupError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Setup container * Set up a new container based on a template */ -export const containerSetup = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ContainerSetupResponse, - ContainerSetupError, - ThrowOnError - >({ - url: "/container/setup", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const containerSetup = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/container/setup', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-container/types.gen.ts b/src/api-clients/client-rest-container/types.gen.ts index 3814bef..b2fa4a4 100644 --- a/src/api-clients/client-rest-container/types.gen.ts +++ b/src/api-clients/client-rest-container/types.gen.ts @@ -1,113 +1,111 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type ProtocolError = { - /** - * Error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; + /** + * Error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; }; export type TaskDto = { - /** - * Task identifier - */ - id: string; - /** - * Task status - */ - status: string; - /** - * Task progress (0-100) - */ - progress: number; -}; - -export type ContainerSetupData = { - body: { /** - * Identifier of the template to use + * Task identifier */ - templateId: string; + id: string; /** - * Arguments for the template + * Task status */ - templateArgs: { - [key: string]: string; + status: string; + /** + * Task progress (0-100) + */ + progress: number; +}; + +export type ContainerSetupData = { + body: { + /** + * Identifier of the template to use + */ + templateId: string; + /** + * Arguments for the template + */ + templateArgs: { + [key: string]: string; + }; + features?: Array<{ + /** + * Feature identifier + */ + id: string; + /** + * Options for the feature + */ + options: { + [key: string]: string; + }; + }> | null; }; - features?: Array<{ - /** - * Feature identifier - */ - id: string; - /** - * Options for the feature - */ - options: { - [key: string]: string; - }; - }> | null; - }; - path?: never; - query?: never; - url: "/container/setup"; + path?: never; + query?: never; + url: '/container/setup'; }; export type ContainerSetupErrors = { - /** - * Error setting up container - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error setting up container + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; -export type ContainerSetupError = - ContainerSetupErrors[keyof ContainerSetupErrors]; +export type ContainerSetupError = ContainerSetupErrors[keyof ContainerSetupErrors]; export type ContainerSetupResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type ContainerSetupResponse = - ContainerSetupResponses[keyof ContainerSetupResponses]; +export type ContainerSetupResponse = ContainerSetupResponses[keyof ContainerSetupResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/client.gen.ts b/src/api-clients/client-rest-fs/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-fs/client.gen.ts +++ b/src/api-clients/client-rest-fs/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/index.ts b/src/api-clients/client-rest-fs/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-fs/index.ts +++ b/src/api-clients/client-rest-fs/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/sdk.gen.ts b/src/api-clients/client-rest-fs/sdk.gen.ts index e9fc1ca..83a4cab 100644 --- a/src/api-clients/client-rest-fs/sdk.gen.ts +++ b/src/api-clients/client-rest-fs/sdk.gen.ts @@ -1,450 +1,280 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - WriteFileData, - WriteFileResponse, - WriteFileError, - FsReadData, - FsReadResponse, - FsReadError, - FsOperationData, - FsOperationResponse, - FsOperationError, - FsSearchData, - FsSearchResponse, - FsSearchError, - FsStreamingSearchData, - FsStreamingSearchResponse, - FsStreamingSearchError, - FsCancelStreamingSearchData, - FsCancelStreamingSearchResponse, - FsCancelStreamingSearchError, - FsPathSearchData, - FsPathSearchResponse, - FsPathSearchError, - FsUploadData, - FsUploadResponse, - FsUploadError, - FsDownloadData, - FsDownloadResponse, - FsDownloadError, - FsReadFileData, - FsReadFileResponse, - FsReadFileError, - FsReadDirData, - FsReadDirResponse, - FsReadDirError, - FsStatData, - FsStatResponse, - FsStatError, - FsCopyData, - FsCopyResponse, - FsCopyError, - FsRenameData, - FsRenameResponse, - FsRenameError, - FsRemoveData, - FsRemoveResponse, - FsRemoveError, - FsMkdirData, - FsMkdirResponse, - FsMkdirError, - FsWatchData, - FsWatchResponse, - FsWatchError, - FsUnwatchData, - FsUnwatchResponse, - FsUnwatchError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { WriteFileData, WriteFileResponse, WriteFileError, FsReadData, FsReadResponse, FsReadError, FsOperationData, FsOperationResponse, FsOperationError, FsSearchData, FsSearchResponse, FsSearchError, FsStreamingSearchData, FsStreamingSearchResponse, FsStreamingSearchError, FsCancelStreamingSearchData, FsCancelStreamingSearchResponse, FsCancelStreamingSearchError, FsPathSearchData, FsPathSearchResponse, FsPathSearchError, FsUploadData, FsUploadResponse, FsUploadError, FsDownloadData, FsDownloadResponse, FsDownloadError, FsReadFileData, FsReadFileResponse, FsReadFileError, FsReadDirData, FsReadDirResponse, FsReadDirError, FsStatData, FsStatResponse, FsStatError, FsCopyData, FsCopyResponse, FsCopyError, FsRenameData, FsRenameResponse, FsRenameError, FsRemoveData, FsRemoveResponse, FsRemoveError, FsMkdirData, FsMkdirResponse, FsMkdirError, FsWatchData, FsWatchResponse, FsWatchError, FsUnwatchData, FsUnwatchResponse, FsUnwatchError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Write to a file * Write content to a file at the specified path */ -export const writeFile = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - WriteFileResponse, - WriteFileError, - ThrowOnError - >({ - url: "/fs/writeFile", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const writeFile = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/writeFile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Read file system * Retrieve the latest snapshot of the server's MemoryFS file and children list */ -export const fsRead = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - FsReadResponse, - FsReadError, - ThrowOnError - >({ - url: "/fs/read", - ...options, - }); +export const fsRead = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + url: '/fs/read', + ...options + }); }; /** * Perform file system operation * Send a tree operation reflecting filesystem operations */ -export const fsOperation = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsOperationResponse, - FsOperationError, - ThrowOnError - >({ - url: "/fs/operation", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsOperation = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/operation', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Search files * Search for content in files */ -export const fsSearch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsSearchResponse, - FsSearchError, - ThrowOnError - >({ - url: "/fs/search", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/search', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Start streaming search * Start a streaming search for content in files */ -export const fsStreamingSearch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsStreamingSearchResponse, - FsStreamingSearchError, - ThrowOnError - >({ - url: "/fs/streamingSearch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsStreamingSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/streamingSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Cancel streaming search * Cancel an ongoing streaming search */ -export const fsCancelStreamingSearch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsCancelStreamingSearchResponse, - FsCancelStreamingSearchError, - ThrowOnError - >({ - url: "/fs/cancelStreamingSearch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsCancelStreamingSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/cancelStreamingSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Search file paths * Search for file paths matching a pattern */ -export const fsPathSearch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsPathSearchResponse, - FsPathSearchError, - ThrowOnError - >({ - url: "/fs/pathSearch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsPathSearch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/pathSearch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Upload file * Upload a file to the specified parent directory */ -export const fsUpload = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsUploadResponse, - FsUploadError, - ThrowOnError - >({ - url: "/fs/upload", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsUpload = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/upload', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Download files * Download files at a specified path as a zip */ -export const fsDownload = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsDownloadResponse, - FsDownloadError, - ThrowOnError - >({ - url: "/fs/download", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsDownload = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/download', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Read file content * Read the content of a file at the specified path */ -export const fsReadFile = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsReadFileResponse, - FsReadFileError, - ThrowOnError - >({ - url: "/fs/readFile", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsReadFile = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/readFile', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Read directory contents * List the contents of a directory at the specified path */ -export const fsReadDir = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsReadDirResponse, - FsReadDirError, - ThrowOnError - >({ - url: "/fs/readdir", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsReadDir = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/readdir', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get file/directory stats * Get stats for a file or directory at the specified path */ -export const fsStat = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsStatResponse, - FsStatError, - ThrowOnError - >({ - url: "/fs/stat", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsStat = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/stat', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Copy file/directory * Copy a file or directory from one location to another */ -export const fsCopy = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsCopyResponse, - FsCopyError, - ThrowOnError - >({ - url: "/fs/copy", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsCopy = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/copy', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Rename file/directory * Rename a file or directory (move from one location to another) */ -export const fsRename = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsRenameResponse, - FsRenameError, - ThrowOnError - >({ - url: "/fs/rename", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsRename = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/rename', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Remove file/directory * Delete a file or directory at the specified path */ -export const fsRemove = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsRemoveResponse, - FsRemoveError, - ThrowOnError - >({ - url: "/fs/remove", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsRemove = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/remove', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Create directory * Create a new directory at the specified path */ -export const fsMkdir = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsMkdirResponse, - FsMkdirError, - ThrowOnError - >({ - url: "/fs/mkdir", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsMkdir = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/mkdir', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Watch file/directory * Watch a file or directory for changes */ -export const fsWatch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsWatchResponse, - FsWatchError, - ThrowOnError - >({ - url: "/fs/watch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const fsWatch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/watch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Stop watching file/directory * Stop watching a file or directory for changes */ -export const fsUnwatch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - FsUnwatchResponse, - FsUnwatchError, - ThrowOnError - >({ - url: "/fs/unwatch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const fsUnwatch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/fs/unwatch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/types.gen.ts b/src/api-clients/client-rest-fs/types.gen.ts index 8aa230c..624cd45 100644 --- a/src/api-clients/client-rest-fs/types.gen.ts +++ b/src/api-clients/client-rest-fs/types.gen.ts @@ -1,1123 +1,1058 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - error: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; - } & RawFsError); + /** + * Status code for error operations + */ + status: 1; + error: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; + } & RawFsError); }; export type DefaultError = { - code: PitcherErrorCode; - /** - * Additional error details - */ - data?: { - [key: string]: unknown; - } | null; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; + code: PitcherErrorCode; + /** + * Additional error details + */ + data?: { + [key: string]: unknown; + } | null; + /** + * Human-readable error message that can be displayed to users + */ + publicMessage?: string | null; }; export type RawFsError = { - /** - * RAWFS_ERROR code - */ - code: 102; - data: { /** - * File system error number, or null if not available + * RAWFS_ERROR code + */ + code: 102; + data: { + /** + * File system error number, or null if not available + */ + errno: unknown; + }; + /** + * Human-readable error message that can be displayed to users */ - errno: unknown; - }; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; + publicMessage?: string | null; }; /** * Enumeration of error codes */ -export type PitcherErrorCode = - | 0 - | 1 - | 2 - | 3 - | 100 - | 101 - | 102 - | 200 - | 201 - | 204 - | 300 - | 400 - | 404 - | 410 - | 420 - | 430 - | 440 - | 450 - | 460 - | 470 - | 500 - | 600 - | 601 - | 602 - | 704 - | 800 - | 801 - | 802 - | 803 - | 814; +export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; export type WriteFileRequest = { - /** - * File path to write to - */ - path: string; - /** - * File content as binary data (Uint8Array) - */ - content: Blob | File; - /** - * Whether to create the file if it doesn't exist - */ - create?: boolean; - /** - * Whether to overwrite the file if it exists - */ - overwrite?: boolean; + /** + * File path to write to + */ + path: string; + /** + * File content as binary data (Uint8Array) + */ + content: Blob | File; + /** + * Whether to create the file if it doesn't exist + */ + create?: boolean; + /** + * Whether to overwrite the file if it exists + */ + overwrite?: boolean; }; export type FsReadResult = { - treeNodes: Array<{ - [key: string]: unknown; - }>; - /** - * Current clock value for the file system - */ - clock: number; + treeNodes: Array<{ + [key: string]: unknown; + }>; + /** + * Current clock value for the file system + */ + clock: number; }; export type FsOperationRequest = { - operation: FsOperation; + operation: FsOperation; }; -export type FsOperation = - | ({ - type?: "FSCreateOperation"; - } & FsCreateOperation) - | ({ - type?: "FSDeleteOperation"; - } & FsDeleteOperation) - | ({ - type?: "FSMoveOperation"; - } & FsMoveOperation); +export type FsOperation = ({ + type?: 'FSCreateOperation'; +} & FsCreateOperation) | ({ + type?: 'FSDeleteOperation'; +} & FsDeleteOperation) | ({ + type?: 'FSMoveOperation'; +} & FsMoveOperation); export type FsCreateOperation = { - type: "create"; - /** - * ID of the parent directory - */ - parentId: string; - newEntry: { + type: 'create'; /** - * ID of the new entry + * ID of the parent directory + */ + parentId: string; + newEntry: { + /** + * ID of the new entry + */ + id: string; + /** + * Type of the node + */ + type: 'directory' | 'file'; + /** + * Name of the new entry + */ + name: string; + }; +}; + +export type FsDeleteOperation = { + type: 'delete'; + /** + * ID of the entry to delete + */ + id: string; +}; + +export type FsMoveOperation = { + type: 'move'; + /** + * ID of the entry to move */ id: string; /** - * Type of the node + * ID of the new parent directory */ - type: "directory" | "file"; + parentId?: string | null; /** - * Name of the new entry + * New name for the entry */ - name: string; - }; + name?: string | null; }; -export type FsDeleteOperation = { - type: "delete"; - /** - * ID of the entry to delete - */ - id: string; +export type FsOperationResult = { + /** + * Success code + */ + code: 0; + /** + * Current clock value + */ + clock: number; +} | { + /** + * Ignored code + */ + code: 1; }; -export type FsMoveOperation = { - type: "move"; - /** - * ID of the entry to move - */ - id: string; - /** - * ID of the new parent directory - */ - parentId?: string | null; - /** - * New name for the entry - */ - name?: string | null; -}; - -export type FsOperationResult = - | { - /** - * Success code - */ - code: 0; - /** - * Current clock value - */ - clock: number; - } - | { - /** - * Ignored code - */ - code: 1; - }; - export type FsSearchParams = { - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: "smart" | "enabled" | "disabled"; + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: 'smart' | 'enabled' | 'disabled'; }; export type SearchResult = { - /** - * ID of the file containing the match - */ - fileId: string; - lines: { /** - * Text of the line containing the match + * ID of the file containing the match */ - text: string; - }; - /** - * Line number of the match - */ - lineNumber: number; - /** - * Absolute offset of the match in the file - */ - absoluteOffset: number; - submatches: Array; + fileId: string; + lines: { + /** + * Text of the line containing the match + */ + text: string; + }; + /** + * Line number of the match + */ + lineNumber: number; + /** + * Absolute offset of the match in the file + */ + absoluteOffset: number; + submatches: Array; }; export type SearchSubMatch = { - match: { + match: { + /** + * Matched text + */ + text: string; + }; /** - * Matched text + * Start position of the match */ - text: string; - }; - /** - * Start position of the match - */ - start: number; - /** - * End position of the match - */ - end: number; + start: number; + /** + * End position of the match + */ + end: number; }; export type FsStreamingSearchParams = { - /** - * ID for the search operation - */ - searchId: string; - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: "smart" | "enabled" | "disabled"; - /** - * Maximum number of results to return (default: 10,000) - */ - maxResults?: number | null; + /** + * ID for the search operation + */ + searchId: string; + /** + * Text to search for + */ + text: string; + /** + * Glob pattern to filter files + */ + glob?: string | null; + /** + * Whether to treat the search text as a regular expression + */ + isRegex?: boolean | null; + /** + * Case sensitivity setting for the search + */ + caseSensitivity?: 'smart' | 'enabled' | 'disabled'; + /** + * Maximum number of results to return (default: 10,000) + */ + maxResults?: number | null; }; export type PathSearchParams = { - /** - * Text to search for in file paths - */ - text: string; + /** + * Text to search for in file paths + */ + text: string; }; export type PathSearchResult = { - matches: Array; + matches: Array; }; export type PathSearchMatch = { - /** - * Path that matched the search - */ - path: string; - submatches: Array; + /** + * Path that matched the search + */ + path: string; + submatches: Array; }; export type InvalidIdError = { - /** - * INVALID_ID error code - */ - code: 100; + /** + * INVALID_ID error code + */ + code: 100; }; export type FsReadFileParams = { - /** - * Path to the file to read - */ - path: string; + /** + * Path to the file to read + */ + path: string; }; export type FsReadFileResult = { - /** - * File content as binary data - */ - content: Blob | File; + /** + * File content as binary data + */ + content: Blob | File; }; export type FsReadDirParams = { - /** - * Path to the directory to read - */ - path: string; + /** + * Path to the directory to read + */ + path: string; }; export type FsReadDirResult = { - entries: Array<{ + entries: Array<{ + /** + * Name of the entry + */ + name: string; + /** + * Type of the entry + */ + type: 'directory' | 'file'; + /** + * Whether the entry is a symlink + */ + isSymlink: boolean; + }>; +}; + +export type FsStatParams = { /** - * Name of the entry + * Path to the file or directory to stat */ - name: string; + path: string; +}; + +export type FsStatResult = { /** * Type of the entry */ - type: "directory" | "file"; + type: 'directory' | 'file'; /** * Whether the entry is a symlink */ isSymlink: boolean; - }>; -}; - -export type FsStatParams = { - /** - * Path to the file or directory to stat - */ - path: string; -}; - -export type FsStatResult = { - /** - * Type of the entry - */ - type: "directory" | "file"; - /** - * Whether the entry is a symlink - */ - isSymlink: boolean; - /** - * Size of the file in bytes - */ - size: number; - /** - * Last modified time - */ - mtime: number; - /** - * Creation time - */ - ctime: number; - /** - * Last accessed time - */ - atime: number; + /** + * Size of the file in bytes + */ + size: number; + /** + * Last modified time + */ + mtime: number; + /** + * Creation time + */ + ctime: number; + /** + * Last accessed time + */ + atime: number; }; export type FsCopyParams = { - /** - * Path to copy from - */ - from: string; - /** - * Path to copy to - */ - to: string; - /** - * Whether to copy directories recursively - */ - recursive?: boolean | null; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; + /** + * Path to copy from + */ + from: string; + /** + * Path to copy to + */ + to: string; + /** + * Whether to copy directories recursively + */ + recursive?: boolean | null; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; }; export type FsRenameParams = { - /** - * Path to rename from - */ - from: string; - /** - * Path to rename to - */ - to: string; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; + /** + * Path to rename from + */ + from: string; + /** + * Path to rename to + */ + to: string; + /** + * Whether to overwrite existing files + */ + overwrite?: boolean | null; }; export type FsRemoveParams = { - /** - * Path to remove - */ - path: string; - /** - * Whether to remove directories recursively - */ - recursive?: boolean | null; + /** + * Path to remove + */ + path: string; + /** + * Whether to remove directories recursively + */ + recursive?: boolean | null; }; export type FsMkdirParams = { - /** - * Path to create directory at - */ - path: string; - /** - * Whether to create parent directories if they don't exist - */ - recursive?: boolean | null; + /** + * Path to create directory at + */ + path: string; + /** + * Whether to create parent directories if they don't exist + */ + recursive?: boolean | null; }; export type FsWatchParams = { - /** - * Path to watch - */ - path: string; - /** - * Whether to watch directories recursively - */ - recursive?: boolean | null; - /** - * Glob patterns to exclude from watching - */ - excludes?: Array | null; + /** + * Path to watch + */ + path: string; + /** + * Whether to watch directories recursively + */ + recursive?: boolean | null; + /** + * Glob patterns to exclude from watching + */ + excludes?: Array | null; }; export type FsWatchResult = { - /** - * ID of the watch - */ - watchId: string; + /** + * ID of the watch + */ + watchId: string; }; export type FsUnwatchParams = { - /** - * ID of the watch to stop - */ - watchId: string; + /** + * ID of the watch to stop + */ + watchId: string; }; export type WriteFileData = { - body: WriteFileRequest; - path?: never; - query?: never; - url: "/fs/writeFile"; + body: WriteFileRequest; + path?: never; + query?: never; + url: '/fs/writeFile'; }; export type WriteFileErrors = { - /** - * Error writing file - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error writing file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; export type WriteFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; export type FsReadData = { - body?: never; - path?: never; - query?: never; - url: "/fs/read"; + body?: never; + path?: never; + query?: never; + url: '/fs/read'; }; export type FsReadErrors = { - /** - * Error reading file system - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error reading file system + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsReadError = FsReadErrors[keyof FsReadErrors]; export type FsReadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadResult; + }; }; export type FsReadResponse = FsReadResponses[keyof FsReadResponses]; export type FsOperationData = { - body: FsOperationRequest; - path?: never; - query?: never; - url: "/fs/operation"; + body: FsOperationRequest; + path?: never; + query?: never; + url: '/fs/operation'; }; export type FsOperationErrors = { - /** - * Error performing operation - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error performing operation + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsOperationError = FsOperationErrors[keyof FsOperationErrors]; export type FsOperationResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsOperationResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsOperationResult; + }; }; -export type FsOperationResponse = - FsOperationResponses[keyof FsOperationResponses]; +export type FsOperationResponse = FsOperationResponses[keyof FsOperationResponses]; export type FsSearchData = { - body: FsSearchParams; - path?: never; - query?: never; - url: "/fs/search"; + body: FsSearchParams; + path?: never; + query?: never; + url: '/fs/search'; }; export type FsSearchErrors = { - /** - * Error searching files - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error searching files + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsSearchError = FsSearchErrors[keyof FsSearchErrors]; export type FsSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array; + }; }; export type FsSearchResponse = FsSearchResponses[keyof FsSearchResponses]; export type FsStreamingSearchData = { - body: FsStreamingSearchParams; - path?: never; - query?: never; - url: "/fs/streamingSearch"; + body: FsStreamingSearchParams; + path?: never; + query?: never; + url: '/fs/streamingSearch'; }; export type FsStreamingSearchErrors = { - /** - * Error starting streaming search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error starting streaming search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; -export type FsStreamingSearchError = - FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; +export type FsStreamingSearchError = FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; export type FsStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the search operation - */ - searchId?: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the search operation + */ + searchId?: string; + }; }; - }; }; -export type FsStreamingSearchResponse = - FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; +export type FsStreamingSearchResponse = FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; export type FsCancelStreamingSearchData = { - body: { - /** - * ID of the search to cancel - */ - searchId: string; - }; - path?: never; - query?: never; - url: "/fs/cancelStreamingSearch"; + body: { + /** + * ID of the search to cancel + */ + searchId: string; + }; + path?: never; + query?: never; + url: '/fs/cancelStreamingSearch'; }; export type FsCancelStreamingSearchErrors = { - /** - * Error cancelling search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error cancelling search + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; -export type FsCancelStreamingSearchError = - FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; +export type FsCancelStreamingSearchError = FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; export type FsCancelStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the cancelled search - */ - searchId?: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the cancelled search + */ + searchId?: string; + }; }; - }; }; -export type FsCancelStreamingSearchResponse = - FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; +export type FsCancelStreamingSearchResponse = FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; export type FsPathSearchData = { - body: PathSearchParams; - path?: never; - query?: never; - url: "/fs/pathSearch"; + body: PathSearchParams; + path?: never; + query?: never; + url: '/fs/pathSearch'; }; export type FsPathSearchErrors = { - /** - * Error searching paths - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error searching paths + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsPathSearchError = FsPathSearchErrors[keyof FsPathSearchErrors]; export type FsPathSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: PathSearchResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: PathSearchResult; + }; }; -export type FsPathSearchResponse = - FsPathSearchResponses[keyof FsPathSearchResponses]; +export type FsPathSearchResponse = FsPathSearchResponses[keyof FsPathSearchResponses]; export type FsUploadData = { - body: { - /** - * ID of the parent directory - */ - parentId: string; - /** - * Name of the file to create - */ - filename: string; - /** - * File content as binary data - */ - content: Blob | File; - }; - path?: never; - query?: never; - url: "/fs/upload"; + body: { + /** + * ID of the parent directory + */ + parentId: string; + /** + * Name of the file to create + */ + filename: string; + /** + * File content as binary data + */ + content: Blob | File; + }; + path?: never; + query?: never; + url: '/fs/upload'; }; export type FsUploadErrors = { - /** - * Error uploading file - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "InvalidIdError"; + /** + * Error uploading file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'InvalidIdError'; } & InvalidIdError); - }; + }; }; export type FsUploadError = FsUploadErrors[keyof FsUploadErrors]; export type FsUploadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the created file - */ - fileId?: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the created file + */ + fileId?: string; + }; }; - }; }; export type FsUploadResponse = FsUploadResponses[keyof FsUploadResponses]; export type FsDownloadData = { - body: { - /** - * Path to download - */ - path: string; - /** - * Glob patterns of files/folders to exclude from the download - */ - excludes?: Array; - }; - path?: never; - query?: never; - url: "/fs/download"; + body: { + /** + * Path to download + */ + path: string; + /** + * Glob patterns of files/folders to exclude from the download + */ + excludes?: Array; + }; + path?: never; + query?: never; + url: '/fs/download'; }; export type FsDownloadErrors = { - /** - * Error creating download - */ - 400: ErrorResponse & { - error?: DefaultError; - }; + /** + * Error creating download + */ + 400: ErrorResponse & { + error?: DefaultError; + }; }; export type FsDownloadError = FsDownloadErrors[keyof FsDownloadErrors]; export type FsDownloadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * URL to download the files from - */ - downloadUrl?: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * URL to download the files from + */ + downloadUrl?: string; + }; }; - }; }; export type FsDownloadResponse = FsDownloadResponses[keyof FsDownloadResponses]; export type FsReadFileData = { - body: FsReadFileParams; - path?: never; - query?: never; - url: "/fs/readFile"; + body: FsReadFileParams; + path?: never; + query?: never; + url: '/fs/readFile'; }; export type FsReadFileErrors = { - /** - * Error reading file - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error reading file + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsReadFileError = FsReadFileErrors[keyof FsReadFileErrors]; export type FsReadFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadFileResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadFileResult; + }; }; export type FsReadFileResponse = FsReadFileResponses[keyof FsReadFileResponses]; export type FsReadDirData = { - body: FsReadDirParams; - path?: never; - query?: never; - url: "/fs/readdir"; + body: FsReadDirParams; + path?: never; + query?: never; + url: '/fs/readdir'; }; export type FsReadDirErrors = { - /** - * Error reading directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error reading directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsReadDirError = FsReadDirErrors[keyof FsReadDirErrors]; export type FsReadDirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadDirResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsReadDirResult; + }; }; export type FsReadDirResponse = FsReadDirResponses[keyof FsReadDirResponses]; export type FsStatData = { - body: FsStatParams; - path?: never; - query?: never; - url: "/fs/stat"; + body: FsStatParams; + path?: never; + query?: never; + url: '/fs/stat'; }; export type FsStatErrors = { - /** - * Error getting stats - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error getting stats + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsStatError = FsStatErrors[keyof FsStatErrors]; export type FsStatResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsStatResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsStatResult; + }; }; export type FsStatResponse = FsStatResponses[keyof FsStatResponses]; export type FsCopyData = { - body: FsCopyParams; - path?: never; - query?: never; - url: "/fs/copy"; + body: FsCopyParams; + path?: never; + query?: never; + url: '/fs/copy'; }; export type FsCopyErrors = { - /** - * Error copying file/directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error copying file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsCopyError = FsCopyErrors[keyof FsCopyErrors]; export type FsCopyResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; export type FsCopyResponse = FsCopyResponses[keyof FsCopyResponses]; export type FsRenameData = { - body: FsRenameParams; - path?: never; - query?: never; - url: "/fs/rename"; + body: FsRenameParams; + path?: never; + query?: never; + url: '/fs/rename'; }; export type FsRenameErrors = { - /** - * Error renaming file/directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error renaming file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsRenameError = FsRenameErrors[keyof FsRenameErrors]; export type FsRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; export type FsRenameResponse = FsRenameResponses[keyof FsRenameResponses]; export type FsRemoveData = { - body: FsRemoveParams; - path?: never; - query?: never; - url: "/fs/remove"; + body: FsRemoveParams; + path?: never; + query?: never; + url: '/fs/remove'; }; export type FsRemoveErrors = { - /** - * Error removing file/directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error removing file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsRemoveError = FsRemoveErrors[keyof FsRemoveErrors]; export type FsRemoveResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; export type FsRemoveResponse = FsRemoveResponses[keyof FsRemoveResponses]; export type FsMkdirData = { - body: FsMkdirParams; - path?: never; - query?: never; - url: "/fs/mkdir"; + body: FsMkdirParams; + path?: never; + query?: never; + url: '/fs/mkdir'; }; export type FsMkdirErrors = { - /** - * Error creating directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error creating directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsMkdirError = FsMkdirErrors[keyof FsMkdirErrors]; export type FsMkdirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; export type FsMkdirResponse = FsMkdirResponses[keyof FsMkdirResponses]; export type FsWatchData = { - body: FsWatchParams; - path?: never; - query?: never; - url: "/fs/watch"; + body: FsWatchParams; + path?: never; + query?: never; + url: '/fs/watch'; }; export type FsWatchErrors = { - /** - * Error watching file/directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error watching file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsWatchError = FsWatchErrors[keyof FsWatchErrors]; export type FsWatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsWatchResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: FsWatchResult; + }; }; export type FsWatchResponse = FsWatchResponses[keyof FsWatchResponses]; export type FsUnwatchData = { - body: FsUnwatchParams; - path?: never; - query?: never; - url: "/fs/unwatch"; + body: FsUnwatchParams; + path?: never; + query?: never; + url: '/fs/unwatch'; }; export type FsUnwatchErrors = { - /** - * Error unwatching file/directory - */ - 400: ErrorResponse & { - error?: - | ({ - code?: "DefaultError"; - } & DefaultError) - | ({ - code?: "RawFsError"; + /** + * Error unwatching file/directory + */ + 400: ErrorResponse & { + error?: ({ + code?: 'DefaultError'; + } & DefaultError) | ({ + code?: 'RawFsError'; } & RawFsError); - }; + }; }; export type FsUnwatchError = FsUnwatchErrors[keyof FsUnwatchErrors]; export type FsUnwatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; -export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; +export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/client.gen.ts b/src/api-clients/client-rest-git/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-git/client.gen.ts +++ b/src/api-clients/client-rest-git/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-git/index.ts b/src/api-clients/client-rest-git/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-git/index.ts +++ b/src/api-clients/client-rest-git/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/sdk.gen.ts b/src/api-clients/client-rest-git/sdk.gen.ts index 6f7d801..0e3b272 100644 --- a/src/api-clients/client-rest-git/sdk.gen.ts +++ b/src/api-clients/client-rest-git/sdk.gen.ts @@ -1,358 +1,224 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - GitStatusData, - GitStatusResponse, - GitStatusError, - GitRemotesData, - GitRemotesResponse, - GitRemotesError, - GitTargetDiffData, - GitTargetDiffResponse, - GitTargetDiffError, - GitPullData, - GitPullResponse, - GitPullError, - GitDiscardData, - GitDiscardResponse, - GitDiscardError, - GitCommitData, - GitCommitResponse, - GitCommitError, - GitPushData, - GitPushResponse, - GitPushError, - GitPushToRemoteData, - GitPushToRemoteResponse, - GitPushToRemoteError, - GitRenameBranchData, - GitRenameBranchResponse, - GitRenameBranchError, - GitRemoteContentData, - GitRemoteContentResponse, - GitRemoteContentError, - GitDiffStatusData, - GitDiffStatusResponse, - GitDiffStatusError, - GitResetLocalWithRemoteData, - GitResetLocalWithRemoteResponse, - GitResetLocalWithRemoteError, - GitCheckoutInitialBranchData, - GitCheckoutInitialBranchResponse, - GitCheckoutInitialBranchError, - GitTransposeLinesData, - GitTransposeLinesResponse, - GitTransposeLinesError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; - -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { GitStatusData, GitStatusResponse, GitStatusError, GitRemotesData, GitRemotesResponse, GitRemotesError, GitTargetDiffData, GitTargetDiffResponse, GitTargetDiffError, GitPullData, GitPullResponse, GitPullError, GitDiscardData, GitDiscardResponse, GitDiscardError, GitCommitData, GitCommitResponse, GitCommitError, GitPushData, GitPushResponse, GitPushError, GitPushToRemoteData, GitPushToRemoteResponse, GitPushToRemoteError, GitRenameBranchData, GitRenameBranchResponse, GitRenameBranchError, GitRemoteContentData, GitRemoteContentResponse, GitRemoteContentError, GitDiffStatusData, GitDiffStatusResponse, GitDiffStatusError, GitResetLocalWithRemoteData, GitResetLocalWithRemoteResponse, GitResetLocalWithRemoteError, GitCheckoutInitialBranchData, GitCheckoutInitialBranchResponse, GitCheckoutInitialBranchError, GitTransposeLinesData, GitTransposeLinesResponse, GitTransposeLinesError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Get git status * Retrieve current git status including changed files, branch information, and commits */ -export const gitStatus = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitStatusResponse, - GitStatusError, - ThrowOnError - >({ - url: "/git/status", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitStatus = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/status', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get git remotes * Retrieve git remote information */ -export const gitRemotes = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitRemotesResponse, - GitRemotesError, - ThrowOnError - >({ - url: "/git/remotes", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitRemotes = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/remotes', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get git target diff * Retrieve diff between current branch and target branch */ -export const gitTargetDiff = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitTargetDiffResponse, - GitTargetDiffError, - ThrowOnError - >({ - url: "/git/targetDiff", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitTargetDiff = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/targetDiff', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Pull from remote * Pull changes from remote repository */ -export const gitPull = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitPullResponse, - GitPullError, - ThrowOnError - >({ - url: "/git/pull", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitPull = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/pull', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Discard changes * Discard local changes for specified paths */ -export const gitDiscard = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitDiscardResponse, - GitDiscardError, - ThrowOnError - >({ - url: "/git/discard", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitDiscard = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/discard', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Commit changes * Commit changes to the repository */ -export const gitCommit = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitCommitResponse, - GitCommitError, - ThrowOnError - >({ - url: "/git/commit", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitCommit = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/commit', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Push changes * Push local commits to remote repository */ -export const gitPush = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitPushResponse, - GitPushError, - ThrowOnError - >({ - url: "/git/push", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitPush = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/push', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Push to remote * Push to a specific remote repository */ -export const gitPushToRemote = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitPushToRemoteResponse, - GitPushToRemoteError, - ThrowOnError - >({ - url: "/git/pushToRemote", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitPushToRemote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/pushToRemote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Rename branch * Rename a git branch */ -export const gitRenameBranch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitRenameBranchResponse, - GitRenameBranchError, - ThrowOnError - >({ - url: "/git/renameBranch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitRenameBranch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/renameBranch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get remote content * Retrieve content from a remote repository */ -export const gitRemoteContent = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitRemoteContentResponse, - GitRemoteContentError, - ThrowOnError - >({ - url: "/git/remoteContent", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitRemoteContent = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/remoteContent', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get diff status * Retrieve diff status between two git references */ -export const gitDiffStatus = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitDiffStatusResponse, - GitDiffStatusError, - ThrowOnError - >({ - url: "/git/diffStatus", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitDiffStatus = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/diffStatus', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Reset local with remote * Reset local repository to match the remote state */ -export const gitResetLocalWithRemote = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitResetLocalWithRemoteResponse, - GitResetLocalWithRemoteError, - ThrowOnError - >({ - url: "/git/resetLocalWithRemote", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitResetLocalWithRemote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/resetLocalWithRemote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Checkout initial branch * Checkout the initial branch of the repository */ -export const gitCheckoutInitialBranch = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitCheckoutInitialBranchResponse, - GitCheckoutInitialBranchError, - ThrowOnError - >({ - url: "/git/checkoutInitialBranch", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const gitCheckoutInitialBranch = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/checkoutInitialBranch', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Transpose lines * Transpose line numbers from one git reference to another */ -export const gitTransposeLines = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - GitTransposeLinesResponse, - GitTransposeLinesError, - ThrowOnError - >({ - url: "/git/transposeLines", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const gitTransposeLines = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/git/transposeLines', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/types.gen.ts b/src/api-clients/client-rest-git/types.gen.ts index 26ac8e7..dce87ba 100644 --- a/src/api-clients/client-rest-git/types.gen.ts +++ b/src/api-clients/client-rest-git/types.gen.ts @@ -1,744 +1,725 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type CommonError = - | { - /** - * Error code - */ - code: "GIT_OPERATION_IN_PROGRESS" | "GIT_REMOTE_FILE_NOT_FOUND"; - /** - * Error message - */ - message: string; - } - | { - /** - * Protocol error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { [key: string]: unknown; - }; }; +}; + +export type CommonError = { + /** + * Error code + */ + code: 'GIT_OPERATION_IN_PROGRESS' | 'GIT_REMOTE_FILE_NOT_FOUND'; + /** + * Error message + */ + message: string; +} | { + /** + * Protocol error code + */ + code: string; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + }; +}; /** * Git status short format codes */ -export type GitStatusShortFormat = "" | "M" | "A" | "D" | "R" | "C" | "U" | "?"; +export type GitStatusShortFormat = '' | 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'; export type GitItem = { - /** - * File path - */ - path: string; - index: GitStatusShortFormat; - workingTree: GitStatusShortFormat; - /** - * Whether the file is staged - */ - isStaged: boolean; - /** - * Whether the file has conflicts - */ - isConflicted: boolean; - /** - * Unique identifier for the file - */ - fileId?: string; + /** + * File path + */ + path: string; + index: GitStatusShortFormat; + workingTree: GitStatusShortFormat; + /** + * Whether the file is staged + */ + isStaged: boolean; + /** + * Whether the file has conflicts + */ + isConflicted: boolean; + /** + * Unique identifier for the file + */ + fileId?: string; }; /** * Map of file IDs to Git items */ export type GitChangedFiles = { - [key: string]: GitItem; + [key: string]: GitItem; }; export type GitBranchProperties = { - /** - * Current HEAD reference - */ - head?: unknown; - /** - * Current branch name - */ - branch?: unknown; - /** - * Number of commits ahead of the remote - */ - ahead: number; - /** - * Number of commits behind the remote - */ - behind: number; - /** - * Whether the branch is safe to operate on - */ - safe: boolean; + /** + * Current HEAD reference + */ + head?: unknown; + /** + * Current branch name + */ + branch?: unknown; + /** + * Number of commits ahead of the remote + */ + ahead: number; + /** + * Number of commits behind the remote + */ + behind: number; + /** + * Whether the branch is safe to operate on + */ + safe: boolean; }; export type GitCommit = { - /** - * Commit hash - */ - hash: string; - /** - * Commit date - */ - date: string; - /** - * Commit message - */ - message: string; - /** - * Commit author - */ - author: string; + /** + * Commit hash + */ + hash: string; + /** + * Commit date + */ + date: string; + /** + * Commit message + */ + message: string; + /** + * Commit author + */ + author: string; }; export type GitStatus = { - changedFiles: GitChangedFiles; - deletedFiles: Array; - /** - * Whether there are remote conflicts - */ - conflicts: boolean; - /** - * Whether there are local changes - */ - localChanges: boolean; - remote: GitBranchProperties; - target: GitBranchProperties; - /** - * Current HEAD reference - */ - head?: string; - commits: Array; - /** - * Current branch name - */ - branch: unknown; - /** - * Whether a merge is in progress - */ - isMerging: boolean; + changedFiles: GitChangedFiles; + deletedFiles: Array; + /** + * Whether there are remote conflicts + */ + conflicts: boolean; + /** + * Whether there are local changes + */ + localChanges: boolean; + remote: GitBranchProperties; + target: GitBranchProperties; + /** + * Current HEAD reference + */ + head?: string; + commits: Array; + /** + * Current branch name + */ + branch: unknown; + /** + * Whether a merge is in progress + */ + isMerging: boolean; }; export type GitTargetDiff = { - /** - * Number of commits ahead of the target - */ - ahead: number; - /** - * Number of commits behind the target - */ - behind: number; - commits: Array; + /** + * Number of commits ahead of the target + */ + ahead: number; + /** + * Number of commits behind the target + */ + behind: number; + commits: Array; }; export type GitRemotes = { - /** - * Origin remote URL - */ - origin: string; - /** - * Upstream remote URL - */ - upstream: string; + /** + * Origin remote URL + */ + origin: string; + /** + * Upstream remote URL + */ + upstream: string; }; export type GitRemoteParams = { - /** - * Branch or commit hash - */ - reference: string; - /** - * Path to the file - */ - path: string; + /** + * Branch or commit hash + */ + reference: string; + /** + * Path to the file + */ + path: string; }; export type GitDiffStatusParams = { - /** - * Base reference used for diffing - */ - base: string; - /** - * Head reference used for diffing - */ - head: string; + /** + * Base reference used for diffing + */ + base: string; + /** + * Head reference used for diffing + */ + head: string; }; export type GitDiffStatusItem = { - status: GitStatusShortFormat; - /** - * Path to the file - */ - path: string; - /** - * Original path for renamed files - */ - oldPath?: string; - hunks: Array<{ - original: { - start: number; - end: number; - }; - modified: { - start: number; - end: number; - }; - }>; + status: GitStatusShortFormat; + /** + * Path to the file + */ + path: string; + /** + * Original path for renamed files + */ + oldPath?: string; + hunks: Array<{ + original: { + start: number; + end: number; + }; + modified: { + start: number; + end: number; + }; + }>; }; export type GitDiffStatusResult = { - files: Array; + files: Array; }; export type GitStatusData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/git/status"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/status'; }; export type GitStatusErrors = { - /** - * Error retrieving git status - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git status + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitStatusError = GitStatusErrors[keyof GitStatusErrors]; export type GitStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitStatus; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitStatus; + }; }; export type GitStatusResponse = GitStatusResponses[keyof GitStatusResponses]; export type GitRemotesData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/git/remotes"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/remotes'; }; export type GitRemotesErrors = { - /** - * Error retrieving git remotes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git remotes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitRemotesError = GitRemotesErrors[keyof GitRemotesErrors]; export type GitRemotesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitRemotes; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitRemotes; + }; }; export type GitRemotesResponse = GitRemotesResponses[keyof GitRemotesResponses]; export type GitTargetDiffData = { - body: { - /** - * Branch to compare against - */ - branch: string; - }; - path?: never; - query?: never; - url: "/git/targetDiff"; + body: { + /** + * Branch to compare against + */ + branch: string; + }; + path?: never; + query?: never; + url: '/git/targetDiff'; }; export type GitTargetDiffErrors = { - /** - * Error retrieving git target diff - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving git target diff + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitTargetDiffError = GitTargetDiffErrors[keyof GitTargetDiffErrors]; export type GitTargetDiffResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitTargetDiff; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitTargetDiff; + }; }; -export type GitTargetDiffResponse = - GitTargetDiffResponses[keyof GitTargetDiffResponses]; +export type GitTargetDiffResponse = GitTargetDiffResponses[keyof GitTargetDiffResponses]; export type GitPullData = { - body: { - /** - * Branch to pull from - */ - branch?: string; - /** - * Force pull even if there are conflicts - */ - force?: boolean; - }; - path?: never; - query?: never; - url: "/git/pull"; + body: { + /** + * Branch to pull from + */ + branch?: string; + /** + * Force pull even if there are conflicts + */ + force?: boolean; + }; + path?: never; + query?: never; + url: '/git/pull'; }; export type GitPullErrors = { - /** - * Error pulling from remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pulling from remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitPullError = GitPullErrors[keyof GitPullErrors]; export type GitPullResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type GitPullResponse = GitPullResponses[keyof GitPullResponses]; export type GitDiscardData = { - body: { - /** - * Paths of files to discard changes - */ - paths?: Array; - }; - path?: never; - query?: never; - url: "/git/discard"; + body: { + /** + * Paths of files to discard changes + */ + paths?: Array; + }; + path?: never; + query?: never; + url: '/git/discard'; }; export type GitDiscardErrors = { - /** - * Error discarding changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error discarding changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitDiscardError = GitDiscardErrors[keyof GitDiscardErrors]; export type GitDiscardResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - paths?: Array; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + paths?: Array; + }; }; - }; }; export type GitDiscardResponse = GitDiscardResponses[keyof GitDiscardResponses]; export type GitCommitData = { - body: { - /** - * Paths of files to commit - */ - paths?: Array; - /** - * Commit message - */ - message: string; - /** - * Whether to push the commit immediately - */ - push?: boolean; - }; - path?: never; - query?: never; - url: "/git/commit"; + body: { + /** + * Paths of files to commit + */ + paths?: Array; + /** + * Commit message + */ + message: string; + /** + * Whether to push the commit immediately + */ + push?: boolean; + }; + path?: never; + query?: never; + url: '/git/commit'; }; export type GitCommitErrors = { - /** - * Error committing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error committing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitCommitError = GitCommitErrors[keyof GitCommitErrors]; export type GitCommitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the shell process - */ - shellId: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * ID of the shell process + */ + shellId: string; + }; }; - }; }; export type GitCommitResponse = GitCommitResponses[keyof GitCommitResponses]; export type GitPushData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/git/push"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/push'; }; export type GitPushErrors = { - /** - * Error pushing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pushing changes + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitPushError = GitPushErrors[keyof GitPushErrors]; export type GitPushResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type GitPushResponse = GitPushResponses[keyof GitPushResponses]; export type GitPushToRemoteData = { - body: { - /** - * URL of the remote repository - */ - url: string; - /** - * Branch to push to - */ - branch: string; - /** - * Whether to squash all commits into one - */ - squashAllCommits?: boolean; - }; - path?: never; - query?: never; - url: "/git/pushToRemote"; + body: { + /** + * URL of the remote repository + */ + url: string; + /** + * Branch to push to + */ + branch: string; + /** + * Whether to squash all commits into one + */ + squashAllCommits?: boolean; + }; + path?: never; + query?: never; + url: '/git/pushToRemote'; }; export type GitPushToRemoteErrors = { - /** - * Error pushing to remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error pushing to remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitPushToRemoteError = - GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; +export type GitPushToRemoteError = GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; export type GitPushToRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitPushToRemoteResponse = - GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; +export type GitPushToRemoteResponse = GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; export type GitRenameBranchData = { - body: { - /** - * Current branch name - */ - oldBranch: string; - /** - * New branch name - */ - newBranch: string; - }; - path?: never; - query?: never; - url: "/git/renameBranch"; + body: { + /** + * Current branch name + */ + oldBranch: string; + /** + * New branch name + */ + newBranch: string; + }; + path?: never; + query?: never; + url: '/git/renameBranch'; }; export type GitRenameBranchErrors = { - /** - * Error renaming branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error renaming branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitRenameBranchError = - GitRenameBranchErrors[keyof GitRenameBranchErrors]; +export type GitRenameBranchError = GitRenameBranchErrors[keyof GitRenameBranchErrors]; export type GitRenameBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitRenameBranchResponse = - GitRenameBranchResponses[keyof GitRenameBranchResponses]; +export type GitRenameBranchResponse = GitRenameBranchResponses[keyof GitRenameBranchResponses]; export type GitRemoteContentData = { - body: GitRemoteParams; - path?: never; - query?: never; - url: "/git/remoteContent"; + body: GitRemoteParams; + path?: never; + query?: never; + url: '/git/remoteContent'; }; export type GitRemoteContentErrors = { - /** - * Error retrieving remote content - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving remote content + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitRemoteContentError = - GitRemoteContentErrors[keyof GitRemoteContentErrors]; +export type GitRemoteContentError = GitRemoteContentErrors[keyof GitRemoteContentErrors]; export type GitRemoteContentResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * Content of the file - */ - content: string; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + /** + * Content of the file + */ + content: string; + }; }; - }; }; -export type GitRemoteContentResponse = - GitRemoteContentResponses[keyof GitRemoteContentResponses]; +export type GitRemoteContentResponse = GitRemoteContentResponses[keyof GitRemoteContentResponses]; export type GitDiffStatusData = { - body: GitDiffStatusParams; - path?: never; - query?: never; - url: "/git/diffStatus"; + body: GitDiffStatusParams; + path?: never; + query?: never; + url: '/git/diffStatus'; }; export type GitDiffStatusErrors = { - /** - * Error retrieving diff status - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving diff status + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type GitDiffStatusError = GitDiffStatusErrors[keyof GitDiffStatusErrors]; export type GitDiffStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitDiffStatusResult; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: GitDiffStatusResult; + }; }; -export type GitDiffStatusResponse = - GitDiffStatusResponses[keyof GitDiffStatusResponses]; +export type GitDiffStatusResponse = GitDiffStatusResponses[keyof GitDiffStatusResponses]; export type GitResetLocalWithRemoteData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/git/resetLocalWithRemote"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/resetLocalWithRemote'; }; export type GitResetLocalWithRemoteErrors = { - /** - * Error resetting local with remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error resetting local with remote + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitResetLocalWithRemoteError = - GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; +export type GitResetLocalWithRemoteError = GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; export type GitResetLocalWithRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitResetLocalWithRemoteResponse = - GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; +export type GitResetLocalWithRemoteResponse = GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; export type GitCheckoutInitialBranchData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/git/checkoutInitialBranch"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/git/checkoutInitialBranch'; }; export type GitCheckoutInitialBranchErrors = { - /** - * Error checking out initial branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error checking out initial branch + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitCheckoutInitialBranchError = - GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; +export type GitCheckoutInitialBranchError = GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; export type GitCheckoutInitialBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type GitCheckoutInitialBranchResponse = - GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; +export type GitCheckoutInitialBranchResponse = GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; export type GitTransposeLinesData = { - body: Array<{ - /** - * Git commit SHA - */ - sha: string; - /** - * Path to the file - */ - path: string; - /** - * Line number to transpose - */ - line: number; - }>; - path?: never; - query?: never; - url: "/git/transposeLines"; + body: Array<{ + /** + * Git commit SHA + */ + sha: string; + /** + * Path to the file + */ + path: string; + /** + * Line number to transpose + */ + line: number; + }>; + path?: never; + query?: never; + url: '/git/transposeLines'; }; export type GitTransposeLinesErrors = { - /** - * Error transposing lines - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error transposing lines + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type GitTransposeLinesError = - GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; +export type GitTransposeLinesError = GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; export type GitTransposeLinesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array< - | { - path: string; - line: number; - } - | unknown - >; - }; -}; - -export type GitTransposeLinesResponse = - GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: Array<{ + path: string; + line: number; + } | unknown>; + }; +}; + +export type GitTransposeLinesResponse = GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/client.gen.ts b/src/api-clients/client-rest-setup/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-setup/client.gen.ts +++ b/src/api-clients/client-rest-setup/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/index.ts b/src/api-clients/client-rest-setup/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-setup/index.ts +++ b/src/api-clients/client-rest-setup/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/sdk.gen.ts b/src/api-clients/client-rest-setup/sdk.gen.ts index 3855e23..1a3ae61 100644 --- a/src/api-clients/client-rest-setup/sdk.gen.ts +++ b/src/api-clients/client-rest-setup/sdk.gen.ts @@ -1,190 +1,119 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - SetupGetData, - SetupGetResponse, - SetupGetError, - SetupSkipStepData, - SetupSkipStepResponse, - SetupSkipStepError, - SetupSkipAllData, - SetupSkipAllResponse, - SetupSkipAllError, - SetupDisableData, - SetupDisableResponse, - SetupDisableError, - SetupEnableData, - SetupEnableResponse, - SetupEnableError, - SetupInitData, - SetupInitResponse, - SetupInitError, - SetupSetStepData, - SetupSetStepResponse, - SetupSetStepError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { SetupGetData, SetupGetResponse, SetupGetError, SetupSkipStepData, SetupSkipStepResponse, SetupSkipStepError, SetupSkipAllData, SetupSkipAllResponse, SetupSkipAllError, SetupDisableData, SetupDisableResponse, SetupDisableError, SetupEnableData, SetupEnableResponse, SetupEnableError, SetupInitData, SetupInitResponse, SetupInitError, SetupSetStepData, SetupSetStepResponse, SetupSetStepError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Get setup progress * Retrieve the current setup progress status */ -export const setupGet = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupGetResponse, - SetupGetError, - ThrowOnError - >({ - url: "/setup/get", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupGet = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/get', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Skip setup step * Skip a specific step in the setup process */ -export const setupSkipStep = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupSkipStepResponse, - SetupSkipStepError, - ThrowOnError - >({ - url: "/setup/skip", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupSkipStep = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/skip', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Skip all setup steps * Skip all remaining steps in the setup process */ -export const setupSkipAll = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupSkipAllResponse, - SetupSkipAllError, - ThrowOnError - >({ - url: "/setup/skipAll", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupSkipAll = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/skipAll', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Disable setup * Disable the setup process */ -export const setupDisable = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupDisableResponse, - SetupDisableError, - ThrowOnError - >({ - url: "/setup/disable", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupDisable = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/disable', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Enable setup * Enable the setup process */ -export const setupEnable = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupEnableResponse, - SetupEnableError, - ThrowOnError - >({ - url: "/setup/enable", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupEnable = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/enable', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Initialize setup * Initialize the setup process */ -export const setupInit = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupInitResponse, - SetupInitError, - ThrowOnError - >({ - url: "/setup/init", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const setupInit = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/init', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Set current setup step * Set the current step in the setup process (used for restarting) */ -export const setupSetStep = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SetupSetStepResponse, - SetupSetStepError, - ThrowOnError - >({ - url: "/setup/setStep", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const setupSetStep = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/setup/setStep', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/types.gen.ts b/src/api-clients/client-rest-setup/types.gen.ts index 8cfaef0..43425e7 100644 --- a/src/api-clients/client-rest-setup/types.gen.ts +++ b/src/api-clients/client-rest-setup/types.gen.ts @@ -1,300 +1,295 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type ProtocolError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; + /** + * Error code + */ + code: number; + /** + * Error message + */ + message: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; }; /** * Status of a setup shell step */ -export type SetupShellStatus = "SUCCEEDED" | "FAILED" | "SKIPPED"; +export type SetupShellStatus = 'SUCCEEDED' | 'FAILED' | 'SKIPPED'; export type Step = { - /** - * Name of the setup step - */ - name: string; - /** - * Command to execute for this step - */ - command: string; - /** - * ID of the shell executing the command - */ - shellId: string | null; - finishStatus: SetupShellStatus; + /** + * Name of the setup step + */ + name: string; + /** + * Command to execute for this step + */ + command: string; + /** + * ID of the shell executing the command + */ + shellId: string | null; + finishStatus: SetupShellStatus; }; export type SetupProgress = { - /** - * Current state of the setup process - */ - state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; - /** - * List of setup steps - */ - steps: Array; - /** - * Index of the current step being executed - */ - currentStepIndex: number; + /** + * Current state of the setup process + */ + state: 'IDLE' | 'IN_PROGRESS' | 'FINISHED' | 'STOPPED'; + /** + * List of setup steps + */ + steps: Array; + /** + * Index of the current step being executed + */ + currentStepIndex: number; }; export type SetupGetData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/setup/get"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/setup/get'; }; export type SetupGetErrors = { - /** - * Error retrieving setup progress - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error retrieving setup progress + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupGetError = SetupGetErrors[keyof SetupGetErrors]; export type SetupGetResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; export type SetupGetResponse = SetupGetResponses[keyof SetupGetResponses]; export type SetupSkipStepData = { - body: { - /** - * Index of the step to skip - */ - stepIndexToSkip: number; - }; - path?: never; - query?: never; - url: "/setup/skip"; + body: { + /** + * Index of the step to skip + */ + stepIndexToSkip: number; + }; + path?: never; + query?: never; + url: '/setup/skip'; }; export type SetupSkipStepErrors = { - /** - * Error skipping step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error skipping step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSkipStepError = SetupSkipStepErrors[keyof SetupSkipStepErrors]; export type SetupSkipStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSkipStepResponse = - SetupSkipStepResponses[keyof SetupSkipStepResponses]; +export type SetupSkipStepResponse = SetupSkipStepResponses[keyof SetupSkipStepResponses]; export type SetupSkipAllData = { - body: unknown; - path?: never; - query?: never; - url: "/setup/skipAll"; + body: unknown; + path?: never; + query?: never; + url: '/setup/skipAll'; }; export type SetupSkipAllErrors = { - /** - * Error skipping all steps - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error skipping all steps + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSkipAllError = SetupSkipAllErrors[keyof SetupSkipAllErrors]; export type SetupSkipAllResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSkipAllResponse = - SetupSkipAllResponses[keyof SetupSkipAllResponses]; +export type SetupSkipAllResponse = SetupSkipAllResponses[keyof SetupSkipAllResponses]; export type SetupDisableData = { - body: unknown; - path?: never; - query?: never; - url: "/setup/disable"; + body: unknown; + path?: never; + query?: never; + url: '/setup/disable'; }; export type SetupDisableErrors = { - /** - * Error disabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error disabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupDisableError = SetupDisableErrors[keyof SetupDisableErrors]; export type SetupDisableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupDisableResponse = - SetupDisableResponses[keyof SetupDisableResponses]; +export type SetupDisableResponse = SetupDisableResponses[keyof SetupDisableResponses]; export type SetupEnableData = { - body: unknown; - path?: never; - query?: never; - url: "/setup/enable"; + body: unknown; + path?: never; + query?: never; + url: '/setup/enable'; }; export type SetupEnableErrors = { - /** - * Error enabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error enabling setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupEnableError = SetupEnableErrors[keyof SetupEnableErrors]; export type SetupEnableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupEnableResponse = - SetupEnableResponses[keyof SetupEnableResponses]; +export type SetupEnableResponse = SetupEnableResponses[keyof SetupEnableResponses]; export type SetupInitData = { - body: unknown; - path?: never; - query?: never; - url: "/setup/init"; + body: unknown; + path?: never; + query?: never; + url: '/setup/init'; }; export type SetupInitErrors = { - /** - * Error initializing setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error initializing setup + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupInitError = SetupInitErrors[keyof SetupInitErrors]; export type SetupInitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; export type SetupInitResponse = SetupInitResponses[keyof SetupInitResponses]; export type SetupSetStepData = { - body: { - /** - * Index of the step to set as current - */ - stepIndex: number; - }; - path?: never; - query?: never; - url: "/setup/setStep"; + body: { + /** + * Index of the step to set as current + */ + stepIndex: number; + }; + path?: never; + query?: never; + url: '/setup/setStep'; }; export type SetupSetStepErrors = { - /** - * Error setting current step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; + /** + * Error setting current step + */ + 400: ErrorResponse & { + error?: ProtocolError; + }; }; export type SetupSetStepError = SetupSetStepErrors[keyof SetupSetStepErrors]; export type SetupSetStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SetupProgress; + }; }; -export type SetupSetStepResponse = - SetupSetStepResponses[keyof SetupSetStepResponses]; +export type SetupSetStepResponse = SetupSetStepResponses[keyof SetupSetStepResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/client.gen.ts b/src/api-clients/client-rest-shell/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-shell/client.gen.ts +++ b/src/api-clients/client-rest-shell/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/index.ts b/src/api-clients/client-rest-shell/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-shell/index.ts +++ b/src/api-clients/client-rest-shell/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/sdk.gen.ts b/src/api-clients/client-rest-shell/sdk.gen.ts index b721e6f..319634a 100644 --- a/src/api-clients/client-rest-shell/sdk.gen.ts +++ b/src/api-clients/client-rest-shell/sdk.gen.ts @@ -1,238 +1,149 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - ShellCreateData, - ShellCreateResponse, - ShellCreateError, - ShellInData, - ShellInResponse, - ShellInError, - ShellListData, - ShellListResponse, - ShellListError, - ShellOpenData, - ShellOpenResponse, - ShellOpenError, - ShellCloseData, - ShellCloseResponse, - ShellCloseError, - ShellRestartData, - ShellRestartResponse, - ShellRestartError, - ShellTerminateData, - ShellTerminateResponse, - ShellTerminateError, - ShellResizeData, - ShellResizeResponse, - ShellResizeError, - ShellRenameData, - ShellRenameResponse, - ShellRenameError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { ShellCreateData, ShellCreateResponse, ShellCreateError, ShellInData, ShellInResponse, ShellInError, ShellListData, ShellListResponse, ShellListError, ShellOpenData, ShellOpenResponse, ShellOpenError, ShellCloseData, ShellCloseResponse, ShellCloseError, ShellRestartData, ShellRestartResponse, ShellRestartError, ShellTerminateData, ShellTerminateResponse, ShellTerminateError, ShellResizeData, ShellResizeResponse, ShellResizeError, ShellRenameData, ShellRenameResponse, ShellRenameError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Create a new shell * Creates a new terminal or command shell */ -export const shellCreate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellCreateResponse, - ShellCreateError, - ThrowOnError - >({ - url: "/shell/create", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/create', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Send input to shell * Sends user input to an active shell */ -export const shellIn = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellInResponse, - ShellInError, - ThrowOnError - >({ - url: "/shell/in", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellIn = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/in', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * List all shells * Retrieves a list of all available shells */ -export const shellList = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellListResponse, - ShellListError, - ThrowOnError - >({ - url: "/shell/list", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellList = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/list', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Open an existing shell * Opens an existing shell and retrieves its buffer */ -export const shellOpen = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellOpenResponse, - ShellOpenError, - ThrowOnError - >({ - url: "/shell/open", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellOpen = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/open', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Close a shell * Closes a shell without terminating the underlying process */ -export const shellClose = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellCloseResponse, - ShellCloseError, - ThrowOnError - >({ - url: "/shell/close", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellClose = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/close', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Restart a shell * Restarts an existing shell process */ -export const shellRestart = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellRestartResponse, - ShellRestartError, - ThrowOnError - >({ - url: "/shell/restart", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellRestart = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/restart', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Terminate a shell * Terminates a shell and its underlying process */ -export const shellTerminate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellTerminateResponse, - ShellTerminateError, - ThrowOnError - >({ - url: "/shell/terminate", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellTerminate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/terminate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Resize a shell * Updates the dimensions of a shell */ -export const shellResize = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellResizeResponse, - ShellResizeError, - ThrowOnError - >({ - url: "/shell/resize", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const shellResize = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/resize', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Rename a shell * Updates the name of a shell */ -export const shellRename = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - ShellRenameResponse, - ShellRenameError, - ThrowOnError - >({ - url: "/shell/rename", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const shellRename = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/shell/rename', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/types.gen.ts b/src/api-clients/client-rest-shell/types.gen.ts index efc243b..45aa947 100644 --- a/src/api-clients/client-rest-shell/types.gen.ts +++ b/src/api-clients/client-rest-shell/types.gen.ts @@ -1,29 +1,29 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; /** @@ -32,429 +32,412 @@ export type ErrorResponse = { export type ShellId = string; export type ShellSize = { - /** - * Number of columns in the terminal - */ - cols: number; - /** - * Number of rows in the terminal - */ - rows: number; + /** + * Number of columns in the terminal + */ + cols: number; + /** + * Number of rows in the terminal + */ + rows: number; }; /** * Type of shell process */ -export type ShellProcessType = "TERMINAL" | "COMMAND"; +export type ShellProcessType = 'TERMINAL' | 'COMMAND'; /** * Current status of the shell process */ -export type ShellProcessStatus = - | "RUNNING" - | "FINISHED" - | "ERROR" - | "KILLED" - | "RESTARTING"; +export type ShellProcessStatus = 'RUNNING' | 'FINISHED' | 'ERROR' | 'KILLED' | 'RESTARTING'; export type BaseShellDto = { - shellId: ShellId; - /** - * Display name of the shell - */ - name: string; - status: ShellProcessStatus; - /** - * Exit code of the process if it has finished - */ - exitCode?: number | null; + shellId: ShellId; + /** + * Display name of the shell + */ + name: string; + status: ShellProcessStatus; + /** + * Exit code of the process if it has finished + */ + exitCode?: number | null; }; export type CommandShellDto = BaseShellDto & { - /** - * Indicates this is a command shell - */ - shellType: "COMMAND"; - /** - * The command that was executed to start this shell - */ - startCommand: string; + /** + * Indicates this is a command shell + */ + shellType: 'COMMAND'; + /** + * The command that was executed to start this shell + */ + startCommand: string; }; export type TerminalShellDto = BaseShellDto & { - /** - * Indicates this is a terminal shell - */ - shellType: "TERMINAL"; - /** - * Username of the shell owner - */ - ownerUsername: string; - /** - * Whether this is a system shell - */ - isSystemShell: boolean; -}; - -export type ShellDto = - | ({ - shellType?: "COMMAND"; - } & CommandShellDto) - | ({ - shellType?: "TERMINAL"; - } & TerminalShellDto); + /** + * Indicates this is a terminal shell + */ + shellType: 'TERMINAL'; + /** + * Username of the shell owner + */ + ownerUsername: string; + /** + * Whether this is a system shell + */ + isSystemShell: boolean; +}; + +export type ShellDto = ({ + shellType?: 'COMMAND'; +} & CommandShellDto) | ({ + shellType?: 'TERMINAL'; +} & TerminalShellDto); export type OpenCommandShellDto = CommandShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; + /** + * Content buffer of the shell + */ + buffer: Array; }; export type OpenTerminalShellDto = TerminalShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; -}; - -export type OpenShellDto = - | ({ - shellType?: "COMMAND"; - } & OpenCommandShellDto) - | ({ - shellType?: "TERMINAL"; - } & OpenTerminalShellDto); - -export type CommonError = - | { - /** - * Error code indicating the shell is not accessible - */ - code: "SHELL_NOT_ACCESSIBLE"; - /** - * Error message - */ - message: string; - } - | { - /** - * Protocol error code - */ - code: string; - /** - * Error message - */ - message: string; - }; + /** + * Content buffer of the shell + */ + buffer: Array; +}; -export type ShellCreateData = { - body: { +export type OpenShellDto = ({ + shellType?: 'COMMAND'; +} & OpenCommandShellDto) | ({ + shellType?: 'TERMINAL'; +} & OpenTerminalShellDto); + +export type CommonError = { + /** + * Error code indicating the shell is not accessible + */ + code: 'SHELL_NOT_ACCESSIBLE'; /** - * Command to execute in the shell + * Error message */ - command?: string; + message: string; +} | { /** - * Working directory for the shell + * Protocol error code */ - cwd?: string; - size?: ShellSize; - type?: ShellProcessType; + code: string; /** - * Whether this shell is started by the editor itself to run a specific process + * Error message */ - isSystemShell?: boolean; - }; - path?: never; - query?: never; - url: "/shell/create"; + message: string; +}; + +export type ShellCreateData = { + body: { + /** + * Command to execute in the shell + */ + command?: string; + /** + * Working directory for the shell + */ + cwd?: string; + size?: ShellSize; + type?: ShellProcessType; + /** + * Whether this shell is started by the editor itself to run a specific process + */ + isSystemShell?: boolean; + }; + path?: never; + query?: never; + url: '/shell/create'; }; export type ShellCreateErrors = { - /** - * Error creating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error creating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellCreateError = ShellCreateErrors[keyof ShellCreateErrors]; export type ShellCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; }; -export type ShellCreateResponse = - ShellCreateResponses[keyof ShellCreateResponses]; +export type ShellCreateResponse = ShellCreateResponses[keyof ShellCreateResponses]; export type ShellInData = { - body: { - shellId: ShellId; - /** - * Input to send to the shell - */ - input: string; - size: ShellSize; - }; - path?: never; - query?: never; - url: "/shell/in"; + body: { + shellId: ShellId; + /** + * Input to send to the shell + */ + input: string; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/in'; }; export type ShellInErrors = { - /** - * Error sending input to shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error sending input to shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellInError = ShellInErrors[keyof ShellInErrors]; export type ShellInResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type ShellInResponse = ShellInResponses[keyof ShellInResponses]; export type ShellListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/shell/list"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/shell/list'; }; export type ShellListErrors = { - /** - * Error listing shells - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error listing shells + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellListError = ShellListErrors[keyof ShellListErrors]; export type ShellListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - shells: Array; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + shells: Array; + }; }; - }; }; export type ShellListResponse = ShellListResponses[keyof ShellListResponses]; export type ShellOpenData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: "/shell/open"; + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/open'; }; export type ShellOpenErrors = { - /** - * Error opening shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error opening shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellOpenError = ShellOpenErrors[keyof ShellOpenErrors]; export type ShellOpenResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: OpenShellDto; + }; }; export type ShellOpenResponse = ShellOpenResponses[keyof ShellOpenResponses]; export type ShellCloseData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: "/shell/close"; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/close'; }; export type ShellCloseErrors = { - /** - * Error closing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error closing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellCloseError = ShellCloseErrors[keyof ShellCloseErrors]; export type ShellCloseResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; export type ShellCloseResponse = ShellCloseResponses[keyof ShellCloseResponses]; export type ShellRestartData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: "/shell/restart"; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/restart'; }; export type ShellRestartErrors = { - /** - * Error restarting shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error restarting shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellRestartError = ShellRestartErrors[keyof ShellRestartErrors]; export type ShellRestartResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellRestartResponse = - ShellRestartResponses[keyof ShellRestartResponses]; +export type ShellRestartResponse = ShellRestartResponses[keyof ShellRestartResponses]; export type ShellTerminateData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: "/shell/terminate"; + body: { + shellId: ShellId; + }; + path?: never; + query?: never; + url: '/shell/terminate'; }; export type ShellTerminateErrors = { - /** - * Error terminating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error terminating shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; -export type ShellTerminateError = - ShellTerminateErrors[keyof ShellTerminateErrors]; +export type ShellTerminateError = ShellTerminateErrors[keyof ShellTerminateErrors]; export type ShellTerminateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: ShellDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: ShellDto; + }; }; -export type ShellTerminateResponse = - ShellTerminateResponses[keyof ShellTerminateResponses]; +export type ShellTerminateResponse = ShellTerminateResponses[keyof ShellTerminateResponses]; export type ShellResizeData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: "/shell/resize"; + body: { + shellId: ShellId; + size: ShellSize; + }; + path?: never; + query?: never; + url: '/shell/resize'; }; export type ShellResizeErrors = { - /** - * Error resizing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error resizing shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellResizeError = ShellResizeErrors[keyof ShellResizeErrors]; export type ShellResizeResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellResizeResponse = - ShellResizeResponses[keyof ShellResizeResponses]; +export type ShellResizeResponse = ShellResizeResponses[keyof ShellResizeResponses]; export type ShellRenameData = { - body: { - shellId: ShellId; - /** - * New name for the shell - */ - name: string; - }; - path?: never; - query?: never; - url: "/shell/rename"; + body: { + shellId: ShellId; + /** + * New name for the shell + */ + name: string; + }; + path?: never; + query?: never; + url: '/shell/rename'; }; export type ShellRenameErrors = { - /** - * Error renaming shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error renaming shell + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type ShellRenameError = ShellRenameErrors[keyof ShellRenameErrors]; export type ShellRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type ShellRenameResponse = - ShellRenameResponses[keyof ShellRenameResponses]; +export type ShellRenameResponse = ShellRenameResponses[keyof ShellRenameResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/client.gen.ts b/src/api-clients/client-rest-system/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-system/client.gen.ts +++ b/src/api-clients/client-rest-system/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-system/index.ts b/src/api-clients/client-rest-system/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-system/index.ts +++ b/src/api-clients/client-rest-system/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/sdk.gen.ts b/src/api-clients/client-rest-system/sdk.gen.ts index 41b133b..d1f450e 100644 --- a/src/api-clients/client-rest-system/sdk.gen.ts +++ b/src/api-clients/client-rest-system/sdk.gen.ts @@ -1,94 +1,59 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - SystemUpdateData, - SystemUpdateResponse, - SystemUpdateError, - SystemHibernateData, - SystemHibernateResponse, - SystemHibernateError, - SystemMetricsData, - SystemMetricsResponse, - SystemMetricsError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { SystemUpdateData, SystemUpdateResponse, SystemUpdateError, SystemHibernateData, SystemHibernateResponse, SystemHibernateError, SystemMetricsData, SystemMetricsResponse, SystemMetricsError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Update system * Update the sandbox system */ -export const systemUpdate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SystemUpdateResponse, - SystemUpdateError, - ThrowOnError - >({ - url: "/system/update", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const systemUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/update', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Hibernate system * Put the sandbox system into hibernation mode */ -export const systemHibernate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SystemHibernateResponse, - SystemHibernateError, - ThrowOnError - >({ - url: "/system/hibernate", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const systemHibernate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/hibernate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Get system metrics * Retrieve current system metrics including CPU, memory and storage usage */ -export const systemMetrics = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SystemMetricsResponse, - SystemMetricsError, - ThrowOnError - >({ - url: "/system/metrics", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const systemMetrics = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/system/metrics', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/types.gen.ts b/src/api-clients/client-rest-system/types.gen.ts index 6aed933..f23b7f1 100644 --- a/src/api-clients/client-rest-system/types.gen.ts +++ b/src/api-clients/client-rest-system/types.gen.ts @@ -1,211 +1,207 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type SystemError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type SystemMetricsStatus = { - cpu: { - /** - * Number of CPU cores - */ - cores: number; /** - * Used CPU resources + * Error code */ - used: number; + code: number; /** - * Configured CPU resources + * Error message */ - configured: number; - }; - memory: { + message: string; /** - * Used memory in bytes + * Additional error data */ - used: number; + data?: { + [key: string]: unknown; + } | null; +}; + +export type SystemMetricsStatus = { + cpu: { + /** + * Number of CPU cores + */ + cores: number; + /** + * Used CPU resources + */ + used: number; + /** + * Configured CPU resources + */ + configured: number; + }; + memory: { + /** + * Used memory in bytes + */ + used: number; + /** + * Total available memory in bytes + */ + total: number; + /** + * Configured memory limit in bytes + */ + configured: number; + }; + storage: { + /** + * Used storage in bytes + */ + used: number; + /** + * Total available storage in bytes + */ + total: number; + /** + * Configured storage limit in bytes + */ + configured: number; + }; +}; + +export type InitStatus = { /** - * Total available memory in bytes + * Status message */ - total: number; + message: string; /** - * Configured memory limit in bytes + * Whether the status represents an error */ - configured: number; - }; - storage: { + isError?: boolean | null; /** - * Used storage in bytes + * Current progress (0-100) */ - used: number; + progress: number; /** - * Total available storage in bytes + * Next progress target (0-100) */ - total: number; + nextProgress: number; /** - * Configured storage limit in bytes + * Standard output from the initialization process */ - configured: number; - }; -}; - -export type InitStatus = { - /** - * Status message - */ - message: string; - /** - * Whether the status represents an error - */ - isError?: boolean | null; - /** - * Current progress (0-100) - */ - progress: number; - /** - * Next progress target (0-100) - */ - nextProgress: number; - /** - * Standard output from the initialization process - */ - stdout?: string | null; + stdout?: string | null; }; export type SystemUpdateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/system/update"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/update'; }; export type SystemUpdateErrors = { - /** - * Error updating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error updating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; export type SystemUpdateError = SystemUpdateErrors[keyof SystemUpdateErrors]; export type SystemUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: { + [key: string]: unknown; + }; }; - }; }; -export type SystemUpdateResponse = - SystemUpdateResponses[keyof SystemUpdateResponses]; +export type SystemUpdateResponse = SystemUpdateResponses[keyof SystemUpdateResponses]; export type SystemHibernateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/system/hibernate"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/hibernate'; }; export type SystemHibernateErrors = { - /** - * Error hibernating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error hibernating system + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; -export type SystemHibernateError = - SystemHibernateErrors[keyof SystemHibernateErrors]; +export type SystemHibernateError = SystemHibernateErrors[keyof SystemHibernateErrors]; export type SystemHibernateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type SystemHibernateResponse = - SystemHibernateResponses[keyof SystemHibernateResponses]; +export type SystemHibernateResponse = SystemHibernateResponses[keyof SystemHibernateResponses]; export type SystemMetricsData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/system/metrics"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/system/metrics'; }; export type SystemMetricsErrors = { - /** - * Error retrieving system metrics - */ - 400: ErrorResponse & { - error?: SystemError; - }; + /** + * Error retrieving system metrics + */ + 400: ErrorResponse & { + error?: SystemError; + }; }; export type SystemMetricsError = SystemMetricsErrors[keyof SystemMetricsErrors]; export type SystemMetricsResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SystemMetricsStatus; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: SystemMetricsStatus; + }; }; -export type SystemMetricsResponse = - SystemMetricsResponses[keyof SystemMetricsResponses]; +export type SystemMetricsResponse = SystemMetricsResponses[keyof SystemMetricsResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/client.gen.ts b/src/api-clients/client-rest-task/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client-rest-task/client.gen.ts +++ b/src/api-clients/client-rest-task/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-task/index.ts b/src/api-clients/client-rest-task/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client-rest-task/index.ts +++ b/src/api-clients/client-rest-task/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/sdk.gen.ts b/src/api-clients/client-rest-task/sdk.gen.ts index d21f21f..133e8f8 100644 --- a/src/api-clients/client-rest-task/sdk.gen.ts +++ b/src/api-clients/client-rest-task/sdk.gen.ts @@ -1,238 +1,149 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - TaskListData, - TaskListResponse, - TaskListError, - TaskRunData, - TaskRunResponse, - TaskRunError, - TaskRunCommandData, - TaskRunCommandResponse, - TaskRunCommandError, - TaskStopData, - TaskStopResponse, - TaskStopError, - TaskCreateData, - TaskCreateResponse, - TaskCreateError, - TaskUpdateData, - TaskUpdateResponse, - TaskUpdateError, - TaskSaveToConfigData, - TaskSaveToConfigResponse, - TaskSaveToConfigError, - TaskGenerateConfigData, - TaskGenerateConfigResponse, - TaskGenerateConfigError, - TaskCreateSetupTasksData, - TaskCreateSetupTasksResponse, - TaskCreateSetupTasksError, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { TaskListData, TaskListResponse, TaskListError, TaskRunData, TaskRunResponse, TaskRunError, TaskRunCommandData, TaskRunCommandResponse, TaskRunCommandError, TaskStopData, TaskStopResponse, TaskStopError, TaskCreateData, TaskCreateResponse, TaskCreateError, TaskUpdateData, TaskUpdateResponse, TaskUpdateError, TaskSaveToConfigData, TaskSaveToConfigResponse, TaskSaveToConfigError, TaskGenerateConfigData, TaskGenerateConfigResponse, TaskGenerateConfigError, TaskCreateSetupTasksData, TaskCreateSetupTasksResponse, TaskCreateSetupTasksError } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * List tasks * Retrieve a list of all configured tasks */ -export const taskList = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskListResponse, - TaskListError, - ThrowOnError - >({ - url: "/task/list", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskList = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/list', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Run task * Start execution of a task by ID */ -export const taskRun = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskRunResponse, - TaskRunError, - ThrowOnError - >({ - url: "/task/run", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskRun = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/run', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Run command * Run a shell command directly, optionally saving it as a task */ -export const taskRunCommand = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskRunCommandResponse, - TaskRunCommandError, - ThrowOnError - >({ - url: "/task/runCommand", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskRunCommand = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/runCommand', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Stop task * Stop execution of a running task */ -export const taskStop = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskStopResponse, - TaskStopError, - ThrowOnError - >({ - url: "/task/stop", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskStop = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/stop', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Create task * Create a new task configuration */ -export const taskCreate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskCreateResponse, - TaskCreateError, - ThrowOnError - >({ - url: "/task/create", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/create', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Update task * Update an existing task configuration */ -export const taskUpdate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskUpdateResponse, - TaskUpdateError, - ThrowOnError - >({ - url: "/task/update", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/update', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Save task to config * Save a runtime task to the configuration file */ -export const taskSaveToConfig = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskSaveToConfigResponse, - TaskSaveToConfigError, - ThrowOnError - >({ - url: "/task/saveToConfig", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskSaveToConfig = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/saveToConfig', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Generate task config * Generate a configuration file from current tasks */ -export const taskGenerateConfig = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskGenerateConfigResponse, - TaskGenerateConfigError, - ThrowOnError - >({ - url: "/task/generateConfig", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const taskGenerateConfig = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/generateConfig', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** * Create setup tasks * Create tasks that run during sandbox setup */ -export const taskCreateSetupTasks = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TaskCreateSetupTasksResponse, - TaskCreateSetupTasksError, - ThrowOnError - >({ - url: "/task/createSetupTasks", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const taskCreateSetupTasks = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/task/createSetupTasks', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/types.gen.ts b/src/api-clients/client-rest-task/types.gen.ts index 984f610..012bd28 100644 --- a/src/api-clients/client-rest-task/types.gen.ts +++ b/src/api-clients/client-rest-task/types.gen.ts @@ -1,519 +1,507 @@ // This file is auto-generated by @hey-api/openapi-ts export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; + /** + * Status code for successful operations + */ + status: 0; + /** + * Result payload for the operation + */ + result: { + [key: string]: unknown; + }; }; export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; + /** + * Status code for error operations + */ + status: 1; + /** + * Error details + */ + error: { + [key: string]: unknown; + }; }; export type CommonError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message?: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type TaskError = - | { - /** - * CONFIG_FILE_ALREADY_EXISTS error code - */ - code: 600; - /** - * Error message - */ - message: string; - } - | { - /** - * TASK_NOT_FOUND error code - */ - code: 601; - /** - * Error message - */ - message: string; - } - | { - /** - * COMMAND_ALREADY_CONFIGURED error code - */ - code: 602; - /** - * Error message - */ - message: string; - } - | ({ - code?: "CommonError"; - } & CommonError); + /** + * Error code + */ + code: number; + /** + * Error message + */ + message?: string; + /** + * Additional error data + */ + data?: { + [key: string]: unknown; + } | null; +}; + +export type TaskError = { + /** + * CONFIG_FILE_ALREADY_EXISTS error code + */ + code: 600; + /** + * Error message + */ + message: string; +} | { + /** + * TASK_NOT_FOUND error code + */ + code: 601; + /** + * Error message + */ + message: string; +} | { + /** + * COMMAND_ALREADY_CONFIGURED error code + */ + code: 602; + /** + * Error message + */ + message: string; +} | ({ + code?: 'CommonError'; +} & CommonError); export type TaskDefinitionDto = { - /** - * Name of the task - */ - name: string; - /** - * Command to run for the task - */ - command: string; - /** - * Whether the task should run when the sandbox starts - */ - runAtStart?: boolean | null; - preview?: { /** - * Port to preview from this task + * Name of the task */ - port?: number | null; + name: string; /** - * Type of PR link to use + * Command to run for the task */ - "pr-link"?: "direct" | "redirect" | "devtool"; - } | null; + command: string; + /** + * Whether the task should run when the sandbox starts + */ + runAtStart?: boolean | null; + preview?: { + /** + * Port to preview from this task + */ + port?: number | null; + /** + * Type of PR link to use + */ + 'pr-link'?: 'direct' | 'redirect' | 'devtool'; + } | null; }; export type CommandShellDto = { - /** - * ID of the shell command - */ - id: string; - /** - * Command being executed - */ - command: string; - /** - * Current status of the shell command - */ - status: "initializing" | "running" | "stopped" | "error"; - /** - * Current output of the command - */ - output: string; + /** + * ID of the shell command + */ + id: string; + /** + * Command being executed + */ + command: string; + /** + * Current status of the shell command + */ + status: 'initializing' | 'running' | 'stopped' | 'error'; + /** + * Current output of the command + */ + output: string; }; export type Port = { - /** - * Port number - */ - port: number; - /** - * Hostname the port is bound to - */ - hostname: string; - /** - * Current status of the port - */ - status: "open" | "closed"; - /** - * ID of the task that opened this port - */ - taskId?: string | null; + /** + * Port number + */ + port: number; + /** + * Hostname the port is bound to + */ + hostname: string; + /** + * Current status of the port + */ + status: 'open' | 'closed'; + /** + * ID of the task that opened this port + */ + taskId?: string | null; }; export type TaskDto = TaskDefinitionDto & { - /** - * Unique ID of the task - */ - id: string; - /** - * Whether this task is unconfigured (not saved in config) - */ - unconfigured?: boolean | null; - shell: CommandShellDto | null; - /** - * Ports opened by this task - */ - ports: Array; + /** + * Unique ID of the task + */ + id: string; + /** + * Whether this task is unconfigured (not saved in config) + */ + unconfigured?: boolean | null; + shell: CommandShellDto | null; + /** + * Ports opened by this task + */ + ports: Array; }; export type TaskListDto = { - /** - * Map of task IDs to task objects - */ - tasks: { - [key: string]: TaskDto; - }; - /** - * Tasks that run during sandbox setup - */ - setupTasks: Array; - /** - * Validation errors in the task configuration - */ - validationErrors: Array; + /** + * Map of task IDs to task objects + */ + tasks: { + [key: string]: TaskDto; + }; + /** + * Tasks that run during sandbox setup + */ + setupTasks: Array; + /** + * Validation errors in the task configuration + */ + validationErrors: Array; }; export type TaskListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/task/list"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/task/list'; }; export type TaskListErrors = { - /** - * Error retrieving task list - */ - 400: ErrorResponse & { - error?: CommonError; - }; + /** + * Error retrieving task list + */ + 400: ErrorResponse & { + error?: CommonError; + }; }; export type TaskListError = TaskListErrors[keyof TaskListErrors]; export type TaskListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; }; export type TaskListResponse = TaskListResponses[keyof TaskListResponses]; export type TaskRunData = { - body: { - /** - * ID of the task to run - */ - taskId: string; - }; - path?: never; - query?: never; - url: "/task/run"; + body: { + /** + * ID of the task to run + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/run'; }; export type TaskRunErrors = { - /** - * Error running task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error running task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskRunError = TaskRunErrors[keyof TaskRunErrors]; export type TaskRunResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; export type TaskRunResponse = TaskRunResponses[keyof TaskRunResponses]; export type TaskRunCommandData = { - body: { - /** - * Command to run - */ - command: string; - /** - * Optional name for the task - */ - name?: string | null; - /** - * Whether to save this command as a task in the config - */ - saveToConfig?: boolean | null; - }; - path?: never; - query?: never; - url: "/task/runCommand"; + body: { + /** + * Command to run + */ + command: string; + /** + * Optional name for the task + */ + name?: string | null; + /** + * Whether to save this command as a task in the config + */ + saveToConfig?: boolean | null; + }; + path?: never; + query?: never; + url: '/task/runCommand'; }; export type TaskRunCommandErrors = { - /** - * Error running command - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error running command + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskRunCommandError = - TaskRunCommandErrors[keyof TaskRunCommandErrors]; +export type TaskRunCommandError = TaskRunCommandErrors[keyof TaskRunCommandErrors]; export type TaskRunCommandResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type TaskRunCommandResponse = - TaskRunCommandResponses[keyof TaskRunCommandResponses]; +export type TaskRunCommandResponse = TaskRunCommandResponses[keyof TaskRunCommandResponses]; export type TaskStopData = { - body: { - /** - * ID of the task to stop - */ - taskId: string; - }; - path?: never; - query?: never; - url: "/task/stop"; + body: { + /** + * ID of the task to stop + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/stop'; }; export type TaskStopErrors = { - /** - * Error stopping task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error stopping task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskStopError = TaskStopErrors[keyof TaskStopErrors]; export type TaskStopResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto | unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto | unknown; + }; }; export type TaskStopResponse = TaskStopResponses[keyof TaskStopResponses]; export type TaskCreateData = { - body: { - taskFields: TaskDefinitionDto; - /** - * Whether to start the task immediately after creation - */ - startTask?: boolean | null; - }; - path?: never; - query?: never; - url: "/task/create"; + body: { + taskFields: TaskDefinitionDto; + /** + * Whether to start the task immediately after creation + */ + startTask?: boolean | null; + }; + path?: never; + query?: never; + url: '/task/create'; }; export type TaskCreateErrors = { - /** - * Error creating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error creating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskCreateError = TaskCreateErrors[keyof TaskCreateErrors]; export type TaskCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskListDto; + }; }; export type TaskCreateResponse = TaskCreateResponses[keyof TaskCreateResponses]; export type TaskUpdateData = { - body: { - /** - * ID of the task to update - */ - taskId: string; - /** - * Fields to update in the task - */ - taskFields: { - /** - * Name of the task - */ - name?: string | null; - /** - * Command to run - */ - command?: string | null; - /** - * Whether to run the task at sandbox start - */ - runAtStart?: boolean | null; - preview?: { + body: { /** - * Port to use for previewing the task + * ID of the task to update */ - port?: number | null; + taskId: string; /** - * Type of PR link to use + * Fields to update in the task */ - "pr-link"?: "direct" | "redirect" | "devtool"; - } | null; + taskFields: { + /** + * Name of the task + */ + name?: string | null; + /** + * Command to run + */ + command?: string | null; + /** + * Whether to run the task at sandbox start + */ + runAtStart?: boolean | null; + preview?: { + /** + * Port to use for previewing the task + */ + port?: number | null; + /** + * Type of PR link to use + */ + 'pr-link'?: 'direct' | 'redirect' | 'devtool'; + } | null; + }; }; - }; - path?: never; - query?: never; - url: "/task/update"; + path?: never; + query?: never; + url: '/task/update'; }; export type TaskUpdateErrors = { - /** - * Error updating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error updating task + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; export type TaskUpdateError = TaskUpdateErrors[keyof TaskUpdateErrors]; export type TaskUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; export type TaskUpdateResponse = TaskUpdateResponses[keyof TaskUpdateResponses]; export type TaskSaveToConfigData = { - body: { - /** - * ID of the task to save to config - */ - taskId: string; - }; - path?: never; - query?: never; - url: "/task/saveToConfig"; + body: { + /** + * ID of the task to save to config + */ + taskId: string; + }; + path?: never; + query?: never; + url: '/task/saveToConfig'; }; export type TaskSaveToConfigErrors = { - /** - * Error saving task to config - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error saving task to config + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskSaveToConfigError = - TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; +export type TaskSaveToConfigError = TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; export type TaskSaveToConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: TaskDto; + }; }; -export type TaskSaveToConfigResponse = - TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; +export type TaskSaveToConfigResponse = TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; export type TaskGenerateConfigData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: "/task/generateConfig"; + body: { + [key: string]: unknown; + }; + path?: never; + query?: never; + url: '/task/generateConfig'; }; export type TaskGenerateConfigErrors = { - /** - * Error generating config - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error generating config + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskGenerateConfigError = - TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; +export type TaskGenerateConfigError = TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; export type TaskGenerateConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type TaskGenerateConfigResponse = - TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; +export type TaskGenerateConfigResponse = TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; export type TaskCreateSetupTasksData = { - body: { - /** - * Setup tasks to create - */ - tasks: Array; - }; - path?: never; - query?: never; - url: "/task/createSetupTasks"; + body: { + /** + * Setup tasks to create + */ + tasks: Array; + }; + path?: never; + query?: never; + url: '/task/createSetupTasks'; }; export type TaskCreateSetupTasksErrors = { - /** - * Error creating setup tasks - */ - 400: ErrorResponse & { - error?: TaskError; - }; + /** + * Error creating setup tasks + */ + 400: ErrorResponse & { + error?: TaskError; + }; }; -export type TaskCreateSetupTasksError = - TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; +export type TaskCreateSetupTasksError = TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; export type TaskCreateSetupTasksResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; + /** + * Successful operation + */ + 200: SuccessResponse & { + result?: unknown; + }; }; -export type TaskCreateSetupTasksResponse = - TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; +export type TaskCreateSetupTasksResponse = TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; \ No newline at end of file diff --git a/src/api-clients/client/client.gen.ts b/src/api-clients/client/client.gen.ts index 853e4ff..1822a95 100644 --- a/src/api-clients/client/client.gen.ts +++ b/src/api-clients/client/client.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from "@hey-api/client-fetch"; +import { createClient, createConfig } from '@hey-api/client-fetch'; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client/index.ts b/src/api-clients/client/index.ts index da87079..e64537d 100644 --- a/src/api-clients/client/index.ts +++ b/src/api-clients/client/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index 8d31f86..b2ab6bf 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,94 +1,26 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { - Options as ClientOptions, - TDataShape, - Client, -} from "@hey-api/client-fetch"; -import type { - MetaInfoData, - MetaInfoResponse, - WorkspaceCreateData, - WorkspaceCreateResponse2, - TokenCreateData, - TokenCreateResponse2, - TokenUpdateData, - TokenUpdateResponse2, - SandboxListData, - SandboxListResponse2, - SandboxCreateData, - SandboxCreateResponse2, - SandboxGetData, - SandboxGetResponse2, - SandboxForkData, - SandboxForkResponse2, - PreviewTokenRevokeAllData, - PreviewTokenRevokeAllResponse2, - PreviewTokenListData, - PreviewTokenListResponse2, - PreviewTokenCreateData, - PreviewTokenCreateResponse2, - PreviewTokenUpdateData, - PreviewTokenUpdateResponse2, - TemplatesCreateData, - TemplatesCreateResponse, - VmAssignTagAliasData, - VmAssignTagAliasResponse2, - VmListClustersData, - VmListClustersResponse2, - VmListRunningVmsData, - VmListRunningVmsResponse2, - VmCreateTagData, - VmCreateTagResponse2, - VmHibernateData, - VmHibernateResponse2, - VmUpdateHibernationTimeoutData, - VmUpdateHibernationTimeoutResponse2, - VmCreateSessionData, - VmCreateSessionResponse2, - VmShutdownData, - VmShutdownResponse2, - VmUpdateSpecsData, - VmUpdateSpecsResponse2, - VmStartData, - VmStartResponse2, - VmUpdateSpecs2Data, - VmUpdateSpecs2Response, - PreviewHostListData, - PreviewHostListResponse2, - PreviewHostCreateData, - PreviewHostCreateResponse, - PreviewHostUpdateData, - PreviewHostUpdateResponse, -} from "./types.gen"; -import { client as _heyApiClient } from "./client.gen"; +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, TemplatesCreateData, TemplatesCreateResponse, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmDeleteData, VmDeleteResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; }; /** * Metadata about the API */ -export const metaInfo = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - MetaInfoResponse, - unknown, - ThrowOnError - >({ - url: "/meta/info", - ...options, - }); +export const metaInfo = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + url: '/meta/info', + ...options + }); }; /** @@ -96,27 +28,21 @@ export const metaInfo = ( * Create a new, empty, workspace in the current organization * */ -export const workspaceCreate = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - WorkspaceCreateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/org/workspace", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const workspaceCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -124,27 +50,21 @@ export const workspaceCreate = ( * Create a new API token for a workspace that is part of the current organization. * */ -export const tokenCreate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - TokenCreateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/org/workspace/{team_id}/tokens", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const tokenCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace/{team_id}/tokens', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -152,27 +72,21 @@ export const tokenCreate = ( * Update an API token for a workspace that is part of the current organization. * */ -export const tokenUpdate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).patch< - TokenUpdateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/org/workspace/{team_id}/tokens/{token_id}", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const tokenUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/org/workspace/{team_id}/tokens/{token_id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -181,23 +95,17 @@ export const tokenUpdate = ( * Results are limited to a maximum of 50 sandboxes per request. * */ -export const sandboxList = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - SandboxListResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox", - ...options, - }); +export const sandboxList = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox', + ...options + }); }; /** @@ -205,27 +113,21 @@ export const sandboxList = ( * Create a new sandbox in the current workspace with file contents * */ -export const sandboxCreate = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - SandboxCreateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const sandboxCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -233,23 +135,17 @@ export const sandboxCreate = ( * Retrieve a sandbox by its ID * */ -export const sandboxGet = ( - options: Options -) => { - return (options.client ?? _heyApiClient).get< - SandboxGetResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}", - ...options, - }); +export const sandboxGet = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}', + ...options + }); }; /** @@ -257,27 +153,21 @@ export const sandboxGet = ( * Fork an existing sandbox to the current workspace * */ -export const sandboxFork = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - SandboxForkResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}/fork", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const sandboxFork = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/fork', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -285,23 +175,17 @@ export const sandboxFork = ( * Immediately expires all active preview tokens associated with this sandbox * */ -export const previewTokenRevokeAll = ( - options: Options -) => { - return (options.client ?? _heyApiClient).delete< - PreviewTokenRevokeAllResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}/tokens", - ...options, - }); +export const previewTokenRevokeAll = (options: Options) => { + return (options.client ?? _heyApiClient).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options + }); }; /** @@ -309,23 +193,17 @@ export const previewTokenRevokeAll = ( * List information about the preview tokens associated with the current sandbox * */ -export const previewTokenList = ( - options: Options -) => { - return (options.client ?? _heyApiClient).get< - PreviewTokenListResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}/tokens", - ...options, - }); +export const previewTokenList = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options + }); }; /** @@ -333,27 +211,21 @@ export const previewTokenList = ( * Create a new Preview token that allow access to a private sandbox * */ -export const previewTokenCreate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - PreviewTokenCreateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}/tokens", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const previewTokenCreate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -361,27 +233,21 @@ export const previewTokenCreate = ( * Update a Preview token that allow access to a private sandbox * */ -export const previewTokenUpdate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).patch< - PreviewTokenUpdateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/sandbox/{id}/tokens/{token_id}", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const previewTokenUpdate = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/sandbox/{id}/tokens/{token_id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -389,27 +255,21 @@ export const previewTokenUpdate = ( * Create a new template in the current workspace (creates 3 sandboxes and tags them) * */ -export const templatesCreate = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - TemplatesCreateResponse, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/templates", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const templatesCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/templates', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -417,27 +277,21 @@ export const templatesCreate = ( * Assign a tag alias to a VM tag. * */ -export const vmAssignTagAlias = ( - options: Options -) => { - return (options.client ?? _heyApiClient).put< - VmAssignTagAliasResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/alias/{namespace}/{alias}", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmAssignTagAlias = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/alias/{namespace}/{alias}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -445,23 +299,17 @@ export const vmAssignTagAlias = ( * List all available clusters. * */ -export const vmListClusters = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - VmListClustersResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/clusters", - ...options, - }); +export const vmListClusters = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/clusters', + ...options + }); }; /** @@ -469,23 +317,17 @@ export const vmListClusters = ( * List information about currently running VMs. This information is updated roughly every 30 seconds, so this data is not guaranteed to be perfectly up-to-date. * */ -export const vmListRunningVms = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - VmListRunningVmsResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/running", - ...options, - }); +export const vmListRunningVms = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/running', + ...options + }); }; /** @@ -493,27 +335,42 @@ export const vmListRunningVms = ( * Creates a new tag for a VM. * */ -export const vmCreateTag = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - VmCreateTagResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/tag", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmCreateTag = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +/** + * Delete a VM + * Deletes a VM, permanently removing it from the system. + * + * This endpoint can only be used on VMs that belong to your team's workspace. + * Deleting a VM is irreversible and will permanently delete all data associated with it. + * + */ +export const vmDelete = (options: Options) => { + return (options.client ?? _heyApiClient).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}', + ...options + }); }; /** @@ -527,27 +384,21 @@ export const vmCreateTag = ( * minimal latency. * */ -export const vmHibernate = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - VmHibernateResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/hibernate", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmHibernate = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/hibernate', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -558,29 +409,21 @@ export const vmHibernate = ( * The new timeout must be greater than 0 and less than or equal to 86400 seconds (24 hours). * */ -export const vmUpdateHibernationTimeout = < - ThrowOnError extends boolean = false ->( - options: Options -) => { - return (options.client ?? _heyApiClient).put< - VmUpdateHibernationTimeoutResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/hibernation_timeout", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmUpdateHibernationTimeout = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/hibernation_timeout', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -594,27 +437,21 @@ export const vmUpdateHibernationTimeout = < * This endpoint requires the VM to be running. If the VM is not running, it will return a 404 error. * */ -export const vmCreateSession = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - VmCreateSessionResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/sessions", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmCreateSession = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/sessions', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -627,27 +464,21 @@ export const vmCreateSession = ( * Shutdown VMs require additional time to start up. * */ -export const vmShutdown = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - VmShutdownResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/shutdown", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmShutdown = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/shutdown', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -658,27 +489,21 @@ export const vmShutdown = ( * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs = ( - options: Options -) => { - return (options.client ?? _heyApiClient).put< - VmUpdateSpecsResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/specs", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmUpdateSpecs = (options: Options) => { + return (options.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/specs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -693,27 +518,21 @@ export const vmUpdateSpecs = ( * This endpoint is subject to special rate limits related to concurrent VM usage. * */ -export const vmStart = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - VmStartResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/start", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmStart = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/start', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -724,27 +543,21 @@ export const vmStart = ( * The new tier must not exceed your team's maximum allowed tier. * */ -export const vmUpdateSpecs2 = ( - options: Options -) => { - return (options.client ?? _heyApiClient).post< - VmUpdateSpecs2Response, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/vm/{id}/update_specs", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const vmUpdateSpecs2 = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/vm/{id}/update_specs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -752,23 +565,17 @@ export const vmUpdateSpecs2 = ( * List all trusted preview hosts for the current team * */ -export const previewHostList = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - PreviewHostListResponse2, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/workspace/preview_hosts", - ...options, - }); +export const previewHostList = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options + }); }; /** @@ -776,27 +583,21 @@ export const previewHostList = ( * Add one or more trusted domains that are allowed to access sandbox previews for this workspace. * */ -export const previewHostCreate = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).post< - PreviewHostCreateResponse, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/workspace/preview_hosts", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); +export const previewHostCreate = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); }; /** @@ -804,25 +605,19 @@ export const previewHostCreate = ( * Replace the list of trusted domains that are allowed to access sandbox previews for this workspace. * */ -export const previewHostUpdate = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).put< - PreviewHostUpdateResponse, - unknown, - ThrowOnError - >({ - security: [ - { - scheme: "bearer", - type: "http", - }, - ], - url: "/workspace/preview_hosts", - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); -}; +export const previewHostUpdate = (options?: Options) => { + return (options?.client ?? _heyApiClient).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/workspace/preview_hosts', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 88e914f..0de3dac 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -1,1465 +1,1403 @@ // This file is auto-generated by @hey-api/openapi-ts export type Response = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; }; export type PreviewTokenListResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - sandbox_id: string; - tokens: Array; - }; + data?: { + sandbox_id: string; + tokens: Array; + }; }; export type VmUpdateHibernationTimeoutRequest = { - /** - * The new hibernation timeout in seconds. - * - * Must be greater than 0 and less than or equal to 86400 (24 hours). - * - */ - hibernation_timeout_seconds: number; + /** + * The new hibernation timeout in seconds. + * + * Must be greater than 0 and less than or equal to 86400 (24 hours). + * + */ + hibernation_timeout_seconds: number; }; export type VmAssignTagAliasResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - alias: string; - namespace: string; - tag_alias_id: string; - tag_id: string; - team_id: string; - }; + data?: { + alias: string; + namespace: string; + tag_alias_id: string; + tag_id: string; + team_id: string; + }; }; export type TemplateCreateRequest = { - /** - * Template description. Maximum 255 characters. Defaults to description of original sandbox. - */ - description?: string; - /** - * Short ID of the sandbox to fork. - */ - forkOf: string; - /** - * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. - */ - tags?: Array; - /** - * Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked). - */ - title?: string; + /** + * Template description. Maximum 255 characters. Defaults to description of original sandbox. + */ + description?: string; + /** + * Short ID of the sandbox to fork. + */ + forkOf: string; + /** + * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. + */ + tags?: Array; + /** + * Template title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + */ + title?: string; }; export type PreviewToken = { - expires_at: string | null; - last_used_at: string | null; - token_id: string; - token_prefix: string; + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; }; export type VmShutdownResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type PreviewTokenRevokeAllResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type Sandbox = { - created_at: string; - description?: string | null; - id: string; - is_frozen: boolean; - privacy: number; - tags: Array; - title?: string | null; - updated_at: string; -}; - -export type _Error = - | string - | { - [key: string]: unknown; - }; + created_at: string; + description?: string | null; + id: string; + is_frozen: boolean; + privacy: number; + tags: Array; + title?: string | null; + updated_at: string; +}; + +export type _Error = string | { + [key: string]: unknown; +}; export type VmHibernateRequest = { - [key: string]: unknown; + [key: string]: unknown; }; export type PreviewTokenCreateRequest = { - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; }; export type VmCreateTagResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - tag_id: string; - }; + data?: { + tag_id: string; + }; }; export type VmListRunningVmsResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; -} & { - data?: { - concurrent_vm_count: number; - concurrent_vm_limit: number; - vms: Array<{ - credit_basis?: string; - id?: string; - last_active_at?: string; - session_started_at?: string; - specs?: { - cpu?: number; - memory?: number; - storage?: number; - }; }>; - }; + success?: boolean; +} & { + data?: { + concurrent_vm_count: number; + concurrent_vm_limit: number; + vms: Array<{ + credit_basis?: string; + id?: string; + last_active_at?: string; + session_started_at?: string; + specs?: { + cpu?: number; + memory?: number; + storage?: number; + }; + }>; + }; }; export type SandboxGetResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: Sandbox; + data?: Sandbox; }; export type SandboxForkRequest = { - /** - * Sandbox description. Maximum 255 characters. Defaults to description of original sandbox. - */ - description?: string; - /** - * Sandbox frozen status. When true, edits to the sandbox are restricted. Defaults to frozen status of the original sandbox. - */ - is_frozen?: boolean; - /** - * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. - */ - path?: string; - /** - * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. - */ - privacy?: number; - /** - * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible - */ - private_preview?: boolean; - /** - * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. - */ - start_options?: { /** - * Configuration for when the VM should automatically wake up from hibernation + * Sandbox description. Maximum 255 characters. Defaults to description of original sandbox. */ - automatic_wakeup_config?: { - /** - * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) - */ - http?: boolean; - /** - * Whether the VM should automatically wake up on WebSocket connections - */ - websocket?: boolean; - }; + description?: string; /** - * The time in seconds after which the VM will hibernate due to inactivity. - * Must be a positive integer between 1 and 86400 (24 hours). - * Defaults to 300 seconds (5 minutes) if not specified. - * + * Sandbox frozen status. When true, edits to the sandbox are restricted. Defaults to frozen status of the original sandbox. */ - hibernation_timeout_seconds?: number; + is_frozen?: boolean; /** - * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. */ - ipcountry?: string; + path?: string; /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * + * Sandbox privacy. 0 for public, 1 for unlisted, and 2 for private. Subject to the minimum privacy restrictions of the workspace. Defaults to the privacy of the original sandbox. + */ + privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; + /** + * Optional VM start configuration. If provided, the sandbox VM will be started immediately after creation. + */ + start_options?: { + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; + /** + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * + */ + hibernation_timeout_seconds?: number; + /** + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + */ + ipcountry?: string; + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; + }; + /** + * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. */ - tier?: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; - }; - /** - * Tags to set on the new sandbox, if any. Will not inherit tags from the source sandbox. - */ - tags?: Array; - /** - * Sandbox title. Maximum 255 characters. Defaults to title of original sandbox with (forked). - */ - title?: string; + tags?: Array; + /** + * Sandbox title. Maximum 255 characters. Defaults to title of original sandbox with (forked). + */ + title?: string; }; export type SandboxListResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - pagination: { - current_page: number; - /** - * The number of the next page, if any. If `null`, the current page is the last page of records. - */ - next_page: number | null; - total_records: number; + data?: { + pagination: { + current_page: number; + /** + * The number of the next page, if any. If `null`, the current page is the last page of records. + */ + next_page: number | null; + total_records: number; + }; + sandboxes: Array; }; - sandboxes: Array; - }; }; export type MetaInformation = { - /** - * Meta information about the CodeSandbox API - */ - api: { - latest_version: string; - name: string; - }; - /** - * Meta information about the current authentication context - */ - auth?: { - scopes: Array; - team: string | null; - version: string; - }; - /** - * Current workspace rate limits - */ - rate_limits?: { - concurrent_vms: { - limit?: number; - remaining?: number; + /** + * Meta information about the CodeSandbox API + */ + api: { + latest_version: string; + name: string; }; - requests_hourly: { - limit?: number; - remaining?: number; - reset?: number; + /** + * Meta information about the current authentication context + */ + auth?: { + scopes: Array; + team: string | null; + version: string; }; - sandboxes_hourly: { - limit?: number; - remaining?: number; - reset?: number; + /** + * Current workspace rate limits + */ + rate_limits?: { + concurrent_vms: { + limit?: number; + remaining?: number; + }; + requests_hourly: { + limit?: number; + remaining?: number; + reset?: number; + }; + sandboxes_hourly: { + limit?: number; + remaining?: number; + reset?: number; + }; }; - }; }; /** * Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value. */ export type TokenUpdateRequest = { - /** - * API Version to use, formatted as YYYY-MM-DD - */ - default_version?: string | null; - /** - * Description of this token, for instance where it will be used. - */ - description?: string | null; - /** - * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. - */ - expires_at?: string | null; - /** - * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. - */ - scopes?: Array< - "sandbox_create" | "sandbox_edit_code" | "sandbox_read" | "vm_manage" - >; + /** + * API Version to use, formatted as YYYY-MM-DD + */ + default_version?: string | null; + /** + * Description of this token, for instance where it will be used. + */ + description?: string | null; + /** + * Expiry time of this token. Cannot be set in the past, and cannot be changed for tokens that have already expired. Pass null to make this token never expire. + */ + expires_at?: string | null; + /** + * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + */ + scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; /** * Assign a tag alias to a VM */ export type VmAssignTagAliasRequest = { - tag_id: string; + tag_id: string; }; export type VmHibernateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - [key: string]: unknown; - }; + data?: { + [key: string]: unknown; + }; }; export type PreviewTokenUpdateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - sandbox_id: string; - token: PreviewToken; - }; + data?: { + sandbox_id: string; + token: PreviewToken; + }; }; export type SandboxCreateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - alias: string; - id: string; - title: string | null; - }; + data?: { + alias: string; + id: string; + title: string | null; + }; }; export type VmStartRequest = { - /** - * Configuration for when the VM should automatically wake up from hibernation - */ - automatic_wakeup_config?: { - /** - * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) - */ - http?: boolean; - /** - * Whether the VM should automatically wake up on WebSocket connections - */ - websocket?: boolean; - }; - /** - * The time in seconds after which the VM will hibernate due to inactivity. - * Must be a positive integer between 1 and 86400 (24 hours). - * Defaults to 300 seconds (5 minutes) if not specified. - * - */ - hibernation_timeout_seconds?: number; - /** - * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). - */ - ipcountry?: string; - /** - * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. - * - * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier?: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; + /** + * Configuration for when the VM should automatically wake up from hibernation + */ + automatic_wakeup_config?: { + /** + * Whether the VM should automatically wake up on HTTP requests (excludes WebSocket requests) + */ + http?: boolean; + /** + * Whether the VM should automatically wake up on WebSocket connections + */ + websocket?: boolean; + }; + /** + * The time in seconds after which the VM will hibernate due to inactivity. + * Must be a positive integer between 1 and 86400 (24 hours). + * Defaults to 300 seconds (5 minutes) if not specified. + * + */ + hibernation_timeout_seconds?: number; + /** + * This determines in which cluster, closest to the given country the VM will be started in. The format is ISO-3166-1 alpha-2. If not set, the VM will be started closest to the caller of this API. This will only be applied when a VM is run for the first time, and will only serve as a hint (e.g. if the template of this sandbox runs in EU cluster, this sandbox will also run in the EU cluster). + */ + ipcountry?: string; + /** + * Determines which specs to start the VM with. If not specified, the VM will start with the default specs for the workspace. + * + * You can only specify a VM tier when starting a VM that is inside your workspace. Specifying a VM tier for someone else's sandbox will return an error. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; export type PreviewTokenUpdateRequest = { - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; }; export type PreviewHostListResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; -} & { - data?: { - preview_hosts: Array<{ - host: string; - inserted_at: string; }>; - }; + success?: boolean; +} & { + data?: { + preview_hosts: Array<{ + host: string; + inserted_at: string; + }>; + }; }; export type VmShutdownRequest = { - [key: string]: unknown; + [key: string]: unknown; }; export type VmUpdateSpecsRequest = { - /** - * Determines which specs to update the VM with. - * - * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. - * - */ - tier: "Pico" | "Nano" | "Micro" | "Small" | "Medium" | "Large" | "XLarge"; + /** + * Determines which specs to update the VM with. + * + * Not all tiers will be available depending on the workspace subscription status, and higher tiers incur higher costs. Please see codesandbox.io/pricing for details on specs and costs. + * + */ + tier: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; export type WorkspaceCreateRequest = { - /** - * Name for the new workspace. Maximum length 64 characters. - */ - name: string; + /** + * Name for the new workspace. Maximum length 64 characters. + */ + name: string; }; /** * Create a tag for a list of VM IDs */ export type VmCreateTagRequest = { - vm_ids: Array; + vm_ids: Array; }; export type PreviewHostRequest = { - hosts: Array; + hosts: Array; }; export type VmStartResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - bootup_type: string; - cluster: string; - id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; - }; + data?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + }; }; export type VmUpdateSpecsResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - id: string; - tier: string; - }; + data?: { + id: string; + tier: string; + }; }; export type SandboxCreateRequest = { - /** - * Optional text description of the sandbox. Defaults to no description. - */ - description?: string; - /** - * Filename of the entrypoint of the sandbox. - */ - entry?: string; - /** - * Array of strings with external resources to load. - */ - external_resources?: Array; - /** - * Map of `path => file` where each file is a map. - */ - files: { - [key: string]: { - /** - * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. - */ - binary_content?: string; - /** - * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. - */ - code?: string; - /** - * Whether the file contains binary contents. - */ - is_binary?: boolean; + /** + * Optional text description of the sandbox. Defaults to no description. + */ + description?: string; + /** + * Filename of the entrypoint of the sandbox. + */ + entry?: string; + /** + * Array of strings with external resources to load. + */ + external_resources?: Array; + /** + * Map of `path => file` where each file is a map. + */ + files: { + [key: string]: { + /** + * If the file has binary (non plain-text) contents, place the base-64 encoded contents in this key. Should be empty or missing if `is_binary` is `false`. + */ + binary_content?: string; + /** + * If the file is non-binary in nature, place the (escaped) plain text contents in this key. Should be empty or missing if `is_binary` is `true`. + */ + code?: string; + /** + * Whether the file contains binary contents. + */ + is_binary?: boolean; + }; }; - }; - /** - * Whether changes to the sandbox are disallowed. Defaults to `false`. - */ - is_frozen?: boolean; - /** - * Map of dependencies and their version specifications. - */ - npm_dependencies?: { - [key: string]: string; - }; - /** - * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. - */ - path?: string; - /** - * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. - */ - privacy?: number; - /** - * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible - */ - private_preview?: boolean; - /** - * Runtime to use for the sandbox. Defaults to `"browser"`. - */ - runtime?: "browser" | "vm"; - /** - * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. - */ - tags?: Array; - /** - * Name of the template from which the sandbox is derived (for example, `"static"`). - */ - template?: string; - /** - * Sandbox title. Maximum 255 characters. Defaults to no title. - */ - title?: string; + /** + * Whether changes to the sandbox are disallowed. Defaults to `false`. + */ + is_frozen?: boolean; + /** + * Map of dependencies and their version specifications. + */ + npm_dependencies?: { + [key: string]: string; + }; + /** + * Path to the collection where the new sandbox should be stored. Defaults to "/". If no collection exists at the given path, it will be created. + */ + path?: string; + /** + * 0 for public, 1 for unlisted, and 2 for private. Privacy is subject to certain restrictions (team minimum setting, drafts must be private, etc.). Defaults to public. + */ + privacy?: number; + /** + * Determines whether the preview of a private sandbox is private or public. Has no effect on public or unlisted sandboxes; their previews are always publicly accessible + */ + private_preview?: boolean; + /** + * Runtime to use for the sandbox. Defaults to `"browser"`. + */ + runtime?: 'browser' | 'vm'; + /** + * Sandbox settings. + */ + settings?: { + /** + * Whether to use Pint for the sandbox. + */ + use_pint?: boolean; + }; + /** + * List of string tags to apply to the sandbox. Only the first ten will be used. Defaults to no tags. + */ + tags?: Array; + /** + * Name of the template from which the sandbox is derived (for example, `"static"`). + */ + template?: string; + /** + * Sandbox title. Maximum 255 characters. Defaults to no title. + */ + title?: string; }; export type VmListClustersResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; -} & { - data?: { - clusters: Array<{ - host: string; - slug: string; }>; - }; + success?: boolean; +} & { + data?: { + clusters: Array<{ + host: string; + slug: string; + }>; + }; }; export type TokenUpdateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - description: string | null; - expires_at?: string | null; - scopes: Array; - team_id: string; - token_id: string; - }; + data?: { + description: string | null; + expires_at?: string | null; + scopes: Array; + team_id: string; + token_id: string; + }; }; export type TokenCreateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - description: string | null; - expires_at: string | null; - scopes: Array; - team_id: string; - token: string; - token_id: string; - }; + data?: { + description: string | null; + expires_at: string | null; + scopes: Array; + team_id: string; + token: string; + token_id: string; + }; }; -export type TemplateCreateResponse = { - errors?: Array< - | string - | { +export type VmDeleteResponse = { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - sandboxes: Array<{ - cluster: string; - id: string; + data?: { + [key: string]: unknown; + }; +}; + +export type TemplateCreateResponse = { + errors?: Array; - tag: string; - }; + success?: boolean; +} & { + data?: { + sandboxes: Array<{ + cluster: string; + id: string; + }>; + tag: string; + }; }; export type TokenCreateRequest = { - /** - * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. - */ - default_version?: string | null; - /** - * Description of this token, for instance where it will be used. - */ - description?: string; - /** - * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. - */ - expires_at?: string | null; - /** - * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. - */ - scopes?: Array< - "sandbox_create" | "sandbox_edit_code" | "sandbox_read" | "vm_manage" - >; + /** + * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. + */ + default_version?: string | null; + /** + * Description of this token, for instance where it will be used. + */ + description?: string; + /** + * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. + */ + expires_at?: string | null; + /** + * Which scopes to grant this token. The given scopes will replace the current scopes, revoking any that are no longer present in the list. + */ + scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; export type VmCreateSessionRequest = { - /** - * GitHub token for the session - */ - git_access_token?: string; - /** - * Git user email to configure for this session - */ - git_user_email?: string; - /** - * Git user name to configure for this session - */ - git_user_name?: string; - /** - * Permission level for the session - */ - permission: "read" | "write"; - /** - * Unique identifier for the session - */ - session_id: string; -}; - -export type VmCreateSessionResponse = { - errors?: Array< - | string - | { - [key: string]: unknown; - } - >; - success?: boolean; -} & { - data?: { /** - * List of capabilities that Pitcher has + * GitHub token for the session */ - capabilities: Array; + git_access_token?: string; /** - * The permissions of the current session + * Git user email to configure for this session */ - permissions: { - [key: string]: unknown; - }; + git_user_email?: string; /** - * Token to authenticate with Pitcher (the agent running inside the VM) + * Git user name to configure for this session */ - pitcher_token: string; + git_user_name?: string; /** - * WebSocket URL to connect to Pitcher + * Permission level for the session */ - pitcher_url: string; + permission: 'read' | 'write'; /** - * Path to the user's workspace in the VM + * Unique identifier for the session */ - user_workspace_path: string; - /** - * The Linux username created for this session - */ - username: string; - }; + session_id: string; +}; + +export type VmCreateSessionResponse = { + errors?: Array; + success?: boolean; +} & { + data?: { + /** + * List of capabilities that Pitcher has + */ + capabilities: Array; + /** + * The permissions of the current session + */ + permissions: { + [key: string]: unknown; + }; + /** + * Token to authenticate with Pitcher (the agent running inside the VM) + */ + pitcher_token: string; + /** + * WebSocket URL to connect to Pitcher + */ + pitcher_url: string; + /** + * Path to the user's workspace in the VM + */ + user_workspace_path: string; + /** + * The Linux username created for this session + */ + username: string; + }; }; export type WorkspaceCreateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - id: string; - name: string; - }; + data?: { + id: string; + name: string; + }; }; export type VmUpdateHibernationTimeoutResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - hibernation_timeout_seconds: number; - id: string; - }; + data?: { + hibernation_timeout_seconds: number; + id: string; + }; }; export type SandboxForkResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - alias: string; - id: string; - /** - * VM start response. Only present when start_options were provided in the request. - */ - start_response?: { - bootup_type: string; - cluster: string; - id: string; - latest_pitcher_version: string; - pitcher_manager_version: string; - pitcher_token: string; - pitcher_url: string; - pitcher_version: string; - reconnect_token: string; - user_workspace_path: string; - workspace_path: string; - } | null; - title: string | null; - }; + data?: { + alias: string; + id: string; + /** + * VM start response. Only present when start_options were provided in the request. + */ + start_response?: { + bootup_type: string; + cluster: string; + id: string; + latest_pitcher_version: string; + pitcher_manager_version: string; + pitcher_token: string; + pitcher_url: string; + pitcher_version: string; + reconnect_token: string; + user_workspace_path: string; + workspace_path: string; + } | null; + title: string | null; + }; }; export type PreviewTokenCreateResponse = { - errors?: Array< - | string - | { + errors?: Array; - success?: boolean; + }>; + success?: boolean; } & { - data?: { - sandbox_id: string; - token: { - expires_at: string | null; - last_used_at: string | null; - token_id: string; - token_prefix: string; - } & { - token: string; + data?: { + sandbox_id: string; + token: { + expires_at: string | null; + last_used_at: string | null; + token_id: string; + token_prefix: string; + } & { + token: string; + }; }; - }; }; export type MetaInfoData = { - body?: never; - path?: never; - query?: never; - url: "/meta/info"; + body?: never; + path?: never; + query?: never; + url: '/meta/info'; }; export type MetaInfoResponses = { - /** - * Meta Info Response - */ - 200: MetaInformation; + /** + * Meta Info Response + */ + 200: MetaInformation; }; export type MetaInfoResponse = MetaInfoResponses[keyof MetaInfoResponses]; export type WorkspaceCreateData = { - /** - * Workspace Create Request - */ - body?: WorkspaceCreateRequest; - path?: never; - query?: never; - url: "/org/workspace"; + /** + * Workspace Create Request + */ + body?: WorkspaceCreateRequest; + path?: never; + query?: never; + url: '/org/workspace'; }; export type WorkspaceCreateResponses = { - /** - * Workspace Create Response - */ - 201: WorkspaceCreateResponse; + /** + * Workspace Create Response + */ + 201: WorkspaceCreateResponse; }; -export type WorkspaceCreateResponse2 = - WorkspaceCreateResponses[keyof WorkspaceCreateResponses]; +export type WorkspaceCreateResponse2 = WorkspaceCreateResponses[keyof WorkspaceCreateResponses]; export type TokenCreateData = { - /** - * Token Create Request - */ - body?: TokenCreateRequest; - path: { /** - * ID of the workspace to create the token in + * Token Create Request */ - team_id: string; - }; - query?: never; - url: "/org/workspace/{team_id}/tokens"; + body?: TokenCreateRequest; + path: { + /** + * ID of the workspace to create the token in + */ + team_id: string; + }; + query?: never; + url: '/org/workspace/{team_id}/tokens'; }; export type TokenCreateResponses = { - /** - * Token Create Response - */ - 201: TokenCreateResponse; + /** + * Token Create Response + */ + 201: TokenCreateResponse; }; -export type TokenCreateResponse2 = - TokenCreateResponses[keyof TokenCreateResponses]; +export type TokenCreateResponse2 = TokenCreateResponses[keyof TokenCreateResponses]; export type TokenUpdateData = { - /** - * Token Update Request - */ - body?: TokenUpdateRequest; - path: { - /** - * ID of the workspace the token belongs to - */ - team_id: string; /** - * ID of token to update + * Token Update Request */ - token_id: string; - }; - query?: never; - url: "/org/workspace/{team_id}/tokens/{token_id}"; + body?: TokenUpdateRequest; + path: { + /** + * ID of the workspace the token belongs to + */ + team_id: string; + /** + * ID of token to update + */ + token_id: string; + }; + query?: never; + url: '/org/workspace/{team_id}/tokens/{token_id}'; }; export type TokenUpdateResponses = { - /** - * Token Update Response - */ - 201: TokenUpdateResponse; + /** + * Token Update Response + */ + 201: TokenUpdateResponse; }; -export type TokenUpdateResponse2 = - TokenUpdateResponses[keyof TokenUpdateResponses]; +export type TokenUpdateResponse2 = TokenUpdateResponses[keyof TokenUpdateResponses]; export type SandboxListData = { - body?: never; - path?: never; - query?: { - /** - * Comma-separated list of tags to filter by - */ - tags?: string; - /** - * Field to order results by - */ - order_by?: "inserted_at" | "updated_at"; - /** - * Order direction - */ - direction?: "asc" | "desc"; - /** - * Maximum number of sandboxes to return in a single response - */ - page_size?: number; - /** - * Page number to return - */ - page?: number; - /** - * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. - */ - status?: "running"; - }; - url: "/sandbox"; + body?: never; + path?: never; + query?: { + /** + * Comma-separated list of tags to filter by + */ + tags?: string; + /** + * Field to order results by + */ + order_by?: 'inserted_at' | 'updated_at'; + /** + * Order direction + */ + direction?: 'asc' | 'desc'; + /** + * Maximum number of sandboxes to return in a single response + */ + page_size?: number; + /** + * Page number to return + */ + page?: number; + /** + * If true, only returns VMs for which a heartbeat was received in the last 30 seconds. + */ + status?: 'running'; + }; + url: '/sandbox'; }; export type SandboxListResponses = { - /** - * Sandbox List Response - */ - 200: SandboxListResponse; + /** + * Sandbox List Response + */ + 200: SandboxListResponse; }; -export type SandboxListResponse2 = - SandboxListResponses[keyof SandboxListResponses]; +export type SandboxListResponse2 = SandboxListResponses[keyof SandboxListResponses]; export type SandboxCreateData = { - /** - * Sandbox Create Request - */ - body?: SandboxCreateRequest; - path?: never; - query?: never; - url: "/sandbox"; + /** + * Sandbox Create Request + */ + body?: SandboxCreateRequest; + path?: never; + query?: never; + url: '/sandbox'; }; export type SandboxCreateResponses = { - /** - * Sandbox Create Response - */ - 201: SandboxCreateResponse; + /** + * Sandbox Create Response + */ + 201: SandboxCreateResponse; }; -export type SandboxCreateResponse2 = - SandboxCreateResponses[keyof SandboxCreateResponses]; +export type SandboxCreateResponse2 = SandboxCreateResponses[keyof SandboxCreateResponses]; export type SandboxGetData = { - body?: never; - path: { - /** - * Short ID of the sandbox to retrieve - */ - id: string; - }; - query?: never; - url: "/sandbox/{id}"; + body?: never; + path: { + /** + * Short ID of the sandbox to retrieve + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}'; }; export type SandboxGetResponses = { - /** - * Sandbox Get Response - */ - 200: SandboxGetResponse; + /** + * Sandbox Get Response + */ + 200: SandboxGetResponse; }; -export type SandboxGetResponse2 = - SandboxGetResponses[keyof SandboxGetResponses]; +export type SandboxGetResponse2 = SandboxGetResponses[keyof SandboxGetResponses]; export type SandboxForkData = { - /** - * Sandbox Fork Request - */ - body?: SandboxForkRequest; - path: { /** - * Short ID of the sandbox to fork + * Sandbox Fork Request */ - id: string; - }; - query?: never; - url: "/sandbox/{id}/fork"; + body?: SandboxForkRequest; + path: { + /** + * Short ID of the sandbox to fork + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/fork'; }; export type SandboxForkResponses = { - /** - * Sandbox Fork Response - */ - 201: SandboxForkResponse; + /** + * Sandbox Fork Response + */ + 201: SandboxForkResponse; }; -export type SandboxForkResponse2 = - SandboxForkResponses[keyof SandboxForkResponses]; +export type SandboxForkResponse2 = SandboxForkResponses[keyof SandboxForkResponses]; export type PreviewTokenRevokeAllData = { - body?: never; - path: { - /** - * Shortid of the sandbox to revoke tokens for - */ - id: string; - }; - query?: never; - url: "/sandbox/{id}/tokens"; + body?: never; + path: { + /** + * Shortid of the sandbox to revoke tokens for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; }; export type PreviewTokenRevokeAllResponses = { - /** - * RevokeAllPreviewTokensResponse - */ - 200: PreviewTokenRevokeAllResponse; + /** + * RevokeAllPreviewTokensResponse + */ + 200: PreviewTokenRevokeAllResponse; }; -export type PreviewTokenRevokeAllResponse2 = - PreviewTokenRevokeAllResponses[keyof PreviewTokenRevokeAllResponses]; +export type PreviewTokenRevokeAllResponse2 = PreviewTokenRevokeAllResponses[keyof PreviewTokenRevokeAllResponses]; export type PreviewTokenListData = { - body?: never; - path: { - /** - * Shortid of the sandbox to list the tokens for - */ - id: string; - }; - query?: never; - url: "/sandbox/{id}/tokens"; + body?: never; + path: { + /** + * Shortid of the sandbox to list the tokens for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; }; export type PreviewTokenListResponses = { - /** - * Token List Response - */ - 201: PreviewTokenListResponse; + /** + * Token List Response + */ + 201: PreviewTokenListResponse; }; -export type PreviewTokenListResponse2 = - PreviewTokenListResponses[keyof PreviewTokenListResponses]; +export type PreviewTokenListResponse2 = PreviewTokenListResponses[keyof PreviewTokenListResponses]; export type PreviewTokenCreateData = { - /** - * Token Create Request - */ - body?: PreviewTokenCreateRequest; - path: { /** - * Shortid of the sandbox to create the token for + * Token Create Request */ - id: string; - }; - query?: never; - url: "/sandbox/{id}/tokens"; + body?: PreviewTokenCreateRequest; + path: { + /** + * Shortid of the sandbox to create the token for + */ + id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens'; }; export type PreviewTokenCreateResponses = { - /** - * Token Create Response - */ - 201: PreviewTokenCreateResponse; + /** + * Token Create Response + */ + 201: PreviewTokenCreateResponse; }; -export type PreviewTokenCreateResponse2 = - PreviewTokenCreateResponses[keyof PreviewTokenCreateResponses]; +export type PreviewTokenCreateResponse2 = PreviewTokenCreateResponses[keyof PreviewTokenCreateResponses]; export type PreviewTokenUpdateData = { - /** - * Token Update Request - */ - body?: PreviewTokenUpdateRequest; - path: { - /** - * Shortid of the sandbox to create the token for - */ - id: string; /** - * ID of the token to update. Does not accept the token itself. + * Token Update Request */ - token_id: string; - }; - query?: never; - url: "/sandbox/{id}/tokens/{token_id}"; + body?: PreviewTokenUpdateRequest; + path: { + /** + * Shortid of the sandbox to create the token for + */ + id: string; + /** + * ID of the token to update. Does not accept the token itself. + */ + token_id: string; + }; + query?: never; + url: '/sandbox/{id}/tokens/{token_id}'; }; export type PreviewTokenUpdateResponses = { - /** - * Token Update Response - */ - 201: PreviewTokenUpdateResponse; + /** + * Token Update Response + */ + 201: PreviewTokenUpdateResponse; }; -export type PreviewTokenUpdateResponse2 = - PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; +export type PreviewTokenUpdateResponse2 = PreviewTokenUpdateResponses[keyof PreviewTokenUpdateResponses]; export type TemplatesCreateData = { - /** - * Template Create Request - */ - body?: TemplateCreateRequest; - path?: never; - query?: never; - url: "/templates"; + /** + * Template Create Request + */ + body?: TemplateCreateRequest; + path?: never; + query?: never; + url: '/templates'; }; export type TemplatesCreateResponses = { - /** - * Template Create Response - */ - 201: TemplateCreateResponse; + /** + * Template Create Response + */ + 201: TemplateCreateResponse; }; -export type TemplatesCreateResponse = - TemplatesCreateResponses[keyof TemplatesCreateResponses]; +export type TemplatesCreateResponse = TemplatesCreateResponses[keyof TemplatesCreateResponses]; export type VmAssignTagAliasData = { - /** - * VM Assign Tag Alias Request - */ - body?: VmAssignTagAliasRequest; - path: { /** - * Tag alias namespace + * VM Assign Tag Alias Request */ - namespace: string; - /** - * Tag alias - */ - alias: string; - }; - query?: never; - url: "/vm/alias/{namespace}/{alias}"; + body?: VmAssignTagAliasRequest; + path: { + /** + * Tag alias namespace + */ + namespace: string; + /** + * Tag alias + */ + alias: string; + }; + query?: never; + url: '/vm/alias/{namespace}/{alias}'; }; export type VmAssignTagAliasResponses = { - /** - * VM Assign Tag Alias Response - */ - 200: VmAssignTagAliasResponse; + /** + * VM Assign Tag Alias Response + */ + 200: VmAssignTagAliasResponse; }; -export type VmAssignTagAliasResponse2 = - VmAssignTagAliasResponses[keyof VmAssignTagAliasResponses]; +export type VmAssignTagAliasResponse2 = VmAssignTagAliasResponses[keyof VmAssignTagAliasResponses]; export type VmListClustersData = { - body?: never; - path?: never; - query?: never; - url: "/vm/clusters"; + body?: never; + path?: never; + query?: never; + url: '/vm/clusters'; }; export type VmListClustersResponses = { - /** - * VM List Clusters Response - */ - 200: VmListClustersResponse; + /** + * VM List Clusters Response + */ + 200: VmListClustersResponse; }; -export type VmListClustersResponse2 = - VmListClustersResponses[keyof VmListClustersResponses]; +export type VmListClustersResponse2 = VmListClustersResponses[keyof VmListClustersResponses]; export type VmListRunningVmsData = { - body?: never; - path?: never; - query?: never; - url: "/vm/running"; + body?: never; + path?: never; + query?: never; + url: '/vm/running'; }; export type VmListRunningVmsResponses = { - /** - * VM List Running VMs Response - */ - 200: VmListRunningVmsResponse; + /** + * VM List Running VMs Response + */ + 200: VmListRunningVmsResponse; }; -export type VmListRunningVmsResponse2 = - VmListRunningVmsResponses[keyof VmListRunningVmsResponses]; +export type VmListRunningVmsResponse2 = VmListRunningVmsResponses[keyof VmListRunningVmsResponses]; export type VmCreateTagData = { - /** - * VM Create Tag Request - */ - body?: VmCreateTagRequest; - path?: never; - query?: never; - url: "/vm/tag"; + /** + * VM Create Tag Request + */ + body?: VmCreateTagRequest; + path?: never; + query?: never; + url: '/vm/tag'; }; export type VmCreateTagResponses = { - /** - * VM Create Tag Response - */ - 200: VmCreateTagResponse; + /** + * VM Create Tag Response + */ + 200: VmCreateTagResponse; +}; + +export type VmCreateTagResponse2 = VmCreateTagResponses[keyof VmCreateTagResponses]; + +export type VmDeleteData = { + body?: never; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}'; +}; + +export type VmDeleteResponses = { + /** + * VM Delete Response + */ + 200: VmDeleteResponse; }; -export type VmCreateTagResponse2 = - VmCreateTagResponses[keyof VmCreateTagResponses]; +export type VmDeleteResponse2 = VmDeleteResponses[keyof VmDeleteResponses]; export type VmHibernateData = { - /** - * VM Hibernate Request - */ - body?: VmHibernateRequest; - path: { /** - * Sandbox ID + * VM Hibernate Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/hibernate"; + body?: VmHibernateRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/hibernate'; }; export type VmHibernateResponses = { - /** - * VM Hibernate Response - */ - 200: VmHibernateResponse; + /** + * VM Hibernate Response + */ + 200: VmHibernateResponse; }; -export type VmHibernateResponse2 = - VmHibernateResponses[keyof VmHibernateResponses]; +export type VmHibernateResponse2 = VmHibernateResponses[keyof VmHibernateResponses]; export type VmUpdateHibernationTimeoutData = { - /** - * VM Update Hibernation Timeout Request - */ - body?: VmUpdateHibernationTimeoutRequest; - path: { /** - * Sandbox ID + * VM Update Hibernation Timeout Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/hibernation_timeout"; + body?: VmUpdateHibernationTimeoutRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/hibernation_timeout'; }; export type VmUpdateHibernationTimeoutResponses = { - /** - * VM Update Hibernation Timeout Response - */ - 200: VmUpdateHibernationTimeoutResponse; + /** + * VM Update Hibernation Timeout Response + */ + 200: VmUpdateHibernationTimeoutResponse; }; -export type VmUpdateHibernationTimeoutResponse2 = - VmUpdateHibernationTimeoutResponses[keyof VmUpdateHibernationTimeoutResponses]; +export type VmUpdateHibernationTimeoutResponse2 = VmUpdateHibernationTimeoutResponses[keyof VmUpdateHibernationTimeoutResponses]; export type VmCreateSessionData = { - /** - * VM Create Session Request - */ - body?: VmCreateSessionRequest; - path: { /** - * Sandbox ID + * VM Create Session Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/sessions"; + body?: VmCreateSessionRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/sessions'; }; export type VmCreateSessionResponses = { - /** - * VM Create Session Response - */ - 200: VmCreateSessionResponse; + /** + * VM Create Session Response + */ + 200: VmCreateSessionResponse; }; -export type VmCreateSessionResponse2 = - VmCreateSessionResponses[keyof VmCreateSessionResponses]; +export type VmCreateSessionResponse2 = VmCreateSessionResponses[keyof VmCreateSessionResponses]; export type VmShutdownData = { - /** - * VM Shutdown Request - */ - body?: VmShutdownRequest; - path: { /** - * Sandbox ID + * VM Shutdown Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/shutdown"; + body?: VmShutdownRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/shutdown'; }; export type VmShutdownResponses = { - /** - * VM Shutdown Response - */ - 200: VmShutdownResponse; + /** + * VM Shutdown Response + */ + 200: VmShutdownResponse; }; -export type VmShutdownResponse2 = - VmShutdownResponses[keyof VmShutdownResponses]; +export type VmShutdownResponse2 = VmShutdownResponses[keyof VmShutdownResponses]; export type VmUpdateSpecsData = { - /** - * VM Update Specs Request - */ - body?: VmUpdateSpecsRequest; - path: { /** - * Sandbox ID + * VM Update Specs Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/specs"; + body?: VmUpdateSpecsRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/specs'; }; export type VmUpdateSpecsResponses = { - /** - * VM Update Specs Response - */ - 200: VmUpdateSpecsResponse; + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; }; -export type VmUpdateSpecsResponse2 = - VmUpdateSpecsResponses[keyof VmUpdateSpecsResponses]; +export type VmUpdateSpecsResponse2 = VmUpdateSpecsResponses[keyof VmUpdateSpecsResponses]; export type VmStartData = { - /** - * VM Start Request - */ - body?: VmStartRequest; - path: { /** - * Sandbox ID + * VM Start Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/start"; + body?: VmStartRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/start'; }; export type VmStartResponses = { - /** - * VM Start Response - */ - 200: VmStartResponse; + /** + * VM Start Response + */ + 200: VmStartResponse; }; export type VmStartResponse2 = VmStartResponses[keyof VmStartResponses]; export type VmUpdateSpecs2Data = { - /** - * VM Update Specs Request - */ - body?: VmUpdateSpecsRequest; - path: { /** - * Sandbox ID + * VM Update Specs Request */ - id: string; - }; - query?: never; - url: "/vm/{id}/update_specs"; + body?: VmUpdateSpecsRequest; + path: { + /** + * Sandbox ID + */ + id: string; + }; + query?: never; + url: '/vm/{id}/update_specs'; }; export type VmUpdateSpecs2Responses = { - /** - * VM Update Specs Response - */ - 200: VmUpdateSpecsResponse; + /** + * VM Update Specs Response + */ + 200: VmUpdateSpecsResponse; }; -export type VmUpdateSpecs2Response = - VmUpdateSpecs2Responses[keyof VmUpdateSpecs2Responses]; +export type VmUpdateSpecs2Response = VmUpdateSpecs2Responses[keyof VmUpdateSpecs2Responses]; export type PreviewHostListData = { - body?: never; - path?: never; - query?: never; - url: "/workspace/preview_hosts"; + body?: never; + path?: never; + query?: never; + url: '/workspace/preview_hosts'; }; export type PreviewHostListResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostListResponse2 = - PreviewHostListResponses[keyof PreviewHostListResponses]; +export type PreviewHostListResponse2 = PreviewHostListResponses[keyof PreviewHostListResponses]; export type PreviewHostCreateData = { - /** - * Preview Host Create Request - */ - body?: PreviewHostRequest; - path?: never; - query?: never; - url: "/workspace/preview_hosts"; + /** + * Preview Host Create Request + */ + body?: PreviewHostRequest; + path?: never; + query?: never; + url: '/workspace/preview_hosts'; }; export type PreviewHostCreateResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostCreateResponse = - PreviewHostCreateResponses[keyof PreviewHostCreateResponses]; +export type PreviewHostCreateResponse = PreviewHostCreateResponses[keyof PreviewHostCreateResponses]; export type PreviewHostUpdateData = { - /** - * Preview Host Update Request - */ - body?: PreviewHostRequest; - path?: never; - query?: never; - url: "/workspace/preview_hosts"; + /** + * Preview Host Update Request + */ + body?: PreviewHostRequest; + path?: never; + query?: never; + url: '/workspace/preview_hosts'; }; export type PreviewHostUpdateResponses = { - /** - * Preview Host List Response - */ - 201: PreviewHostListResponse; + /** + * Preview Host List Response + */ + 201: PreviewHostListResponse; }; -export type PreviewHostUpdateResponse = - PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; +export type PreviewHostUpdateResponse = PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; \ No newline at end of file diff --git a/src/bin/utils/files.ts b/src/bin/utils/files.ts index 2879909..1e6a996 100644 --- a/src/bin/utils/files.ts +++ b/src/bin/utils/files.ts @@ -14,7 +14,6 @@ export async function hashDirectory(dirPath: string): Promise { const fullPath = join(dirPath, file); if (existsSync(fullPath)) { ig.add(readFileSync(fullPath, "utf8")); - ig.add(file); } }); diff --git a/src/types.ts b/src/types.ts index 3aca37d..4c20bce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -199,7 +199,7 @@ export type CreateSandboxBaseOpts = { }; export interface SessionCreateOptions { - id: string; + id?: string; permission?: "read" | "write"; git?: { provider: string; From 93e65b3ae1754db43f72ae223feecb8e912ae7ee Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Fri, 26 Sep 2025 15:15:53 +0200 Subject: [PATCH 225/241] feat: trigger release after squash merge (#195) From b5c6df9cd6b4c43859e85635c5fde4de50674b3d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 29 Sep 2025 10:00:05 +0200 Subject: [PATCH 226/241] fix: Remove devcontainer folder when .codesandbox/Dockerfile identified (#196) * When building a template we should identify if there is a .codesandbox/Dockerfile in the template, if so during writing of files to the Sandbox we should also delete the .devcontainer file already in the Sandbox. This is related to the CLI build command * chore: clean up --------- Co-authored-by: kodebots --- src/bin/commands/build.ts | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/bin/commands/build.ts b/src/bin/commands/build.ts index bb75785..ca59ffe 100644 --- a/src/bin/commands/build.ts +++ b/src/bin/commands/build.ts @@ -37,6 +37,37 @@ async function writeFileEnsureDir(filePath, data) { await writeFile(filePath, data); } +async function hasDockerfile(templateDirectory: string): Promise { + try { + const dockerfilePath = path.join( + templateDirectory, + ".codesandbox", + "Dockerfile" + ); + await fs.access(dockerfilePath); + return true; + } catch { + return false; + } +} + +async function removeDevcontainerFiles(session: SandboxClient): Promise { + try { + // Check if .devcontainer directory exists + const devcontainerPath = ".devcontainer"; + try { + await session.fs.stat(devcontainerPath); + // If we reach here, the directory exists, so remove it + await session.fs.remove(devcontainerPath, true); + } catch { + // Directory doesn't exist, nothing to remove + } + } catch (error) { + // Log but don't fail the build if devcontainer cleanup fails + console.warn(`Warning: Failed to remove .devcontainer files: ${error}`); + } +} + function stripAnsiCodes(str: string) { // Matches ESC [ params … finalChar // \x1B = ESC @@ -103,31 +134,34 @@ export const buildCommand: yargs.CommandModule< // Validate ports parameter - ensure all values are valid numbers if (argv.ports && argv.ports.length > 0) { const invalidPortsWithOriginal: string[] = []; - + // Get the original arguments to show what the user actually typed const originalArgs = process.argv; const portArgIndices: number[] = []; - + // Find all --ports arguments in the original command originalArgs.forEach((arg, i) => { - if (arg === '--ports' && i + 1 < originalArgs.length) { + if (arg === "--ports" && i + 1 < originalArgs.length) { portArgIndices.push(i + 1); } }); - + argv.ports.forEach((port, i) => { - const isInvalid = !Number.isInteger(port) || + const isInvalid = + !Number.isInteger(port) || port <= 0 || port > 65535 || !Number.isFinite(port); - + if (isInvalid) { // Try to get the original input, fallback to the parsed value - const originalInput = portArgIndices[i] ? originalArgs[portArgIndices[i]] : String(port); + const originalInput = portArgIndices[i] + ? originalArgs[portArgIndices[i]] + : String(port); invalidPortsWithOriginal.push(originalInput); } }); - + if (invalidPortsWithOriginal.length > 0) { throw new Error( `Invalid port value(s): ${invalidPortsWithOriginal.join( @@ -140,7 +174,6 @@ export const buildCommand: yargs.CommandModule< }), handler: async (argv) => { - const apiKey = getInferredApiKey(); const api = new API({ apiKey, instrumentation: instrumentedFetch }); const sdk = new CodeSandbox(apiKey); @@ -294,6 +327,14 @@ export const buildCommand: yargs.CommandModule< throw new Error(`Failed to write files to sandbox: ${error}`); }); + // Check if template has .codesandbox/Dockerfile and remove .devcontainer files if so + if (await hasDockerfile(argv.directory)) { + spinner.start( + updateSpinnerMessage(index, "Configuring Docker file...") + ); + await removeDevcontainerFiles(session); + } + // Dispose of the session after writing files to prevent reconnection session.dispose(); currentSession = null; From 8a4d86a06f5e9ea4f5621255d2e0eaf368a0befe Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Mon, 29 Sep 2025 10:03:40 +0200 Subject: [PATCH 227/241] chore(main): release 2.3.0 (#194) --- CHANGELOG.md | 13 +++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 672806e..f251ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [2.3.0](https://github.com/codesandbox/codesandbox-sdk/compare/v2.2.1...v2.3.0) (2025-09-29) + + +### Features + +* trigger release after squash merge ([#195](https://github.com/codesandbox/codesandbox-sdk/issues/195)) ([93e65b3](https://github.com/codesandbox/codesandbox-sdk/commit/93e65b3ae1754db43f72ae223feecb8e912ae7ee)) + + +### Bug Fixes + +* allow --help without api key ([#190](https://github.com/codesandbox/codesandbox-sdk/issues/190)) ([3f14843](https://github.com/codesandbox/codesandbox-sdk/commit/3f14843c2ea354cb9ba0c1b9137ca9ca92c2ee60)) +* Remove devcontainer folder when .codesandbox/Dockerfile identified ([#196](https://github.com/codesandbox/codesandbox-sdk/issues/196)) ([b5c6df9](https://github.com/codesandbox/codesandbox-sdk/commit/b5c6df9cd6b4c43859e85635c5fde4de50674b3d)) + ## [2.2.1](https://github.com/codesandbox/codesandbox-sdk/compare/v2.2.0...v2.2.1) (2025-09-16) diff --git a/package-lock.json b/package-lock.json index e63ba55..e87d29d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@codesandbox/sdk", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@codesandbox/sdk", - "version": "2.2.1", + "version": "2.3.0", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.7.3", diff --git a/package.json b/package.json index 74f9564..4f9ea34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codesandbox/sdk", - "version": "2.2.1", + "version": "2.3.0", "description": "The CodeSandbox SDK", "author": "CodeSandbox", "license": "MIT", From 489f033ddb4fe8d61982655449f6df9c3c538025 Mon Sep 17 00:00:00 2001 From: Mohamed AbdEl Mohaimen Date: Thu, 2 Oct 2025 13:29:08 +0200 Subject: [PATCH 228/241] chore: add pint openapi bundled json file (#198) --- pint-openapi-bundled.json | 2330 +++++++++++++++++++++++++++++++++++++ 1 file changed, 2330 insertions(+) create mode 100644 pint-openapi-bundled.json diff --git a/pint-openapi-bundled.json b/pint-openapi-bundled.json new file mode 100644 index 0000000..0f7faaf --- /dev/null +++ b/pint-openapi-bundled.json @@ -0,0 +1,2330 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Pint API", + "description": "Pint (formerly Sandbox Agent) is a Go CLI tool that exposes REST API endpoints for file operations, shell management, and task execution, designed to enable building code editor applications similar to VSCode.\n\nThe application uses file-based authentication with admin and readonly tokens, supporting CodeSandbox-compatible task management with robust workspace isolation.\n\n## Versioning\nThis API uses path-based versioning. Current versions:\n- **v1**: Current stable version at `/api/v1/`\n- **v2**: Future version (planned) at `/api/v2/`\n\nUnversioned endpoints (health, metrics) are available directly at the root.", + "version": "1.0.0", + "contact": { + "name": "CodeSandbox", + "url": "https://codesandbox.io" + }, + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "url": "http://localhost:57468", + "description": "Default development server" + }, + { + "url": "http://localhost:{port}", + "description": "Custom port server", + "variables": { + "port": { + "default": "57468", + "description": "Custom port number" + } + } + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/api/v1/files/{path}": { + "post": { + "summary": "Create a file", + "tags": [ + "files" + ], + "description": "Creates a new file at the specified path with optional content.", + "operationId": "createFile", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "File path", + "schema": { + "type": "string" + }, + "example": "workspace/src/main.go" + } + ], + "requestBody": { + "description": "File creation request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "File created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileReadResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required or invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to create file", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "get": { + "summary": "Read file content", + "tags": [ + "files" + ], + "description": "Reads the content of a file at the specified path.", + "operationId": "readFile", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "File path", + "schema": { + "type": "string" + }, + "example": "workspace/src/main.go" + } + ], + "responses": { + "200": { + "description": "File content retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileReadResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required or invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "summary": "Perform file actions", + "tags": [ + "files" + ], + "description": "Performs actions on files (e.g., move operations).", + "operationId": "performFileAction", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "Source file path (will be URL decoded)", + "schema": { + "type": "string" + }, + "example": "workspace/src/old-name.go" + } + ], + "requestBody": { + "required": true, + "description": "File action request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileActionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Action performed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileActionResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required, invalid action, or missing destination", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to perform action", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a file", + "tags": [ + "files" + ], + "description": "Deletes a file at the specified path.", + "operationId": "deleteFile", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "File path", + "schema": { + "type": "string" + }, + "example": "workspace/src/main.go" + } + ], + "responses": { + "200": { + "description": "File deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileOperationResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required or invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to delete file", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/directories/{path}": { + "post": { + "summary": "Create a directory", + "tags": [ + "directories" + ], + "description": "Creates a new directory at the specified path.", + "operationId": "createDirectory", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "Directory path", + "schema": { + "type": "string" + }, + "example": "workspace/src/utils" + } + ], + "responses": { + "201": { + "description": "Directory created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileOperationResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required or invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to create directory", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "get": { + "summary": "List directory contents", + "tags": [ + "directories" + ], + "description": "Lists the contents of a directory at the specified path.", + "operationId": "listDirectory", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "Directory path (will be URL decoded). Use \"/\" for root directory.", + "schema": { + "type": "string" + }, + "example": "workspace/src" + } + ], + "responses": { + "200": { + "description": "Directory contents retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DirectoryListResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Directory not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a directory", + "tags": [ + "directories" + ], + "description": "Deletes a directory at the specified path.", + "operationId": "deleteDirectory", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "description": "Directory path", + "schema": { + "type": "string" + }, + "example": "workspace/src/utils" + } + ], + "responses": { + "200": { + "description": "Directory deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileOperationResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Path is required or invalid path", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Directory not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to delete directory", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/execs": { + "post": { + "summary": "Create a new exec", + "tags": [ + "execs" + ], + "description": "Creates a new exec with specified command and arguments.", + "operationId": "createExec", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "description": "Exec creation request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateExecRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Exec created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecItem" + } + } + } + }, + "400": { + "description": "Bad Request - Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to create exec", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "get": { + "summary": "List all execs", + "tags": [ + "execs" + ], + "description": "Returns a list of all active execs.", + "operationId": "listExecs", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Execs retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecListResponse" + } + }, + "text/event-stream": { + "schema": { + "type": "string", + "description": "Server-Sent Events stream of exec updates" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/execs/{id}": { + "get": { + "summary": "Get exec by ID", + "tags": [ + "execs" + ], + "description": "Retrieves a specific exec by its ID.", + "operationId": "getExec", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Exec retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecItem" + } + }, + "text/event-stream": { + "schema": { + "type": "string", + "description": "Server-Sent Events stream of exec updates" + } + } + } + }, + "400": { + "description": "Bad Request - Exec ID is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "summary": "Update exec", + "tags": [ + "execs" + ], + "description": "Updates exec status (e.g., start a stopped exec).", + "operationId": "updateExec", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "description": "Exec update request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateExecRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Exec updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecItem" + } + } + } + }, + "400": { + "description": "Bad Request - Exec ID is required or invalid status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Conflict - Exec is already in the requested state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to update exec", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "summary": "Delete Exec", + "tags": [ + "execs" + ], + "description": "Deletes a exec and execs its process.", + "operationId": "deleteExec", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Exec deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecDeleteResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Exec ID is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/execs/{id}/io": { + "get": { + "summary": "Get Exec output", + "tags": [ + "execs" + ], + "description": "Retrieves the plain text output from a exec's buffer.", + "operationId": "getExecOutput", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Exec output retrieved successfully", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ExecStdout", + "description": "Plain text exec output" + } + }, + "text/event-stream": { + "schema": { + "type": "string", + "description": "Server-Sent Events stream of exec updates with same format as ExecStdout" + }, + "example": "data: {\"type\":\"stdout\",\"output\":\"Exec output line 1\\n\", \"sequence\" : 1, \"timestamp\":\"2024-10-01T12:00:00Z\"}\n" + } + } + }, + "400": { + "description": "Bad Request - Exec ID is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "summary": "exec exec stdin", + "tags": [ + "execs" + ], + "description": "exec exec command (e.g., npm install).", + "operationId": "execExecStdin", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "description": "Exec update request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecStdin" + } + } + } + }, + "responses": { + "200": { + "description": "Exec stdin executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecItem" + } + } + } + }, + "400": { + "description": "Bad Request - Exec ID is required or invalid status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to update exec", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/ws/v1/execs/{id}": { + "get": { + "summary": "Connect to exec via WebSocket", + "tags": [ + "execs" + ], + "description": "Establishes a WebSocket connection for real-time exec interaction.\n\nAuthentication can be provided via:\n- Authorization header: `Authorization: Bearer `\n- Query parameter: `?token=`\n\nPermissions:\n- Admin users: Can send input and receive output\n- Readonly users: Can only receive output\n", + "operationId": "connectToExecWebSocket", + "security": [ + { + "bearerAuth": [] + }, + { + "queryToken": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Exec identifier", + "schema": { + "type": "string" + } + } + ], + "responses": { + "101": { + "description": "WebSocket connection established successfully" + }, + "400": { + "description": "Bad Request - Exec ID is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized - Authentication token required or invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Exec not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to establish WebSocket connection", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/tasks": { + "get": { + "summary": "List all tasks", + "tags": [ + "tasks" + ], + "description": "Lists all configured tasks from .codesandbox/tasks.json with their current status.", + "operationId": "listTasks", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "List of tasks retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/tasks/{id}": { + "get": { + "summary": "Get task by ID", + "tags": [ + "tasks" + ], + "description": "Retrieves a specific task by its ID with current status and configuration.", + "operationId": "getTask", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Task identifier", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Task retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTaskResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Task ID is required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Task not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/tasks/{id}/actions": { + "patch": { + "summary": "Execute task action", + "tags": [ + "tasks" + ], + "description": "Executes an action on a specific task (start, stop, or restart).", + "operationId": "executeTaskAction", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Task identifier", + "schema": { + "type": "string" + } + }, + { + "name": "actionType", + "in": "query", + "required": true, + "description": "Type of action to execute", + "schema": { + "$ref": "#/components/schemas/TaskActionType" + } + } + ], + "responses": { + "200": { + "description": "Action executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskActionResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Task ID is required, invalid action type, or invalid command", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Task not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "409": { + "description": "Conflict - Invalid state transition (e.g., task already running for start, task not running for stop)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to execute action", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/setup-tasks": { + "get": { + "summary": "List setup tasks", + "tags": [ + "tasks" + ], + "description": "Lists all setup tasks with their execution status. Setup tasks are auto-executed during server start.", + "operationId": "listSetupTasks", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Setup tasks retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetupTaskListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/ports": { + "get": { + "summary": "List open ports", + "tags": [ + "ports" + ], + "description": "Lists all open TCP ports on the system, excluding ignored ports configured in the server.", + "operationId": "listPorts", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Open ports retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortsListResponse" + }, + "examples": { + "default": { + "summary": "Example ports response", + "value": { + "ports": [ + { + "port": 8080, + "address": "0.0.0.0" + }, + { + "port": 3000, + "address": "127.0.0.1" + }, + { + "port": 5432, + "address": "::1" + } + ] + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to read port information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/ports/stream": { + "get": { + "summary": "List open ports using Server-Sent Events (SSE)", + "tags": [ + "ports" + ], + "description": "Lists all open TCP ports on the system AND LISTEN TO THE CHANGES, excluding ignored ports configured in the server.", + "operationId": "ListPortsSSE", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Open ports retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortsListResponse" + }, + "examples": { + "default": { + "summary": "Example ports response", + "value": { + "ports": [ + { + "port": 8080, + "address": "0.0.0.0" + }, + { + "port": 3000, + "address": "127.0.0.1" + }, + { + "port": 5432, + "address": "::1" + } + ] + } + } + } + }, + "text/event-stream": { + "schema": { + "type": "string", + "description": "Server-Sent Events stream of ports list updates" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error - Failed to read port information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "queryToken": { + "type": "apiKey", + "in": "query", + "name": "token" + } + }, + "schemas": { + "Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ] + }, + "FileReadResponse": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "File path" + }, + "content": { + "type": "string", + "description": "File content" + } + }, + "required": [ + "path", + "content" + ] + }, + "FileCreateRequest": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "File content to create" + } + } + }, + "FileOperationResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Operation result message" + }, + "path": { + "type": "string", + "description": "File or directory path" + } + }, + "required": [ + "message", + "path" + ] + }, + "FileActionRequest": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "move" + ], + "description": "Type of action to perform on the file" + }, + "destination": { + "type": "string", + "description": "Destination path for move operation" + } + }, + "required": [ + "action", + "destination" + ] + }, + "FileActionResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Operation result message" + }, + "from": { + "type": "string", + "description": "Source path" + }, + "to": { + "type": "string", + "description": "Destination path" + } + }, + "required": [ + "message", + "from", + "to" + ] + }, + "FileInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "File or directory name" + }, + "path": { + "type": "string", + "description": "Full path to the file or directory" + }, + "isDir": { + "type": "boolean", + "description": "Whether this entry is a directory" + }, + "size": { + "type": "integer", + "format": "int64", + "description": "File size in bytes" + }, + "modTime": { + "type": "string", + "description": "Last modification time" + } + }, + "required": [ + "name", + "path", + "isDir", + "size", + "modTime" + ] + }, + "DirectoryListResponse": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Directory path" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileInfo" + }, + "description": "List of files and directories" + } + }, + "required": [ + "path", + "files" + ] + }, + "ExecItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Exec unique identifier" + }, + "command": { + "type": "string", + "description": "Command being executed" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Command line arguments" + }, + "status": { + "type": "string", + "description": "Exec status (e.g., running, stopped, finished)" + }, + "pid": { + "type": "integer", + "description": "Process ID of the exec" + } + }, + "required": [ + "id", + "command", + "args", + "status", + "pid" + ] + }, + "ExecListResponse": { + "type": "object", + "properties": { + "execs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExecItem" + }, + "description": "List of execs" + } + }, + "required": [ + "execs" + ] + }, + "CreateExecRequest": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to execute in the exec" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Command line arguments" + }, + "autorun": { + "type": "boolean", + "description": "Whether to automatically start the exec (defaults to true)" + }, + "interactive": { + "type": "boolean", + "description": "Whether to start interactive shell session or not (defaults to false)" + } + }, + "required": [ + "command", + "args" + ] + }, + "UpdateExecRequest": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "running" + ], + "description": "Status to set for the exec (currently only 'running' is supported)" + } + }, + "required": [ + "status" + ] + }, + "ExecDeleteResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Deletion confirmation message" + } + }, + "required": [ + "message" + ] + }, + "ExecStdout": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the exec output", + "enum": [ + "stdout", + "stderr" + ] + }, + "output": { + "type": "string", + "description": "Data associated with the exec output" + }, + "sequence": { + "type": "integer", + "format": "int32", + "description": "Sequence number of the output message" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of when the output was generated" + } + }, + "required": [ + "type", + "output", + "sequence" + ] + }, + "ExecStdin": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the exec input", + "enum": [ + "stdin", + "resize" + ] + }, + "input": { + "type": "string", + "description": "Data associated with the exec input" + } + }, + "required": [ + "type", + "input" + ] + }, + "TaskStatus": { + "type": "string", + "enum": [ + "RUNNING", + "FINISHED", + "ERROR", + "KILLED", + "RESTARTING", + "IDLE" + ] + }, + "TaskBase": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/TaskStatus" + }, + "execId": { + "type": "string", + "description": "Exec identifier associated with the task" + }, + "startTime": { + "type": "string", + "description": "Task start time in RFC3339 format", + "example": "2017-07-21T17:32:28Z" + }, + "endTime": { + "type": "string", + "description": "Task end time in RFC3339 format", + "example": "2017-08-21T17:32:28Z" + } + }, + "required": [ + "status", + "execId", + "startTime", + "endTime" + ], + "description": "Base schema for a task item, containing common fields shared across different task types.", + "title": "TaskBase" + }, + "TaskRestart": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "clone": { + "type": "boolean" + }, + "resume": { + "type": "boolean" + }, + "branch": { + "type": "boolean" + } + }, + "required": [ + "files", + "clone", + "resume", + "branch" + ] + }, + "TaskPreview": { + "type": "object", + "properties": { + "port": { + "type": "integer" + } + }, + "required": [ + "port" + ] + }, + "TaskConfig": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "command": { + "type": "string" + }, + "runAtStart": { + "type": "boolean" + }, + "restartOn": { + "$ref": "#/components/schemas/TaskRestart" + }, + "preview": { + "$ref": "#/components/schemas/TaskPreview" + } + }, + "required": [ + "name", + "command", + "runAtStart", + "restartOn", + "preview" + ] + }, + "TaskItem": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskBase" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Task identifier" + }, + "config": { + "$ref": "#/components/schemas/TaskConfig" + } + }, + "required": [ + "id", + "config" + ] + } + ] + }, + "TaskListResponse": { + "type": "object", + "properties": { + "tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskItem" + } + } + }, + "required": [ + "tasks" + ] + }, + "GetTaskResponse": { + "type": "object", + "properties": { + "task": { + "$ref": "#/components/schemas/TaskItem" + } + }, + "required": [ + "task" + ] + }, + "TaskActionType": { + "type": "string", + "enum": [ + "start", + "stop", + "restart" + ], + "description": "Type of action to execute on a task" + }, + "TaskActionResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskBase" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Task identifier" + }, + "name": { + "type": "string", + "description": "Task name" + }, + "command": { + "type": "string", + "description": "Task command" + }, + "message": { + "type": "string", + "description": "Action result message" + } + }, + "required": [ + "id", + "name", + "message", + "command" + ] + } + ], + "description": "Schema for task action responses, containing details about the task and the action performed.", + "title": "TaskActionResponse" + }, + "SetupTaskItem": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskBase" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Setup task name" + }, + "command": { + "type": "string", + "description": "Setup task command" + } + }, + "required": [ + "name", + "command", + "execId" + ] + } + ] + }, + "SetupTaskListResponse": { + "type": "object", + "properties": { + "setupTasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SetupTaskItem" + } + } + }, + "required": [ + "setupTasks" + ] + }, + "PortInfo": { + "type": "object", + "properties": { + "port": { + "type": "integer", + "format": "uint16", + "minimum": 1, + "maximum": 65535, + "description": "Port number", + "example": 8080 + }, + "address": { + "type": "string", + "description": "IP address the port is bound to", + "example": "0.0.0.0" + } + }, + "required": [ + "port", + "address" + ] + }, + "PortsListResponse": { + "type": "object", + "properties": { + "ports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PortInfo" + }, + "description": "List of open ports" + } + }, + "required": [ + "ports" + ] + }, + "Task": { + "$ref": "#/components/schemas/TaskItem" + } + } + } +} \ No newline at end of file From ddb42b0f54d6002aa8657402829e794781f6395e Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 2 Oct 2025 14:52:57 +0200 Subject: [PATCH 229/241] initial ports implementation --- openapi-git-spec.json | 1348 ----------- openapi-git.json | 0 openapi-port.json | 151 -- openapi-sandbox-container.json | 179 -- openapi-sandbox-fs.json | 2005 ----------------- openapi-sandbox-git.json | 1369 ----------- openapi-sandbox-setup.json | 570 ----- openapi-sandbox-shell.json | 916 -------- openapi-sandbox-system.json | 348 --- openapi-sandbox-task.json | 947 -------- openapi.json | 13 +- package-lock.json | 269 ++- package.json | 15 +- pint-openapi-bundled.json | 285 +-- src/API.ts | 5 +- src/AgentClient/AgentConnection.ts | 2 +- src/AgentClient/index.ts | 2 +- src/PintClient/index.ts | 95 + src/SandboxClient/commands.ts | 5 +- src/SandboxClient/filesystem.ts | 2 +- src/SandboxClient/index.ts | 2 +- src/SandboxClient/ports.ts | 2 +- src/SandboxClient/setup.ts | 2 +- src/SandboxClient/tasks.ts | 2 +- src/SandboxClient/terminals.ts | 2 +- .../agent-client-interface.ts | 4 +- .../client-rest-container/client.gen.ts | 5 - .../client-rest-container/index.ts | 3 - .../client-rest-container/sdk.gen.ts | 29 - .../client-rest-container/types.gen.ts | 111 - src/api-clients/client-rest-fs/client.gen.ts | 5 - src/api-clients/client-rest-fs/index.ts | 3 - src/api-clients/client-rest-fs/sdk.gen.ts | 280 --- src/api-clients/client-rest-fs/types.gen.ts | 1058 --------- src/api-clients/client-rest-git/client.gen.ts | 5 - src/api-clients/client-rest-git/index.ts | 3 - src/api-clients/client-rest-git/sdk.gen.ts | 224 -- src/api-clients/client-rest-git/types.gen.ts | 725 ------ .../client-rest-setup/client.gen.ts | 5 - src/api-clients/client-rest-setup/index.ts | 3 - src/api-clients/client-rest-setup/sdk.gen.ts | 119 - .../client-rest-setup/types.gen.ts | 295 --- .../client-rest-shell/client.gen.ts | 5 - src/api-clients/client-rest-shell/index.ts | 3 - src/api-clients/client-rest-shell/sdk.gen.ts | 149 -- .../client-rest-shell/types.gen.ts | 443 ---- .../client-rest-system/client.gen.ts | 5 - src/api-clients/client-rest-system/index.ts | 3 - src/api-clients/client-rest-system/sdk.gen.ts | 59 - .../client-rest-system/types.gen.ts | 207 -- .../client-rest-task/client.gen.ts | 5 - src/api-clients/client-rest-task/index.ts | 3 - src/api-clients/client-rest-task/sdk.gen.ts | 149 -- src/api-clients/client-rest-task/types.gen.ts | 507 ----- src/api-clients/client/client.gen.ts | 17 +- src/api-clients/client/client/client.gen.ts | 268 +++ src/api-clients/client/client/index.ts | 25 + src/api-clients/client/client/types.gen.ts | 268 +++ src/api-clients/client/client/utils.gen.ts | 331 +++ src/api-clients/client/core/auth.gen.ts | 42 + .../client/core/bodySerializer.gen.ts | 92 + src/api-clients/client/core/params.gen.ts | 153 ++ .../client/core/pathSerializer.gen.ts | 181 ++ .../client/core/serverSentEvents.gen.ts | 264 +++ src/api-clients/client/core/types.gen.ts | 118 + src/api-clients/client/core/utils.gen.ts | 143 ++ src/api-clients/client/index.ts | 5 +- src/api-clients/client/sdk.gen.ts | 97 +- src/api-clients/client/types.gen.ts | 218 +- src/api-clients/pint/client.gen.ts | 18 + src/api-clients/pint/client/client.gen.ts | 268 +++ src/api-clients/pint/client/index.ts | 25 + src/api-clients/pint/client/types.gen.ts | 268 +++ src/api-clients/pint/client/utils.gen.ts | 331 +++ src/api-clients/pint/core/auth.gen.ts | 42 + .../pint/core/bodySerializer.gen.ts | 92 + src/api-clients/pint/core/params.gen.ts | 153 ++ .../pint/core/pathSerializer.gen.ts | 181 ++ .../pint/core/serverSentEvents.gen.ts | 264 +++ src/api-clients/pint/core/types.gen.ts | 118 + src/api-clients/pint/core/utils.gen.ts | 143 ++ src/api-clients/pint/index.ts | 4 + src/api-clients/pint/sdk.gen.ts | 410 ++++ src/api-clients/pint/types.gen.ts | 1184 ++++++++++ src/utils/api.ts | 10 +- src/utils/event.ts | 50 + tests/emitter-subscription.test.ts | 212 ++ 87 files changed, 6323 insertions(+), 12623 deletions(-) delete mode 100644 openapi-git-spec.json delete mode 100644 openapi-git.json delete mode 100644 openapi-port.json delete mode 100644 openapi-sandbox-container.json delete mode 100644 openapi-sandbox-fs.json delete mode 100644 openapi-sandbox-git.json delete mode 100644 openapi-sandbox-setup.json delete mode 100644 openapi-sandbox-shell.json delete mode 100644 openapi-sandbox-system.json delete mode 100644 openapi-sandbox-task.json create mode 100644 src/PintClient/index.ts rename src/{AgentClient => }/agent-client-interface.ts (98%) delete mode 100644 src/api-clients/client-rest-container/client.gen.ts delete mode 100644 src/api-clients/client-rest-container/index.ts delete mode 100644 src/api-clients/client-rest-container/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-container/types.gen.ts delete mode 100644 src/api-clients/client-rest-fs/client.gen.ts delete mode 100644 src/api-clients/client-rest-fs/index.ts delete mode 100644 src/api-clients/client-rest-fs/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-fs/types.gen.ts delete mode 100644 src/api-clients/client-rest-git/client.gen.ts delete mode 100644 src/api-clients/client-rest-git/index.ts delete mode 100644 src/api-clients/client-rest-git/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-git/types.gen.ts delete mode 100644 src/api-clients/client-rest-setup/client.gen.ts delete mode 100644 src/api-clients/client-rest-setup/index.ts delete mode 100644 src/api-clients/client-rest-setup/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-setup/types.gen.ts delete mode 100644 src/api-clients/client-rest-shell/client.gen.ts delete mode 100644 src/api-clients/client-rest-shell/index.ts delete mode 100644 src/api-clients/client-rest-shell/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-shell/types.gen.ts delete mode 100644 src/api-clients/client-rest-system/client.gen.ts delete mode 100644 src/api-clients/client-rest-system/index.ts delete mode 100644 src/api-clients/client-rest-system/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-system/types.gen.ts delete mode 100644 src/api-clients/client-rest-task/client.gen.ts delete mode 100644 src/api-clients/client-rest-task/index.ts delete mode 100644 src/api-clients/client-rest-task/sdk.gen.ts delete mode 100644 src/api-clients/client-rest-task/types.gen.ts create mode 100644 src/api-clients/client/client/client.gen.ts create mode 100644 src/api-clients/client/client/index.ts create mode 100644 src/api-clients/client/client/types.gen.ts create mode 100644 src/api-clients/client/client/utils.gen.ts create mode 100644 src/api-clients/client/core/auth.gen.ts create mode 100644 src/api-clients/client/core/bodySerializer.gen.ts create mode 100644 src/api-clients/client/core/params.gen.ts create mode 100644 src/api-clients/client/core/pathSerializer.gen.ts create mode 100644 src/api-clients/client/core/serverSentEvents.gen.ts create mode 100644 src/api-clients/client/core/types.gen.ts create mode 100644 src/api-clients/client/core/utils.gen.ts create mode 100644 src/api-clients/pint/client.gen.ts create mode 100644 src/api-clients/pint/client/client.gen.ts create mode 100644 src/api-clients/pint/client/index.ts create mode 100644 src/api-clients/pint/client/types.gen.ts create mode 100644 src/api-clients/pint/client/utils.gen.ts create mode 100644 src/api-clients/pint/core/auth.gen.ts create mode 100644 src/api-clients/pint/core/bodySerializer.gen.ts create mode 100644 src/api-clients/pint/core/params.gen.ts create mode 100644 src/api-clients/pint/core/pathSerializer.gen.ts create mode 100644 src/api-clients/pint/core/serverSentEvents.gen.ts create mode 100644 src/api-clients/pint/core/types.gen.ts create mode 100644 src/api-clients/pint/core/utils.gen.ts create mode 100644 src/api-clients/pint/index.ts create mode 100644 src/api-clients/pint/sdk.gen.ts create mode 100644 src/api-clients/pint/types.gen.ts create mode 100644 tests/emitter-subscription.test.ts diff --git a/openapi-git-spec.json b/openapi-git-spec.json deleted file mode 100644 index 3a8d660..0000000 --- a/openapi-git-spec.json +++ /dev/null @@ -1,1348 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Git API", - "description": "API for interacting with Git version control in sandboxes", - "version": "1.0.0" - }, - "paths": { - "/git/status": { - "post": { - "summary": "Get git status", - "description": "Retrieve the current git status of the repository", - "operationId": "gitStatus", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitStatus" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving git status", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/remotes": { - "post": { - "summary": "Get git remotes", - "description": "Retrieve the remote repositories configured for the git repository", - "operationId": "gitRemotes", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitRemotes" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving git remotes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/targetDiff": { - "post": { - "summary": "Get target diff", - "description": "Retrieve the difference between the current branch and a target branch", - "operationId": "gitTargetDiff", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "branch": { - "type": "string", - "description": "Target branch name" - } - }, - "required": ["branch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitTargetDiff" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving target diff", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/pull": { - "post": { - "summary": "Pull changes", - "description": "Pull changes from the remote repository", - "operationId": "gitPull", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "branch": { - "type": "string", - "description": "Branch to pull from" - }, - "force": { - "type": "boolean", - "description": "Force pull" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pulling changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/discard": { - "post": { - "summary": "Discard changes", - "description": "Discard changes to specified paths or all changes", - "operationId": "gitDiscard", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths to discard changes for" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths that were discarded" - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error discarding changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/commit": { - "post": { - "summary": "Commit changes", - "description": "Commit changes to the local repository", - "operationId": "gitCommit", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths to commit" - }, - "message": { - "type": "string", - "description": "Commit message" - }, - "push": { - "type": "boolean", - "description": "Whether to push after committing" - } - }, - "required": ["message"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "shellId": { - "type": "string", - "description": "ID of the shell process" - } - }, - "required": ["shellId"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error committing changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/push": { - "post": { - "summary": "Push changes", - "description": "Push changes to the remote repository", - "operationId": "gitPush", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pushing changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/pushToRemote": { - "post": { - "summary": "Push to remote", - "description": "Push changes to a specific remote repository and branch", - "operationId": "gitPushToRemote", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "URL of the remote repository" - }, - "branch": { - "type": "string", - "description": "Branch to push to" - }, - "squashAllCommits": { - "type": "boolean", - "description": "Whether to squash all commits before pushing" - } - }, - "required": ["url", "branch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pushing to remote", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/renameBranch": { - "post": { - "summary": "Rename branch", - "description": "Rename a branch in the local repository", - "operationId": "gitRenameBranch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "oldBranch": { - "type": "string", - "description": "Name of the branch to rename" - }, - "newBranch": { - "type": "string", - "description": "New name for the branch" - } - }, - "required": ["oldBranch", "newBranch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error renaming branch", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/remoteContent": { - "post": { - "summary": "Get remote content", - "description": "Retrieve the content of a file from a remote branch or commit", - "operationId": "gitRemoteContent", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GitRemoteParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the file" - } - }, - "required": ["content"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving remote content", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/diffStatus": { - "post": { - "summary": "Get diff status", - "description": "Retrieve the status of changes between two references", - "operationId": "gitDiffStatus", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GitDiffStatusParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitDiffStatusResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving diff status", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/resetLocalWithRemote": { - "post": { - "summary": "Reset local with remote", - "description": "Reset the local repository to match the remote", - "operationId": "gitResetLocalWithRemote", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error resetting local with remote", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/checkoutInitialBranch": { - "post": { - "summary": "Checkout initial branch", - "description": "Checkout the initial branch of the repository", - "operationId": "gitCheckoutInitialBranch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error checking out initial branch", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/transposeLines": { - "post": { - "summary": "Transpose lines", - "description": "Map line numbers between different git commits", - "operationId": "gitTransposeLines", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "sha": { - "type": "string", - "description": "Commit SHA" - }, - "path": { - "type": "string", - "description": "File path" - }, - "line": { - "type": "number", - "description": "Line number" - } - }, - "required": ["sha", "path", "line"] - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path" - }, - "line": { - "type": "number", - "description": "Line number" - } - }, - "required": ["path", "line"], - "nullable": true - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error transposing lines", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "CommonError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code", "message"] - }, - "GitStatusShortFormat": { - "type": "string", - "enum": ["", "M", "A", "D", "R", "C", "U", "?"], - "description": "Git status short format codes" - }, - "GitItem": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path" - }, - "index": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "workingTree": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "isStaged": { - "type": "boolean", - "description": "Whether the file is staged" - }, - "isConflicted": { - "type": "boolean", - "description": "Whether the file has conflicts" - }, - "fileId": { - "type": "string", - "description": "File ID" - } - }, - "required": ["path", "index", "workingTree", "isStaged", "isConflicted"] - }, - "GitChangedFiles": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/GitItem" - }, - "description": "Map of file IDs to GitItems" - }, - "GitBranchProperties": { - "type": "object", - "properties": { - "head": { - "type": "string", - "nullable": true, - "description": "Head commit" - }, - "branch": { - "type": "string", - "nullable": true, - "description": "Branch name" - }, - "ahead": { - "type": "number", - "description": "Number of commits ahead" - }, - "behind": { - "type": "number", - "description": "Number of commits behind" - }, - "safe": { - "type": "boolean", - "description": "Whether the branch is safe to use" - } - }, - "required": ["ahead", "behind", "safe"] - }, - "GitCommit": { - "type": "object", - "properties": { - "hash": { - "type": "string", - "description": "Commit hash" - }, - "date": { - "type": "string", - "description": "Commit date" - }, - "message": { - "type": "string", - "description": "Commit message" - }, - "author": { - "type": "string", - "description": "Commit author" - } - }, - "required": ["hash", "date", "message", "author"] - }, - "GitStatus": { - "type": "object", - "properties": { - "changedFiles": { - "$ref": "#/components/schemas/GitChangedFiles" - }, - "deletedFiles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitItem" - } - }, - "conflicts": { - "type": "boolean", - "description": "Whether there are remote conflicts" - }, - "localChanges": { - "type": "boolean", - "description": "Whether there are local changes" - }, - "remote": { - "$ref": "#/components/schemas/GitBranchProperties" - }, - "target": { - "$ref": "#/components/schemas/GitBranchProperties" - }, - "head": { - "type": "string", - "description": "Current HEAD commit" - }, - "commits": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitCommit" - } - }, - "branch": { - "type": "string", - "nullable": true, - "description": "Current branch name" - }, - "isMerging": { - "type": "boolean", - "description": "Whether a merge is in progress" - } - }, - "required": [ - "changedFiles", - "deletedFiles", - "conflicts", - "localChanges", - "remote", - "target", - "commits", - "branch", - "isMerging" - ] - }, - "GitTargetDiff": { - "type": "object", - "properties": { - "ahead": { - "type": "number", - "description": "Number of commits ahead of target" - }, - "behind": { - "type": "number", - "description": "Number of commits behind target" - }, - "commits": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitCommit" - } - } - }, - "required": ["ahead", "behind", "commits"] - }, - "GitRemotes": { - "type": "object", - "properties": { - "origin": { - "type": "string", - "description": "Origin remote URL" - }, - "upstream": { - "type": "string", - "description": "Upstream remote URL" - } - }, - "required": ["origin", "upstream"] - }, - "GitRemoteParams": { - "type": "object", - "properties": { - "reference": { - "type": "string", - "description": "Branch or commit hash" - }, - "path": { - "type": "string", - "description": "File path" - } - }, - "required": ["reference", "path"] - }, - "GitDiffStatusParams": { - "type": "object", - "properties": { - "base": { - "type": "string", - "description": "Base reference for diffing" - }, - "head": { - "type": "string", - "description": "Head reference for diffing" - } - }, - "required": ["base", "head"] - }, - "GitDiffStatusItem": { - "type": "object", - "properties": { - "status": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "path": { - "type": "string", - "description": "File path" - }, - "oldPath": { - "type": "string", - "description": "Original file path (for renames)" - }, - "hunks": { - "type": "array", - "items": { - "type": "object", - "properties": { - "original": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start", "end"] - }, - "modified": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start", "end"] - } - }, - "required": ["original", "modified"] - } - } - }, - "required": ["status", "path", "hunks"] - }, - "GitDiffStatusResult": { - "type": "object", - "properties": { - "files": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitDiffStatusItem" - } - } - }, - "required": ["files"] - } - } - } -} diff --git a/openapi-git.json b/openapi-git.json deleted file mode 100644 index e69de29..0000000 diff --git a/openapi-port.json b/openapi-port.json deleted file mode 100644 index 176f301..0000000 --- a/openapi-port.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Port API", - "description": "API for managing sandbox port operations", - "version": "1.0.0" - }, - "paths": { - "/port/list": { - "post": { - "summary": "List ports", - "description": "Retrieve a list of available ports and their URLs", - "operationId": "portList", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Port" - }, - "description": "List of available ports" - } - }, - "required": ["list"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error listing ports", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "CommonError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code", "message"] - }, - "Port": { - "type": "object", - "properties": { - "port": { - "type": "number", - "description": "Port number" - }, - "url": { - "type": "string", - "description": "URL to access the service on this port" - } - }, - "required": ["port", "url"] - } - } - } -} diff --git a/openapi-sandbox-container.json b/openapi-sandbox-container.json deleted file mode 100644 index f272b37..0000000 --- a/openapi-sandbox-container.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Container API", - "description": "API for managing sandbox container operations", - "version": "1.0.0" - }, - "paths": { - "/container/setup": { - "post": { - "summary": "Setup container", - "description": "Set up a new container based on a template", - "operationId": "containerSetup", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "templateId": { - "type": "string", - "description": "Identifier of the template to use" - }, - "templateArgs": { - "type": "object", - "description": "Arguments for the template", - "additionalProperties": { - "type": "string" - } - }, - "features": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Feature identifier" - }, - "options": { - "type": "object", - "description": "Options for the feature", - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["id", "options"] - }, - "nullable": true - } - }, - "required": ["templateId", "templateArgs"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error setting up container", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "ProtocolError": { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code", "message"] - }, - "TaskDTO": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Task identifier" - }, - "status": { - "type": "string", - "description": "Task status" - }, - "progress": { - "type": "number", - "description": "Task progress (0-100)" - } - }, - "required": ["id", "status", "progress"] - } - } - } -} diff --git a/openapi-sandbox-fs.json b/openapi-sandbox-fs.json deleted file mode 100644 index 57c3c6c..0000000 --- a/openapi-sandbox-fs.json +++ /dev/null @@ -1,2005 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Rest FS API", - "description": "FS API for interacting with sandbox", - "version": "1.0.0" - }, - "paths": { - "/fs/writeFile": { - "post": { - "summary": "Write to a file", - "description": "Write content to a file at the specified path", - "operationId": "writeFile", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WriteFileRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error writing file", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/read": { - "post": { - "summary": "Read file system", - "description": "Retrieve the latest snapshot of the server's MemoryFS file and children list", - "operationId": "fsRead", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSReadResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error reading file system", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/operation": { - "post": { - "summary": "Perform file system operation", - "description": "Send a tree operation reflecting filesystem operations", - "operationId": "fsOperation", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSOperationRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSOperationResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error performing operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/search": { - "post": { - "summary": "Search files", - "description": "Search for content in files", - "operationId": "fsSearch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSSearchParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SearchResult" - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error searching files", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/streamingSearch": { - "post": { - "summary": "Start streaming search", - "description": "Start a streaming search for content in files", - "operationId": "fsStreamingSearch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSStreamingSearchParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "searchId": { - "type": "string", - "description": "ID of the search operation" - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error starting streaming search", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/cancelStreamingSearch": { - "post": { - "summary": "Cancel streaming search", - "description": "Cancel an ongoing streaming search", - "operationId": "fsCancelStreamingSearch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "searchId": { - "type": "string", - "description": "ID of the search to cancel" - } - }, - "required": ["searchId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "searchId": { - "type": "string", - "description": "ID of the cancelled search" - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error cancelling search", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/pathSearch": { - "post": { - "summary": "Search file paths", - "description": "Search for file paths matching a pattern", - "operationId": "fsPathSearch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PathSearchParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/PathSearchResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error searching paths", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/upload": { - "post": { - "summary": "Upload file", - "description": "Upload a file to the specified parent directory", - "operationId": "fsUpload", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "parentId": { - "type": "string", - "description": "ID of the parent directory" - }, - "filename": { - "type": "string", - "description": "Name of the file to create" - }, - "content": { - "type": "string", - "format": "binary", - "description": "File content as binary data" - } - }, - "required": ["parentId", "filename", "content"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "ID of the created file" - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error uploading file", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/InvalidIdError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/download": { - "post": { - "summary": "Download files", - "description": "Download files at a specified path as a zip", - "operationId": "fsDownload", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to download" - }, - "excludes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns of files/folders to exclude from the download" - } - }, - "required": ["path"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "downloadUrl": { - "type": "string", - "description": "URL to download the files from" - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error creating download", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/DefaultError" - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/readFile": { - "post": { - "summary": "Read file content", - "description": "Read the content of a file at the specified path", - "operationId": "fsReadFile", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSReadFileParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSReadFileResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error reading file", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/readdir": { - "post": { - "summary": "Read directory contents", - "description": "List the contents of a directory at the specified path", - "operationId": "fsReadDir", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSReadDirParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSReadDirResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error reading directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/stat": { - "post": { - "summary": "Get file/directory stats", - "description": "Get stats for a file or directory at the specified path", - "operationId": "fsStat", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSStatParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSStatResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error getting stats", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/copy": { - "post": { - "summary": "Copy file/directory", - "description": "Copy a file or directory from one location to another", - "operationId": "fsCopy", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSCopyParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error copying file/directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/rename": { - "post": { - "summary": "Rename file/directory", - "description": "Rename a file or directory (move from one location to another)", - "operationId": "fsRename", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSRenameParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error renaming file/directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/remove": { - "post": { - "summary": "Remove file/directory", - "description": "Delete a file or directory at the specified path", - "operationId": "fsRemove", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSRemoveParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error removing file/directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/mkdir": { - "post": { - "summary": "Create directory", - "description": "Create a new directory at the specified path", - "operationId": "fsMkdir", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSMkdirParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error creating directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/watch": { - "post": { - "summary": "Watch file/directory", - "description": "Watch a file or directory for changes", - "operationId": "fsWatch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSWatchParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/FSWatchResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error watching file/directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - }, - "/fs/unwatch": { - "post": { - "summary": "Stop watching file/directory", - "description": "Stop watching a file or directory for changes", - "operationId": "fsUnwatch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FSUnwatchParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error unwatching file/directory", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "oneOf": [ - { - "$ref": "#/components/schemas/DefaultError" - }, - { - "$ref": "#/components/schemas/RawFsError" - } - ], - "discriminator": { - "propertyName": "code" - } - } - }, - "required": ["status", "error"] - }, - "DefaultError": { - "type": "object", - "properties": { - "code": { - "$ref": "#/components/schemas/PitcherErrorCode", - "description": "Error code identifying the type of error" - }, - "data": { - "type": "object", - "description": "Additional error details", - "nullable": true - }, - "publicMessage": { - "type": "string", - "description": "Human-readable error message that can be displayed to users", - "nullable": true - } - }, - "required": ["code"] - }, - "RawFsError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [102], - "description": "RAWFS_ERROR code" - }, - "data": { - "type": "object", - "properties": { - "errno": { - "type": ["number", "null"], - "description": "File system error number, or null if not available" - } - }, - "required": ["errno"] - }, - "publicMessage": { - "type": "string", - "description": "Human-readable error message that can be displayed to users", - "nullable": true - } - }, - "required": ["code", "data"] - }, - "PitcherErrorCode": { - "type": "integer", - "description": "Enumeration of error codes", - "enum": [ - 0, 1, 2, 3, 100, 101, 102, 200, 201, 204, 300, 400, 404, 410, 420, - 430, 440, 450, 460, 470, 500, 600, 601, 602, 704, 800, 801, 802, 803, - 814 - ], - "x-enum-descriptions": [ - "CRITICAL_ERROR", - "FEATURE_UNAVAILABLE", - "NO_ACCESS", - "RATE_LIMIT", - "INVALID_ID", - "INVALID_PATH", - "RAWFS_ERROR", - "SHELL_NOT_ACCESSIBLE", - "SHELL_CLOSED", - "SHELL_NOT_FOUND", - "MODEL_NOT_FOUND", - "GIT_OPERATION_IN_PROGRESS", - "GIT_REMOTE_FILE_NOT_FOUND", - "GIT_FETCH_FAIL", - "GIT_PULL_CONFLICT", - "GIT_RESET_LOCAL_REMOTE_ERROR", - "GIT_PUSH_FAIL", - "GIT_RESET_CHECKOUT_INITIAL_BRANCH_FAIL", - "GIT_PULL_FAIL", - "GIT_TRANSPOSE_LINES_FAIL", - "CHANNEL_NOT_FOUND", - "CONFIG_FILE_ALREADY_EXISTS", - "TASK_NOT_FOUND", - "COMMAND_ALREADY_CONFIGURED", - "COMMAND_NOT_FOUND", - "AI_NOT_AVAILABLE", - "PROMPT_TOO_BIG", - "FAILED_TO_RESPOND", - "AI_TOO_FREQUENT_REQUESTS", - "AI_CHAT_NOT_FOUND" - ] - }, - "WriteFileRequest": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path to write to" - }, - "content": { - "type": "string", - "format": "binary", - "description": "File content as binary data (Uint8Array)" - }, - "create": { - "type": "boolean", - "description": "Whether to create the file if it doesn't exist", - "default": false - }, - "overwrite": { - "type": "boolean", - "description": "Whether to overwrite the file if it exists", - "default": false - } - }, - "required": ["path", "content"] - }, - "FSReadResult": { - "type": "object", - "properties": { - "treeNodes": { - "type": "array", - "items": { - "type": "object", - "description": "JSON representation of a node in the file system" - } - }, - "clock": { - "type": "number", - "description": "Current clock value for the file system" - } - }, - "required": ["treeNodes", "clock"] - }, - "FSOperationRequest": { - "type": "object", - "properties": { - "operation": { - "$ref": "#/components/schemas/FSOperation" - } - }, - "required": ["operation"] - }, - "FSOperation": { - "oneOf": [ - { - "$ref": "#/components/schemas/FSCreateOperation" - }, - { - "$ref": "#/components/schemas/FSDeleteOperation" - }, - { - "$ref": "#/components/schemas/FSMoveOperation" - } - ], - "discriminator": { - "propertyName": "type" - } - }, - "FSCreateOperation": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["create"] - }, - "parentId": { - "type": "string", - "description": "ID of the parent directory" - }, - "newEntry": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the new entry" - }, - "type": { - "type": "string", - "enum": ["directory", "file"], - "description": "Type of the node" - }, - "name": { - "type": "string", - "description": "Name of the new entry" - } - }, - "required": ["id", "type", "name"] - } - }, - "required": ["type", "parentId", "newEntry"] - }, - "FSDeleteOperation": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["delete"] - }, - "id": { - "type": "string", - "description": "ID of the entry to delete" - } - }, - "required": ["type", "id"] - }, - "FSMoveOperation": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["move"] - }, - "id": { - "type": "string", - "description": "ID of the entry to move" - }, - "parentId": { - "type": "string", - "description": "ID of the new parent directory", - "nullable": true - }, - "name": { - "type": "string", - "description": "New name for the entry", - "nullable": true - } - }, - "required": ["type", "id"] - }, - "FSOperationResult": { - "oneOf": [ - { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [0], - "description": "Success code" - }, - "clock": { - "type": "number", - "description": "Current clock value" - } - }, - "required": ["code", "clock"] - }, - { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [1], - "description": "Ignored code" - } - }, - "required": ["code"] - } - ], - "discriminator": { - "propertyName": "code" - } - }, - "FSSearchParams": { - "type": "object", - "properties": { - "text": { - "type": "string", - "description": "Text to search for" - }, - "glob": { - "type": "string", - "description": "Glob pattern to filter files", - "nullable": true - }, - "isRegex": { - "type": "boolean", - "description": "Whether to treat the search text as a regular expression", - "nullable": true - }, - "caseSensitivity": { - "type": "string", - "enum": ["smart", "enabled", "disabled"], - "description": "Case sensitivity setting for the search", - "nullable": true - } - }, - "required": ["text"] - }, - "SearchResult": { - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "ID of the file containing the match" - }, - "lines": { - "type": "object", - "properties": { - "text": { - "type": "string", - "description": "Text of the line containing the match" - } - }, - "required": ["text"] - }, - "lineNumber": { - "type": "integer", - "description": "Line number of the match" - }, - "absoluteOffset": { - "type": "integer", - "description": "Absolute offset of the match in the file" - }, - "submatches": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SearchSubMatch" - } - } - }, - "required": [ - "fileId", - "lines", - "lineNumber", - "absoluteOffset", - "submatches" - ] - }, - "SearchSubMatch": { - "type": "object", - "properties": { - "match": { - "type": "object", - "properties": { - "text": { - "type": "string", - "description": "Matched text" - } - }, - "required": ["text"] - }, - "start": { - "type": "integer", - "description": "Start position of the match" - }, - "end": { - "type": "integer", - "description": "End position of the match" - } - }, - "required": ["match", "start", "end"] - }, - "FSStreamingSearchParams": { - "type": "object", - "properties": { - "searchId": { - "type": "string", - "description": "ID for the search operation" - }, - "text": { - "type": "string", - "description": "Text to search for" - }, - "glob": { - "type": "string", - "description": "Glob pattern to filter files", - "nullable": true - }, - "isRegex": { - "type": "boolean", - "description": "Whether to treat the search text as a regular expression", - "nullable": true - }, - "caseSensitivity": { - "type": "string", - "enum": ["smart", "enabled", "disabled"], - "description": "Case sensitivity setting for the search", - "nullable": true - }, - "maxResults": { - "type": "integer", - "description": "Maximum number of results to return (default: 10,000)", - "nullable": true - } - }, - "required": ["searchId", "text"] - }, - "PathSearchParams": { - "type": "object", - "properties": { - "text": { - "type": "string", - "description": "Text to search for in file paths" - } - }, - "required": ["text"] - }, - "PathSearchResult": { - "type": "object", - "properties": { - "matches": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PathSearchMatch" - } - } - }, - "required": ["matches"] - }, - "PathSearchMatch": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path that matched the search" - }, - "submatches": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SearchSubMatch" - } - } - }, - "required": ["path", "submatches"] - }, - "InvalidIdError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [100], - "description": "INVALID_ID error code" - } - }, - "required": ["code"] - }, - "FSReadFileParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file to read" - } - }, - "required": ["path"] - }, - "FSReadFileResult": { - "type": "object", - "properties": { - "content": { - "type": "string", - "format": "binary", - "description": "File content as binary data" - } - }, - "required": ["content"] - }, - "FSReadDirParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the directory to read" - } - }, - "required": ["path"] - }, - "FSReadDirResult": { - "type": "object", - "properties": { - "entries": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the entry" - }, - "type": { - "type": "string", - "enum": ["directory", "file"], - "description": "Type of the entry" - }, - "isSymlink": { - "type": "boolean", - "description": "Whether the entry is a symlink" - } - }, - "required": ["name", "type", "isSymlink"] - } - } - }, - "required": ["entries"] - }, - "FSStatParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file or directory to stat" - } - }, - "required": ["path"] - }, - "FSStatResult": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["directory", "file"], - "description": "Type of the entry" - }, - "isSymlink": { - "type": "boolean", - "description": "Whether the entry is a symlink" - }, - "size": { - "type": "integer", - "description": "Size of the file in bytes" - }, - "mtime": { - "type": "integer", - "description": "Last modified time" - }, - "ctime": { - "type": "integer", - "description": "Creation time" - }, - "atime": { - "type": "integer", - "description": "Last accessed time" - } - }, - "required": ["type", "isSymlink", "size", "mtime", "ctime", "atime"] - }, - "FSCopyParams": { - "type": "object", - "properties": { - "from": { - "type": "string", - "description": "Path to copy from" - }, - "to": { - "type": "string", - "description": "Path to copy to" - }, - "recursive": { - "type": "boolean", - "description": "Whether to copy directories recursively", - "nullable": true - }, - "overwrite": { - "type": "boolean", - "description": "Whether to overwrite existing files", - "nullable": true - } - }, - "required": ["from", "to"] - }, - "FSRenameParams": { - "type": "object", - "properties": { - "from": { - "type": "string", - "description": "Path to rename from" - }, - "to": { - "type": "string", - "description": "Path to rename to" - }, - "overwrite": { - "type": "boolean", - "description": "Whether to overwrite existing files", - "nullable": true - } - }, - "required": ["from", "to"] - }, - "FSRemoveParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to remove" - }, - "recursive": { - "type": "boolean", - "description": "Whether to remove directories recursively", - "nullable": true - } - }, - "required": ["path"] - }, - "FSMkdirParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to create directory at" - }, - "recursive": { - "type": "boolean", - "description": "Whether to create parent directories if they don't exist", - "nullable": true - } - }, - "required": ["path"] - }, - "FSWatchParams": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to watch" - }, - "recursive": { - "type": "boolean", - "description": "Whether to watch directories recursively", - "nullable": true - }, - "excludes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns to exclude from watching", - "nullable": true - } - }, - "required": ["path"] - }, - "FSWatchResult": { - "type": "object", - "properties": { - "watchId": { - "type": "string", - "description": "ID of the watch" - } - }, - "required": ["watchId"] - }, - "FSUnwatchParams": { - "type": "object", - "properties": { - "watchId": { - "type": "string", - "description": "ID of the watch to stop" - } - }, - "required": ["watchId"] - } - } - } -} diff --git a/openapi-sandbox-git.json b/openapi-sandbox-git.json deleted file mode 100644 index 827a6e9..0000000 --- a/openapi-sandbox-git.json +++ /dev/null @@ -1,1369 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Git API", - "description": "API for managing git operations in CodeSandbox", - "version": "1.0.0" - }, - "paths": { - "/git/status": { - "post": { - "summary": "Get git status", - "description": "Retrieve current git status including changed files, branch information, and commits", - "operationId": "gitStatus", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitStatus" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving git status", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/remotes": { - "post": { - "summary": "Get git remotes", - "description": "Retrieve git remote information", - "operationId": "gitRemotes", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitRemotes" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving git remotes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/targetDiff": { - "post": { - "summary": "Get git target diff", - "description": "Retrieve diff between current branch and target branch", - "operationId": "gitTargetDiff", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "branch": { - "type": "string", - "description": "Branch to compare against" - } - }, - "required": ["branch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitTargetDiff" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving git target diff", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/pull": { - "post": { - "summary": "Pull from remote", - "description": "Pull changes from remote repository", - "operationId": "gitPull", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "branch": { - "type": "string", - "description": "Branch to pull from" - }, - "force": { - "type": "boolean", - "description": "Force pull even if there are conflicts" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pulling from remote", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/discard": { - "post": { - "summary": "Discard changes", - "description": "Discard local changes for specified paths", - "operationId": "gitDiscard", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths of files to discard changes" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error discarding changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/commit": { - "post": { - "summary": "Commit changes", - "description": "Commit changes to the repository", - "operationId": "gitCommit", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "paths": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths of files to commit" - }, - "message": { - "type": "string", - "description": "Commit message" - }, - "push": { - "type": "boolean", - "description": "Whether to push the commit immediately" - } - }, - "required": ["message"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "shellId": { - "type": "string", - "description": "ID of the shell process" - } - }, - "required": ["shellId"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error committing changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/push": { - "post": { - "summary": "Push changes", - "description": "Push local commits to remote repository", - "operationId": "gitPush", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pushing changes", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/pushToRemote": { - "post": { - "summary": "Push to remote", - "description": "Push to a specific remote repository", - "operationId": "gitPushToRemote", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "URL of the remote repository" - }, - "branch": { - "type": "string", - "description": "Branch to push to" - }, - "squashAllCommits": { - "type": "boolean", - "description": "Whether to squash all commits into one" - } - }, - "required": ["url", "branch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error pushing to remote", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/renameBranch": { - "post": { - "summary": "Rename branch", - "description": "Rename a git branch", - "operationId": "gitRenameBranch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "oldBranch": { - "type": "string", - "description": "Current branch name" - }, - "newBranch": { - "type": "string", - "description": "New branch name" - } - }, - "required": ["oldBranch", "newBranch"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error renaming branch", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/remoteContent": { - "post": { - "summary": "Get remote content", - "description": "Retrieve content from a remote repository", - "operationId": "gitRemoteContent", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GitRemoteParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the file" - } - }, - "required": ["content"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving remote content", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/diffStatus": { - "post": { - "summary": "Get diff status", - "description": "Retrieve diff status between two git references", - "operationId": "gitDiffStatus", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GitDiffStatusParams" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/GitDiffStatusResult" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving diff status", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/resetLocalWithRemote": { - "post": { - "summary": "Reset local with remote", - "description": "Reset local repository to match the remote state", - "operationId": "gitResetLocalWithRemote", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error resetting local with remote", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/checkoutInitialBranch": { - "post": { - "summary": "Checkout initial branch", - "description": "Checkout the initial branch of the repository", - "operationId": "gitCheckoutInitialBranch", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error checking out initial branch", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/git/transposeLines": { - "post": { - "summary": "Transpose lines", - "description": "Transpose line numbers from one git reference to another", - "operationId": "gitTransposeLines", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "sha": { - "type": "string", - "description": "Git commit SHA" - }, - "path": { - "type": "string", - "description": "Path to the file" - }, - "line": { - "type": "number", - "description": "Line number to transpose" - } - }, - "required": ["sha", "path", "line"] - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "line": { - "type": "number" - } - }, - "required": ["path", "line"] - }, - { - "type": "null" - } - ] - } - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error transposing lines", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "CommonError": { - "oneOf": [ - { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "GIT_OPERATION_IN_PROGRESS", - "GIT_REMOTE_FILE_NOT_FOUND" - ], - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Protocol error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data" - } - }, - "required": ["code", "message"] - } - ] - }, - "GitStatusShortFormat": { - "type": "string", - "enum": ["", "M", "A", "D", "R", "C", "U", "?"], - "description": "Git status short format codes" - }, - "GitItem": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path" - }, - "index": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "workingTree": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "isStaged": { - "type": "boolean", - "description": "Whether the file is staged" - }, - "isConflicted": { - "type": "boolean", - "description": "Whether the file has conflicts" - }, - "fileId": { - "type": "string", - "description": "Unique identifier for the file" - } - }, - "required": ["path", "index", "workingTree", "isStaged", "isConflicted"] - }, - "GitChangedFiles": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/GitItem" - }, - "description": "Map of file IDs to Git items" - }, - "GitBranchProperties": { - "type": "object", - "properties": { - "head": { - "type": ["string", "null"], - "description": "Current HEAD reference" - }, - "branch": { - "type": ["string", "null"], - "description": "Current branch name" - }, - "ahead": { - "type": "number", - "description": "Number of commits ahead of the remote" - }, - "behind": { - "type": "number", - "description": "Number of commits behind the remote" - }, - "safe": { - "type": "boolean", - "description": "Whether the branch is safe to operate on" - } - }, - "required": ["ahead", "behind", "safe"] - }, - "GitCommit": { - "type": "object", - "properties": { - "hash": { - "type": "string", - "description": "Commit hash" - }, - "date": { - "type": "string", - "description": "Commit date" - }, - "message": { - "type": "string", - "description": "Commit message" - }, - "author": { - "type": "string", - "description": "Commit author" - } - }, - "required": ["hash", "date", "message", "author"] - }, - "GitStatus": { - "type": "object", - "properties": { - "changedFiles": { - "$ref": "#/components/schemas/GitChangedFiles" - }, - "deletedFiles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitItem" - } - }, - "conflicts": { - "type": "boolean", - "description": "Whether there are remote conflicts" - }, - "localChanges": { - "type": "boolean", - "description": "Whether there are local changes" - }, - "remote": { - "$ref": "#/components/schemas/GitBranchProperties" - }, - "target": { - "$ref": "#/components/schemas/GitBranchProperties" - }, - "head": { - "type": "string", - "description": "Current HEAD reference" - }, - "commits": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitCommit" - } - }, - "branch": { - "type": ["string", "null"], - "description": "Current branch name" - }, - "isMerging": { - "type": "boolean", - "description": "Whether a merge is in progress" - } - }, - "required": [ - "changedFiles", - "deletedFiles", - "conflicts", - "localChanges", - "remote", - "target", - "commits", - "branch", - "isMerging" - ] - }, - "GitTargetDiff": { - "type": "object", - "properties": { - "ahead": { - "type": "number", - "description": "Number of commits ahead of the target" - }, - "behind": { - "type": "number", - "description": "Number of commits behind the target" - }, - "commits": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitCommit" - } - } - }, - "required": ["ahead", "behind", "commits"] - }, - "GitRemotes": { - "type": "object", - "properties": { - "origin": { - "type": "string", - "description": "Origin remote URL" - }, - "upstream": { - "type": "string", - "description": "Upstream remote URL" - } - }, - "required": ["origin", "upstream"] - }, - "GitRemoteParams": { - "type": "object", - "properties": { - "reference": { - "type": "string", - "description": "Branch or commit hash" - }, - "path": { - "type": "string", - "description": "Path to the file" - } - }, - "required": ["reference", "path"] - }, - "GitDiffStatusParams": { - "type": "object", - "properties": { - "base": { - "type": "string", - "description": "Base reference used for diffing" - }, - "head": { - "type": "string", - "description": "Head reference used for diffing" - } - }, - "required": ["base", "head"] - }, - "GitDiffStatusItem": { - "type": "object", - "properties": { - "status": { - "$ref": "#/components/schemas/GitStatusShortFormat" - }, - "path": { - "type": "string", - "description": "Path to the file" - }, - "oldPath": { - "type": "string", - "description": "Original path for renamed files" - }, - "hunks": { - "type": "array", - "items": { - "type": "object", - "properties": { - "original": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start", "end"] - }, - "modified": { - "type": "object", - "properties": { - "start": { - "type": "number" - }, - "end": { - "type": "number" - } - }, - "required": ["start", "end"] - } - }, - "required": ["original", "modified"] - } - } - }, - "required": ["status", "path", "hunks"] - }, - "GitDiffStatusResult": { - "type": "object", - "properties": { - "files": { - "type": "array", - "items": { - "$ref": "#/components/schemas/GitDiffStatusItem" - } - } - }, - "required": ["files"] - } - } - } -} diff --git a/openapi-sandbox-setup.json b/openapi-sandbox-setup.json deleted file mode 100644 index bc8b5c4..0000000 --- a/openapi-sandbox-setup.json +++ /dev/null @@ -1,570 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Setup API", - "description": "API for managing sandbox setup operations", - "version": "1.0.0" - }, - "paths": { - "/setup/get": { - "post": { - "summary": "Get setup progress", - "description": "Retrieve the current setup progress status", - "operationId": "setupGet", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving setup progress", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/skip": { - "post": { - "summary": "Skip setup step", - "description": "Skip a specific step in the setup process", - "operationId": "setupSkipStep", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "stepIndexToSkip": { - "type": "number", - "description": "Index of the step to skip" - } - }, - "required": ["stepIndexToSkip"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error skipping step", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/skipAll": { - "post": { - "summary": "Skip all setup steps", - "description": "Skip all remaining steps in the setup process", - "operationId": "setupSkipAll", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "null" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error skipping all steps", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/disable": { - "post": { - "summary": "Disable setup", - "description": "Disable the setup process", - "operationId": "setupDisable", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "null" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error disabling setup", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/enable": { - "post": { - "summary": "Enable setup", - "description": "Enable the setup process", - "operationId": "setupEnable", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "null" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error enabling setup", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/init": { - "post": { - "summary": "Initialize setup", - "description": "Initialize the setup process", - "operationId": "setupInit", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "null" - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error initializing setup", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - }, - "/setup/setStep": { - "post": { - "summary": "Set current setup step", - "description": "Set the current step in the setup process (used for restarting)", - "operationId": "setupSetStep", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "stepIndex": { - "type": "number", - "description": "Index of the step to set as current" - } - }, - "required": ["stepIndex"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SetupProgress" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error setting current step", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/ProtocolError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "ProtocolError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code", "message"] - }, - "SetupShellStatus": { - "type": "string", - "enum": ["SUCCEEDED", "FAILED", "SKIPPED"], - "description": "Status of a setup shell step" - }, - "Step": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the setup step" - }, - "command": { - "type": "string", - "description": "Command to execute for this step" - }, - "shellId": { - "type": "string", - "description": "ID of the shell executing the command", - "nullable": true - }, - "finishStatus": { - "$ref": "#/components/schemas/SetupShellStatus", - "nullable": true, - "description": "Status of the step after completion" - } - }, - "required": ["name", "command", "shellId", "finishStatus"] - }, - "SetupProgress": { - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": ["IDLE", "IN_PROGRESS", "FINISHED", "STOPPED"], - "description": "Current state of the setup process" - }, - "steps": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Step" - }, - "description": "List of setup steps" - }, - "currentStepIndex": { - "type": "number", - "description": "Index of the current step being executed" - } - }, - "required": ["state", "steps", "currentStepIndex"] - } - } - } -} diff --git a/openapi-sandbox-shell.json b/openapi-sandbox-shell.json deleted file mode 100644 index e362489..0000000 --- a/openapi-sandbox-shell.json +++ /dev/null @@ -1,916 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Shell API", - "description": "API for managing terminal and command shells in the sandbox", - "version": "1.0.0" - }, - "paths": { - "/shell/create": { - "post": { - "summary": "Create a new shell", - "description": "Creates a new terminal or command shell", - "operationId": "shellCreate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "Command to execute in the shell" - }, - "cwd": { - "type": "string", - "description": "Working directory for the shell" - }, - "size": { - "$ref": "#/components/schemas/ShellSize", - "description": "Terminal size dimensions" - }, - "type": { - "$ref": "#/components/schemas/ShellProcessType", - "description": "Type of shell to create" - }, - "isSystemShell": { - "type": "boolean", - "description": "Whether this shell is started by the editor itself to run a specific process" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/OpenShellDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error creating shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/in": { - "post": { - "summary": "Send input to shell", - "description": "Sends user input to an active shell", - "operationId": "shellIn", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the target shell" - }, - "input": { - "type": "string", - "description": "Input to send to the shell" - }, - "size": { - "$ref": "#/components/schemas/ShellSize", - "description": "Current terminal dimensions" - } - }, - "required": ["shellId", "input", "size"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error sending input to shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/list": { - "post": { - "summary": "List all shells", - "description": "Retrieves a list of all available shells", - "operationId": "shellList", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "shells": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ShellDTO" - } - } - }, - "required": ["shells"] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error listing shells", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/open": { - "post": { - "summary": "Open an existing shell", - "description": "Opens an existing shell and retrieves its buffer", - "operationId": "shellOpen", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to open" - }, - "size": { - "$ref": "#/components/schemas/ShellSize", - "description": "Terminal dimensions" - } - }, - "required": ["shellId", "size"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/OpenShellDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error opening shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/close": { - "post": { - "summary": "Close a shell", - "description": "Closes a shell without terminating the underlying process", - "operationId": "shellClose", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to close" - } - }, - "required": ["shellId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error closing shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/restart": { - "post": { - "summary": "Restart a shell", - "description": "Restarts an existing shell process", - "operationId": "shellRestart", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to restart" - } - }, - "required": ["shellId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error restarting shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/terminate": { - "post": { - "summary": "Terminate a shell", - "description": "Terminates a shell and its underlying process", - "operationId": "shellTerminate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to terminate" - } - }, - "required": ["shellId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/ShellDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error terminating shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/resize": { - "post": { - "summary": "Resize a shell", - "description": "Updates the dimensions of a shell", - "operationId": "shellResize", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to resize" - }, - "size": { - "$ref": "#/components/schemas/ShellSize", - "description": "New terminal dimensions" - } - }, - "required": ["shellId", "size"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error resizing shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/shell/rename": { - "post": { - "summary": "Rename a shell", - "description": "Updates the name of a shell", - "operationId": "shellRename", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId", - "description": "ID of the shell to rename" - }, - "name": { - "type": "string", - "description": "New name for the shell" - } - }, - "required": ["shellId", "name"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error renaming shell", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "ShellId": { - "type": "string", - "description": "Unique identifier for a shell" - }, - "ShellSize": { - "type": "object", - "properties": { - "cols": { - "type": "number", - "description": "Number of columns in the terminal" - }, - "rows": { - "type": "number", - "description": "Number of rows in the terminal" - } - }, - "required": ["cols", "rows"] - }, - "ShellProcessType": { - "type": "string", - "enum": ["TERMINAL", "COMMAND"], - "description": "Type of shell process" - }, - "ShellProcessStatus": { - "type": "string", - "enum": ["RUNNING", "FINISHED", "ERROR", "KILLED", "RESTARTING"], - "description": "Current status of the shell process" - }, - "BaseShellDTO": { - "type": "object", - "properties": { - "shellId": { - "$ref": "#/components/schemas/ShellId" - }, - "name": { - "type": "string", - "description": "Display name of the shell" - }, - "status": { - "$ref": "#/components/schemas/ShellProcessStatus" - }, - "exitCode": { - "type": "number", - "description": "Exit code of the process if it has finished", - "nullable": true - } - }, - "required": ["shellId", "name", "status"] - }, - "CommandShellDTO": { - "allOf": [ - { - "$ref": "#/components/schemas/BaseShellDTO" - }, - { - "type": "object", - "properties": { - "shellType": { - "type": "string", - "enum": ["COMMAND"], - "description": "Indicates this is a command shell" - }, - "startCommand": { - "type": "string", - "description": "The command that was executed to start this shell" - } - }, - "required": ["shellType", "startCommand"] - } - ] - }, - "TerminalShellDTO": { - "allOf": [ - { - "$ref": "#/components/schemas/BaseShellDTO" - }, - { - "type": "object", - "properties": { - "shellType": { - "type": "string", - "enum": ["TERMINAL"], - "description": "Indicates this is a terminal shell" - }, - "ownerUsername": { - "type": "string", - "description": "Username of the shell owner" - }, - "isSystemShell": { - "type": "boolean", - "description": "Whether this is a system shell" - } - }, - "required": ["shellType", "ownerUsername", "isSystemShell"] - } - ] - }, - "ShellDTO": { - "oneOf": [ - { - "$ref": "#/components/schemas/CommandShellDTO" - }, - { - "$ref": "#/components/schemas/TerminalShellDTO" - } - ], - "discriminator": { - "propertyName": "shellType", - "mapping": { - "COMMAND": "#/components/schemas/CommandShellDTO", - "TERMINAL": "#/components/schemas/TerminalShellDTO" - } - } - }, - "OpenCommandShellDTO": { - "allOf": [ - { - "$ref": "#/components/schemas/CommandShellDTO" - }, - { - "type": "object", - "properties": { - "buffer": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Content buffer of the shell" - } - }, - "required": ["buffer"] - } - ] - }, - "OpenTerminalShellDTO": { - "allOf": [ - { - "$ref": "#/components/schemas/TerminalShellDTO" - }, - { - "type": "object", - "properties": { - "buffer": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Content buffer of the shell" - } - }, - "required": ["buffer"] - } - ] - }, - "OpenShellDTO": { - "oneOf": [ - { - "$ref": "#/components/schemas/OpenCommandShellDTO" - }, - { - "$ref": "#/components/schemas/OpenTerminalShellDTO" - } - ], - "discriminator": { - "propertyName": "shellType", - "mapping": { - "COMMAND": "#/components/schemas/OpenCommandShellDTO", - "TERMINAL": "#/components/schemas/OpenTerminalShellDTO" - } - } - }, - "CommonError": { - "oneOf": [ - { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": ["SHELL_NOT_ACCESSIBLE"], - "description": "Error code indicating the shell is not accessible" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Protocol error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - } - ] - } - } - } -} diff --git a/openapi-sandbox-system.json b/openapi-sandbox-system.json deleted file mode 100644 index 501f2f5..0000000 --- a/openapi-sandbox-system.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox System API", - "description": "API for managing sandbox system operations", - "version": "1.0.0" - }, - "paths": { - "/system/update": { - "post": { - "summary": "Update system", - "description": "Update the sandbox system", - "operationId": "systemUpdate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": {} - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error updating system", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/SystemError" - } - } - } - ] - } - } - } - } - } - } - }, - "/system/hibernate": { - "post": { - "summary": "Hibernate system", - "description": "Put the sandbox system into hibernation mode", - "operationId": "systemHibernate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error hibernating system", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/SystemError" - } - } - } - ] - } - } - } - } - } - } - }, - "/system/metrics": { - "post": { - "summary": "Get system metrics", - "description": "Retrieve current system metrics including CPU, memory and storage usage", - "operationId": "systemMetrics", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/SystemMetricsStatus" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving system metrics", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/SystemError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "SystemError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code", "message"] - }, - "SystemMetricsStatus": { - "type": "object", - "properties": { - "cpu": { - "type": "object", - "properties": { - "cores": { - "type": "number", - "description": "Number of CPU cores" - }, - "used": { - "type": "number", - "description": "Used CPU resources" - }, - "configured": { - "type": "number", - "description": "Configured CPU resources" - } - }, - "required": ["cores", "used", "configured"] - }, - "memory": { - "type": "object", - "properties": { - "used": { - "type": "number", - "description": "Used memory in bytes" - }, - "total": { - "type": "number", - "description": "Total available memory in bytes" - }, - "configured": { - "type": "number", - "description": "Configured memory limit in bytes" - } - }, - "required": ["used", "total", "configured"] - }, - "storage": { - "type": "object", - "properties": { - "used": { - "type": "number", - "description": "Used storage in bytes" - }, - "total": { - "type": "number", - "description": "Total available storage in bytes" - }, - "configured": { - "type": "number", - "description": "Configured storage limit in bytes" - } - }, - "required": ["used", "total", "configured"] - } - }, - "required": ["cpu", "memory", "storage"] - }, - "InitStatus": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Status message" - }, - "isError": { - "type": "boolean", - "description": "Whether the status represents an error", - "nullable": true - }, - "progress": { - "type": "number", - "description": "Current progress (0-100)", - "minimum": 0, - "maximum": 100 - }, - "nextProgress": { - "type": "number", - "description": "Next progress target (0-100)", - "minimum": 0, - "maximum": 100 - }, - "stdout": { - "type": "string", - "description": "Standard output from the initialization process", - "nullable": true - } - }, - "required": ["message", "progress", "nextProgress"] - } - } - } -} diff --git a/openapi-sandbox-task.json b/openapi-sandbox-task.json deleted file mode 100644 index 6b5e320..0000000 --- a/openapi-sandbox-task.json +++ /dev/null @@ -1,947 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Sandbox Task API", - "description": "API for managing tasks in sandbox", - "version": "1.0.0" - }, - "paths": { - "/task/list": { - "post": { - "summary": "List tasks", - "description": "Retrieve a list of all configured tasks", - "operationId": "taskList", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskListDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error retrieving task list", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/CommonError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/run": { - "post": { - "summary": "Run task", - "description": "Start execution of a task by ID", - "operationId": "taskRun", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "taskId": { - "type": "string", - "description": "ID of the task to run" - } - }, - "required": ["taskId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error running task", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/runCommand": { - "post": { - "summary": "Run command", - "description": "Run a shell command directly, optionally saving it as a task", - "operationId": "taskRunCommand", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "Command to run" - }, - "name": { - "type": "string", - "description": "Optional name for the task", - "nullable": true - }, - "saveToConfig": { - "type": "boolean", - "description": "Whether to save this command as a task in the config", - "nullable": true - } - }, - "required": ["command"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error running command", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/stop": { - "post": { - "summary": "Stop task", - "description": "Stop execution of a running task", - "operationId": "taskStop", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "taskId": { - "type": "string", - "description": "ID of the task to stop" - } - }, - "required": ["taskId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "oneOf": [ - { - "$ref": "#/components/schemas/TaskDTO" - }, - { - "type": "null", - "description": "Null when stopping an unconfigured task" - } - ] - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error stopping task", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/create": { - "post": { - "summary": "Create task", - "description": "Create a new task configuration", - "operationId": "taskCreate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "taskFields": { - "$ref": "#/components/schemas/TaskDefinitionDTO" - }, - "startTask": { - "type": "boolean", - "description": "Whether to start the task immediately after creation", - "nullable": true - } - }, - "required": ["taskFields"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskListDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error creating task", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/update": { - "post": { - "summary": "Update task", - "description": "Update an existing task configuration", - "operationId": "taskUpdate", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "taskId": { - "type": "string", - "description": "ID of the task to update" - }, - "taskFields": { - "type": "object", - "description": "Fields to update in the task", - "properties": { - "name": { - "type": "string", - "description": "Name of the task", - "nullable": true - }, - "command": { - "type": "string", - "description": "Command to run", - "nullable": true - }, - "runAtStart": { - "type": "boolean", - "description": "Whether to run the task at sandbox start", - "nullable": true - }, - "preview": { - "type": "object", - "properties": { - "port": { - "type": "number", - "description": "Port to use for previewing the task", - "nullable": true - }, - "pr-link": { - "type": "string", - "enum": ["direct", "redirect", "devtool"], - "description": "Type of PR link to use", - "nullable": true - } - }, - "nullable": true - } - } - } - }, - "required": ["taskId", "taskFields"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error updating task", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/saveToConfig": { - "post": { - "summary": "Save task to config", - "description": "Save a runtime task to the configuration file", - "operationId": "taskSaveToConfig", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "taskId": { - "type": "string", - "description": "ID of the task to save to config" - } - }, - "required": ["taskId"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/TaskDTO" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error saving task to config", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/generateConfig": { - "post": { - "summary": "Generate task config", - "description": "Generate a configuration file from current tasks", - "operationId": "taskGenerateConfig", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error generating config", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - }, - "/task/createSetupTasks": { - "post": { - "summary": "Create setup tasks", - "description": "Create tasks that run during sandbox setup", - "operationId": "taskCreateSetupTasks", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "tasks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TaskDefinitionDTO" - }, - "description": "Setup tasks to create" - } - }, - "required": ["tasks"] - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessResponse" - }, - { - "type": "object", - "properties": { - "result": { - "type": "null" - } - } - } - ] - } - } - } - }, - "400": { - "description": "Error creating setup tasks", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ErrorResponse" - }, - { - "type": "object", - "properties": { - "error": { - "$ref": "#/components/schemas/TaskError" - } - } - } - ] - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "SuccessResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [0], - "description": "Status code for successful operations" - }, - "result": { - "type": "object", - "description": "Result payload for the operation" - } - }, - "required": ["status", "result"] - }, - "ErrorResponse": { - "type": "object", - "properties": { - "status": { - "type": "number", - "enum": [1], - "description": "Status code for error operations" - }, - "error": { - "type": "object", - "description": "Error details" - } - }, - "required": ["status", "error"] - }, - "CommonError": { - "type": "object", - "properties": { - "code": { - "type": "number", - "description": "Error code" - }, - "message": { - "type": "string", - "description": "Error message" - }, - "data": { - "type": "object", - "description": "Additional error data", - "nullable": true - } - }, - "required": ["code"] - }, - "TaskError": { - "oneOf": [ - { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [600], - "description": "CONFIG_FILE_ALREADY_EXISTS error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - }, - { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [601], - "description": "TASK_NOT_FOUND error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - }, - { - "type": "object", - "properties": { - "code": { - "type": "number", - "enum": [602], - "description": "COMMAND_ALREADY_CONFIGURED error code" - }, - "message": { - "type": "string", - "description": "Error message" - } - }, - "required": ["code", "message"] - }, - { - "$ref": "#/components/schemas/CommonError" - } - ], - "discriminator": { - "propertyName": "code" - } - }, - "TaskDefinitionDTO": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the task" - }, - "command": { - "type": "string", - "description": "Command to run for the task" - }, - "runAtStart": { - "type": "boolean", - "description": "Whether the task should run when the sandbox starts", - "nullable": true - }, - "preview": { - "type": "object", - "properties": { - "port": { - "type": "number", - "description": "Port to preview from this task", - "nullable": true - }, - "pr-link": { - "type": "string", - "enum": ["direct", "redirect", "devtool"], - "description": "Type of PR link to use", - "nullable": true - } - }, - "nullable": true - } - }, - "required": ["name", "command"] - }, - "CommandShellDTO": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the shell command" - }, - "command": { - "type": "string", - "description": "Command being executed" - }, - "status": { - "type": "string", - "enum": ["initializing", "running", "stopped", "error"], - "description": "Current status of the shell command" - }, - "output": { - "type": "string", - "description": "Current output of the command" - } - }, - "required": ["id", "command", "status", "output"] - }, - "Port": { - "type": "object", - "properties": { - "port": { - "type": "number", - "description": "Port number" - }, - "hostname": { - "type": "string", - "description": "Hostname the port is bound to" - }, - "status": { - "type": "string", - "enum": ["open", "closed"], - "description": "Current status of the port" - }, - "taskId": { - "type": "string", - "description": "ID of the task that opened this port", - "nullable": true - } - }, - "required": ["port", "hostname", "status"] - }, - "TaskDTO": { - "allOf": [ - { - "$ref": "#/components/schemas/TaskDefinitionDTO" - }, - { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Unique ID of the task" - }, - "unconfigured": { - "type": "boolean", - "description": "Whether this task is unconfigured (not saved in config)", - "nullable": true - }, - "shell": { - "type": "object", - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/CommandShellDTO" - } - ] - }, - "ports": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Port" - }, - "description": "Ports opened by this task" - } - }, - "required": ["id", "shell", "ports"] - } - ] - }, - "TaskListDTO": { - "type": "object", - "properties": { - "tasks": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/TaskDTO" - }, - "description": "Map of task IDs to task objects" - }, - "setupTasks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TaskDefinitionDTO" - }, - "description": "Tasks that run during sandbox setup" - }, - "validationErrors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Validation errors in the task configuration" - } - }, - "required": ["tasks", "setupTasks", "validationErrors"] - } - } - } -} diff --git a/openapi.json b/openapi.json index 5890be8..07c5176 100644 --- a/openapi.json +++ b/openapi.json @@ -219,6 +219,10 @@ "id": { "type": "string" }, "is_frozen": { "type": "boolean" }, "privacy": { "type": "integer" }, + "settings": { + "properties": { "use_pint": { "type": "boolean" } }, + "type": "object" + }, "tags": { "items": { "type": "string" }, "type": "array" }, "title": { "nullable": true, "type": "string" }, "updated_at": { "format": "date-time", "type": "string" } @@ -229,7 +233,8 @@ "is_frozen", "created_at", "updated_at", - "tags" + "tags", + "settings" ], "title": "Sandbox", "type": "object" @@ -932,6 +937,7 @@ "pitcher_url": { "type": "string" }, "pitcher_version": { "type": "string" }, "reconnect_token": { "type": "string" }, + "use_pint": { "type": "boolean" }, "user_workspace_path": { "type": "string" }, "workspace_path": { "type": "string" } }, @@ -945,6 +951,7 @@ "pitcher_url", "pitcher_version", "reconnect_token", + "use_pint", "user_workspace_path", "workspace_path" ], @@ -1542,6 +1549,7 @@ "pitcher_url": { "type": "string" }, "pitcher_version": { "type": "string" }, "reconnect_token": { "type": "string" }, + "use_pint": { "type": "boolean" }, "user_workspace_path": { "type": "string" }, "workspace_path": { "type": "string" } }, @@ -1555,6 +1563,7 @@ "pitcher_url", "pitcher_version", "reconnect_token", + "use_pint", "user_workspace_path", "workspace_path" ], @@ -2671,6 +2680,6 @@ } }, "security": [], - "servers": [{ "url": "https://api.codesandbox.stream", "variables": {} }], + "servers": [{ "url": "https://api.codesandbox.io", "variables": {} }], "tags": [] } diff --git a/package-lock.json b/package-lock.json index e87d29d..05cc710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.3.0", "license": "MIT", "dependencies": { - "@hey-api/client-fetch": "^0.7.3", + "@hey-api/client-fetch": "^0.13.1", "@inkjs/ui": "^2.0.0", "@opentelemetry/api": "^1.9.0", "@xterm/addon-serialize": "^0.13.0", @@ -31,7 +31,7 @@ "csb": "dist/bin/codesandbox.mjs" }, "devDependencies": { - "@hey-api/openapi-ts": "^0.63.2", + "@hey-api/openapi-ts": "^0.84.4", "@msgpack/msgpack": "^2.7.1", "@parcel/watcher": "^2.5.1", "@tanstack/react-query": "^5.76.1", @@ -537,24 +537,43 @@ } }, "node_modules/@hey-api/client-fetch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.7.3.tgz", - "integrity": "sha512-nysIXMag9nr5ENy+47G0AYsegdT7vT6S4KLfY7NVgM6HsyZ0DrhCZvz5nP70M16x9i860SrnXhjpcuHx0g5sDQ==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.13.1.tgz", + "integrity": "sha512-29jBRYNdxVGlx5oewFgOrkulZckpIpBIRHth3uHFn1PrL2ucMy52FvWOY3U3dVx2go1Z3kUmMi6lr07iOpUqqA==", + "deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.", "license": "MIT", "funding": { "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "@hey-api/openapi-ts": "< 2" + } + }, + "node_modules/@hey-api/codegen-core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.2.0.tgz", + "integrity": "sha512-c7VjBy/8ed0EVLNgaeS9Xxams1Tuv/WK/b4xXH3Qr4wjzYeJUtxOcoP8YdwNLavqKP8pGiuctjX2Z1Pwc4jMgQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=22.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3" } }, "node_modules/@hey-api/json-schema-ref-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.1.tgz", - "integrity": "sha512-dBt0A7op9kf4BcK++x6HBYDmvCvnJUZEGe5QytghPFHnMXPyKwDKomwL/v5e9ERk6E0e1GzL/e/y6pWUso9zrQ==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.1.0.tgz", + "integrity": "sha512-+5eg9pgAAM9oSqJQuUtfTKbLz8yieFKN91myyXiLnprqFj8ROfxUKJLr9DKq/hGKyybKT1WxFSetDqCFm80pCA==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" + "js-yaml": "^4.1.0", + "lodash": "^4.17.21" }, "engines": { "node": ">= 16" @@ -564,28 +583,44 @@ } }, "node_modules/@hey-api/openapi-ts": { - "version": "0.63.2", - "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.63.2.tgz", - "integrity": "sha512-HC5fR3+07P1AvDYrcZv0kbnhWogvMFot848PfpS3Gbncm47mDdO/uhNGIr2RF9CaRIvJNSJvAf1jL3XAQZ18RA==", - "dev": true, + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.84.4.tgz", + "integrity": "sha512-yKR/KFQVCoAbvkpRM13uYxMUyZl3whwEY8Nj4X9Yra18dWpfzmdTyTxP8KtYzdfNMC9xIw4TAgK5+1FJctENBw==", "license": "MIT", "dependencies": { - "@hey-api/json-schema-ref-parser": "1.0.1", + "@hey-api/codegen-core": "^0.2.0", + "@hey-api/json-schema-ref-parser": "1.1.0", + "ansi-colors": "4.1.3", "c12": "2.0.1", + "color-support": "1.1.3", "commander": "13.0.0", - "handlebars": "4.7.8" + "handlebars": "4.7.8", + "open": "10.1.2", + "semver": "7.7.2" }, "bin": { "openapi-ts": "bin/index.cjs" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=22.11.0" + "node": "^18.18.0 || ^20.9.0 || >=22.10.0" }, "funding": { "url": "https://github.com/sponsors/hey-api" }, "peerDependencies": { - "typescript": "^5.5.3" + "typescript": ">=5.5.3" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@inkjs/ui": { @@ -672,7 +707,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true, "license": "MIT" }, "node_modules/@msgpack/msgpack": { @@ -2083,7 +2117,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/mysql": { @@ -2343,7 +2376,6 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2362,6 +2394,15 @@ "acorn": "^8" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", @@ -2411,7 +2452,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/asn1.js": { @@ -2709,11 +2749,25 @@ "node": ">=6.14.2" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/c12": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", - "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.1", @@ -2857,7 +2911,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2873,7 +2926,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -2897,7 +2949,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "dev": true, "license": "MIT", "dependencies": { "consola": "^3.2.3" @@ -3140,11 +3191,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3154,14 +3213,12 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -3324,6 +3381,34 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3341,11 +3426,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true, "license": "MIT" }, "node_modules/des.js": { @@ -3363,7 +3459,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "dev": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -3401,7 +3496,6 @@ "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3750,7 +3844,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -3763,7 +3856,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -3858,7 +3950,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.5.tgz", "integrity": "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==", - "dev": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -3877,7 +3968,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/gl-matrix": { @@ -3926,7 +4016,6 @@ "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.5", @@ -4323,6 +4412,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4386,6 +4490,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -4460,6 +4582,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4516,7 +4653,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -4532,7 +4668,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4815,7 +4950,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4835,7 +4969,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -4849,7 +4982,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -4862,7 +4994,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -4875,7 +5006,6 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.14.0", @@ -4888,7 +5018,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/module-details-from-path": { @@ -4928,7 +5057,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/nock": { @@ -4965,7 +5093,6 @@ "version": "1.6.6", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "dev": true, "license": "MIT" }, "node_modules/node-gyp-build": { @@ -4996,7 +5123,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.5.4.tgz", "integrity": "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==", - "dev": true, "license": "MIT", "dependencies": { "citty": "^0.1.6", @@ -5017,14 +5143,12 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/ohash": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.6.tgz", "integrity": "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==", - "dev": true, "license": "MIT" }, "node_modules/onetime": { @@ -5042,6 +5166,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optimist": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", @@ -5224,7 +5366,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -5258,7 +5399,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, "license": "MIT" }, "node_modules/pg-int8": { @@ -5339,7 +5479,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -5351,7 +5490,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/png-js": { @@ -5529,7 +5667,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "dev": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -5593,7 +5730,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -5750,6 +5886,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5940,7 +6088,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -6238,7 +6385,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -6256,7 +6402,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -6278,7 +6423,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { @@ -6383,7 +6527,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6397,14 +6540,12 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, "license": "MIT" }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, "license": "BSD-2-Clause", "optional": true, "bin": { @@ -6780,7 +6921,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { @@ -6972,7 +7112,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC" }, "node_modules/yargs": { diff --git a/package.json b/package.json index 4f9ea34..617338b 100644 --- a/package.json +++ b/package.json @@ -42,16 +42,9 @@ "build:esbuild": "node esbuild.cjs", "build:cjs:types": "tsc -p ./tsconfig.build-cjs.json --emitDeclarationOnly", "build:esm:types": "tsc -p ./tsconfig.build-esm.json --emitDeclarationOnly", - "build-openapi": "rimraf src/api-clients && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/api-clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", + "build-openapi": "rimraf src/api-clients && curl -o openapi.json https://api.codesandbox.io/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/api-clients/client -c @hey-api/client-fetch && npm run build-openapi-pint", "build-openapi:staging": "rimraf src/api-clients && curl -o openapi.json https://api.codesandbox.stream/meta/openapi && npx prettier --write ./openapi.json && node_modules/.bin/openapi-ts -i ./openapi.json -o src/api-clients/client -c @hey-api/client-fetch && npm run build-openapi-rest", - "build-openapi-rest": "npm run build-openapi-rest-fs && npm run build-openapi-rest-task && npm run build-openapi-rest-container && npm run build-openapi-rest-git && npm run build-openapi-rest-setup && npm run build-openapi-rest-shell && npm run build-openapi-rest-system", - "build-openapi-rest-container": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-container.json -o src/api-clients/client-rest-container -c @hey-api/client-fetch", - "build-openapi-rest-fs": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-fs.json -o src/api-clients/client-rest-fs -c @hey-api/client-fetch", - "build-openapi-rest-git": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-git.json -o src/api-clients/client-rest-git -c @hey-api/client-fetch", - "build-openapi-rest-setup": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-setup.json -o src/api-clients/client-rest-setup -c @hey-api/client-fetch", - "build-openapi-rest-shell": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-shell.json -o src/api-clients/client-rest-shell -c @hey-api/client-fetch", - "build-openapi-rest-system": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-system.json -o src/api-clients/client-rest-system -c @hey-api/client-fetch", - "build-openapi-rest-task": "node_modules/.bin/openapi-ts -i ./openapi-sandbox-task.json -o src/api-clients/client-rest-task -c @hey-api/client-fetch", + "build-openapi-pint": "node_modules/.bin/openapi-ts -i ./pint-openapi-bundled.json -o src/api-clients/pint -c @hey-api/client-fetch", "clean": "rimraf ./dist", "test": "vitest", "typecheck": "tsc --noEmit", @@ -70,7 +63,7 @@ "README.md" ], "devDependencies": { - "@hey-api/openapi-ts": "^0.63.2", + "@hey-api/openapi-ts": "^0.84.4", "@msgpack/msgpack": "^2.7.1", "@parcel/watcher": "^2.5.1", "@tanstack/react-query": "^5.76.1", @@ -98,7 +91,7 @@ "why-is-node-running": "^2.3.0" }, "dependencies": { - "@hey-api/client-fetch": "^0.7.3", + "@hey-api/client-fetch": "^0.13.1", "@inkjs/ui": "^2.0.0", "@opentelemetry/api": "^1.9.0", "@xterm/addon-serialize": "^0.13.0", diff --git a/pint-openapi-bundled.json b/pint-openapi-bundled.json index 0f7faaf..6d8a5ce 100644 --- a/pint-openapi-bundled.json +++ b/pint-openapi-bundled.json @@ -37,9 +37,7 @@ "/api/v1/files/{path}": { "post": { "summary": "Create a file", - "tags": [ - "files" - ], + "tags": ["files"], "description": "Creates a new file at the specified path with optional content.", "operationId": "createFile", "security": [ @@ -124,9 +122,7 @@ }, "get": { "summary": "Read file content", - "tags": [ - "files" - ], + "tags": ["files"], "description": "Reads the content of a file at the specified path.", "operationId": "readFile", "security": [ @@ -201,9 +197,7 @@ }, "patch": { "summary": "Perform file actions", - "tags": [ - "files" - ], + "tags": ["files"], "description": "Performs actions on files (e.g., move operations).", "operationId": "performFileAction", "security": [ @@ -289,9 +283,7 @@ }, "delete": { "summary": "Delete a file", - "tags": [ - "files" - ], + "tags": ["files"], "description": "Deletes a file at the specified path.", "operationId": "deleteFile", "security": [ @@ -378,9 +370,7 @@ "/api/v1/directories/{path}": { "post": { "summary": "Create a directory", - "tags": [ - "directories" - ], + "tags": ["directories"], "description": "Creates a new directory at the specified path.", "operationId": "createDirectory", "security": [ @@ -455,9 +445,7 @@ }, "get": { "summary": "List directory contents", - "tags": [ - "directories" - ], + "tags": ["directories"], "description": "Lists the contents of a directory at the specified path.", "operationId": "listDirectory", "security": [ @@ -532,9 +520,7 @@ }, "delete": { "summary": "Delete a directory", - "tags": [ - "directories" - ], + "tags": ["directories"], "description": "Deletes a directory at the specified path.", "operationId": "deleteDirectory", "security": [ @@ -621,9 +607,7 @@ "/api/v1/execs": { "post": { "summary": "Create a new exec", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Creates a new exec with specified command and arguments.", "operationId": "createExec", "security": [ @@ -697,9 +681,7 @@ }, "get": { "summary": "List all execs", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Returns a list of all active execs.", "operationId": "listExecs", "security": [ @@ -750,9 +732,7 @@ "/api/v1/execs/{id}": { "get": { "summary": "Get exec by ID", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Retrieves a specific exec by its ID.", "operationId": "getExec", "security": [ @@ -832,9 +812,7 @@ }, "put": { "summary": "Update exec", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Updates exec status (e.g., start a stopped exec).", "operationId": "updateExec", "security": [ @@ -939,9 +917,7 @@ }, "delete": { "summary": "Delete Exec", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Deletes a exec and execs its process.", "operationId": "deleteExec", "security": [ @@ -1017,9 +993,7 @@ "/api/v1/execs/{id}/io": { "get": { "summary": "Get Exec output", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Retrieves the plain text output from a exec's buffer.", "operationId": "getExecOutput", "security": [ @@ -1101,9 +1075,7 @@ }, "post": { "summary": "exec exec stdin", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "exec exec command (e.g., npm install).", "operationId": "execExecStdin", "security": [ @@ -1200,9 +1172,7 @@ "/ws/v1/execs/{id}": { "get": { "summary": "Connect to exec via WebSocket", - "tags": [ - "execs" - ], + "tags": ["execs"], "description": "Establishes a WebSocket connection for real-time exec interaction.\n\nAuthentication can be provided via:\n- Authorization header: `Authorization: Bearer `\n- Query parameter: `?token=`\n\nPermissions:\n- Admin users: Can send input and receive output\n- Readonly users: Can only receive output\n", "operationId": "connectToExecWebSocket", "security": [ @@ -1284,9 +1254,7 @@ "/api/v1/tasks": { "get": { "summary": "List all tasks", - "tags": [ - "tasks" - ], + "tags": ["tasks"], "description": "Lists all configured tasks from .codesandbox/tasks.json with their current status.", "operationId": "listTasks", "security": [ @@ -1331,9 +1299,7 @@ "/api/v1/tasks/{id}": { "get": { "summary": "Get task by ID", - "tags": [ - "tasks" - ], + "tags": ["tasks"], "description": "Retrieves a specific task by its ID with current status and configuration.", "operationId": "getTask", "security": [ @@ -1409,9 +1375,7 @@ "/api/v1/tasks/{id}/actions": { "patch": { "summary": "Execute task action", - "tags": [ - "tasks" - ], + "tags": ["tasks"], "description": "Executes an action on a specific task (start, stop, or restart).", "operationId": "executeTaskAction", "security": [ @@ -1516,9 +1480,7 @@ "/api/v1/setup-tasks": { "get": { "summary": "List setup tasks", - "tags": [ - "tasks" - ], + "tags": ["tasks"], "description": "Lists all setup tasks with their execution status. Setup tasks are auto-executed during server start.", "operationId": "listSetupTasks", "security": [ @@ -1563,9 +1525,7 @@ "/api/v1/ports": { "get": { "summary": "List open ports", - "tags": [ - "ports" - ], + "tags": ["ports"], "description": "Lists all open TCP ports on the system, excluding ignored ports configured in the server.", "operationId": "listPorts", "security": [ @@ -1641,9 +1601,7 @@ "/api/v1/ports/stream": { "get": { "summary": "List open ports using Server-Sent Events (SSE)", - "tags": [ - "ports" - ], + "tags": ["ports"], "description": "Lists all open TCP ports on the system AND LISTEN TO THE CHANGES, excluding ignored ports configured in the server.", "operationId": "ListPortsSSE", "security": [ @@ -1655,32 +1613,6 @@ "200": { "description": "Open ports retrieved successfully", "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PortsListResponse" - }, - "examples": { - "default": { - "summary": "Example ports response", - "value": { - "ports": [ - { - "port": 8080, - "address": "0.0.0.0" - }, - { - "port": 3000, - "address": "127.0.0.1" - }, - { - "port": 5432, - "address": "::1" - } - ] - } - } - } - }, "text/event-stream": { "schema": { "type": "string", @@ -1747,10 +1679,7 @@ "type": "string" } }, - "required": [ - "code", - "message" - ] + "required": ["code", "message"] }, "FileReadResponse": { "type": "object", @@ -1764,10 +1693,7 @@ "description": "File content" } }, - "required": [ - "path", - "content" - ] + "required": ["path", "content"] }, "FileCreateRequest": { "type": "object", @@ -1790,19 +1716,14 @@ "description": "File or directory path" } }, - "required": [ - "message", - "path" - ] + "required": ["message", "path"] }, "FileActionRequest": { "type": "object", "properties": { "action": { "type": "string", - "enum": [ - "move" - ], + "enum": ["move"], "description": "Type of action to perform on the file" }, "destination": { @@ -1810,10 +1731,7 @@ "description": "Destination path for move operation" } }, - "required": [ - "action", - "destination" - ] + "required": ["action", "destination"] }, "FileActionResponse": { "type": "object", @@ -1831,11 +1749,7 @@ "description": "Destination path" } }, - "required": [ - "message", - "from", - "to" - ] + "required": ["message", "from", "to"] }, "FileInfo": { "type": "object", @@ -1862,13 +1776,7 @@ "description": "Last modification time" } }, - "required": [ - "name", - "path", - "isDir", - "size", - "modTime" - ] + "required": ["name", "path", "isDir", "size", "modTime"] }, "DirectoryListResponse": { "type": "object", @@ -1885,10 +1793,7 @@ "description": "List of files and directories" } }, - "required": [ - "path", - "files" - ] + "required": ["path", "files"] }, "ExecItem": { "type": "object", @@ -1917,13 +1822,7 @@ "description": "Process ID of the exec" } }, - "required": [ - "id", - "command", - "args", - "status", - "pid" - ] + "required": ["id", "command", "args", "status", "pid"] }, "ExecListResponse": { "type": "object", @@ -1936,9 +1835,7 @@ "description": "List of execs" } }, - "required": [ - "execs" - ] + "required": ["execs"] }, "CreateExecRequest": { "type": "object", @@ -1963,25 +1860,18 @@ "description": "Whether to start interactive shell session or not (defaults to false)" } }, - "required": [ - "command", - "args" - ] + "required": ["command", "args"] }, "UpdateExecRequest": { "type": "object", "properties": { "status": { "type": "string", - "enum": [ - "running" - ], + "enum": ["running"], "description": "Status to set for the exec (currently only 'running' is supported)" } }, - "required": [ - "status" - ] + "required": ["status"] }, "ExecDeleteResponse": { "type": "object", @@ -1991,9 +1881,7 @@ "description": "Deletion confirmation message" } }, - "required": [ - "message" - ] + "required": ["message"] }, "ExecStdout": { "type": "object", @@ -2001,10 +1889,7 @@ "type": { "type": "string", "description": "Type of the exec output", - "enum": [ - "stdout", - "stderr" - ] + "enum": ["stdout", "stderr"] }, "output": { "type": "string", @@ -2021,11 +1906,7 @@ "description": "Timestamp of when the output was generated" } }, - "required": [ - "type", - "output", - "sequence" - ] + "required": ["type", "output", "sequence"] }, "ExecStdin": { "type": "object", @@ -2033,31 +1914,18 @@ "type": { "type": "string", "description": "Type of the exec input", - "enum": [ - "stdin", - "resize" - ] + "enum": ["stdin", "resize"] }, "input": { "type": "string", "description": "Data associated with the exec input" } }, - "required": [ - "type", - "input" - ] + "required": ["type", "input"] }, "TaskStatus": { "type": "string", - "enum": [ - "RUNNING", - "FINISHED", - "ERROR", - "KILLED", - "RESTARTING", - "IDLE" - ] + "enum": ["RUNNING", "FINISHED", "ERROR", "KILLED", "RESTARTING", "IDLE"] }, "TaskBase": { "type": "object", @@ -2080,12 +1948,7 @@ "example": "2017-08-21T17:32:28Z" } }, - "required": [ - "status", - "execId", - "startTime", - "endTime" - ], + "required": ["status", "execId", "startTime", "endTime"], "description": "Base schema for a task item, containing common fields shared across different task types.", "title": "TaskBase" }, @@ -2108,12 +1971,7 @@ "type": "boolean" } }, - "required": [ - "files", - "clone", - "resume", - "branch" - ] + "required": ["files", "clone", "resume", "branch"] }, "TaskPreview": { "type": "object", @@ -2122,9 +1980,7 @@ "type": "integer" } }, - "required": [ - "port" - ] + "required": ["port"] }, "TaskConfig": { "type": "object", @@ -2145,13 +2001,7 @@ "$ref": "#/components/schemas/TaskPreview" } }, - "required": [ - "name", - "command", - "runAtStart", - "restartOn", - "preview" - ] + "required": ["name", "command", "runAtStart", "restartOn", "preview"] }, "TaskItem": { "allOf": [ @@ -2169,10 +2019,7 @@ "$ref": "#/components/schemas/TaskConfig" } }, - "required": [ - "id", - "config" - ] + "required": ["id", "config"] } ] }, @@ -2186,9 +2033,7 @@ } } }, - "required": [ - "tasks" - ] + "required": ["tasks"] }, "GetTaskResponse": { "type": "object", @@ -2197,17 +2042,11 @@ "$ref": "#/components/schemas/TaskItem" } }, - "required": [ - "task" - ] + "required": ["task"] }, "TaskActionType": { "type": "string", - "enum": [ - "start", - "stop", - "restart" - ], + "enum": ["start", "stop", "restart"], "description": "Type of action to execute on a task" }, "TaskActionResponse": { @@ -2235,12 +2074,7 @@ "description": "Action result message" } }, - "required": [ - "id", - "name", - "message", - "command" - ] + "required": ["id", "name", "message", "command"] } ], "description": "Schema for task action responses, containing details about the task and the action performed.", @@ -2263,11 +2097,7 @@ "description": "Setup task command" } }, - "required": [ - "name", - "command", - "execId" - ] + "required": ["name", "command", "execId"] } ] }, @@ -2281,9 +2111,7 @@ } } }, - "required": [ - "setupTasks" - ] + "required": ["setupTasks"] }, "PortInfo": { "type": "object", @@ -2302,10 +2130,7 @@ "example": "0.0.0.0" } }, - "required": [ - "port", - "address" - ] + "required": ["port", "address"] }, "PortsListResponse": { "type": "object", @@ -2318,13 +2143,11 @@ "description": "List of open ports" } }, - "required": [ - "ports" - ] + "required": ["ports"] }, "Task": { "$ref": "#/components/schemas/TaskItem" } } } -} \ No newline at end of file +} diff --git a/src/API.ts b/src/API.ts index 42898a9..34360b6 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1,6 +1,4 @@ -import type { Client, Config } from "@hey-api/client-fetch"; import { handleResponse, retryWithDelay, createApiClient } from "./utils/api"; -import { getInferredBaseUrl } from "./utils/constants"; import { metaInfo, workspaceCreate, @@ -56,7 +54,8 @@ import type { PreviewHostUpdateData, } from "./api-clients/client"; import { PitcherManagerResponse } from "./types"; - +import { Config } from "./api-clients/client/client"; +import { Client } from "./api-clients/pint/client"; export interface APIOptions { apiKey: string; diff --git a/src/AgentClient/AgentConnection.ts b/src/AgentClient/AgentConnection.ts index 8627ee5..c20dfb3 100644 --- a/src/AgentClient/AgentConnection.ts +++ b/src/AgentClient/AgentConnection.ts @@ -14,7 +14,7 @@ import type { import { PendingPitcherMessage } from "./PendingPitcherMessage"; import { createWebSocketClient, WebSocketClient } from "./WebSocketClient"; -import { IAgentClientState } from "./agent-client-interface"; +import { IAgentClientState } from "../agent-client-interface"; import { DEFAULT_SUBSCRIPTIONS } from "../types"; import { Emitter } from "../utils/event"; import { SliceList } from "../utils/sliceList"; diff --git a/src/AgentClient/index.ts b/src/AgentClient/index.ts index c6915b6..20dbadb 100644 --- a/src/AgentClient/index.ts +++ b/src/AgentClient/index.ts @@ -17,7 +17,7 @@ import { IAgentClientSystem, IAgentClientTasks, PickRawFsResult, -} from "./agent-client-interface"; +} from "../agent-client-interface"; import { AgentConnection } from "./AgentConnection"; import { Emitter, Event } from "../utils/event"; import { DEFAULT_SUBSCRIPTIONS, SandboxSession } from "../types"; diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts new file mode 100644 index 0000000..cae0548 --- /dev/null +++ b/src/PintClient/index.ts @@ -0,0 +1,95 @@ +import { + IAgentClient, + IAgentClientPorts, + IAgentClientState, +} from "../agent-client-interface"; +import { Port } from "../pitcher-protocol/messages/port"; +import { listPorts, listPortsSse } from "../api-clients/pint"; +import { SandboxSession } from "../types"; +import { Emitter, EmitterSubscription, Event } from "../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Client, createClient, createConfig } from "../api-clients/pint/client"; + +class PintPortsClient implements IAgentClientPorts { + private onPortsUpdatedEmitter = new EmitterSubscription((fire) => { + const abortController = new AbortController(); + + listPortsSse({ + signal: abortController.signal, + headers: { + headers: { Accept: "text/event-stream" }, + }, + }).then(async ({ stream }) => { + for await (const evt of stream) { + const evtWithoutDataPrefix = evt.substring(5); + + fire(JSON.parse(evtWithoutDataPrefix)); + } + }); + + return Disposable.create(() => { + abortController.abort(); + }); + }); + onPortsUpdated = this.onPortsUpdatedEmitter.event; + + constructor(private apiClient: Client, private sandboxId: string) {} + + async getPorts(): Promise { + const ports = await listPorts({ + client: this.apiClient, + }); + + return ( + ports.data?.ports.map((port) => ({ + port: port.port, + url: `https://${this.sandboxId}-${port.port}.csb.app`, + })) ?? [] + ); + } +} + +export class PintClient implements IAgentClient { + static async create(session: SandboxSession) { + return new PintClient(session); + } + + // Since there is no websocket connection or internal hibernation, the state + // will always be CONNECTED. No state change events will be triggered + readonly state = "CONNECTED"; + private onStateChangeEmitter = new Emitter(); + onStateChange = this.onStateChangeEmitter.event; + + sandboxId: string; + workspacePath: string; + isUpToDate: boolean; + + ports: IAgentClientPorts; + shells: any = null; // TODO: Implement + fs: any = null; // TODO: Implement + setup: any = null; // TODO: Implement + tasks: any = null; // TODO: Implement + system: any = null; // TODO: Implement + + constructor(session: SandboxSession) { + this.sandboxId = session.sandboxId; + this.workspacePath = session.workspacePath; + this.isUpToDate = true; + + const apiClient = createClient( + createConfig({ + baseUrl: session.pitcherURL, + headers: { + Authorization: `Bearer ${session.pitcherToken}`, + }, + }) + ); + + this.ports = new PintPortsClient(apiClient, this.sandboxId); + } + + ping(): void {} + async reconnect(): Promise {} + async disconnect(): Promise {} + dispose(): void {} +} diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index c2a0c9b..2b52abb 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -1,6 +1,6 @@ import { Disposable, DisposableStore } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import * as protocol from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; @@ -186,7 +186,8 @@ export class SandboxCommands { return shells .filter( - (shell) => shell.shellType === "TERMINAL" && isCommandShell(shell) + (shell): shell is protocol.shell.CommandShellDTO => + shell.shellType === "TERMINAL" && isCommandShell(shell) ) .map( (shell) => diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index 3e1ac32..68440a5 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -1,4 +1,4 @@ -import { type IAgentClient } from "../AgentClient/agent-client-interface"; +import { type IAgentClient } from "../agent-client-interface"; import { Disposable } from "../utils/disposable"; import { Emitter, type Event } from "../utils/event"; diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index a8b0fc6..ca83b2f 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -10,7 +10,7 @@ import { Terminals } from "./terminals"; import { SandboxCommands } from "./commands"; import { HostToken } from "../HostTokens"; import { Hosts } from "./hosts"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import { setup, system } from "../pitcher-protocol"; import { Barrier } from "../utils/barrier"; import { AgentClient } from "../AgentClient"; diff --git a/src/SandboxClient/ports.ts b/src/SandboxClient/ports.ts index 9666646..f0ccf1d 100644 --- a/src/SandboxClient/ports.ts +++ b/src/SandboxClient/ports.ts @@ -1,6 +1,6 @@ import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type Port = { diff --git a/src/SandboxClient/setup.ts b/src/SandboxClient/setup.ts index d13a3bb..e322cd2 100644 --- a/src/SandboxClient/setup.ts +++ b/src/SandboxClient/setup.ts @@ -2,7 +2,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export class Setup { diff --git a/src/SandboxClient/tasks.ts b/src/SandboxClient/tasks.ts index 8ca7aa3..e8fce74 100644 --- a/src/SandboxClient/tasks.ts +++ b/src/SandboxClient/tasks.ts @@ -1,7 +1,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable, IDisposable } from "../utils/disposable"; import { DEFAULT_SHELL_SIZE } from "./terminals"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import { Emitter } from "../utils/event"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; diff --git a/src/SandboxClient/terminals.ts b/src/SandboxClient/terminals.ts index ae544a8..408e5b6 100644 --- a/src/SandboxClient/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -2,7 +2,7 @@ import * as protocol from "../pitcher-protocol"; import { Disposable } from "../utils/disposable"; import { Emitter } from "../utils/event"; import { isCommandShell, ShellRunOpts } from "./commands"; -import { IAgentClient } from "../AgentClient/agent-client-interface"; +import { IAgentClient } from "../agent-client-interface"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; export type ShellSize = { cols: number; rows: number }; diff --git a/src/AgentClient/agent-client-interface.ts b/src/agent-client-interface.ts similarity index 98% rename from src/AgentClient/agent-client-interface.ts rename to src/agent-client-interface.ts index c84dca5..a5f9a55 100644 --- a/src/AgentClient/agent-client-interface.ts +++ b/src/agent-client-interface.ts @@ -8,8 +8,8 @@ import { setup, task, system, -} from "../pitcher-protocol"; -import { Event } from "../utils/event"; +} from "./pitcher-protocol"; +import { Event } from "./utils/event"; export interface IAgentClientShells { onShellExited: Event<{ diff --git a/src/api-clients/client-rest-container/client.gen.ts b/src/api-clients/client-rest-container/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-container/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-container/index.ts b/src/api-clients/client-rest-container/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-container/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-container/sdk.gen.ts b/src/api-clients/client-rest-container/sdk.gen.ts deleted file mode 100644 index bfa7256..0000000 --- a/src/api-clients/client-rest-container/sdk.gen.ts +++ /dev/null @@ -1,29 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { ContainerSetupData, ContainerSetupResponse, ContainerSetupError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Setup container - * Set up a new container based on a template - */ -export const containerSetup = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/container/setup', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-container/types.gen.ts b/src/api-clients/client-rest-container/types.gen.ts deleted file mode 100644 index b2fa4a4..0000000 --- a/src/api-clients/client-rest-container/types.gen.ts +++ /dev/null @@ -1,111 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type ProtocolError = { - /** - * Error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type TaskDto = { - /** - * Task identifier - */ - id: string; - /** - * Task status - */ - status: string; - /** - * Task progress (0-100) - */ - progress: number; -}; - -export type ContainerSetupData = { - body: { - /** - * Identifier of the template to use - */ - templateId: string; - /** - * Arguments for the template - */ - templateArgs: { - [key: string]: string; - }; - features?: Array<{ - /** - * Feature identifier - */ - id: string; - /** - * Options for the feature - */ - options: { - [key: string]: string; - }; - }> | null; - }; - path?: never; - query?: never; - url: '/container/setup'; -}; - -export type ContainerSetupErrors = { - /** - * Error setting up container - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type ContainerSetupError = ContainerSetupErrors[keyof ContainerSetupErrors]; - -export type ContainerSetupResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; -}; - -export type ContainerSetupResponse = ContainerSetupResponses[keyof ContainerSetupResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/client.gen.ts b/src/api-clients/client-rest-fs/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-fs/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/index.ts b/src/api-clients/client-rest-fs/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-fs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/sdk.gen.ts b/src/api-clients/client-rest-fs/sdk.gen.ts deleted file mode 100644 index 83a4cab..0000000 --- a/src/api-clients/client-rest-fs/sdk.gen.ts +++ /dev/null @@ -1,280 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { WriteFileData, WriteFileResponse, WriteFileError, FsReadData, FsReadResponse, FsReadError, FsOperationData, FsOperationResponse, FsOperationError, FsSearchData, FsSearchResponse, FsSearchError, FsStreamingSearchData, FsStreamingSearchResponse, FsStreamingSearchError, FsCancelStreamingSearchData, FsCancelStreamingSearchResponse, FsCancelStreamingSearchError, FsPathSearchData, FsPathSearchResponse, FsPathSearchError, FsUploadData, FsUploadResponse, FsUploadError, FsDownloadData, FsDownloadResponse, FsDownloadError, FsReadFileData, FsReadFileResponse, FsReadFileError, FsReadDirData, FsReadDirResponse, FsReadDirError, FsStatData, FsStatResponse, FsStatError, FsCopyData, FsCopyResponse, FsCopyError, FsRenameData, FsRenameResponse, FsRenameError, FsRemoveData, FsRemoveResponse, FsRemoveError, FsMkdirData, FsMkdirResponse, FsMkdirError, FsWatchData, FsWatchResponse, FsWatchError, FsUnwatchData, FsUnwatchResponse, FsUnwatchError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Write to a file - * Write content to a file at the specified path - */ -export const writeFile = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/writeFile', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Read file system - * Retrieve the latest snapshot of the server's MemoryFS file and children list - */ -export const fsRead = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ - url: '/fs/read', - ...options - }); -}; - -/** - * Perform file system operation - * Send a tree operation reflecting filesystem operations - */ -export const fsOperation = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/operation', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Search files - * Search for content in files - */ -export const fsSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/search', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Start streaming search - * Start a streaming search for content in files - */ -export const fsStreamingSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/streamingSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Cancel streaming search - * Cancel an ongoing streaming search - */ -export const fsCancelStreamingSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/cancelStreamingSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Search file paths - * Search for file paths matching a pattern - */ -export const fsPathSearch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/pathSearch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Upload file - * Upload a file to the specified parent directory - */ -export const fsUpload = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/upload', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Download files - * Download files at a specified path as a zip - */ -export const fsDownload = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/download', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Read file content - * Read the content of a file at the specified path - */ -export const fsReadFile = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/readFile', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Read directory contents - * List the contents of a directory at the specified path - */ -export const fsReadDir = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/readdir', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get file/directory stats - * Get stats for a file or directory at the specified path - */ -export const fsStat = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/stat', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Copy file/directory - * Copy a file or directory from one location to another - */ -export const fsCopy = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/copy', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Rename file/directory - * Rename a file or directory (move from one location to another) - */ -export const fsRename = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/rename', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Remove file/directory - * Delete a file or directory at the specified path - */ -export const fsRemove = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/remove', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Create directory - * Create a new directory at the specified path - */ -export const fsMkdir = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/mkdir', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Watch file/directory - * Watch a file or directory for changes - */ -export const fsWatch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/watch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Stop watching file/directory - * Stop watching a file or directory for changes - */ -export const fsUnwatch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/fs/unwatch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-fs/types.gen.ts b/src/api-clients/client-rest-fs/types.gen.ts deleted file mode 100644 index 624cd45..0000000 --- a/src/api-clients/client-rest-fs/types.gen.ts +++ /dev/null @@ -1,1058 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - error: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); -}; - -export type DefaultError = { - code: PitcherErrorCode; - /** - * Additional error details - */ - data?: { - [key: string]: unknown; - } | null; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; -}; - -export type RawFsError = { - /** - * RAWFS_ERROR code - */ - code: 102; - data: { - /** - * File system error number, or null if not available - */ - errno: unknown; - }; - /** - * Human-readable error message that can be displayed to users - */ - publicMessage?: string | null; -}; - -/** - * Enumeration of error codes - */ -export type PitcherErrorCode = 0 | 1 | 2 | 3 | 100 | 101 | 102 | 200 | 201 | 204 | 300 | 400 | 404 | 410 | 420 | 430 | 440 | 450 | 460 | 470 | 500 | 600 | 601 | 602 | 704 | 800 | 801 | 802 | 803 | 814; - -export type WriteFileRequest = { - /** - * File path to write to - */ - path: string; - /** - * File content as binary data (Uint8Array) - */ - content: Blob | File; - /** - * Whether to create the file if it doesn't exist - */ - create?: boolean; - /** - * Whether to overwrite the file if it exists - */ - overwrite?: boolean; -}; - -export type FsReadResult = { - treeNodes: Array<{ - [key: string]: unknown; - }>; - /** - * Current clock value for the file system - */ - clock: number; -}; - -export type FsOperationRequest = { - operation: FsOperation; -}; - -export type FsOperation = ({ - type?: 'FSCreateOperation'; -} & FsCreateOperation) | ({ - type?: 'FSDeleteOperation'; -} & FsDeleteOperation) | ({ - type?: 'FSMoveOperation'; -} & FsMoveOperation); - -export type FsCreateOperation = { - type: 'create'; - /** - * ID of the parent directory - */ - parentId: string; - newEntry: { - /** - * ID of the new entry - */ - id: string; - /** - * Type of the node - */ - type: 'directory' | 'file'; - /** - * Name of the new entry - */ - name: string; - }; -}; - -export type FsDeleteOperation = { - type: 'delete'; - /** - * ID of the entry to delete - */ - id: string; -}; - -export type FsMoveOperation = { - type: 'move'; - /** - * ID of the entry to move - */ - id: string; - /** - * ID of the new parent directory - */ - parentId?: string | null; - /** - * New name for the entry - */ - name?: string | null; -}; - -export type FsOperationResult = { - /** - * Success code - */ - code: 0; - /** - * Current clock value - */ - clock: number; -} | { - /** - * Ignored code - */ - code: 1; -}; - -export type FsSearchParams = { - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: 'smart' | 'enabled' | 'disabled'; -}; - -export type SearchResult = { - /** - * ID of the file containing the match - */ - fileId: string; - lines: { - /** - * Text of the line containing the match - */ - text: string; - }; - /** - * Line number of the match - */ - lineNumber: number; - /** - * Absolute offset of the match in the file - */ - absoluteOffset: number; - submatches: Array; -}; - -export type SearchSubMatch = { - match: { - /** - * Matched text - */ - text: string; - }; - /** - * Start position of the match - */ - start: number; - /** - * End position of the match - */ - end: number; -}; - -export type FsStreamingSearchParams = { - /** - * ID for the search operation - */ - searchId: string; - /** - * Text to search for - */ - text: string; - /** - * Glob pattern to filter files - */ - glob?: string | null; - /** - * Whether to treat the search text as a regular expression - */ - isRegex?: boolean | null; - /** - * Case sensitivity setting for the search - */ - caseSensitivity?: 'smart' | 'enabled' | 'disabled'; - /** - * Maximum number of results to return (default: 10,000) - */ - maxResults?: number | null; -}; - -export type PathSearchParams = { - /** - * Text to search for in file paths - */ - text: string; -}; - -export type PathSearchResult = { - matches: Array; -}; - -export type PathSearchMatch = { - /** - * Path that matched the search - */ - path: string; - submatches: Array; -}; - -export type InvalidIdError = { - /** - * INVALID_ID error code - */ - code: 100; -}; - -export type FsReadFileParams = { - /** - * Path to the file to read - */ - path: string; -}; - -export type FsReadFileResult = { - /** - * File content as binary data - */ - content: Blob | File; -}; - -export type FsReadDirParams = { - /** - * Path to the directory to read - */ - path: string; -}; - -export type FsReadDirResult = { - entries: Array<{ - /** - * Name of the entry - */ - name: string; - /** - * Type of the entry - */ - type: 'directory' | 'file'; - /** - * Whether the entry is a symlink - */ - isSymlink: boolean; - }>; -}; - -export type FsStatParams = { - /** - * Path to the file or directory to stat - */ - path: string; -}; - -export type FsStatResult = { - /** - * Type of the entry - */ - type: 'directory' | 'file'; - /** - * Whether the entry is a symlink - */ - isSymlink: boolean; - /** - * Size of the file in bytes - */ - size: number; - /** - * Last modified time - */ - mtime: number; - /** - * Creation time - */ - ctime: number; - /** - * Last accessed time - */ - atime: number; -}; - -export type FsCopyParams = { - /** - * Path to copy from - */ - from: string; - /** - * Path to copy to - */ - to: string; - /** - * Whether to copy directories recursively - */ - recursive?: boolean | null; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; -}; - -export type FsRenameParams = { - /** - * Path to rename from - */ - from: string; - /** - * Path to rename to - */ - to: string; - /** - * Whether to overwrite existing files - */ - overwrite?: boolean | null; -}; - -export type FsRemoveParams = { - /** - * Path to remove - */ - path: string; - /** - * Whether to remove directories recursively - */ - recursive?: boolean | null; -}; - -export type FsMkdirParams = { - /** - * Path to create directory at - */ - path: string; - /** - * Whether to create parent directories if they don't exist - */ - recursive?: boolean | null; -}; - -export type FsWatchParams = { - /** - * Path to watch - */ - path: string; - /** - * Whether to watch directories recursively - */ - recursive?: boolean | null; - /** - * Glob patterns to exclude from watching - */ - excludes?: Array | null; -}; - -export type FsWatchResult = { - /** - * ID of the watch - */ - watchId: string; -}; - -export type FsUnwatchParams = { - /** - * ID of the watch to stop - */ - watchId: string; -}; - -export type WriteFileData = { - body: WriteFileRequest; - path?: never; - query?: never; - url: '/fs/writeFile'; -}; - -export type WriteFileErrors = { - /** - * Error writing file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type WriteFileError = WriteFileErrors[keyof WriteFileErrors]; - -export type WriteFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type WriteFileResponse = WriteFileResponses[keyof WriteFileResponses]; - -export type FsReadData = { - body?: never; - path?: never; - query?: never; - url: '/fs/read'; -}; - -export type FsReadErrors = { - /** - * Error reading file system - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsReadError = FsReadErrors[keyof FsReadErrors]; - -export type FsReadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadResult; - }; -}; - -export type FsReadResponse = FsReadResponses[keyof FsReadResponses]; - -export type FsOperationData = { - body: FsOperationRequest; - path?: never; - query?: never; - url: '/fs/operation'; -}; - -export type FsOperationErrors = { - /** - * Error performing operation - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsOperationError = FsOperationErrors[keyof FsOperationErrors]; - -export type FsOperationResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsOperationResult; - }; -}; - -export type FsOperationResponse = FsOperationResponses[keyof FsOperationResponses]; - -export type FsSearchData = { - body: FsSearchParams; - path?: never; - query?: never; - url: '/fs/search'; -}; - -export type FsSearchErrors = { - /** - * Error searching files - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsSearchError = FsSearchErrors[keyof FsSearchErrors]; - -export type FsSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array; - }; -}; - -export type FsSearchResponse = FsSearchResponses[keyof FsSearchResponses]; - -export type FsStreamingSearchData = { - body: FsStreamingSearchParams; - path?: never; - query?: never; - url: '/fs/streamingSearch'; -}; - -export type FsStreamingSearchErrors = { - /** - * Error starting streaming search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsStreamingSearchError = FsStreamingSearchErrors[keyof FsStreamingSearchErrors]; - -export type FsStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the search operation - */ - searchId?: string; - }; - }; -}; - -export type FsStreamingSearchResponse = FsStreamingSearchResponses[keyof FsStreamingSearchResponses]; - -export type FsCancelStreamingSearchData = { - body: { - /** - * ID of the search to cancel - */ - searchId: string; - }; - path?: never; - query?: never; - url: '/fs/cancelStreamingSearch'; -}; - -export type FsCancelStreamingSearchErrors = { - /** - * Error cancelling search - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsCancelStreamingSearchError = FsCancelStreamingSearchErrors[keyof FsCancelStreamingSearchErrors]; - -export type FsCancelStreamingSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the cancelled search - */ - searchId?: string; - }; - }; -}; - -export type FsCancelStreamingSearchResponse = FsCancelStreamingSearchResponses[keyof FsCancelStreamingSearchResponses]; - -export type FsPathSearchData = { - body: PathSearchParams; - path?: never; - query?: never; - url: '/fs/pathSearch'; -}; - -export type FsPathSearchErrors = { - /** - * Error searching paths - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsPathSearchError = FsPathSearchErrors[keyof FsPathSearchErrors]; - -export type FsPathSearchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: PathSearchResult; - }; -}; - -export type FsPathSearchResponse = FsPathSearchResponses[keyof FsPathSearchResponses]; - -export type FsUploadData = { - body: { - /** - * ID of the parent directory - */ - parentId: string; - /** - * Name of the file to create - */ - filename: string; - /** - * File content as binary data - */ - content: Blob | File; - }; - path?: never; - query?: never; - url: '/fs/upload'; -}; - -export type FsUploadErrors = { - /** - * Error uploading file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'InvalidIdError'; - } & InvalidIdError); - }; -}; - -export type FsUploadError = FsUploadErrors[keyof FsUploadErrors]; - -export type FsUploadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the created file - */ - fileId?: string; - }; - }; -}; - -export type FsUploadResponse = FsUploadResponses[keyof FsUploadResponses]; - -export type FsDownloadData = { - body: { - /** - * Path to download - */ - path: string; - /** - * Glob patterns of files/folders to exclude from the download - */ - excludes?: Array; - }; - path?: never; - query?: never; - url: '/fs/download'; -}; - -export type FsDownloadErrors = { - /** - * Error creating download - */ - 400: ErrorResponse & { - error?: DefaultError; - }; -}; - -export type FsDownloadError = FsDownloadErrors[keyof FsDownloadErrors]; - -export type FsDownloadResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * URL to download the files from - */ - downloadUrl?: string; - }; - }; -}; - -export type FsDownloadResponse = FsDownloadResponses[keyof FsDownloadResponses]; - -export type FsReadFileData = { - body: FsReadFileParams; - path?: never; - query?: never; - url: '/fs/readFile'; -}; - -export type FsReadFileErrors = { - /** - * Error reading file - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsReadFileError = FsReadFileErrors[keyof FsReadFileErrors]; - -export type FsReadFileResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadFileResult; - }; -}; - -export type FsReadFileResponse = FsReadFileResponses[keyof FsReadFileResponses]; - -export type FsReadDirData = { - body: FsReadDirParams; - path?: never; - query?: never; - url: '/fs/readdir'; -}; - -export type FsReadDirErrors = { - /** - * Error reading directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsReadDirError = FsReadDirErrors[keyof FsReadDirErrors]; - -export type FsReadDirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsReadDirResult; - }; -}; - -export type FsReadDirResponse = FsReadDirResponses[keyof FsReadDirResponses]; - -export type FsStatData = { - body: FsStatParams; - path?: never; - query?: never; - url: '/fs/stat'; -}; - -export type FsStatErrors = { - /** - * Error getting stats - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsStatError = FsStatErrors[keyof FsStatErrors]; - -export type FsStatResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsStatResult; - }; -}; - -export type FsStatResponse = FsStatResponses[keyof FsStatResponses]; - -export type FsCopyData = { - body: FsCopyParams; - path?: never; - query?: never; - url: '/fs/copy'; -}; - -export type FsCopyErrors = { - /** - * Error copying file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsCopyError = FsCopyErrors[keyof FsCopyErrors]; - -export type FsCopyResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type FsCopyResponse = FsCopyResponses[keyof FsCopyResponses]; - -export type FsRenameData = { - body: FsRenameParams; - path?: never; - query?: never; - url: '/fs/rename'; -}; - -export type FsRenameErrors = { - /** - * Error renaming file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsRenameError = FsRenameErrors[keyof FsRenameErrors]; - -export type FsRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type FsRenameResponse = FsRenameResponses[keyof FsRenameResponses]; - -export type FsRemoveData = { - body: FsRemoveParams; - path?: never; - query?: never; - url: '/fs/remove'; -}; - -export type FsRemoveErrors = { - /** - * Error removing file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsRemoveError = FsRemoveErrors[keyof FsRemoveErrors]; - -export type FsRemoveResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type FsRemoveResponse = FsRemoveResponses[keyof FsRemoveResponses]; - -export type FsMkdirData = { - body: FsMkdirParams; - path?: never; - query?: never; - url: '/fs/mkdir'; -}; - -export type FsMkdirErrors = { - /** - * Error creating directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsMkdirError = FsMkdirErrors[keyof FsMkdirErrors]; - -export type FsMkdirResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type FsMkdirResponse = FsMkdirResponses[keyof FsMkdirResponses]; - -export type FsWatchData = { - body: FsWatchParams; - path?: never; - query?: never; - url: '/fs/watch'; -}; - -export type FsWatchErrors = { - /** - * Error watching file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsWatchError = FsWatchErrors[keyof FsWatchErrors]; - -export type FsWatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: FsWatchResult; - }; -}; - -export type FsWatchResponse = FsWatchResponses[keyof FsWatchResponses]; - -export type FsUnwatchData = { - body: FsUnwatchParams; - path?: never; - query?: never; - url: '/fs/unwatch'; -}; - -export type FsUnwatchErrors = { - /** - * Error unwatching file/directory - */ - 400: ErrorResponse & { - error?: ({ - code?: 'DefaultError'; - } & DefaultError) | ({ - code?: 'RawFsError'; - } & RawFsError); - }; -}; - -export type FsUnwatchError = FsUnwatchErrors[keyof FsUnwatchErrors]; - -export type FsUnwatchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type FsUnwatchResponse = FsUnwatchResponses[keyof FsUnwatchResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/client.gen.ts b/src/api-clients/client-rest-git/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-git/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-git/index.ts b/src/api-clients/client-rest-git/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-git/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/sdk.gen.ts b/src/api-clients/client-rest-git/sdk.gen.ts deleted file mode 100644 index 0e3b272..0000000 --- a/src/api-clients/client-rest-git/sdk.gen.ts +++ /dev/null @@ -1,224 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { GitStatusData, GitStatusResponse, GitStatusError, GitRemotesData, GitRemotesResponse, GitRemotesError, GitTargetDiffData, GitTargetDiffResponse, GitTargetDiffError, GitPullData, GitPullResponse, GitPullError, GitDiscardData, GitDiscardResponse, GitDiscardError, GitCommitData, GitCommitResponse, GitCommitError, GitPushData, GitPushResponse, GitPushError, GitPushToRemoteData, GitPushToRemoteResponse, GitPushToRemoteError, GitRenameBranchData, GitRenameBranchResponse, GitRenameBranchError, GitRemoteContentData, GitRemoteContentResponse, GitRemoteContentError, GitDiffStatusData, GitDiffStatusResponse, GitDiffStatusError, GitResetLocalWithRemoteData, GitResetLocalWithRemoteResponse, GitResetLocalWithRemoteError, GitCheckoutInitialBranchData, GitCheckoutInitialBranchResponse, GitCheckoutInitialBranchError, GitTransposeLinesData, GitTransposeLinesResponse, GitTransposeLinesError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Get git status - * Retrieve current git status including changed files, branch information, and commits - */ -export const gitStatus = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/status', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get git remotes - * Retrieve git remote information - */ -export const gitRemotes = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/remotes', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get git target diff - * Retrieve diff between current branch and target branch - */ -export const gitTargetDiff = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/targetDiff', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Pull from remote - * Pull changes from remote repository - */ -export const gitPull = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/pull', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Discard changes - * Discard local changes for specified paths - */ -export const gitDiscard = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/discard', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Commit changes - * Commit changes to the repository - */ -export const gitCommit = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/commit', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Push changes - * Push local commits to remote repository - */ -export const gitPush = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/push', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Push to remote - * Push to a specific remote repository - */ -export const gitPushToRemote = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/pushToRemote', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Rename branch - * Rename a git branch - */ -export const gitRenameBranch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/renameBranch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get remote content - * Retrieve content from a remote repository - */ -export const gitRemoteContent = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/remoteContent', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get diff status - * Retrieve diff status between two git references - */ -export const gitDiffStatus = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/diffStatus', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Reset local with remote - * Reset local repository to match the remote state - */ -export const gitResetLocalWithRemote = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/resetLocalWithRemote', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Checkout initial branch - * Checkout the initial branch of the repository - */ -export const gitCheckoutInitialBranch = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/checkoutInitialBranch', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Transpose lines - * Transpose line numbers from one git reference to another - */ -export const gitTransposeLines = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/git/transposeLines', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-git/types.gen.ts b/src/api-clients/client-rest-git/types.gen.ts deleted file mode 100644 index dce87ba..0000000 --- a/src/api-clients/client-rest-git/types.gen.ts +++ /dev/null @@ -1,725 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type CommonError = { - /** - * Error code - */ - code: 'GIT_OPERATION_IN_PROGRESS' | 'GIT_REMOTE_FILE_NOT_FOUND'; - /** - * Error message - */ - message: string; -} | { - /** - * Protocol error code - */ - code: string; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - }; -}; - -/** - * Git status short format codes - */ -export type GitStatusShortFormat = '' | 'M' | 'A' | 'D' | 'R' | 'C' | 'U' | '?'; - -export type GitItem = { - /** - * File path - */ - path: string; - index: GitStatusShortFormat; - workingTree: GitStatusShortFormat; - /** - * Whether the file is staged - */ - isStaged: boolean; - /** - * Whether the file has conflicts - */ - isConflicted: boolean; - /** - * Unique identifier for the file - */ - fileId?: string; -}; - -/** - * Map of file IDs to Git items - */ -export type GitChangedFiles = { - [key: string]: GitItem; -}; - -export type GitBranchProperties = { - /** - * Current HEAD reference - */ - head?: unknown; - /** - * Current branch name - */ - branch?: unknown; - /** - * Number of commits ahead of the remote - */ - ahead: number; - /** - * Number of commits behind the remote - */ - behind: number; - /** - * Whether the branch is safe to operate on - */ - safe: boolean; -}; - -export type GitCommit = { - /** - * Commit hash - */ - hash: string; - /** - * Commit date - */ - date: string; - /** - * Commit message - */ - message: string; - /** - * Commit author - */ - author: string; -}; - -export type GitStatus = { - changedFiles: GitChangedFiles; - deletedFiles: Array; - /** - * Whether there are remote conflicts - */ - conflicts: boolean; - /** - * Whether there are local changes - */ - localChanges: boolean; - remote: GitBranchProperties; - target: GitBranchProperties; - /** - * Current HEAD reference - */ - head?: string; - commits: Array; - /** - * Current branch name - */ - branch: unknown; - /** - * Whether a merge is in progress - */ - isMerging: boolean; -}; - -export type GitTargetDiff = { - /** - * Number of commits ahead of the target - */ - ahead: number; - /** - * Number of commits behind the target - */ - behind: number; - commits: Array; -}; - -export type GitRemotes = { - /** - * Origin remote URL - */ - origin: string; - /** - * Upstream remote URL - */ - upstream: string; -}; - -export type GitRemoteParams = { - /** - * Branch or commit hash - */ - reference: string; - /** - * Path to the file - */ - path: string; -}; - -export type GitDiffStatusParams = { - /** - * Base reference used for diffing - */ - base: string; - /** - * Head reference used for diffing - */ - head: string; -}; - -export type GitDiffStatusItem = { - status: GitStatusShortFormat; - /** - * Path to the file - */ - path: string; - /** - * Original path for renamed files - */ - oldPath?: string; - hunks: Array<{ - original: { - start: number; - end: number; - }; - modified: { - start: number; - end: number; - }; - }>; -}; - -export type GitDiffStatusResult = { - files: Array; -}; - -export type GitStatusData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/status'; -}; - -export type GitStatusErrors = { - /** - * Error retrieving git status - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitStatusError = GitStatusErrors[keyof GitStatusErrors]; - -export type GitStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitStatus; - }; -}; - -export type GitStatusResponse = GitStatusResponses[keyof GitStatusResponses]; - -export type GitRemotesData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/remotes'; -}; - -export type GitRemotesErrors = { - /** - * Error retrieving git remotes - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitRemotesError = GitRemotesErrors[keyof GitRemotesErrors]; - -export type GitRemotesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitRemotes; - }; -}; - -export type GitRemotesResponse = GitRemotesResponses[keyof GitRemotesResponses]; - -export type GitTargetDiffData = { - body: { - /** - * Branch to compare against - */ - branch: string; - }; - path?: never; - query?: never; - url: '/git/targetDiff'; -}; - -export type GitTargetDiffErrors = { - /** - * Error retrieving git target diff - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitTargetDiffError = GitTargetDiffErrors[keyof GitTargetDiffErrors]; - -export type GitTargetDiffResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitTargetDiff; - }; -}; - -export type GitTargetDiffResponse = GitTargetDiffResponses[keyof GitTargetDiffResponses]; - -export type GitPullData = { - body: { - /** - * Branch to pull from - */ - branch?: string; - /** - * Force pull even if there are conflicts - */ - force?: boolean; - }; - path?: never; - query?: never; - url: '/git/pull'; -}; - -export type GitPullErrors = { - /** - * Error pulling from remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitPullError = GitPullErrors[keyof GitPullErrors]; - -export type GitPullResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitPullResponse = GitPullResponses[keyof GitPullResponses]; - -export type GitDiscardData = { - body: { - /** - * Paths of files to discard changes - */ - paths?: Array; - }; - path?: never; - query?: never; - url: '/git/discard'; -}; - -export type GitDiscardErrors = { - /** - * Error discarding changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitDiscardError = GitDiscardErrors[keyof GitDiscardErrors]; - -export type GitDiscardResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - paths?: Array; - }; - }; -}; - -export type GitDiscardResponse = GitDiscardResponses[keyof GitDiscardResponses]; - -export type GitCommitData = { - body: { - /** - * Paths of files to commit - */ - paths?: Array; - /** - * Commit message - */ - message: string; - /** - * Whether to push the commit immediately - */ - push?: boolean; - }; - path?: never; - query?: never; - url: '/git/commit'; -}; - -export type GitCommitErrors = { - /** - * Error committing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitCommitError = GitCommitErrors[keyof GitCommitErrors]; - -export type GitCommitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * ID of the shell process - */ - shellId: string; - }; - }; -}; - -export type GitCommitResponse = GitCommitResponses[keyof GitCommitResponses]; - -export type GitPushData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/push'; -}; - -export type GitPushErrors = { - /** - * Error pushing changes - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitPushError = GitPushErrors[keyof GitPushErrors]; - -export type GitPushResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitPushResponse = GitPushResponses[keyof GitPushResponses]; - -export type GitPushToRemoteData = { - body: { - /** - * URL of the remote repository - */ - url: string; - /** - * Branch to push to - */ - branch: string; - /** - * Whether to squash all commits into one - */ - squashAllCommits?: boolean; - }; - path?: never; - query?: never; - url: '/git/pushToRemote'; -}; - -export type GitPushToRemoteErrors = { - /** - * Error pushing to remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitPushToRemoteError = GitPushToRemoteErrors[keyof GitPushToRemoteErrors]; - -export type GitPushToRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitPushToRemoteResponse = GitPushToRemoteResponses[keyof GitPushToRemoteResponses]; - -export type GitRenameBranchData = { - body: { - /** - * Current branch name - */ - oldBranch: string; - /** - * New branch name - */ - newBranch: string; - }; - path?: never; - query?: never; - url: '/git/renameBranch'; -}; - -export type GitRenameBranchErrors = { - /** - * Error renaming branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitRenameBranchError = GitRenameBranchErrors[keyof GitRenameBranchErrors]; - -export type GitRenameBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitRenameBranchResponse = GitRenameBranchResponses[keyof GitRenameBranchResponses]; - -export type GitRemoteContentData = { - body: GitRemoteParams; - path?: never; - query?: never; - url: '/git/remoteContent'; -}; - -export type GitRemoteContentErrors = { - /** - * Error retrieving remote content - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitRemoteContentError = GitRemoteContentErrors[keyof GitRemoteContentErrors]; - -export type GitRemoteContentResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - /** - * Content of the file - */ - content: string; - }; - }; -}; - -export type GitRemoteContentResponse = GitRemoteContentResponses[keyof GitRemoteContentResponses]; - -export type GitDiffStatusData = { - body: GitDiffStatusParams; - path?: never; - query?: never; - url: '/git/diffStatus'; -}; - -export type GitDiffStatusErrors = { - /** - * Error retrieving diff status - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitDiffStatusError = GitDiffStatusErrors[keyof GitDiffStatusErrors]; - -export type GitDiffStatusResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: GitDiffStatusResult; - }; -}; - -export type GitDiffStatusResponse = GitDiffStatusResponses[keyof GitDiffStatusResponses]; - -export type GitResetLocalWithRemoteData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/resetLocalWithRemote'; -}; - -export type GitResetLocalWithRemoteErrors = { - /** - * Error resetting local with remote - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitResetLocalWithRemoteError = GitResetLocalWithRemoteErrors[keyof GitResetLocalWithRemoteErrors]; - -export type GitResetLocalWithRemoteResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitResetLocalWithRemoteResponse = GitResetLocalWithRemoteResponses[keyof GitResetLocalWithRemoteResponses]; - -export type GitCheckoutInitialBranchData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/git/checkoutInitialBranch'; -}; - -export type GitCheckoutInitialBranchErrors = { - /** - * Error checking out initial branch - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitCheckoutInitialBranchError = GitCheckoutInitialBranchErrors[keyof GitCheckoutInitialBranchErrors]; - -export type GitCheckoutInitialBranchResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type GitCheckoutInitialBranchResponse = GitCheckoutInitialBranchResponses[keyof GitCheckoutInitialBranchResponses]; - -export type GitTransposeLinesData = { - body: Array<{ - /** - * Git commit SHA - */ - sha: string; - /** - * Path to the file - */ - path: string; - /** - * Line number to transpose - */ - line: number; - }>; - path?: never; - query?: never; - url: '/git/transposeLines'; -}; - -export type GitTransposeLinesErrors = { - /** - * Error transposing lines - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type GitTransposeLinesError = GitTransposeLinesErrors[keyof GitTransposeLinesErrors]; - -export type GitTransposeLinesResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: Array<{ - path: string; - line: number; - } | unknown>; - }; -}; - -export type GitTransposeLinesResponse = GitTransposeLinesResponses[keyof GitTransposeLinesResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/client.gen.ts b/src/api-clients/client-rest-setup/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-setup/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/index.ts b/src/api-clients/client-rest-setup/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-setup/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/sdk.gen.ts b/src/api-clients/client-rest-setup/sdk.gen.ts deleted file mode 100644 index 1a3ae61..0000000 --- a/src/api-clients/client-rest-setup/sdk.gen.ts +++ /dev/null @@ -1,119 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { SetupGetData, SetupGetResponse, SetupGetError, SetupSkipStepData, SetupSkipStepResponse, SetupSkipStepError, SetupSkipAllData, SetupSkipAllResponse, SetupSkipAllError, SetupDisableData, SetupDisableResponse, SetupDisableError, SetupEnableData, SetupEnableResponse, SetupEnableError, SetupInitData, SetupInitResponse, SetupInitError, SetupSetStepData, SetupSetStepResponse, SetupSetStepError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Get setup progress - * Retrieve the current setup progress status - */ -export const setupGet = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/get', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Skip setup step - * Skip a specific step in the setup process - */ -export const setupSkipStep = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/skip', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Skip all setup steps - * Skip all remaining steps in the setup process - */ -export const setupSkipAll = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/skipAll', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Disable setup - * Disable the setup process - */ -export const setupDisable = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/disable', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Enable setup - * Enable the setup process - */ -export const setupEnable = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/enable', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Initialize setup - * Initialize the setup process - */ -export const setupInit = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/init', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Set current setup step - * Set the current step in the setup process (used for restarting) - */ -export const setupSetStep = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/setup/setStep', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-setup/types.gen.ts b/src/api-clients/client-rest-setup/types.gen.ts deleted file mode 100644 index 43425e7..0000000 --- a/src/api-clients/client-rest-setup/types.gen.ts +++ /dev/null @@ -1,295 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type ProtocolError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -/** - * Status of a setup shell step - */ -export type SetupShellStatus = 'SUCCEEDED' | 'FAILED' | 'SKIPPED'; - -export type Step = { - /** - * Name of the setup step - */ - name: string; - /** - * Command to execute for this step - */ - command: string; - /** - * ID of the shell executing the command - */ - shellId: string | null; - finishStatus: SetupShellStatus; -}; - -export type SetupProgress = { - /** - * Current state of the setup process - */ - state: 'IDLE' | 'IN_PROGRESS' | 'FINISHED' | 'STOPPED'; - /** - * List of setup steps - */ - steps: Array; - /** - * Index of the current step being executed - */ - currentStepIndex: number; -}; - -export type SetupGetData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/setup/get'; -}; - -export type SetupGetErrors = { - /** - * Error retrieving setup progress - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupGetError = SetupGetErrors[keyof SetupGetErrors]; - -export type SetupGetResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupGetResponse = SetupGetResponses[keyof SetupGetResponses]; - -export type SetupSkipStepData = { - body: { - /** - * Index of the step to skip - */ - stepIndexToSkip: number; - }; - path?: never; - query?: never; - url: '/setup/skip'; -}; - -export type SetupSkipStepErrors = { - /** - * Error skipping step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupSkipStepError = SetupSkipStepErrors[keyof SetupSkipStepErrors]; - -export type SetupSkipStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupSkipStepResponse = SetupSkipStepResponses[keyof SetupSkipStepResponses]; - -export type SetupSkipAllData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/skipAll'; -}; - -export type SetupSkipAllErrors = { - /** - * Error skipping all steps - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupSkipAllError = SetupSkipAllErrors[keyof SetupSkipAllErrors]; - -export type SetupSkipAllResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupSkipAllResponse = SetupSkipAllResponses[keyof SetupSkipAllResponses]; - -export type SetupDisableData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/disable'; -}; - -export type SetupDisableErrors = { - /** - * Error disabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupDisableError = SetupDisableErrors[keyof SetupDisableErrors]; - -export type SetupDisableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupDisableResponse = SetupDisableResponses[keyof SetupDisableResponses]; - -export type SetupEnableData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/enable'; -}; - -export type SetupEnableErrors = { - /** - * Error enabling setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupEnableError = SetupEnableErrors[keyof SetupEnableErrors]; - -export type SetupEnableResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupEnableResponse = SetupEnableResponses[keyof SetupEnableResponses]; - -export type SetupInitData = { - body: unknown; - path?: never; - query?: never; - url: '/setup/init'; -}; - -export type SetupInitErrors = { - /** - * Error initializing setup - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupInitError = SetupInitErrors[keyof SetupInitErrors]; - -export type SetupInitResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupInitResponse = SetupInitResponses[keyof SetupInitResponses]; - -export type SetupSetStepData = { - body: { - /** - * Index of the step to set as current - */ - stepIndex: number; - }; - path?: never; - query?: never; - url: '/setup/setStep'; -}; - -export type SetupSetStepErrors = { - /** - * Error setting current step - */ - 400: ErrorResponse & { - error?: ProtocolError; - }; -}; - -export type SetupSetStepError = SetupSetStepErrors[keyof SetupSetStepErrors]; - -export type SetupSetStepResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SetupProgress; - }; -}; - -export type SetupSetStepResponse = SetupSetStepResponses[keyof SetupSetStepResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/client.gen.ts b/src/api-clients/client-rest-shell/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-shell/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/index.ts b/src/api-clients/client-rest-shell/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-shell/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/sdk.gen.ts b/src/api-clients/client-rest-shell/sdk.gen.ts deleted file mode 100644 index 319634a..0000000 --- a/src/api-clients/client-rest-shell/sdk.gen.ts +++ /dev/null @@ -1,149 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { ShellCreateData, ShellCreateResponse, ShellCreateError, ShellInData, ShellInResponse, ShellInError, ShellListData, ShellListResponse, ShellListError, ShellOpenData, ShellOpenResponse, ShellOpenError, ShellCloseData, ShellCloseResponse, ShellCloseError, ShellRestartData, ShellRestartResponse, ShellRestartError, ShellTerminateData, ShellTerminateResponse, ShellTerminateError, ShellResizeData, ShellResizeResponse, ShellResizeError, ShellRenameData, ShellRenameResponse, ShellRenameError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Create a new shell - * Creates a new terminal or command shell - */ -export const shellCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/create', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Send input to shell - * Sends user input to an active shell - */ -export const shellIn = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/in', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * List all shells - * Retrieves a list of all available shells - */ -export const shellList = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/list', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Open an existing shell - * Opens an existing shell and retrieves its buffer - */ -export const shellOpen = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/open', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Close a shell - * Closes a shell without terminating the underlying process - */ -export const shellClose = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/close', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Restart a shell - * Restarts an existing shell process - */ -export const shellRestart = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/restart', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Terminate a shell - * Terminates a shell and its underlying process - */ -export const shellTerminate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/terminate', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Resize a shell - * Updates the dimensions of a shell - */ -export const shellResize = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/resize', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Rename a shell - * Updates the name of a shell - */ -export const shellRename = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/shell/rename', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-shell/types.gen.ts b/src/api-clients/client-rest-shell/types.gen.ts deleted file mode 100644 index 45aa947..0000000 --- a/src/api-clients/client-rest-shell/types.gen.ts +++ /dev/null @@ -1,443 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -/** - * Unique identifier for a shell - */ -export type ShellId = string; - -export type ShellSize = { - /** - * Number of columns in the terminal - */ - cols: number; - /** - * Number of rows in the terminal - */ - rows: number; -}; - -/** - * Type of shell process - */ -export type ShellProcessType = 'TERMINAL' | 'COMMAND'; - -/** - * Current status of the shell process - */ -export type ShellProcessStatus = 'RUNNING' | 'FINISHED' | 'ERROR' | 'KILLED' | 'RESTARTING'; - -export type BaseShellDto = { - shellId: ShellId; - /** - * Display name of the shell - */ - name: string; - status: ShellProcessStatus; - /** - * Exit code of the process if it has finished - */ - exitCode?: number | null; -}; - -export type CommandShellDto = BaseShellDto & { - /** - * Indicates this is a command shell - */ - shellType: 'COMMAND'; - /** - * The command that was executed to start this shell - */ - startCommand: string; -}; - -export type TerminalShellDto = BaseShellDto & { - /** - * Indicates this is a terminal shell - */ - shellType: 'TERMINAL'; - /** - * Username of the shell owner - */ - ownerUsername: string; - /** - * Whether this is a system shell - */ - isSystemShell: boolean; -}; - -export type ShellDto = ({ - shellType?: 'COMMAND'; -} & CommandShellDto) | ({ - shellType?: 'TERMINAL'; -} & TerminalShellDto); - -export type OpenCommandShellDto = CommandShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; -}; - -export type OpenTerminalShellDto = TerminalShellDto & { - /** - * Content buffer of the shell - */ - buffer: Array; -}; - -export type OpenShellDto = ({ - shellType?: 'COMMAND'; -} & OpenCommandShellDto) | ({ - shellType?: 'TERMINAL'; -} & OpenTerminalShellDto); - -export type CommonError = { - /** - * Error code indicating the shell is not accessible - */ - code: 'SHELL_NOT_ACCESSIBLE'; - /** - * Error message - */ - message: string; -} | { - /** - * Protocol error code - */ - code: string; - /** - * Error message - */ - message: string; -}; - -export type ShellCreateData = { - body: { - /** - * Command to execute in the shell - */ - command?: string; - /** - * Working directory for the shell - */ - cwd?: string; - size?: ShellSize; - type?: ShellProcessType; - /** - * Whether this shell is started by the editor itself to run a specific process - */ - isSystemShell?: boolean; - }; - path?: never; - query?: never; - url: '/shell/create'; -}; - -export type ShellCreateErrors = { - /** - * Error creating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellCreateError = ShellCreateErrors[keyof ShellCreateErrors]; - -export type ShellCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; -}; - -export type ShellCreateResponse = ShellCreateResponses[keyof ShellCreateResponses]; - -export type ShellInData = { - body: { - shellId: ShellId; - /** - * Input to send to the shell - */ - input: string; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/in'; -}; - -export type ShellInErrors = { - /** - * Error sending input to shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellInError = ShellInErrors[keyof ShellInErrors]; - -export type ShellInResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type ShellInResponse = ShellInResponses[keyof ShellInResponses]; - -export type ShellListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/shell/list'; -}; - -export type ShellListErrors = { - /** - * Error listing shells - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellListError = ShellListErrors[keyof ShellListErrors]; - -export type ShellListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - shells: Array; - }; - }; -}; - -export type ShellListResponse = ShellListResponses[keyof ShellListResponses]; - -export type ShellOpenData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/open'; -}; - -export type ShellOpenErrors = { - /** - * Error opening shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellOpenError = ShellOpenErrors[keyof ShellOpenErrors]; - -export type ShellOpenResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: OpenShellDto; - }; -}; - -export type ShellOpenResponse = ShellOpenResponses[keyof ShellOpenResponses]; - -export type ShellCloseData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/close'; -}; - -export type ShellCloseErrors = { - /** - * Error closing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellCloseError = ShellCloseErrors[keyof ShellCloseErrors]; - -export type ShellCloseResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type ShellCloseResponse = ShellCloseResponses[keyof ShellCloseResponses]; - -export type ShellRestartData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/restart'; -}; - -export type ShellRestartErrors = { - /** - * Error restarting shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellRestartError = ShellRestartErrors[keyof ShellRestartErrors]; - -export type ShellRestartResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type ShellRestartResponse = ShellRestartResponses[keyof ShellRestartResponses]; - -export type ShellTerminateData = { - body: { - shellId: ShellId; - }; - path?: never; - query?: never; - url: '/shell/terminate'; -}; - -export type ShellTerminateErrors = { - /** - * Error terminating shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellTerminateError = ShellTerminateErrors[keyof ShellTerminateErrors]; - -export type ShellTerminateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: ShellDto; - }; -}; - -export type ShellTerminateResponse = ShellTerminateResponses[keyof ShellTerminateResponses]; - -export type ShellResizeData = { - body: { - shellId: ShellId; - size: ShellSize; - }; - path?: never; - query?: never; - url: '/shell/resize'; -}; - -export type ShellResizeErrors = { - /** - * Error resizing shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellResizeError = ShellResizeErrors[keyof ShellResizeErrors]; - -export type ShellResizeResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type ShellResizeResponse = ShellResizeResponses[keyof ShellResizeResponses]; - -export type ShellRenameData = { - body: { - shellId: ShellId; - /** - * New name for the shell - */ - name: string; - }; - path?: never; - query?: never; - url: '/shell/rename'; -}; - -export type ShellRenameErrors = { - /** - * Error renaming shell - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type ShellRenameError = ShellRenameErrors[keyof ShellRenameErrors]; - -export type ShellRenameResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type ShellRenameResponse = ShellRenameResponses[keyof ShellRenameResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/client.gen.ts b/src/api-clients/client-rest-system/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-system/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-system/index.ts b/src/api-clients/client-rest-system/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-system/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/sdk.gen.ts b/src/api-clients/client-rest-system/sdk.gen.ts deleted file mode 100644 index d1f450e..0000000 --- a/src/api-clients/client-rest-system/sdk.gen.ts +++ /dev/null @@ -1,59 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { SystemUpdateData, SystemUpdateResponse, SystemUpdateError, SystemHibernateData, SystemHibernateResponse, SystemHibernateError, SystemMetricsData, SystemMetricsResponse, SystemMetricsError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * Update system - * Update the sandbox system - */ -export const systemUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/update', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Hibernate system - * Put the sandbox system into hibernation mode - */ -export const systemHibernate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/hibernate', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Get system metrics - * Retrieve current system metrics including CPU, memory and storage usage - */ -export const systemMetrics = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/system/metrics', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-system/types.gen.ts b/src/api-clients/client-rest-system/types.gen.ts deleted file mode 100644 index f23b7f1..0000000 --- a/src/api-clients/client-rest-system/types.gen.ts +++ /dev/null @@ -1,207 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type SystemError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type SystemMetricsStatus = { - cpu: { - /** - * Number of CPU cores - */ - cores: number; - /** - * Used CPU resources - */ - used: number; - /** - * Configured CPU resources - */ - configured: number; - }; - memory: { - /** - * Used memory in bytes - */ - used: number; - /** - * Total available memory in bytes - */ - total: number; - /** - * Configured memory limit in bytes - */ - configured: number; - }; - storage: { - /** - * Used storage in bytes - */ - used: number; - /** - * Total available storage in bytes - */ - total: number; - /** - * Configured storage limit in bytes - */ - configured: number; - }; -}; - -export type InitStatus = { - /** - * Status message - */ - message: string; - /** - * Whether the status represents an error - */ - isError?: boolean | null; - /** - * Current progress (0-100) - */ - progress: number; - /** - * Next progress target (0-100) - */ - nextProgress: number; - /** - * Standard output from the initialization process - */ - stdout?: string | null; -}; - -export type SystemUpdateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/update'; -}; - -export type SystemUpdateErrors = { - /** - * Error updating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; -}; - -export type SystemUpdateError = SystemUpdateErrors[keyof SystemUpdateErrors]; - -export type SystemUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: { - [key: string]: unknown; - }; - }; -}; - -export type SystemUpdateResponse = SystemUpdateResponses[keyof SystemUpdateResponses]; - -export type SystemHibernateData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/hibernate'; -}; - -export type SystemHibernateErrors = { - /** - * Error hibernating system - */ - 400: ErrorResponse & { - error?: SystemError; - }; -}; - -export type SystemHibernateError = SystemHibernateErrors[keyof SystemHibernateErrors]; - -export type SystemHibernateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type SystemHibernateResponse = SystemHibernateResponses[keyof SystemHibernateResponses]; - -export type SystemMetricsData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/system/metrics'; -}; - -export type SystemMetricsErrors = { - /** - * Error retrieving system metrics - */ - 400: ErrorResponse & { - error?: SystemError; - }; -}; - -export type SystemMetricsError = SystemMetricsErrors[keyof SystemMetricsErrors]; - -export type SystemMetricsResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: SystemMetricsStatus; - }; -}; - -export type SystemMetricsResponse = SystemMetricsResponses[keyof SystemMetricsResponses]; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/client.gen.ts b/src/api-clients/client-rest-task/client.gen.ts deleted file mode 100644 index 1822a95..0000000 --- a/src/api-clients/client-rest-task/client.gen.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig } from '@hey-api/client-fetch'; - -export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/api-clients/client-rest-task/index.ts b/src/api-clients/client-rest-task/index.ts deleted file mode 100644 index e64537d..0000000 --- a/src/api-clients/client-rest-task/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/sdk.gen.ts b/src/api-clients/client-rest-task/sdk.gen.ts deleted file mode 100644 index 133e8f8..0000000 --- a/src/api-clients/client-rest-task/sdk.gen.ts +++ /dev/null @@ -1,149 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { TaskListData, TaskListResponse, TaskListError, TaskRunData, TaskRunResponse, TaskRunError, TaskRunCommandData, TaskRunCommandResponse, TaskRunCommandError, TaskStopData, TaskStopResponse, TaskStopError, TaskCreateData, TaskCreateResponse, TaskCreateError, TaskUpdateData, TaskUpdateResponse, TaskUpdateError, TaskSaveToConfigData, TaskSaveToConfigResponse, TaskSaveToConfigError, TaskGenerateConfigData, TaskGenerateConfigResponse, TaskGenerateConfigError, TaskCreateSetupTasksData, TaskCreateSetupTasksResponse, TaskCreateSetupTasksError } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; - -export type Options = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; -}; - -/** - * List tasks - * Retrieve a list of all configured tasks - */ -export const taskList = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/list', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Run task - * Start execution of a task by ID - */ -export const taskRun = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/run', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Run command - * Run a shell command directly, optionally saving it as a task - */ -export const taskRunCommand = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/runCommand', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Stop task - * Stop execution of a running task - */ -export const taskStop = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/stop', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Create task - * Create a new task configuration - */ -export const taskCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/create', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Update task - * Update an existing task configuration - */ -export const taskUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/update', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Save task to config - * Save a runtime task to the configuration file - */ -export const taskSaveToConfig = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/saveToConfig', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Generate task config - * Generate a configuration file from current tasks - */ -export const taskGenerateConfig = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/generateConfig', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - -/** - * Create setup tasks - * Create tasks that run during sandbox setup - */ -export const taskCreateSetupTasks = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/task/createSetupTasks', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; \ No newline at end of file diff --git a/src/api-clients/client-rest-task/types.gen.ts b/src/api-clients/client-rest-task/types.gen.ts deleted file mode 100644 index 012bd28..0000000 --- a/src/api-clients/client-rest-task/types.gen.ts +++ /dev/null @@ -1,507 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type SuccessResponse = { - /** - * Status code for successful operations - */ - status: 0; - /** - * Result payload for the operation - */ - result: { - [key: string]: unknown; - }; -}; - -export type ErrorResponse = { - /** - * Status code for error operations - */ - status: 1; - /** - * Error details - */ - error: { - [key: string]: unknown; - }; -}; - -export type CommonError = { - /** - * Error code - */ - code: number; - /** - * Error message - */ - message?: string; - /** - * Additional error data - */ - data?: { - [key: string]: unknown; - } | null; -}; - -export type TaskError = { - /** - * CONFIG_FILE_ALREADY_EXISTS error code - */ - code: 600; - /** - * Error message - */ - message: string; -} | { - /** - * TASK_NOT_FOUND error code - */ - code: 601; - /** - * Error message - */ - message: string; -} | { - /** - * COMMAND_ALREADY_CONFIGURED error code - */ - code: 602; - /** - * Error message - */ - message: string; -} | ({ - code?: 'CommonError'; -} & CommonError); - -export type TaskDefinitionDto = { - /** - * Name of the task - */ - name: string; - /** - * Command to run for the task - */ - command: string; - /** - * Whether the task should run when the sandbox starts - */ - runAtStart?: boolean | null; - preview?: { - /** - * Port to preview from this task - */ - port?: number | null; - /** - * Type of PR link to use - */ - 'pr-link'?: 'direct' | 'redirect' | 'devtool'; - } | null; -}; - -export type CommandShellDto = { - /** - * ID of the shell command - */ - id: string; - /** - * Command being executed - */ - command: string; - /** - * Current status of the shell command - */ - status: 'initializing' | 'running' | 'stopped' | 'error'; - /** - * Current output of the command - */ - output: string; -}; - -export type Port = { - /** - * Port number - */ - port: number; - /** - * Hostname the port is bound to - */ - hostname: string; - /** - * Current status of the port - */ - status: 'open' | 'closed'; - /** - * ID of the task that opened this port - */ - taskId?: string | null; -}; - -export type TaskDto = TaskDefinitionDto & { - /** - * Unique ID of the task - */ - id: string; - /** - * Whether this task is unconfigured (not saved in config) - */ - unconfigured?: boolean | null; - shell: CommandShellDto | null; - /** - * Ports opened by this task - */ - ports: Array; -}; - -export type TaskListDto = { - /** - * Map of task IDs to task objects - */ - tasks: { - [key: string]: TaskDto; - }; - /** - * Tasks that run during sandbox setup - */ - setupTasks: Array; - /** - * Validation errors in the task configuration - */ - validationErrors: Array; -}; - -export type TaskListData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/task/list'; -}; - -export type TaskListErrors = { - /** - * Error retrieving task list - */ - 400: ErrorResponse & { - error?: CommonError; - }; -}; - -export type TaskListError = TaskListErrors[keyof TaskListErrors]; - -export type TaskListResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; -}; - -export type TaskListResponse = TaskListResponses[keyof TaskListResponses]; - -export type TaskRunData = { - body: { - /** - * ID of the task to run - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/run'; -}; - -export type TaskRunErrors = { - /** - * Error running task - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskRunError = TaskRunErrors[keyof TaskRunErrors]; - -export type TaskRunResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; -}; - -export type TaskRunResponse = TaskRunResponses[keyof TaskRunResponses]; - -export type TaskRunCommandData = { - body: { - /** - * Command to run - */ - command: string; - /** - * Optional name for the task - */ - name?: string | null; - /** - * Whether to save this command as a task in the config - */ - saveToConfig?: boolean | null; - }; - path?: never; - query?: never; - url: '/task/runCommand'; -}; - -export type TaskRunCommandErrors = { - /** - * Error running command - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskRunCommandError = TaskRunCommandErrors[keyof TaskRunCommandErrors]; - -export type TaskRunCommandResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; -}; - -export type TaskRunCommandResponse = TaskRunCommandResponses[keyof TaskRunCommandResponses]; - -export type TaskStopData = { - body: { - /** - * ID of the task to stop - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/stop'; -}; - -export type TaskStopErrors = { - /** - * Error stopping task - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskStopError = TaskStopErrors[keyof TaskStopErrors]; - -export type TaskStopResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto | unknown; - }; -}; - -export type TaskStopResponse = TaskStopResponses[keyof TaskStopResponses]; - -export type TaskCreateData = { - body: { - taskFields: TaskDefinitionDto; - /** - * Whether to start the task immediately after creation - */ - startTask?: boolean | null; - }; - path?: never; - query?: never; - url: '/task/create'; -}; - -export type TaskCreateErrors = { - /** - * Error creating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskCreateError = TaskCreateErrors[keyof TaskCreateErrors]; - -export type TaskCreateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskListDto; - }; -}; - -export type TaskCreateResponse = TaskCreateResponses[keyof TaskCreateResponses]; - -export type TaskUpdateData = { - body: { - /** - * ID of the task to update - */ - taskId: string; - /** - * Fields to update in the task - */ - taskFields: { - /** - * Name of the task - */ - name?: string | null; - /** - * Command to run - */ - command?: string | null; - /** - * Whether to run the task at sandbox start - */ - runAtStart?: boolean | null; - preview?: { - /** - * Port to use for previewing the task - */ - port?: number | null; - /** - * Type of PR link to use - */ - 'pr-link'?: 'direct' | 'redirect' | 'devtool'; - } | null; - }; - }; - path?: never; - query?: never; - url: '/task/update'; -}; - -export type TaskUpdateErrors = { - /** - * Error updating task - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskUpdateError = TaskUpdateErrors[keyof TaskUpdateErrors]; - -export type TaskUpdateResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; -}; - -export type TaskUpdateResponse = TaskUpdateResponses[keyof TaskUpdateResponses]; - -export type TaskSaveToConfigData = { - body: { - /** - * ID of the task to save to config - */ - taskId: string; - }; - path?: never; - query?: never; - url: '/task/saveToConfig'; -}; - -export type TaskSaveToConfigErrors = { - /** - * Error saving task to config - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskSaveToConfigError = TaskSaveToConfigErrors[keyof TaskSaveToConfigErrors]; - -export type TaskSaveToConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: TaskDto; - }; -}; - -export type TaskSaveToConfigResponse = TaskSaveToConfigResponses[keyof TaskSaveToConfigResponses]; - -export type TaskGenerateConfigData = { - body: { - [key: string]: unknown; - }; - path?: never; - query?: never; - url: '/task/generateConfig'; -}; - -export type TaskGenerateConfigErrors = { - /** - * Error generating config - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskGenerateConfigError = TaskGenerateConfigErrors[keyof TaskGenerateConfigErrors]; - -export type TaskGenerateConfigResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type TaskGenerateConfigResponse = TaskGenerateConfigResponses[keyof TaskGenerateConfigResponses]; - -export type TaskCreateSetupTasksData = { - body: { - /** - * Setup tasks to create - */ - tasks: Array; - }; - path?: never; - query?: never; - url: '/task/createSetupTasks'; -}; - -export type TaskCreateSetupTasksErrors = { - /** - * Error creating setup tasks - */ - 400: ErrorResponse & { - error?: TaskError; - }; -}; - -export type TaskCreateSetupTasksError = TaskCreateSetupTasksErrors[keyof TaskCreateSetupTasksErrors]; - -export type TaskCreateSetupTasksResponses = { - /** - * Successful operation - */ - 200: SuccessResponse & { - result?: unknown; - }; -}; - -export type TaskCreateSetupTasksResponse = TaskCreateSetupTasksResponses[keyof TaskCreateSetupTasksResponses]; \ No newline at end of file diff --git a/src/api-clients/client/client.gen.ts b/src/api-clients/client/client.gen.ts index 1822a95..eb153b1 100644 --- a/src/api-clients/client/client.gen.ts +++ b/src/api-clients/client/client.gen.ts @@ -1,5 +1,18 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createClient, createConfig } from '@hey-api/client-fetch'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; -export const client = createClient(createConfig()); \ No newline at end of file +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'https://api.codesandbox.io' +})); diff --git a/src/api-clients/client/client/client.gen.ts b/src/api-clients/client/client/client.gen.ts new file mode 100644 index 0000000..a439d27 --- /dev/null +++ b/src/api-clients/client/client/client.gen.ts @@ -0,0 +1,268 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + const beforeRequest = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const url = buildUrl(opts); + + return { opts, url }; + }; + + const request: Client['request'] = async (options) => { + // @ts-expect-error + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response = await _fetch(request); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await response[parseAs](); + break; + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + const error = jsonError ?? textError; + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + ...result, + }; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/src/api-clients/client/client/index.ts b/src/api-clients/client/client/index.ts new file mode 100644 index 0000000..318a84b --- /dev/null +++ b/src/api-clients/client/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/src/api-clients/client/client/types.gen.ts b/src/api-clients/client/client/types.gen.ts new file mode 100644 index 0000000..1a005b5 --- /dev/null +++ b/src/api-clients/client/client/types.gen.ts @@ -0,0 +1,268 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/src/api-clients/client/client/utils.gen.ts b/src/api-clients/client/client/utils.gen.ts new file mode 100644 index 0000000..b4bcc4d --- /dev/null +++ b/src/api-clients/client/client/utils.gen.ts @@ -0,0 +1,331 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/src/api-clients/client/core/auth.gen.ts b/src/api-clients/client/core/auth.gen.ts new file mode 100644 index 0000000..f8a7326 --- /dev/null +++ b/src/api-clients/client/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/src/api-clients/client/core/bodySerializer.gen.ts b/src/api-clients/client/core/bodySerializer.gen.ts new file mode 100644 index 0000000..49cd892 --- /dev/null +++ b/src/api-clients/client/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/src/api-clients/client/core/params.gen.ts b/src/api-clients/client/core/params.gen.ts new file mode 100644 index 0000000..71c88e8 --- /dev/null +++ b/src/api-clients/client/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/src/api-clients/client/core/pathSerializer.gen.ts b/src/api-clients/client/core/pathSerializer.gen.ts new file mode 100644 index 0000000..8d99931 --- /dev/null +++ b/src/api-clients/client/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/src/api-clients/client/core/serverSentEvents.gen.ts b/src/api-clients/client/core/serverSentEvents.gen.ts new file mode 100644 index 0000000..f8fd78e --- /dev/null +++ b/src/api-clients/client/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/src/api-clients/client/core/types.gen.ts b/src/api-clients/client/core/types.gen.ts new file mode 100644 index 0000000..643c070 --- /dev/null +++ b/src/api-clients/client/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/src/api-clients/client/core/utils.gen.ts b/src/api-clients/client/core/utils.gen.ts new file mode 100644 index 0000000..0b5389d --- /dev/null +++ b/src/api-clients/client/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/src/api-clients/client/index.ts b/src/api-clients/client/index.ts index e64537d..c352c10 100644 --- a/src/api-clients/client/index.ts +++ b/src/api-clients/client/index.ts @@ -1,3 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; -export * from './sdk.gen'; \ No newline at end of file + +export type * from './types.gen'; +export * from './sdk.gen'; diff --git a/src/api-clients/client/sdk.gen.ts b/src/api-clients/client/sdk.gen.ts index b2ab6bf..a9a0dee 100644 --- a/src/api-clients/client/sdk.gen.ts +++ b/src/api-clients/client/sdk.gen.ts @@ -1,23 +1,28 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { MetaInfoData, MetaInfoResponse, WorkspaceCreateData, WorkspaceCreateResponse2, TokenCreateData, TokenCreateResponse2, TokenUpdateData, TokenUpdateResponse2, SandboxListData, SandboxListResponse2, SandboxCreateData, SandboxCreateResponse2, SandboxGetData, SandboxGetResponse2, SandboxForkData, SandboxForkResponse2, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponse2, PreviewTokenListData, PreviewTokenListResponse2, PreviewTokenCreateData, PreviewTokenCreateResponse2, PreviewTokenUpdateData, PreviewTokenUpdateResponse2, TemplatesCreateData, TemplatesCreateResponse, VmAssignTagAliasData, VmAssignTagAliasResponse2, VmListClustersData, VmListClustersResponse2, VmListRunningVmsData, VmListRunningVmsResponse2, VmCreateTagData, VmCreateTagResponse2, VmDeleteData, VmDeleteResponse2, VmHibernateData, VmHibernateResponse2, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponse2, VmCreateSessionData, VmCreateSessionResponse2, VmShutdownData, VmShutdownResponse2, VmUpdateSpecsData, VmUpdateSpecsResponse2, VmStartData, VmStartResponse2, VmUpdateSpecs2Data, VmUpdateSpecs2Response, PreviewHostListData, PreviewHostListResponse2, PreviewHostCreateData, PreviewHostCreateResponse, PreviewHostUpdateData, PreviewHostUpdateResponse } from './types.gen'; -import { client as _heyApiClient } from './client.gen'; +import type { Client, Options as Options2, TDataShape } from './client'; +import { client } from './client.gen'; +import type { MetaInfoData, MetaInfoResponses, PreviewHostCreateData, PreviewHostCreateResponses, PreviewHostListData, PreviewHostListResponses, PreviewHostUpdateData, PreviewHostUpdateResponses, PreviewTokenCreateData, PreviewTokenCreateResponses, PreviewTokenListData, PreviewTokenListResponses, PreviewTokenRevokeAllData, PreviewTokenRevokeAllResponses, PreviewTokenUpdateData, PreviewTokenUpdateResponses, SandboxCreateData, SandboxCreateResponses, SandboxForkData, SandboxForkResponses, SandboxGetData, SandboxGetResponses, SandboxListData, SandboxListResponses, TemplatesCreateData, TemplatesCreateResponses, TokenCreateData, TokenCreateResponses, TokenUpdateData, TokenUpdateResponses, VmAssignTagAliasData, VmAssignTagAliasResponses, VmCreateSessionData, VmCreateSessionResponses, VmCreateTagData, VmCreateTagResponses, VmDeleteData, VmDeleteResponses, VmHibernateData, VmHibernateResponses, VmListClustersData, VmListClustersResponses, VmListRunningVmsData, VmListRunningVmsResponses, VmShutdownData, VmShutdownResponses, VmStartData, VmStartResponses, VmUpdateHibernationTimeoutData, VmUpdateHibernationTimeoutResponses, VmUpdateSpecs2Data, VmUpdateSpecs2Responses, VmUpdateSpecsData, VmUpdateSpecsResponses, WorkspaceCreateData, WorkspaceCreateResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a * custom client. */ client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; }; /** * Metadata about the API */ export const metaInfo = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? client).get({ url: '/meta/info', ...options }); @@ -29,7 +34,7 @@ export const metaInfo = (options?: Options * */ export const workspaceCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? client).post({ security: [ { scheme: 'bearer', @@ -51,7 +56,7 @@ export const workspaceCreate = (options?: * */ export const tokenCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -62,7 +67,7 @@ export const tokenCreate = (options: Optio ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -73,7 +78,7 @@ export const tokenCreate = (options: Optio * */ export const tokenUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).patch({ + return (options.client ?? client).patch({ security: [ { scheme: 'bearer', @@ -84,7 +89,7 @@ export const tokenUpdate = (options: Optio ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -96,7 +101,7 @@ export const tokenUpdate = (options: Optio * */ export const sandboxList = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? client).get({ security: [ { scheme: 'bearer', @@ -114,7 +119,7 @@ export const sandboxList = (options?: Opti * */ export const sandboxCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? client).post({ security: [ { scheme: 'bearer', @@ -136,7 +141,7 @@ export const sandboxCreate = (options?: Op * */ export const sandboxGet = (options: Options) => { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? client).get({ security: [ { scheme: 'bearer', @@ -154,7 +159,7 @@ export const sandboxGet = (options: Option * */ export const sandboxFork = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -165,7 +170,7 @@ export const sandboxFork = (options: Optio ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -176,7 +181,7 @@ export const sandboxFork = (options: Optio * */ export const previewTokenRevokeAll = (options: Options) => { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? client).delete({ security: [ { scheme: 'bearer', @@ -194,7 +199,7 @@ export const previewTokenRevokeAll = (opti * */ export const previewTokenList = (options: Options) => { - return (options.client ?? _heyApiClient).get({ + return (options.client ?? client).get({ security: [ { scheme: 'bearer', @@ -212,7 +217,7 @@ export const previewTokenList = (options: * */ export const previewTokenCreate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -223,7 +228,7 @@ export const previewTokenCreate = (options ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -234,7 +239,7 @@ export const previewTokenCreate = (options * */ export const previewTokenUpdate = (options: Options) => { - return (options.client ?? _heyApiClient).patch({ + return (options.client ?? client).patch({ security: [ { scheme: 'bearer', @@ -245,7 +250,7 @@ export const previewTokenUpdate = (options ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -256,7 +261,7 @@ export const previewTokenUpdate = (options * */ export const templatesCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? client).post({ security: [ { scheme: 'bearer', @@ -278,7 +283,7 @@ export const templatesCreate = (options?: * */ export const vmAssignTagAlias = (options: Options) => { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? client).put({ security: [ { scheme: 'bearer', @@ -289,7 +294,7 @@ export const vmAssignTagAlias = (options: ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -300,7 +305,7 @@ export const vmAssignTagAlias = (options: * */ export const vmListClusters = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? client).get({ security: [ { scheme: 'bearer', @@ -318,7 +323,7 @@ export const vmListClusters = (options?: O * */ export const vmListRunningVms = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? client).get({ security: [ { scheme: 'bearer', @@ -336,7 +341,7 @@ export const vmListRunningVms = (options?: * */ export const vmCreateTag = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? client).post({ security: [ { scheme: 'bearer', @@ -361,7 +366,7 @@ export const vmCreateTag = (options?: Opti * */ export const vmDelete = (options: Options) => { - return (options.client ?? _heyApiClient).delete({ + return (options.client ?? client).delete({ security: [ { scheme: 'bearer', @@ -385,7 +390,7 @@ export const vmDelete = (options: Options< * */ export const vmHibernate = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -396,7 +401,7 @@ export const vmHibernate = (options: Optio ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -410,7 +415,7 @@ export const vmHibernate = (options: Optio * */ export const vmUpdateHibernationTimeout = (options: Options) => { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? client).put({ security: [ { scheme: 'bearer', @@ -421,7 +426,7 @@ export const vmUpdateHibernationTimeout = ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -438,7 +443,7 @@ export const vmUpdateHibernationTimeout = * */ export const vmCreateSession = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -449,7 +454,7 @@ export const vmCreateSession = (options: O ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -465,7 +470,7 @@ export const vmCreateSession = (options: O * */ export const vmShutdown = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -476,7 +481,7 @@ export const vmShutdown = (options: Option ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -490,7 +495,7 @@ export const vmShutdown = (options: Option * */ export const vmUpdateSpecs = (options: Options) => { - return (options.client ?? _heyApiClient).put({ + return (options.client ?? client).put({ security: [ { scheme: 'bearer', @@ -501,7 +506,7 @@ export const vmUpdateSpecs = (options: Opt ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -519,7 +524,7 @@ export const vmUpdateSpecs = (options: Opt * */ export const vmStart = (options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -530,7 +535,7 @@ export const vmStart = (options: Options(options: Options(options: Options) => { - return (options.client ?? _heyApiClient).post({ + return (options.client ?? client).post({ security: [ { scheme: 'bearer', @@ -555,7 +560,7 @@ export const vmUpdateSpecs2 = (options: Op ...options, headers: { 'Content-Type': 'application/json', - ...options?.headers + ...options.headers } }); }; @@ -566,7 +571,7 @@ export const vmUpdateSpecs2 = (options: Op * */ export const previewHostList = (options?: Options) => { - return (options?.client ?? _heyApiClient).get({ + return (options?.client ?? client).get({ security: [ { scheme: 'bearer', @@ -584,7 +589,7 @@ export const previewHostList = (options?: * */ export const previewHostCreate = (options?: Options) => { - return (options?.client ?? _heyApiClient).post({ + return (options?.client ?? client).post({ security: [ { scheme: 'bearer', @@ -606,7 +611,7 @@ export const previewHostCreate = (options? * */ export const previewHostUpdate = (options?: Options) => { - return (options?.client ?? _heyApiClient).put({ + return (options?.client ?? client).put({ security: [ { scheme: 'bearer', @@ -620,4 +625,4 @@ export const previewHostUpdate = (options? ...options?.headers } }); -}; \ No newline at end of file +}; diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index 0de3dac..ee3c79a 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -1,13 +1,29 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'https://api.codesandbox.io' | (string & {}); +}; + +/** + * Response + */ export type Response = { + /** + * Error + */ errors?: Array; success?: boolean; }; +/** + * PreviewTokenListResponse + */ export type PreviewTokenListResponse = { + /** + * Error + */ errors?: Array; @@ -19,6 +35,9 @@ export type PreviewTokenListResponse = { }; }; +/** + * VMUpdateHibernationTimeoutRequest + */ export type VmUpdateHibernationTimeoutRequest = { /** * The new hibernation timeout in seconds. @@ -29,7 +48,13 @@ export type VmUpdateHibernationTimeoutRequest = { hibernation_timeout_seconds: number; }; +/** + * VMAssignTagAliasResponse + */ export type VmAssignTagAliasResponse = { + /** + * Error + */ errors?: Array; @@ -44,6 +69,9 @@ export type VmAssignTagAliasResponse = { }; }; +/** + * TemplateCreateRequest + */ export type TemplateCreateRequest = { /** * Template description. Maximum 255 characters. Defaults to description of original sandbox. @@ -63,6 +91,9 @@ export type TemplateCreateRequest = { title?: string; }; +/** + * PreviewToken + */ export type PreviewToken = { expires_at: string | null; last_used_at: string | null; @@ -70,7 +101,13 @@ export type PreviewToken = { token_prefix: string; }; +/** + * VMShutdownResponse + */ export type VmShutdownResponse = { + /** + * Error + */ errors?: Array; @@ -81,7 +118,13 @@ export type VmShutdownResponse = { }; }; +/** + * PreviewTokenRevokeAllResponse + */ export type PreviewTokenRevokeAllResponse = { + /** + * Error + */ errors?: Array; @@ -92,25 +135,40 @@ export type PreviewTokenRevokeAllResponse = { }; }; +/** + * Sandbox + */ export type Sandbox = { created_at: string; description?: string | null; id: string; is_frozen: boolean; privacy: number; + settings: { + use_pint?: boolean; + }; tags: Array; title?: string | null; updated_at: string; }; +/** + * Error + */ export type _Error = string | { [key: string]: unknown; }; +/** + * VMHibernateRequest + */ export type VmHibernateRequest = { [key: string]: unknown; }; +/** + * PreviewTokenCreateRequest + */ export type PreviewTokenCreateRequest = { /** * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. @@ -118,7 +176,13 @@ export type PreviewTokenCreateRequest = { expires_at?: string | null; }; +/** + * VMCreateTagResponse + */ export type VmCreateTagResponse = { + /** + * Error + */ errors?: Array; @@ -129,7 +193,13 @@ export type VmCreateTagResponse = { }; }; +/** + * VMListRunningVMsResponse + */ export type VmListRunningVmsResponse = { + /** + * Error + */ errors?: Array; @@ -152,7 +222,13 @@ export type VmListRunningVmsResponse = { }; }; +/** + * SandboxGetResponse + */ export type SandboxGetResponse = { + /** + * Error + */ errors?: Array; @@ -161,6 +237,9 @@ export type SandboxGetResponse = { data?: Sandbox; }; +/** + * SandboxForkRequest + */ export type SandboxForkRequest = { /** * Sandbox description. Maximum 255 characters. Defaults to description of original sandbox. @@ -230,7 +309,13 @@ export type SandboxForkRequest = { title?: string; }; +/** + * SandboxListResponse + */ export type SandboxListResponse = { + /** + * Error + */ errors?: Array; @@ -249,6 +334,9 @@ export type SandboxListResponse = { }; }; +/** + * MetaInformation + */ export type MetaInformation = { /** * Meta information about the CodeSandbox API @@ -287,6 +375,7 @@ export type MetaInformation = { }; /** + * TokenUpdateRequest * Updateable fields for API Tokens. Omitting a field will not update it; explicitly passing null or an empty list will clear the value. */ export type TokenUpdateRequest = { @@ -309,13 +398,20 @@ export type TokenUpdateRequest = { }; /** + * VMAssignTagAliasRequest * Assign a tag alias to a VM */ export type VmAssignTagAliasRequest = { tag_id: string; }; +/** + * VMHibernateResponse + */ export type VmHibernateResponse = { + /** + * Error + */ errors?: Array; @@ -326,7 +422,13 @@ export type VmHibernateResponse = { }; }; +/** + * PreviewTokenUpdateResponse + */ export type PreviewTokenUpdateResponse = { + /** + * Error + */ errors?: Array; @@ -338,7 +440,13 @@ export type PreviewTokenUpdateResponse = { }; }; +/** + * SandboxCreateResponse + */ export type SandboxCreateResponse = { + /** + * Error + */ errors?: Array; @@ -351,6 +459,9 @@ export type SandboxCreateResponse = { }; }; +/** + * VMStartRequest + */ export type VmStartRequest = { /** * Configuration for when the VM should automatically wake up from hibernation @@ -387,6 +498,9 @@ export type VmStartRequest = { tier?: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; +/** + * PreviewTokenUpdateRequest + */ export type PreviewTokenUpdateRequest = { /** * UTC Timestamp until when this token is valid. Omitting this field will create a token without an expiry. @@ -394,7 +508,13 @@ export type PreviewTokenUpdateRequest = { expires_at?: string | null; }; +/** + * PreviewHostListResponse + */ export type PreviewHostListResponse = { + /** + * Error + */ errors?: Array; @@ -408,10 +528,16 @@ export type PreviewHostListResponse = { }; }; +/** + * VMShutdownRequest + */ export type VmShutdownRequest = { [key: string]: unknown; }; +/** + * VMUpdateSpecsRequest + */ export type VmUpdateSpecsRequest = { /** * Determines which specs to update the VM with. @@ -422,6 +548,9 @@ export type VmUpdateSpecsRequest = { tier: 'Pico' | 'Nano' | 'Micro' | 'Small' | 'Medium' | 'Large' | 'XLarge'; }; +/** + * WorkspaceCreateRequest + */ export type WorkspaceCreateRequest = { /** * Name for the new workspace. Maximum length 64 characters. @@ -430,17 +559,27 @@ export type WorkspaceCreateRequest = { }; /** + * VMCreateTagRequest * Create a tag for a list of VM IDs */ export type VmCreateTagRequest = { vm_ids: Array; }; +/** + * PreviewHostRequest + */ export type PreviewHostRequest = { hosts: Array; }; +/** + * VMStartResponse + */ export type VmStartResponse = { + /** + * Error + */ errors?: Array; @@ -456,12 +595,19 @@ export type VmStartResponse = { pitcher_url: string; pitcher_version: string; reconnect_token: string; + use_pint: boolean; user_workspace_path: string; workspace_path: string; }; }; +/** + * VMUpdateSpecsResponse + */ export type VmUpdateSpecsResponse = { + /** + * Error + */ errors?: Array; @@ -473,6 +619,9 @@ export type VmUpdateSpecsResponse = { }; }; +/** + * SandboxCreateRequest + */ export type SandboxCreateRequest = { /** * Optional text description of the sandbox. Defaults to no description. @@ -554,7 +703,13 @@ export type SandboxCreateRequest = { title?: string; }; +/** + * VMListClustersResponse + */ export type VmListClustersResponse = { + /** + * Error + */ errors?: Array; @@ -568,7 +723,13 @@ export type VmListClustersResponse = { }; }; +/** + * TokenUpdateResponse + */ export type TokenUpdateResponse = { + /** + * Error + */ errors?: Array; @@ -583,7 +744,13 @@ export type TokenUpdateResponse = { }; }; +/** + * TokenCreateResponse + */ export type TokenCreateResponse = { + /** + * Error + */ errors?: Array; @@ -599,7 +766,13 @@ export type TokenCreateResponse = { }; }; +/** + * VMDeleteResponse + */ export type VmDeleteResponse = { + /** + * Error + */ errors?: Array; @@ -610,7 +783,13 @@ export type VmDeleteResponse = { }; }; +/** + * TemplateCreateResponse + */ export type TemplateCreateResponse = { + /** + * Error + */ errors?: Array; @@ -625,6 +804,9 @@ export type TemplateCreateResponse = { }; }; +/** + * TokenCreateRequest + */ export type TokenCreateRequest = { /** * API Version to use, formatted as YYYY-MM-DD. Defaults to the latest version at time of creation. @@ -644,6 +826,9 @@ export type TokenCreateRequest = { scopes?: Array<'sandbox_create' | 'sandbox_edit_code' | 'sandbox_read' | 'vm_manage'>; }; +/** + * VMCreateSessionRequest + */ export type VmCreateSessionRequest = { /** * GitHub token for the session @@ -667,7 +852,13 @@ export type VmCreateSessionRequest = { session_id: string; }; +/** + * VMCreateSessionResponse + */ export type VmCreateSessionResponse = { + /** + * Error + */ errors?: Array; @@ -703,7 +894,13 @@ export type VmCreateSessionResponse = { }; }; +/** + * WorkspaceCreateResponse + */ export type WorkspaceCreateResponse = { + /** + * Error + */ errors?: Array; @@ -715,7 +912,13 @@ export type WorkspaceCreateResponse = { }; }; +/** + * VMUpdateHibernationTimeoutResponse + */ export type VmUpdateHibernationTimeoutResponse = { + /** + * Error + */ errors?: Array; @@ -727,7 +930,13 @@ export type VmUpdateHibernationTimeoutResponse = { }; }; +/** + * SandboxForkResponse + */ export type SandboxForkResponse = { + /** + * Error + */ errors?: Array; @@ -749,6 +958,7 @@ export type SandboxForkResponse = { pitcher_url: string; pitcher_version: string; reconnect_token: string; + use_pint: boolean; user_workspace_path: string; workspace_path: string; } | null; @@ -756,7 +966,13 @@ export type SandboxForkResponse = { }; }; +/** + * PreviewTokenCreateResponse + */ export type PreviewTokenCreateResponse = { + /** + * Error + */ errors?: Array; @@ -1400,4 +1616,4 @@ export type PreviewHostUpdateResponses = { 201: PreviewHostListResponse; }; -export type PreviewHostUpdateResponse = PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; \ No newline at end of file +export type PreviewHostUpdateResponse = PreviewHostUpdateResponses[keyof PreviewHostUpdateResponses]; diff --git a/src/api-clients/pint/client.gen.ts b/src/api-clients/pint/client.gen.ts new file mode 100644 index 0000000..4a7b977 --- /dev/null +++ b/src/api-clients/pint/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:57468' +})); diff --git a/src/api-clients/pint/client/client.gen.ts b/src/api-clients/pint/client/client.gen.ts new file mode 100644 index 0000000..a439d27 --- /dev/null +++ b/src/api-clients/pint/client/client.gen.ts @@ -0,0 +1,268 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + const beforeRequest = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const url = buildUrl(opts); + + return { opts, url }; + }; + + const request: Client['request'] = async (options) => { + // @ts-expect-error + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response = await _fetch(request); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await response[parseAs](); + break; + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + const error = jsonError ?? textError; + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + ...result, + }; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/src/api-clients/pint/client/index.ts b/src/api-clients/pint/client/index.ts new file mode 100644 index 0000000..318a84b --- /dev/null +++ b/src/api-clients/pint/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/src/api-clients/pint/client/types.gen.ts b/src/api-clients/pint/client/types.gen.ts new file mode 100644 index 0000000..1a005b5 --- /dev/null +++ b/src/api-clients/pint/client/types.gen.ts @@ -0,0 +1,268 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/src/api-clients/pint/client/utils.gen.ts b/src/api-clients/pint/client/utils.gen.ts new file mode 100644 index 0000000..b4bcc4d --- /dev/null +++ b/src/api-clients/pint/client/utils.gen.ts @@ -0,0 +1,331 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/src/api-clients/pint/core/auth.gen.ts b/src/api-clients/pint/core/auth.gen.ts new file mode 100644 index 0000000..f8a7326 --- /dev/null +++ b/src/api-clients/pint/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/src/api-clients/pint/core/bodySerializer.gen.ts b/src/api-clients/pint/core/bodySerializer.gen.ts new file mode 100644 index 0000000..49cd892 --- /dev/null +++ b/src/api-clients/pint/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/src/api-clients/pint/core/params.gen.ts b/src/api-clients/pint/core/params.gen.ts new file mode 100644 index 0000000..71c88e8 --- /dev/null +++ b/src/api-clients/pint/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/src/api-clients/pint/core/pathSerializer.gen.ts b/src/api-clients/pint/core/pathSerializer.gen.ts new file mode 100644 index 0000000..8d99931 --- /dev/null +++ b/src/api-clients/pint/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/src/api-clients/pint/core/serverSentEvents.gen.ts b/src/api-clients/pint/core/serverSentEvents.gen.ts new file mode 100644 index 0000000..f8fd78e --- /dev/null +++ b/src/api-clients/pint/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/src/api-clients/pint/core/types.gen.ts b/src/api-clients/pint/core/types.gen.ts new file mode 100644 index 0000000..643c070 --- /dev/null +++ b/src/api-clients/pint/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/src/api-clients/pint/core/utils.gen.ts b/src/api-clients/pint/core/utils.gen.ts new file mode 100644 index 0000000..0b5389d --- /dev/null +++ b/src/api-clients/pint/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/src/api-clients/pint/index.ts b/src/api-clients/pint/index.ts new file mode 100644 index 0000000..c352c10 --- /dev/null +++ b/src/api-clients/pint/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type * from './types.gen'; +export * from './sdk.gen'; diff --git a/src/api-clients/pint/sdk.gen.ts b/src/api-clients/pint/sdk.gen.ts new file mode 100644 index 0000000..d7e4b86 --- /dev/null +++ b/src/api-clients/pint/sdk.gen.ts @@ -0,0 +1,410 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, Options as Options2, TDataShape } from './client'; +import { client } from './client.gen'; +import type { ConnectToExecWebSocketData, ConnectToExecWebSocketErrors, ConnectToExecWebSocketResponses, CreateDirectoryData, CreateDirectoryErrors, CreateDirectoryResponses, CreateExecData, CreateExecErrors, CreateExecResponses, CreateFileData, CreateFileErrors, CreateFileResponses, DeleteDirectoryData, DeleteDirectoryErrors, DeleteDirectoryResponses, DeleteExecData, DeleteExecErrors, DeleteExecResponses, DeleteFileData, DeleteFileErrors, DeleteFileResponses, ExecExecStdinData, ExecExecStdinErrors, ExecExecStdinResponses, ExecuteTaskActionData, ExecuteTaskActionErrors, ExecuteTaskActionResponses, GetExecData, GetExecErrors, GetExecOutputData, GetExecOutputErrors, GetExecOutputResponses, GetExecResponses, GetTaskData, GetTaskErrors, GetTaskResponses, ListDirectoryData, ListDirectoryErrors, ListDirectoryResponses, ListExecsData, ListExecsErrors, ListExecsResponses, ListPortsData, ListPortsErrors, ListPortsResponses, ListPortsSseData, ListPortsSseErrors, ListPortsSseResponses, ListSetupTasksData, ListSetupTasksErrors, ListSetupTasksResponses, ListTasksData, ListTasksErrors, ListTasksResponses, PerformFileActionData, PerformFileActionErrors, PerformFileActionResponses, ReadFileData, ReadFileErrors, ReadFileResponses, UpdateExecData, UpdateExecErrors, UpdateExecResponses } from './types.gen'; + +export type Options = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Delete a file + * Deletes a file at the specified path. + */ +export const deleteFile = (options: Options) => { + return (options.client ?? client).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/files/{path}', + ...options + }); +}; + +/** + * Read file content + * Reads the content of a file at the specified path. + */ +export const readFile = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/files/{path}', + ...options + }); +}; + +/** + * Perform file actions + * Performs actions on files (e.g., move operations). + */ +export const performFileAction = (options: Options) => { + return (options.client ?? client).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/files/{path}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Create a file + * Creates a new file at the specified path with optional content. + */ +export const createFile = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/files/{path}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Delete a directory + * Deletes a directory at the specified path. + */ +export const deleteDirectory = (options: Options) => { + return (options.client ?? client).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/directories/{path}', + ...options + }); +}; + +/** + * List directory contents + * Lists the contents of a directory at the specified path. + */ +export const listDirectory = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/directories/{path}', + ...options + }); +}; + +/** + * Create a directory + * Creates a new directory at the specified path. + */ +export const createDirectory = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/directories/{path}', + ...options + }); +}; + +/** + * List all execs + * Returns a list of all active execs. + */ +export const listExecs = (options?: Options) => { + return (options?.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs', + ...options + }); +}; + +/** + * Create a new exec + * Creates a new exec with specified command and arguments. + */ +export const createExec = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Delete Exec + * Deletes a exec and execs its process. + */ +export const deleteExec = (options: Options) => { + return (options.client ?? client).delete({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs/{id}', + ...options + }); +}; + +/** + * Get exec by ID + * Retrieves a specific exec by its ID. + */ +export const getExec = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs/{id}', + ...options + }); +}; + +/** + * Update exec + * Updates exec status (e.g., start a stopped exec). + */ +export const updateExec = (options: Options) => { + return (options.client ?? client).put({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Get Exec output + * Retrieves the plain text output from a exec's buffer. + */ +export const getExecOutput = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs/{id}/io', + ...options + }); +}; + +/** + * exec exec stdin + * exec exec command (e.g., npm install). + */ +export const execExecStdin = (options: Options) => { + return (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/execs/{id}/io', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Connect to exec via WebSocket + * Establishes a WebSocket connection for real-time exec interaction. + * + * Authentication can be provided via: + * - Authorization header: `Authorization: Bearer ` + * - Query parameter: `?token=` + * + * Permissions: + * - Admin users: Can send input and receive output + * - Readonly users: Can only receive output + * + */ +export const connectToExecWebSocket = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + }, + { + in: 'query', + name: 'token', + type: 'apiKey' + } + ], + url: '/ws/v1/execs/{id}', + ...options + }); +}; + +/** + * List all tasks + * Lists all configured tasks from .codesandbox/tasks.json with their current status. + */ +export const listTasks = (options?: Options) => { + return (options?.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/tasks', + ...options + }); +}; + +/** + * Get task by ID + * Retrieves a specific task by its ID with current status and configuration. + */ +export const getTask = (options: Options) => { + return (options.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/tasks/{id}', + ...options + }); +}; + +/** + * Execute task action + * Executes an action on a specific task (start, stop, or restart). + */ +export const executeTaskAction = (options: Options) => { + return (options.client ?? client).patch({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/tasks/{id}/actions', + ...options + }); +}; + +/** + * List setup tasks + * Lists all setup tasks with their execution status. Setup tasks are auto-executed during server start. + */ +export const listSetupTasks = (options?: Options) => { + return (options?.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/setup-tasks', + ...options + }); +}; + +/** + * List open ports + * Lists all open TCP ports on the system, excluding ignored ports configured in the server. + */ +export const listPorts = (options?: Options) => { + return (options?.client ?? client).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/ports', + ...options + }); +}; + +/** + * List open ports using Server-Sent Events (SSE) + * Lists all open TCP ports on the system AND LISTEN TO THE CHANGES, excluding ignored ports configured in the server. + */ +export const listPortsSse = (options?: Options) => { + return (options?.client ?? client).sse.get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/ports/stream', + ...options + }); +}; diff --git a/src/api-clients/pint/types.gen.ts b/src/api-clients/pint/types.gen.ts new file mode 100644 index 0000000..b9bd964 --- /dev/null +++ b/src/api-clients/pint/types.gen.ts @@ -0,0 +1,1184 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'http://localhost:57468' | 'http://localhost:{port}' | (string & {}); +}; + +export type _Error = { + code: number; + message: string; +}; + +export type FileReadResponse = { + /** + * File path + */ + path: string; + /** + * File content + */ + content: string; +}; + +export type FileCreateRequest = { + /** + * File content to create + */ + content?: string; +}; + +export type FileOperationResponse = { + /** + * Operation result message + */ + message: string; + /** + * File or directory path + */ + path: string; +}; + +export type FileActionRequest = { + /** + * Type of action to perform on the file + */ + action: 'move'; + /** + * Destination path for move operation + */ + destination: string; +}; + +export type FileActionResponse = { + /** + * Operation result message + */ + message: string; + /** + * Source path + */ + from: string; + /** + * Destination path + */ + to: string; +}; + +export type FileInfo = { + /** + * File or directory name + */ + name: string; + /** + * Full path to the file or directory + */ + path: string; + /** + * Whether this entry is a directory + */ + isDir: boolean; + /** + * File size in bytes + */ + size: number; + /** + * Last modification time + */ + modTime: string; +}; + +export type DirectoryListResponse = { + /** + * Directory path + */ + path: string; + /** + * List of files and directories + */ + files: Array; +}; + +export type ExecItem = { + /** + * Exec unique identifier + */ + id: string; + /** + * Command being executed + */ + command: string; + /** + * Command line arguments + */ + args: Array; + /** + * Exec status (e.g., running, stopped, finished) + */ + status: string; + /** + * Process ID of the exec + */ + pid: number; +}; + +export type ExecListResponse = { + /** + * List of execs + */ + execs: Array; +}; + +export type CreateExecRequest = { + /** + * Command to execute in the exec + */ + command: string; + /** + * Command line arguments + */ + args: Array; + /** + * Whether to automatically start the exec (defaults to true) + */ + autorun?: boolean; + /** + * Whether to start interactive shell session or not (defaults to false) + */ + interactive?: boolean; +}; + +export type UpdateExecRequest = { + /** + * Status to set for the exec (currently only 'running' is supported) + */ + status: 'running'; +}; + +export type ExecDeleteResponse = { + /** + * Deletion confirmation message + */ + message: string; +}; + +export type ExecStdout = { + /** + * Type of the exec output + */ + type: 'stdout' | 'stderr'; + /** + * Data associated with the exec output + */ + output: string; + /** + * Sequence number of the output message + */ + sequence: number; + /** + * Timestamp of when the output was generated + */ + timestamp?: string; +}; + +export type ExecStdin = { + /** + * Type of the exec input + */ + type: 'stdin' | 'resize'; + /** + * Data associated with the exec input + */ + input: string; +}; + +export type TaskStatus = 'RUNNING' | 'FINISHED' | 'ERROR' | 'KILLED' | 'RESTARTING' | 'IDLE'; + +/** + * TaskBase + * Base schema for a task item, containing common fields shared across different task types. + */ +export type TaskBase = { + status: TaskStatus; + /** + * Exec identifier associated with the task + */ + execId: string; + /** + * Task start time in RFC3339 format + */ + startTime: string; + /** + * Task end time in RFC3339 format + */ + endTime: string; +}; + +export type TaskRestart = { + files: Array; + clone: boolean; + resume: boolean; + branch: boolean; +}; + +export type TaskPreview = { + port: number; +}; + +export type TaskConfig = { + name: string; + command: string; + runAtStart: boolean; + restartOn: TaskRestart; + preview: TaskPreview; +}; + +export type TaskItem = TaskBase & { + /** + * Task identifier + */ + id: string; + config: TaskConfig; +}; + +export type TaskListResponse = { + tasks: Array; +}; + +export type GetTaskResponse = { + task: TaskItem; +}; + +/** + * Type of action to execute on a task + */ +export type TaskActionType = 'start' | 'stop' | 'restart'; + +/** + * TaskActionResponse + * Schema for task action responses, containing details about the task and the action performed. + */ +export type TaskActionResponse = TaskBase & { + /** + * Task identifier + */ + id: string; + /** + * Task name + */ + name: string; + /** + * Task command + */ + command: string; + /** + * Action result message + */ + message: string; +}; + +export type SetupTaskItem = TaskBase & { + /** + * Setup task name + */ + name: string; + /** + * Setup task command + */ + command: string; +}; + +export type SetupTaskListResponse = { + setupTasks: Array; +}; + +export type PortInfo = { + /** + * Port number + */ + port: number; + /** + * IP address the port is bound to + */ + address: string; +}; + +export type PortsListResponse = { + /** + * List of open ports + */ + ports: Array; +}; + +export type Task = TaskItem; + +export type DeleteFileData = { + body?: never; + path: { + /** + * File path + */ + path: string; + }; + query?: never; + url: '/api/v1/files/{path}'; +}; + +export type DeleteFileErrors = { + /** + * Bad Request - Path is required or invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * File not found + */ + 404: _Error; + /** + * Internal Server Error - Failed to delete file + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type DeleteFileError = DeleteFileErrors[keyof DeleteFileErrors]; + +export type DeleteFileResponses = { + /** + * File deleted successfully + */ + 200: FileOperationResponse; +}; + +export type DeleteFileResponse = DeleteFileResponses[keyof DeleteFileResponses]; + +export type ReadFileData = { + body?: never; + path: { + /** + * File path + */ + path: string; + }; + query?: never; + url: '/api/v1/files/{path}'; +}; + +export type ReadFileErrors = { + /** + * Bad Request - Path is required or invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * File not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ReadFileError = ReadFileErrors[keyof ReadFileErrors]; + +export type ReadFileResponses = { + /** + * File content retrieved successfully + */ + 200: FileReadResponse; +}; + +export type ReadFileResponse = ReadFileResponses[keyof ReadFileResponses]; + +export type PerformFileActionData = { + /** + * File action request + */ + body: FileActionRequest; + path: { + /** + * Source file path (will be URL decoded) + */ + path: string; + }; + query?: never; + url: '/api/v1/files/{path}'; +}; + +export type PerformFileActionErrors = { + /** + * Bad Request - Path is required, invalid action, or missing destination + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to perform action + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type PerformFileActionError = PerformFileActionErrors[keyof PerformFileActionErrors]; + +export type PerformFileActionResponses = { + /** + * Action performed successfully + */ + 200: FileActionResponse; +}; + +export type PerformFileActionResponse = PerformFileActionResponses[keyof PerformFileActionResponses]; + +export type CreateFileData = { + /** + * File creation request + */ + body?: FileCreateRequest; + path: { + /** + * File path + */ + path: string; + }; + query?: never; + url: '/api/v1/files/{path}'; +}; + +export type CreateFileErrors = { + /** + * Bad Request - Path is required or invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to create file + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type CreateFileError = CreateFileErrors[keyof CreateFileErrors]; + +export type CreateFileResponses = { + /** + * File created successfully + */ + 201: FileReadResponse; +}; + +export type CreateFileResponse = CreateFileResponses[keyof CreateFileResponses]; + +export type DeleteDirectoryData = { + body?: never; + path: { + /** + * Directory path + */ + path: string; + }; + query?: never; + url: '/api/v1/directories/{path}'; +}; + +export type DeleteDirectoryErrors = { + /** + * Bad Request - Path is required or invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Directory not found + */ + 404: _Error; + /** + * Internal Server Error - Failed to delete directory + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type DeleteDirectoryError = DeleteDirectoryErrors[keyof DeleteDirectoryErrors]; + +export type DeleteDirectoryResponses = { + /** + * Directory deleted successfully + */ + 200: FileOperationResponse; +}; + +export type DeleteDirectoryResponse = DeleteDirectoryResponses[keyof DeleteDirectoryResponses]; + +export type ListDirectoryData = { + body?: never; + path: { + /** + * Directory path (will be URL decoded). Use "/" for root directory. + */ + path: string; + }; + query?: never; + url: '/api/v1/directories/{path}'; +}; + +export type ListDirectoryErrors = { + /** + * Bad Request - Invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Directory not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListDirectoryError = ListDirectoryErrors[keyof ListDirectoryErrors]; + +export type ListDirectoryResponses = { + /** + * Directory contents retrieved successfully + */ + 200: DirectoryListResponse; +}; + +export type ListDirectoryResponse = ListDirectoryResponses[keyof ListDirectoryResponses]; + +export type CreateDirectoryData = { + body?: never; + path: { + /** + * Directory path + */ + path: string; + }; + query?: never; + url: '/api/v1/directories/{path}'; +}; + +export type CreateDirectoryErrors = { + /** + * Bad Request - Path is required or invalid path + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to create directory + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type CreateDirectoryError = CreateDirectoryErrors[keyof CreateDirectoryErrors]; + +export type CreateDirectoryResponses = { + /** + * Directory created successfully + */ + 201: FileOperationResponse; +}; + +export type CreateDirectoryResponse = CreateDirectoryResponses[keyof CreateDirectoryResponses]; + +export type ListExecsData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/execs'; +}; + +export type ListExecsErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListExecsError = ListExecsErrors[keyof ListExecsErrors]; + +export type ListExecsResponses = { + /** + * Execs retrieved successfully + */ + 200: ExecListResponse; +}; + +export type ListExecsResponse = ListExecsResponses[keyof ListExecsResponses]; + +export type CreateExecData = { + /** + * Exec creation request + */ + body: CreateExecRequest; + path?: never; + query?: never; + url: '/api/v1/execs'; +}; + +export type CreateExecErrors = { + /** + * Bad Request - Invalid request body + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to create exec + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type CreateExecError = CreateExecErrors[keyof CreateExecErrors]; + +export type CreateExecResponses = { + /** + * Exec created successfully + */ + 201: ExecItem; +}; + +export type CreateExecResponse = CreateExecResponses[keyof CreateExecResponses]; + +export type DeleteExecData = { + body?: never; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/execs/{id}'; +}; + +export type DeleteExecErrors = { + /** + * Bad Request - Exec ID is required + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type DeleteExecError = DeleteExecErrors[keyof DeleteExecErrors]; + +export type DeleteExecResponses = { + /** + * Exec deleted successfully + */ + 200: ExecDeleteResponse; +}; + +export type DeleteExecResponse = DeleteExecResponses[keyof DeleteExecResponses]; + +export type GetExecData = { + body?: never; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/execs/{id}'; +}; + +export type GetExecErrors = { + /** + * Bad Request - Exec ID is required + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type GetExecError = GetExecErrors[keyof GetExecErrors]; + +export type GetExecResponses = { + /** + * Exec retrieved successfully + */ + 200: ExecItem; +}; + +export type GetExecResponse = GetExecResponses[keyof GetExecResponses]; + +export type UpdateExecData = { + /** + * Exec update request + */ + body: UpdateExecRequest; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/execs/{id}'; +}; + +export type UpdateExecErrors = { + /** + * Bad Request - Exec ID is required or invalid status + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Conflict - Exec is already in the requested state + */ + 409: _Error; + /** + * Internal Server Error - Failed to update exec + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type UpdateExecError = UpdateExecErrors[keyof UpdateExecErrors]; + +export type UpdateExecResponses = { + /** + * Exec updated successfully + */ + 200: ExecItem; +}; + +export type UpdateExecResponse = UpdateExecResponses[keyof UpdateExecResponses]; + +export type GetExecOutputData = { + body?: never; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/execs/{id}/io'; +}; + +export type GetExecOutputErrors = { + /** + * Bad Request - Exec ID is required + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type GetExecOutputError = GetExecOutputErrors[keyof GetExecOutputErrors]; + +export type GetExecOutputResponses = { + /** + * Exec output retrieved successfully + */ + 200: ExecStdout; +}; + +export type GetExecOutputResponse = GetExecOutputResponses[keyof GetExecOutputResponses]; + +export type ExecExecStdinData = { + /** + * Exec update request + */ + body: ExecStdin; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/execs/{id}/io'; +}; + +export type ExecExecStdinErrors = { + /** + * Bad Request - Exec ID is required or invalid status + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Internal Server Error - Failed to update exec + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ExecExecStdinError = ExecExecStdinErrors[keyof ExecExecStdinErrors]; + +export type ExecExecStdinResponses = { + /** + * Exec stdin executed successfully + */ + 200: ExecItem; +}; + +export type ExecExecStdinResponse = ExecExecStdinResponses[keyof ExecExecStdinResponses]; + +export type ConnectToExecWebSocketData = { + body?: never; + path: { + /** + * Exec identifier + */ + id: string; + }; + query?: never; + url: '/ws/v1/execs/{id}'; +}; + +export type ConnectToExecWebSocketErrors = { + /** + * Bad Request - Exec ID is required + */ + 400: _Error; + /** + * Unauthorized - Authentication token required or invalid + */ + 401: _Error; + /** + * Exec not found + */ + 404: _Error; + /** + * Internal Server Error - Failed to establish WebSocket connection + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ConnectToExecWebSocketError = ConnectToExecWebSocketErrors[keyof ConnectToExecWebSocketErrors]; + +export type ConnectToExecWebSocketResponses = { + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ConnectToExecWebSocketResponse = ConnectToExecWebSocketResponses[keyof ConnectToExecWebSocketResponses]; + +export type ListTasksData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/tasks'; +}; + +export type ListTasksErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListTasksError = ListTasksErrors[keyof ListTasksErrors]; + +export type ListTasksResponses = { + /** + * List of tasks retrieved successfully + */ + 200: TaskListResponse; +}; + +export type ListTasksResponse = ListTasksResponses[keyof ListTasksResponses]; + +export type GetTaskData = { + body?: never; + path: { + /** + * Task identifier + */ + id: string; + }; + query?: never; + url: '/api/v1/tasks/{id}'; +}; + +export type GetTaskErrors = { + /** + * Bad Request - Task ID is required + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Task not found + */ + 404: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type GetTaskError = GetTaskErrors[keyof GetTaskErrors]; + +export type GetTaskResponses = { + /** + * Task retrieved successfully + */ + 200: GetTaskResponse; +}; + +export type GetTaskResponse2 = GetTaskResponses[keyof GetTaskResponses]; + +export type ExecuteTaskActionData = { + body?: never; + path: { + /** + * Task identifier + */ + id: string; + }; + query: { + /** + * Type of action to execute + */ + actionType: TaskActionType; + }; + url: '/api/v1/tasks/{id}/actions'; +}; + +export type ExecuteTaskActionErrors = { + /** + * Bad Request - Task ID is required, invalid action type, or invalid command + */ + 400: _Error; + /** + * Unauthorized + */ + 401: _Error; + /** + * Task not found + */ + 404: _Error; + /** + * Conflict - Invalid state transition (e.g., task already running for start, task not running for stop) + */ + 409: _Error; + /** + * Internal Server Error - Failed to execute action + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ExecuteTaskActionError = ExecuteTaskActionErrors[keyof ExecuteTaskActionErrors]; + +export type ExecuteTaskActionResponses = { + /** + * Action executed successfully + */ + 200: TaskActionResponse; +}; + +export type ExecuteTaskActionResponse = ExecuteTaskActionResponses[keyof ExecuteTaskActionResponses]; + +export type ListSetupTasksData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/setup-tasks'; +}; + +export type ListSetupTasksErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListSetupTasksError = ListSetupTasksErrors[keyof ListSetupTasksErrors]; + +export type ListSetupTasksResponses = { + /** + * Setup tasks retrieved successfully + */ + 200: SetupTaskListResponse; +}; + +export type ListSetupTasksResponse = ListSetupTasksResponses[keyof ListSetupTasksResponses]; + +export type ListPortsData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/ports'; +}; + +export type ListPortsErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to read port information + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListPortsError = ListPortsErrors[keyof ListPortsErrors]; + +export type ListPortsResponses = { + /** + * Open ports retrieved successfully + */ + 200: PortsListResponse; +}; + +export type ListPortsResponse = ListPortsResponses[keyof ListPortsResponses]; + +export type ListPortsSseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/ports/stream'; +}; + +export type ListPortsSseErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Internal Server Error - Failed to read port information + */ + 500: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type ListPortsSseError = ListPortsSseErrors[keyof ListPortsSseErrors]; + +export type ListPortsSseResponses = { + /** + * Server-Sent Events stream of ports list updates + */ + 200: string; +}; + +export type ListPortsSseResponse = ListPortsSseResponses[keyof ListPortsSseResponses]; diff --git a/src/utils/api.ts b/src/utils/api.ts index 72dd452..671b8c5 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,19 +1,21 @@ import { StartSandboxOpts } from "../types"; import { RateLimitError } from "./rate-limit"; +import { getInferredBaseUrl } from "./constants"; import { Client, Config, createClient, createConfig, -} from "@hey-api/client-fetch"; -import { getInferredBaseUrl } from "./constants"; +} from "../api-clients/client/client"; async function enhanceFetch( - request: Request, + request: RequestInfo | URL, instrumentation?: (request: Request) => Promise ) { // Clone the request to modify headers - const headers = new Headers(request.headers); + const headers = new Headers( + request instanceof Request ? request.headers : undefined + ); const existingUserAgent = headers.get("User-Agent") || ""; // Extend User-Agent with SDK version diff --git a/src/utils/event.ts b/src/utils/event.ts index c7a32f0..270219a 100644 --- a/src/utils/event.ts +++ b/src/utils/event.ts @@ -130,3 +130,53 @@ export class AsyncEmitter implements IDisposable { this.registeredListeners = new Set(); } } + +/** + * EmitterSubscription provides an abstraction that manages a subscription lifecycle + * tied to the number of listeners on an emitter. The subscription is created when + * the first listener is added and disposed when the last listener is removed. + */ +export class EmitterSubscription implements IDisposable { + private emitter = new Emitter(); + private subscription: IDisposable | undefined; + private listenerCount = 0; + + constructor( + private createSubscription: (fire: (value: T) => void) => IDisposable + ) {} + + get event(): Event { + return (listener: (e: T) => void) => { + // Add listener to emitter + const listenerDisposable = this.emitter.event(listener); + + // Create subscription if this is the first listener + if (this.listenerCount === 0) { + this.subscription = this.createSubscription((value) => + this.emitter.fire(value) + ); + } + + this.listenerCount++; + + // Return disposable that removes listener and cleans up subscription if needed + return Disposable.create(() => { + listenerDisposable.dispose(); + this.listenerCount--; + + // Dispose subscription when last listener is removed + if (this.listenerCount === 0 && this.subscription) { + this.subscription.dispose(); + this.subscription = undefined; + } + }); + }; + } + + dispose(): void { + this.subscription?.dispose(); + this.subscription = undefined; + this.emitter.dispose(); + this.listenerCount = 0; + } +} diff --git a/tests/emitter-subscription.test.ts b/tests/emitter-subscription.test.ts new file mode 100644 index 0000000..4aaf590 --- /dev/null +++ b/tests/emitter-subscription.test.ts @@ -0,0 +1,212 @@ +import { describe, it, expect, vi } from 'vitest' +import { EmitterSubscription } from '../src/utils/event' +import { Disposable } from '../src/utils/disposable' + +describe('EmitterSubscription', () => { + it('should create subscription when first listener is added', () => { + const createSubscription = vi.fn(() => Disposable.create(() => {})) + const subscription = new EmitterSubscription(createSubscription) + + expect(createSubscription).not.toHaveBeenCalled() + + const disposable = subscription.event(() => {}) + + expect(createSubscription).toHaveBeenCalledTimes(1) + + disposable.dispose() + }) + + it('should not create multiple subscriptions for multiple listeners', () => { + const createSubscription = vi.fn(() => Disposable.create(() => {})) + const subscription = new EmitterSubscription(createSubscription) + + const disposable1 = subscription.event(() => {}) + const disposable2 = subscription.event(() => {}) + const disposable3 = subscription.event(() => {}) + + expect(createSubscription).toHaveBeenCalledTimes(1) + + disposable1.dispose() + disposable2.dispose() + disposable3.dispose() + }) + + it('should fire events to all listeners', () => { + const subscription = new EmitterSubscription((fire) => { + fire(42) + return Disposable.create(() => {}) + }) + + const listener1 = vi.fn() + const listener2 = vi.fn() + const listener3 = vi.fn() + + subscription.event(listener1) + subscription.event(listener2) + subscription.event(listener3) + + expect(listener1).toHaveBeenCalledWith(42) + expect(listener2).toHaveBeenCalledWith(42) + expect(listener3).toHaveBeenCalledWith(42) + }) + + it('should allow firing events from subscription callback', () => { + let fireFn: ((value: number) => void) | undefined + + const subscription = new EmitterSubscription((fire) => { + fireFn = fire + return Disposable.create(() => {}) + }) + + const listener = vi.fn() + subscription.event(listener) + + expect(fireFn).toBeDefined() + + fireFn!(100) + fireFn!(200) + fireFn!(300) + + expect(listener).toHaveBeenCalledTimes(3) + expect(listener).toHaveBeenNthCalledWith(1, 100) + expect(listener).toHaveBeenNthCalledWith(2, 200) + expect(listener).toHaveBeenNthCalledWith(3, 300) + }) + + it('should dispose subscription when last listener is removed', () => { + const dispose = vi.fn() + const createSubscription = vi.fn(() => Disposable.create(dispose)) + const subscription = new EmitterSubscription(createSubscription) + + const disposable1 = subscription.event(() => {}) + const disposable2 = subscription.event(() => {}) + + expect(dispose).not.toHaveBeenCalled() + + disposable1.dispose() + expect(dispose).not.toHaveBeenCalled() + + disposable2.dispose() + expect(dispose).toHaveBeenCalledTimes(1) + }) + + it('should recreate subscription if listener is added again after all removed', () => { + const dispose = vi.fn() + const createSubscription = vi.fn(() => Disposable.create(dispose)) + const subscription = new EmitterSubscription(createSubscription) + + const disposable1 = subscription.event(() => {}) + disposable1.dispose() + + expect(createSubscription).toHaveBeenCalledTimes(1) + expect(dispose).toHaveBeenCalledTimes(1) + + const disposable2 = subscription.event(() => {}) + + expect(createSubscription).toHaveBeenCalledTimes(2) + expect(dispose).toHaveBeenCalledTimes(1) + + disposable2.dispose() + expect(dispose).toHaveBeenCalledTimes(2) + }) + + it('should stop firing to disposed listeners', () => { + let fireFn: ((value: number) => void) | undefined + + const subscription = new EmitterSubscription((fire) => { + fireFn = fire + return Disposable.create(() => {}) + }) + + const listener1 = vi.fn() + const listener2 = vi.fn() + const listener3 = vi.fn() + + const disposable1 = subscription.event(listener1) + subscription.event(listener2) + subscription.event(listener3) + + fireFn!(1) + expect(listener1).toHaveBeenCalledTimes(1) + expect(listener2).toHaveBeenCalledTimes(1) + expect(listener3).toHaveBeenCalledTimes(1) + + disposable1.dispose() + + fireFn!(2) + expect(listener1).toHaveBeenCalledTimes(1) // Not called again + expect(listener2).toHaveBeenCalledTimes(2) + expect(listener3).toHaveBeenCalledTimes(2) + }) + + it('should cleanup everything on dispose', () => { + const subscriptionDispose = vi.fn() + const createSubscription = vi.fn(() => Disposable.create(subscriptionDispose)) + const subscription = new EmitterSubscription(createSubscription) + + let fireFn: ((value: number) => void) | undefined + subscription = new EmitterSubscription((fire) => { + fireFn = fire + return Disposable.create(subscriptionDispose) + }) + + const listener = vi.fn() + subscription.event(listener) + + subscription.dispose() + + expect(subscriptionDispose).toHaveBeenCalledTimes(1) + + // Should not fire to listeners after dispose + fireFn!(42) + expect(listener).not.toHaveBeenCalled() + }) + + it('should work with interval example', () => { + vi.useFakeTimers() + + let intervalId: NodeJS.Timeout + const subscription = new EmitterSubscription((fire) => { + intervalId = setInterval(() => fire(Date.now()), 1000) + return Disposable.create(() => clearInterval(intervalId)) + }) + + const listener = vi.fn() + const disposable = subscription.event(listener) + + vi.advanceTimersByTime(3500) + expect(listener).toHaveBeenCalledTimes(3) + + disposable.dispose() + + // Should not receive more events after dispose + vi.advanceTimersByTime(5000) + expect(listener).toHaveBeenCalledTimes(3) + + vi.useRealTimers() + }) + + it('should handle multiple add/remove cycles correctly', () => { + const dispose = vi.fn() + const createSubscription = vi.fn(() => Disposable.create(dispose)) + const subscription = new EmitterSubscription(createSubscription) + + // Cycle 1 + const d1 = subscription.event(() => {}) + d1.dispose() + expect(createSubscription).toHaveBeenCalledTimes(1) + expect(dispose).toHaveBeenCalledTimes(1) + + // Cycle 2 + const d2 = subscription.event(() => {}) + d2.dispose() + expect(createSubscription).toHaveBeenCalledTimes(2) + expect(dispose).toHaveBeenCalledTimes(2) + + // Cycle 3 + const d3 = subscription.event(() => {}) + d3.dispose() + expect(createSubscription).toHaveBeenCalledTimes(3) + expect(dispose).toHaveBeenCalledTimes(3) + }) +}) From 199ec3f8d2cdb4663ea3a29d66e58f6c657a5c35 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 8 Oct 2025 14:03:23 +0200 Subject: [PATCH 230/241] basic exec run and list --- src/PintClient/index.ts | 112 ++++++++++++++++++++++++++++++++++++- src/Sandbox.ts | 7 +++ src/SandboxClient/index.ts | 15 +++++ src/types.ts | 3 +- 4 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index cae0548..eda4247 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -1,14 +1,31 @@ import { IAgentClient, IAgentClientPorts, + IAgentClientShells, IAgentClientState, } from "../agent-client-interface"; import { Port } from "../pitcher-protocol/messages/port"; -import { listPorts, listPortsSse } from "../api-clients/pint"; +import { + createExec, + ExecItem, + listExecs, + listPorts, + listPortsSse, +} from "../api-clients/pint"; import { SandboxSession } from "../types"; import { Emitter, EmitterSubscription, Event } from "../utils/event"; import { Disposable } from "../utils/disposable"; import { Client, createClient, createConfig } from "../api-clients/pint/client"; +import { + ShellSize, + ShellProcessType, + OpenShellDTO, + CommandShellDTO, + ShellId, + TerminalShellDTO, + ShellDTO, + ShellProcessStatus, +} from "../pitcher-protocol/messages/shell"; class PintPortsClient implements IAgentClientPorts { private onPortsUpdatedEmitter = new EmitterSubscription((fire) => { @@ -49,6 +66,96 @@ class PintPortsClient implements IAgentClientPorts { } } +export class PintShellsClient implements IAgentClientShells { + private onShellExitedEmitter = new EmitterSubscription<{ + shellId: string; + exitCode: number; + }>(() => { + return Disposable.create(() => {}); + }); + onShellExited = this.onShellExitedEmitter.event; + private onShellOutEmitter = new EmitterSubscription<{ + shellId: ShellId; + out: string; + }>(() => { + return Disposable.create(() => {}); + }); + onShellOut = this.onShellOutEmitter.event; + private onShellTerminatedEmitter = new EmitterSubscription<{ + shellId: ShellId; + author: string; + }>(() => { + return Disposable.create(() => {}); + }); + onShellTerminated = this.onShellTerminatedEmitter.event; + constructor(private apiClient: Client, private sandboxId: string) {} + private convertExecToShellDTO(exec: ExecItem) { + return { + isSystemShell: true, + name: JSON.stringify({ + type: "command", + command: exec.command, + name: "", + }), + ownerUsername: "root", + shellId: exec.id, + shellType: "TERMINAL" as const, + startCommand: exec.command, + status: exec.status as ShellProcessStatus, + }; + } + async create( + projectPath: string, + size: ShellSize, + command?: string, + type?: ShellProcessType, + isSystemShell?: boolean + ): Promise { + const exec = await createExec({ + client: this.apiClient, + body: { + args: [], + command: command || "bash", + interactive: true, + }, + }); + + if (!exec.data) { + console.log(exec); + throw new Error("Nooooooooo"); + } + + return { + ...this.convertExecToShellDTO(exec.data), + buffer: [], + }; + } + delete(shellId: ShellId): Promise { + throw new Error("Not implemented"); + } + async getShells(): Promise { + const execs = await listExecs({ + client: this.apiClient, + }); + + return ( + execs.data?.execs.map((exec) => this.convertExecToShellDTO(exec)) ?? [] + ); + } + open(shellId: ShellId, size: ShellSize): Promise { + throw new Error("Not implemented"); + } + rename(shellId: ShellId, name: string): Promise { + throw new Error("Not implemented"); + } + restart(shellId: ShellId): Promise { + throw new Error("Not implemented"); + } + send(shellId: ShellId, input: string, size: ShellSize): Promise { + throw new Error("Not implemented"); + } +} + export class PintClient implements IAgentClient { static async create(session: SandboxSession) { return new PintClient(session); @@ -65,7 +172,7 @@ export class PintClient implements IAgentClient { isUpToDate: boolean; ports: IAgentClientPorts; - shells: any = null; // TODO: Implement + shells: IAgentClientShells; fs: any = null; // TODO: Implement setup: any = null; // TODO: Implement tasks: any = null; // TODO: Implement @@ -86,6 +193,7 @@ export class PintClient implements IAgentClient { ); this.ports = new PintPortsClient(apiClient, this.sandboxId); + this.shells = new PintShellsClient(apiClient, this.sandboxId); } ping(): void {} diff --git a/src/Sandbox.ts b/src/Sandbox.ts index 76e9287..ad87957 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -178,8 +178,14 @@ export class Sandbox { pitcherManagerResponse: PitcherManagerResponse, customSession?: SessionCreateOptions ): Promise { + // HACK: we currently do not get a flag for pint, but this is a check we can use for now + const isPint = + pitcherManagerResponse.userWorkspacePath === + pitcherManagerResponse.workspacePath; + if (!customSession || !customSession.id) { return { + isPint, sandboxId: this.id, bootupType: this.bootupType, hostToken: customSession?.hostToken, @@ -204,6 +210,7 @@ export class Sandbox { }); return { + isPint, sandboxId: this.id, sessionId: customSession?.id, hostToken: customSession?.hostToken, diff --git a/src/SandboxClient/index.ts b/src/SandboxClient/index.ts index ca83b2f..105d054 100644 --- a/src/SandboxClient/index.ts +++ b/src/SandboxClient/index.ts @@ -16,6 +16,7 @@ import { Barrier } from "../utils/barrier"; import { AgentClient } from "../AgentClient"; import { SandboxSession } from "../types"; import { Tracer, SpanStatusCode } from "@opentelemetry/api"; +import { PintClient } from "../PintClient"; export * from "./filesystem"; export * from "./ports"; @@ -41,6 +42,20 @@ export class SandboxClient { initStatusCb?: (event: system.InitStatus) => void, tracer?: Tracer ) { + if (session.isPint) { + const agentClient = await PintClient.create(session); + + return new SandboxClient( + agentClient, + { hostToken: session.hostToken, tracer }, + { + currentStepIndex: 0, + state: "FINISHED", + steps: [], + } + ); + } + const { client: agentClient, joinResult } = await AgentClient.create({ session, getSession, diff --git a/src/types.ts b/src/types.ts index 4c20bce..75990d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { VMTier } from "./VMTier"; import { HostToken } from "./HostTokens"; -import { Config } from "@hey-api/client-fetch"; import { Tracer } from "@opentelemetry/api"; +import { Config } from "./api-clients/client/client"; export interface PitcherManagerResponse { bootupType: "RUNNING" | "CLEAN" | "RESUME" | "FORK"; @@ -235,6 +235,7 @@ export type SandboxOpts = { }; export type SandboxSession = PitcherManagerResponse & { + isPint: boolean; sandboxId: string; sessionId?: string; hostToken?: HostToken; From 6ec99e48d4996878896eeb4002714c80b861f6fd Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 13 Oct 2025 09:45:01 +0200 Subject: [PATCH 231/241] WIP: running execs --- .gitignore | 1 - src/PintClient/index.ts | 6 ++++-- src/Sandbox.ts | 1 + src/SandboxClient/commands.ts | 6 +++--- test.ts | 22 ++++++++++++++++++++++ 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 test.ts diff --git a/.gitignore b/.gitignore index 68e2089..e344c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ # Generated stuff dist -test.ts test-template ### macOS ### diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index eda4247..27330a5 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -111,11 +111,13 @@ export class PintShellsClient implements IAgentClientShells { type?: ShellProcessType, isSystemShell?: boolean ): Promise { + const [cmd, ...rgs] = command!.split(" "); + const exec = await createExec({ client: this.apiClient, body: { - args: [], - command: command || "bash", + args: rgs, + command: cmd, interactive: true, }, }); diff --git a/src/Sandbox.ts b/src/Sandbox.ts index ad87957..bf9e8fb 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -145,6 +145,7 @@ export class Sandbox { }) .join("\n"); const cmd = [ + `mkdir -p "$HOME/.private"`, `cat << 'EOF' > "$HOME/.private/.env"`, envStrings, `EOF`, diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 2b52abb..24cbc5f 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -103,12 +103,12 @@ export class SandboxCommands { // TODO: use a new shell API that natively supports cwd & env let commandWithEnv = Object.keys(passedEnv).length - ? `source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( + ? `bash -c 'source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( passedEnv ) .map(([key, value]) => `${key}=${value}`) - .join(" ")} bash -c '${escapedCommand}'` - : `source $HOME/.private/.env 2>/dev/null || true && bash -c '${escapedCommand}'`; + .join(" ")} ${escapedCommand}'` + : `bash -c 'source $HOME/.private/.env 2>/dev/null || true && ${escapedCommand}'`; if (opts?.cwd) { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..e8f449d --- /dev/null +++ b/test.ts @@ -0,0 +1,22 @@ +import { CodeSandbox } from "@codesandbox/sdk"; + +const sdk = new CodeSandbox( + "csb_v1_uv6a0J6nM43YEMkqJ2RE3T8fuFGmPKsnkZEUkhCBbcU", + { + baseUrl: "https://api.codesandbox.stream", + } +); +console.log("Getting sandbox..."); +let sandbox = await sdk.sandboxes.resume("37zz6l"); + +sandbox["pitcherManagerResponse"].pitcherURL = "https://37zz6l-57468.csb.dev"; +sandbox["pitcherManagerResponse"].pitcherToken = + "0085a669aaf703166263c038751432a5fcb5e56a7ededa131a953b253b5bee12"; + +const client = await sandbox.connect({ + env: { + FOO: "BAR", + }, +}); + +console.log(client.commands.run("")); From 5d643026f96d9d2aabe3a3772344abdfc12bd6c3 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 16 Oct 2025 09:35:49 +0200 Subject: [PATCH 232/241] next step --- src/AgentClient/index.ts | 24 ++++++++++++++-------- src/PintClient/index.ts | 36 ++++++++++++++++++++------------- src/SandboxClient/commands.ts | 35 ++++++++++++++++---------------- src/SandboxClient/filesystem.ts | 17 +++++++++------- src/SandboxClient/terminals.ts | 15 +++++++------- src/agent-client-interface.ts | 15 +++++++------- test.ts | 6 +++++- 7 files changed, 87 insertions(+), 61 deletions(-) diff --git a/src/AgentClient/index.ts b/src/AgentClient/index.ts index 20dbadb..c0c23ea 100644 --- a/src/AgentClient/index.ts +++ b/src/AgentClient/index.ts @@ -61,17 +61,25 @@ class AgentClientShells implements IAgentClientShells { this.onShellOutEmitter.fire(params); }); } - create( - projectPath: string, - size: shell.ShellSize, - command?: string, - type?: shell.ShellProcessType, - isSystemShell?: boolean - ): Promise { + create({ + command, + args, + size, + type, + isSystemShell, + projectPath, + }: { + command: string; + args: string[]; + projectPath: string; + size: shell.ShellSize; + type?: shell.ShellProcessType; + isSystemShell?: boolean; + }): Promise { return this.agentConnection.request({ method: "shell/create", params: { - command, + command: command + args.join(""), size, type, isSystemShell, diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index 27330a5..1ac0cf9 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -104,21 +104,27 @@ export class PintShellsClient implements IAgentClientShells { status: exec.status as ShellProcessStatus, }; } - async create( - projectPath: string, - size: ShellSize, - command?: string, - type?: ShellProcessType, - isSystemShell?: boolean - ): Promise { - const [cmd, ...rgs] = command!.split(" "); - + async create({ + command, + args, + projectPath, + size, + type, + }: { + command: string; + args: string[]; + projectPath: string; + size: ShellSize; + type?: ShellProcessType; + isSystemShell?: boolean; + }): Promise { + console.log("creating shell", { args, command, type }); const exec = await createExec({ client: this.apiClient, body: { - args: rgs, - command: cmd, - interactive: true, + args, + command, + interactive: type === "COMMAND" ? false : true, }, }); @@ -127,6 +133,8 @@ export class PintShellsClient implements IAgentClientShells { throw new Error("Nooooooooo"); } + console.log("Gotz shell", exec.data); + return { ...this.convertExecToShellDTO(exec.data), buffer: [], @@ -147,8 +155,8 @@ export class PintShellsClient implements IAgentClientShells { open(shellId: ShellId, size: ShellSize): Promise { throw new Error("Not implemented"); } - rename(shellId: ShellId, name: string): Promise { - throw new Error("Not implemented"); + async rename(shellId: ShellId, name: string): Promise { + return null; } restart(shellId: ShellId): Promise { throw new Error("Not implemented"); diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 24cbc5f..96cf3cb 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -99,28 +99,29 @@ export class SandboxCommands { const passedEnv = Object.assign(opts?.env ?? {}); - const escapedCommand = command.replace(/'/g, "'\\''"); + // Build bash args array + const args = ["-c", "source $HOME/.private/.env 2>/dev/null || true"]; - // TODO: use a new shell API that natively supports cwd & env - let commandWithEnv = Object.keys(passedEnv).length - ? `bash -c 'source $HOME/.private/.env 2>/dev/null || true && env ${Object.entries( - passedEnv - ) - .map(([key, value]) => `${key}=${value}`) - .join(" ")} ${escapedCommand}'` - : `bash -c 'source $HOME/.private/.env 2>/dev/null || true && ${escapedCommand}'`; + if (Object.keys(passedEnv).length) { + Object.entries(passedEnv).forEach(([key, value]) => { + args.push("&&", "env", `${key}=${value}`); + }); + } if (opts?.cwd) { - commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; + args.push("&&", "cd", opts.cwd); } - const shell = await this.agentClient.shells.create( - this.agentClient.workspacePath, - opts?.dimensions ?? DEFAULT_SHELL_SIZE, - commandWithEnv, - opts?.asGlobalSession ? "COMMAND" : "TERMINAL", - true - ); + args.push("&&", command); + + const shell = await this.agentClient.shells.create({ + command: "bash", + args, + projectPath: this.agentClient.workspacePath, + size: opts?.dimensions ?? DEFAULT_SHELL_SIZE, + type: opts?.asGlobalSession ? "COMMAND" : "TERMINAL", + isSystemShell: true, + }); if (shell.status === "ERROR" || shell.status === "KILLED") { throw new Error(`Failed to create shell: ${shell.buffer.join("\n")}`); diff --git a/src/SandboxClient/filesystem.ts b/src/SandboxClient/filesystem.ts index 68440a5..91bd312 100644 --- a/src/SandboxClient/filesystem.ts +++ b/src/SandboxClient/filesystem.ts @@ -184,13 +184,16 @@ export class FileSystem { try { // Extract the zip file using unzip command - const result = await this.agentClient.shells.create( - this.agentClient.workspacePath, - { cols: 128, rows: 24 }, - `cd ${this.agentClient.workspacePath} && unzip -o ${tempZipPath}`, - "COMMAND", - true - ); + const result = await this.agentClient.shells.create({ + projectPath: this.agentClient.workspacePath, + size: { cols: 128, rows: 24 }, + command: "bash", + args: [ + `cd ${this.agentClient.workspacePath} && unzip -o ${tempZipPath}`, + ], + type: "COMMAND", + isSystemShell: true, + }); if (result.status === "ERROR" || result.status === "KILLED") { throw new Error( diff --git a/src/SandboxClient/terminals.ts b/src/SandboxClient/terminals.ts index 408e5b6..236b808 100644 --- a/src/SandboxClient/terminals.ts +++ b/src/SandboxClient/terminals.ts @@ -86,13 +86,14 @@ export class Terminals { commandWithEnv = `cd ${opts.cwd} && ${commandWithEnv}`; } - const shell = await this.agentClient.shells.create( - this.agentClient.workspacePath, - opts?.dimensions ?? DEFAULT_SHELL_SIZE, - commandWithEnv, - "TERMINAL", - true - ); + const shell = await this.agentClient.shells.create({ + projectPath: this.agentClient.workspacePath, + size: opts?.dimensions ?? DEFAULT_SHELL_SIZE, + command: "bash", + args: [commandWithEnv], + type: "TERMINAL", + isSystemShell: true, + }); if (opts?.name) { this.agentClient.shells.rename(shell.shellId, opts.name); diff --git a/src/agent-client-interface.ts b/src/agent-client-interface.ts index a5f9a55..403c65f 100644 --- a/src/agent-client-interface.ts +++ b/src/agent-client-interface.ts @@ -18,13 +18,14 @@ export interface IAgentClientShells { }>; onShellTerminated: Event; onShellOut: Event; - create( - projectPath: string, - size: shell.ShellSize, - command?: string, - type?: shell.ShellProcessType, - isSystemShell?: boolean - ): Promise; + create(options: { + command: string; + args: string[]; + projectPath: string; + size: shell.ShellSize; + type?: shell.ShellProcessType; + isSystemShell?: boolean; + }): Promise; rename(shellId: shell.ShellId, name: string): Promise; getShells(): Promise; open( diff --git a/test.ts b/test.ts index e8f449d..35beda0 100644 --- a/test.ts +++ b/test.ts @@ -13,10 +13,14 @@ sandbox["pitcherManagerResponse"].pitcherURL = "https://37zz6l-57468.csb.dev"; sandbox["pitcherManagerResponse"].pitcherToken = "0085a669aaf703166263c038751432a5fcb5e56a7ededa131a953b253b5bee12"; +console.log("Connecting..."); + const client = await sandbox.connect({ env: { FOO: "BAR", }, }); -console.log(client.commands.run("")); +console.log("Running command..."); + +console.log(await client.commands.run("echo $FOO")); From a4efb95085c83ea67048edf7a2770bb478274a41 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 22 Oct 2025 10:07:45 +0200 Subject: [PATCH 233/241] WIP --- pint-openapi-bundled.json | 68 +++++++++++++++----- src/PintClient/index.ts | 100 +++++++++++++++++++++++++++--- src/SandboxClient/commands.ts | 6 +- src/api-clients/pint/sdk.gen.ts | 27 ++++++-- src/api-clients/pint/types.gen.ts | 49 ++++++++++++--- test.ts | 22 ++++--- 6 files changed, 222 insertions(+), 50 deletions(-) diff --git a/pint-openapi-bundled.json b/pint-openapi-bundled.json index 6d8a5ce..faeb1a5 100644 --- a/pint-openapi-bundled.json +++ b/pint-openapi-bundled.json @@ -697,12 +697,6 @@ "schema": { "$ref": "#/components/schemas/ExecListResponse" } - }, - "text/event-stream": { - "schema": { - "type": "string", - "description": "Server-Sent Events stream of exec updates" - } } } }, @@ -1016,12 +1010,6 @@ "200": { "description": "Exec output retrieved successfully", "content": { - "text/plain": { - "schema": { - "$ref": "#/components/schemas/ExecStdout", - "description": "Plain text exec output" - } - }, "text/event-stream": { "schema": { "type": "string", @@ -1598,12 +1586,58 @@ } } }, - "/api/v1/ports/stream": { + "/api/v1/stream/execs": { + "get": { + "summary": "List all execs", + "tags": ["execs"], + "description": "Returns a list of all active execs using SSE.", + "operationId": "StreamExecsList", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Execs retrieved successfully", + "content": { + "text/event-stream": { + "schema": { + "type": "string", + "description": "Server-Sent Events stream of exec updates" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/v1/stream/ports": { "get": { "summary": "List open ports using Server-Sent Events (SSE)", "tags": ["ports"], "description": "Lists all open TCP ports on the system AND LISTEN TO THE CHANGES, excluding ignored ports configured in the server.", - "operationId": "ListPortsSSE", + "operationId": "StreamPortsList", "security": [ { "bearerAuth": [] @@ -1820,9 +1854,13 @@ "pid": { "type": "integer", "description": "Process ID of the exec" + }, + "interactive": { + "type": "boolean", + "description": "Whether the exec is interactive" } }, - "required": ["id", "command", "args", "status", "pid"] + "required": ["id", "command", "args", "status", "pid", "interactive"] }, "ExecListResponse": { "type": "object", diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index 1ac0cf9..e0febb3 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -8,9 +8,12 @@ import { Port } from "../pitcher-protocol/messages/port"; import { createExec, ExecItem, + getExecOutput, listExecs, listPorts, - listPortsSse, + PortInfo, + streamExecsList, + streamPortsList, } from "../api-clients/pint"; import { SandboxSession } from "../types"; import { Emitter, EmitterSubscription, Event } from "../utils/event"; @@ -27,20 +30,31 @@ import { ShellProcessStatus, } from "../pitcher-protocol/messages/shell"; +function parseStreamEvent(evt: string): T { + const evtWithoutDataPrefix = evt.substring(5); + + return JSON.parse(evtWithoutDataPrefix); +} + class PintPortsClient implements IAgentClientPorts { private onPortsUpdatedEmitter = new EmitterSubscription((fire) => { const abortController = new AbortController(); - listPortsSse({ + streamPortsList({ signal: abortController.signal, headers: { headers: { Accept: "text/event-stream" }, }, }).then(async ({ stream }) => { for await (const evt of stream) { - const evtWithoutDataPrefix = evt.substring(5); + const data = parseStreamEvent(evt); - fire(JSON.parse(evtWithoutDataPrefix)); + fire( + data.map((pintPort) => ({ + port: pintPort.port, + url: pintPort.address, + })) + ); } }); @@ -67,11 +81,55 @@ class PintPortsClient implements IAgentClientPorts { } export class PintShellsClient implements IAgentClientShells { + private openShells: Record = {}; private onShellExitedEmitter = new EmitterSubscription<{ shellId: string; exitCode: number; - }>(() => { - return Disposable.create(() => {}); + }>((fire) => { + const abortController = new AbortController(); + + let prevExecs: ExecItem[] | undefined; + + streamExecsList({ + signal: abortController.signal, + headers: { + headers: { Accept: "text/event-stream" }, + }, + }).then(async ({ stream }) => { + for await (const evt of stream) { + const execs = parseStreamEvent(evt); + + if (prevExecs) { + execs.forEach((exec) => { + const prevExec = prevExecs?.find( + (execItem) => execItem.id === exec.id + ); + + if (!prevExec) { + return; + } + + console.log("WTF", exec); + + if ( + prevExec.status === "RUNNING" && + (exec.status === "STOPPED" || exec.status === "FINISHED") + ) { + fire({ + shellId: exec.id, + exitCode: 0, + }); + } + }); + } + + prevExecs = execs; + } + }); + + return Disposable.create(() => { + abortController.abort(); + }); }); onShellExited = this.onShellExitedEmitter.event; private onShellOutEmitter = new EmitterSubscription<{ @@ -118,7 +176,6 @@ export class PintShellsClient implements IAgentClientShells { type?: ShellProcessType; isSystemShell?: boolean; }): Promise { - console.log("creating shell", { args, command, type }); const exec = await createExec({ client: this.apiClient, body: { @@ -135,6 +192,8 @@ export class PintShellsClient implements IAgentClientShells { console.log("Gotz shell", exec.data); + await this.open(exec.data.id, { cols: 200, rows: 80 }); + return { ...this.convertExecToShellDTO(exec.data), buffer: [], @@ -152,8 +211,31 @@ export class PintShellsClient implements IAgentClientShells { execs.data?.execs.map((exec) => this.convertExecToShellDTO(exec)) ?? [] ); } - open(shellId: ShellId, size: ShellSize): Promise { - throw new Error("Not implemented"); + async open(shellId: ShellId, size: ShellSize): Promise { + const abortController = new AbortController(); + + this.openShells[shellId] = abortController; + + getExecOutput({ + path: { id: shellId }, + signal: abortController.signal, + headers: { + headers: { Accept: "text/event-stream" }, + }, + }).then(async ({ stream }) => { + for await (const evt of stream) { + const data = parseStreamEvent(evt); + + fire( + data.map((pintPort) => ({ + port: pintPort.port, + url: pintPort.address, + })) + ); + } + }); + + return {}; } async rename(shellId: ShellId, name: string): Promise { return null; diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 96cf3cb..56552e8 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -91,10 +91,6 @@ export class SandboxCommands { "command.name": opts?.name || "", }, async () => { - const disposableStore = new DisposableStore(); - const onOutput = new Emitter(); - disposableStore.add(onOutput); - command = Array.isArray(command) ? command.join(" && ") : command; const passedEnv = Object.assign(opts?.env ?? {}); @@ -299,6 +295,8 @@ export class Command { return; } + console.log("GOTZ OUT"); + this.onOutputEmitter.fire(out); this.output.push(out); diff --git a/src/api-clients/pint/sdk.gen.ts b/src/api-clients/pint/sdk.gen.ts index d7e4b86..0344941 100644 --- a/src/api-clients/pint/sdk.gen.ts +++ b/src/api-clients/pint/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { ConnectToExecWebSocketData, ConnectToExecWebSocketErrors, ConnectToExecWebSocketResponses, CreateDirectoryData, CreateDirectoryErrors, CreateDirectoryResponses, CreateExecData, CreateExecErrors, CreateExecResponses, CreateFileData, CreateFileErrors, CreateFileResponses, DeleteDirectoryData, DeleteDirectoryErrors, DeleteDirectoryResponses, DeleteExecData, DeleteExecErrors, DeleteExecResponses, DeleteFileData, DeleteFileErrors, DeleteFileResponses, ExecExecStdinData, ExecExecStdinErrors, ExecExecStdinResponses, ExecuteTaskActionData, ExecuteTaskActionErrors, ExecuteTaskActionResponses, GetExecData, GetExecErrors, GetExecOutputData, GetExecOutputErrors, GetExecOutputResponses, GetExecResponses, GetTaskData, GetTaskErrors, GetTaskResponses, ListDirectoryData, ListDirectoryErrors, ListDirectoryResponses, ListExecsData, ListExecsErrors, ListExecsResponses, ListPortsData, ListPortsErrors, ListPortsResponses, ListPortsSseData, ListPortsSseErrors, ListPortsSseResponses, ListSetupTasksData, ListSetupTasksErrors, ListSetupTasksResponses, ListTasksData, ListTasksErrors, ListTasksResponses, PerformFileActionData, PerformFileActionErrors, PerformFileActionResponses, ReadFileData, ReadFileErrors, ReadFileResponses, UpdateExecData, UpdateExecErrors, UpdateExecResponses } from './types.gen'; +import type { ConnectToExecWebSocketData, ConnectToExecWebSocketErrors, ConnectToExecWebSocketResponses, CreateDirectoryData, CreateDirectoryErrors, CreateDirectoryResponses, CreateExecData, CreateExecErrors, CreateExecResponses, CreateFileData, CreateFileErrors, CreateFileResponses, DeleteDirectoryData, DeleteDirectoryErrors, DeleteDirectoryResponses, DeleteExecData, DeleteExecErrors, DeleteExecResponses, DeleteFileData, DeleteFileErrors, DeleteFileResponses, ExecExecStdinData, ExecExecStdinErrors, ExecExecStdinResponses, ExecuteTaskActionData, ExecuteTaskActionErrors, ExecuteTaskActionResponses, GetExecData, GetExecErrors, GetExecOutputData, GetExecOutputErrors, GetExecOutputResponses, GetExecResponses, GetTaskData, GetTaskErrors, GetTaskResponses, ListDirectoryData, ListDirectoryErrors, ListDirectoryResponses, ListExecsData, ListExecsErrors, ListExecsResponses, ListPortsData, ListPortsErrors, ListPortsResponses, ListSetupTasksData, ListSetupTasksErrors, ListSetupTasksResponses, ListTasksData, ListTasksErrors, ListTasksResponses, PerformFileActionData, PerformFileActionErrors, PerformFileActionResponses, ReadFileData, ReadFileErrors, ReadFileResponses, StreamExecsListData, StreamExecsListErrors, StreamExecsListResponses, StreamPortsListData, StreamPortsListErrors, StreamPortsListResponses, UpdateExecData, UpdateExecErrors, UpdateExecResponses } from './types.gen'; export type Options = Options2 & { /** @@ -243,7 +243,7 @@ export const updateExec = (options: Option * Retrieves the plain text output from a exec's buffer. */ export const getExecOutput = (options: Options) => { - return (options.client ?? client).get({ + return (options.client ?? client).sse.get({ security: [ { scheme: 'bearer', @@ -392,19 +392,36 @@ export const listPorts = (options?: Option }); }; +/** + * List all execs + * Returns a list of all active execs using SSE. + */ +export const streamExecsList = (options?: Options) => { + return (options?.client ?? client).sse.get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/api/v1/stream/execs', + ...options + }); +}; + /** * List open ports using Server-Sent Events (SSE) * Lists all open TCP ports on the system AND LISTEN TO THE CHANGES, excluding ignored ports configured in the server. */ -export const listPortsSse = (options?: Options) => { - return (options?.client ?? client).sse.get({ +export const streamPortsList = (options?: Options) => { + return (options?.client ?? client).sse.get({ security: [ { scheme: 'bearer', type: 'http' } ], - url: '/api/v1/ports/stream', + url: '/api/v1/stream/ports', ...options }); }; diff --git a/src/api-clients/pint/types.gen.ts b/src/api-clients/pint/types.gen.ts index b9bd964..4b5489a 100644 --- a/src/api-clients/pint/types.gen.ts +++ b/src/api-clients/pint/types.gen.ts @@ -119,6 +119,10 @@ export type ExecItem = { * Process ID of the exec */ pid: number; + /** + * Whether the exec is interactive + */ + interactive: boolean; }; export type ExecListResponse = { @@ -860,9 +864,9 @@ export type GetExecOutputError = GetExecOutputErrors[keyof GetExecOutputErrors]; export type GetExecOutputResponses = { /** - * Exec output retrieved successfully + * Server-Sent Events stream of exec updates with same format as ExecStdout */ - 200: ExecStdout; + 200: string; }; export type GetExecOutputResponse = GetExecOutputResponses[keyof GetExecOutputResponses]; @@ -1150,14 +1154,43 @@ export type ListPortsResponses = { export type ListPortsResponse = ListPortsResponses[keyof ListPortsResponses]; -export type ListPortsSseData = { +export type StreamExecsListData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/stream/execs'; +}; + +export type StreamExecsListErrors = { + /** + * Unauthorized + */ + 401: _Error; + /** + * Unexpected Error + */ + default: _Error; +}; + +export type StreamExecsListError = StreamExecsListErrors[keyof StreamExecsListErrors]; + +export type StreamExecsListResponses = { + /** + * Server-Sent Events stream of exec updates + */ + 200: string; +}; + +export type StreamExecsListResponse = StreamExecsListResponses[keyof StreamExecsListResponses]; + +export type StreamPortsListData = { body?: never; path?: never; query?: never; - url: '/api/v1/ports/stream'; + url: '/api/v1/stream/ports'; }; -export type ListPortsSseErrors = { +export type StreamPortsListErrors = { /** * Unauthorized */ @@ -1172,13 +1205,13 @@ export type ListPortsSseErrors = { default: _Error; }; -export type ListPortsSseError = ListPortsSseErrors[keyof ListPortsSseErrors]; +export type StreamPortsListError = StreamPortsListErrors[keyof StreamPortsListErrors]; -export type ListPortsSseResponses = { +export type StreamPortsListResponses = { /** * Server-Sent Events stream of ports list updates */ 200: string; }; -export type ListPortsSseResponse = ListPortsSseResponses[keyof ListPortsSseResponses]; +export type StreamPortsListResponse = StreamPortsListResponses[keyof StreamPortsListResponses]; diff --git a/test.ts b/test.ts index 35beda0..d9e2955 100644 --- a/test.ts +++ b/test.ts @@ -1,5 +1,12 @@ import { CodeSandbox } from "@codesandbox/sdk"; +const sdk = new CodeSandbox(); + +const sandbox = await sdk.sandboxes.create(); +const client = await sandbox.connect(); +console.log(await client.commands.run('echo "Hello World"')); + +/* const sdk = new CodeSandbox( "csb_v1_uv6a0J6nM43YEMkqJ2RE3T8fuFGmPKsnkZEUkhCBbcU", { @@ -7,20 +14,17 @@ const sdk = new CodeSandbox( } ); console.log("Getting sandbox..."); -let sandbox = await sdk.sandboxes.resume("37zz6l"); +let sandbox = await sdk.sandboxes.resume("cyshxw"); -sandbox["pitcherManagerResponse"].pitcherURL = "https://37zz6l-57468.csb.dev"; +sandbox["pitcherManagerResponse"].pitcherURL = "https://cyshxw-57468.csb.dev"; sandbox["pitcherManagerResponse"].pitcherToken = - "0085a669aaf703166263c038751432a5fcb5e56a7ededa131a953b253b5bee12"; + "1ee18f9b607570eedfe4f809f2f66511fb4ba0a9c1008e60a769a3e9d81496d5"; console.log("Connecting..."); -const client = await sandbox.connect({ - env: { - FOO: "BAR", - }, -}); +const client = await sandbox.connect(); console.log("Running command..."); -console.log(await client.commands.run("echo $FOO")); +console.log(await client.commands.run("echo Hello World")); +*/ From 2415405ea8cb1730bf1cc3c3f2e040a31051b01d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 6 Nov 2025 15:06:23 +0100 Subject: [PATCH 234/241] almost there --- openapi.json | 4 + pint-openapi-bundled.json | 78 ++++++++++------ src/PintClient/index.ts | 140 +++++++++++++++++++--------- src/SandboxClient/commands.ts | 4 +- src/agent-client-interface.ts | 1 + src/api-clients/client/types.gen.ts | 4 + src/api-clients/pint/types.gen.ts | 53 +++++++---- test.ts | 16 +--- 8 files changed, 194 insertions(+), 106 deletions(-) diff --git a/openapi.json b/openapi.json index 07c5176..17bd4c6 100644 --- a/openapi.json +++ b/openapi.json @@ -932,6 +932,8 @@ "cluster": { "type": "string" }, "id": { "type": "string" }, "latest_pitcher_version": { "type": "string" }, + "pint_token": { "type": "string" }, + "pint_url": { "type": "string" }, "pitcher_manager_version": { "type": "string" }, "pitcher_token": { "type": "string" }, "pitcher_url": { "type": "string" }, @@ -1544,6 +1546,8 @@ "cluster": { "type": "string" }, "id": { "type": "string" }, "latest_pitcher_version": { "type": "string" }, + "pint_token": { "type": "string" }, + "pint_url": { "type": "string" }, "pitcher_manager_version": { "type": "string" }, "pitcher_token": { "type": "string" }, "pitcher_url": { "type": "string" }, diff --git a/pint-openapi-bundled.json b/pint-openapi-bundled.json index faeb1a5..2d63658 100644 --- a/pint-openapi-bundled.json +++ b/pint-openapi-bundled.json @@ -1004,6 +1004,16 @@ "schema": { "type": "string" } + }, + { + "name": "lastSequence", + "in": "query", + "required": false, + "description": "Last sequence number received by the client. Used to fetch only new output since that sequence or before if it is negative.", + "schema": { + "type": "integer", + "format": "int32" + } } ], "responses": { @@ -1858,9 +1868,21 @@ "interactive": { "type": "boolean", "description": "Whether the exec is interactive" + }, + "exitCode": { + "type": "integer", + "description": "Exit code of the process (only present when process has exited)" } }, - "required": ["id", "command", "args", "status", "pid", "interactive"] + "required": [ + "id", + "command", + "args", + "status", + "pid", + "interactive", + "exitCode" + ] }, "ExecListResponse": { "type": "object", @@ -1921,31 +1943,6 @@ }, "required": ["message"] }, - "ExecStdout": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Type of the exec output", - "enum": ["stdout", "stderr"] - }, - "output": { - "type": "string", - "description": "Data associated with the exec output" - }, - "sequence": { - "type": "integer", - "format": "int32", - "description": "Sequence number of the output message" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "Timestamp of when the output was generated" - } - }, - "required": ["type", "output", "sequence"] - }, "ExecStdin": { "type": "object", "properties": { @@ -2183,6 +2180,35 @@ }, "required": ["ports"] }, + "ExecStdout": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the exec output", + "enum": ["stdout", "stderr"] + }, + "output": { + "type": "string", + "description": "Data associated with the exec output" + }, + "sequence": { + "type": "integer", + "format": "int32", + "description": "Sequence number of the output message" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Timestamp of when the output was generated" + }, + "exitCode": { + "type": "integer", + "description": "Exit code of the process (only present when process has exited)" + } + }, + "required": ["type", "output", "sequence"] + }, "Task": { "$ref": "#/components/schemas/TaskItem" } diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index e0febb3..33c125e 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -8,6 +8,8 @@ import { Port } from "../pitcher-protocol/messages/port"; import { createExec, ExecItem, + ExecListResponse, + getExec, getExecOutput, listExecs, listPorts, @@ -30,7 +32,11 @@ import { ShellProcessStatus, } from "../pitcher-protocol/messages/shell"; -function parseStreamEvent(evt: string): T { +function parseStreamEvent(evt: unknown): T { + if (typeof evt !== "string") { + return evt as T; + } + const evtWithoutDataPrefix = evt.substring(5); return JSON.parse(evtWithoutDataPrefix); @@ -82,22 +88,25 @@ class PintPortsClient implements IAgentClientPorts { export class PintShellsClient implements IAgentClientShells { private openShells: Record = {}; - private onShellExitedEmitter = new EmitterSubscription<{ - shellId: string; - exitCode: number; - }>((fire) => { + private subscribeAndEvaluateExecsUpdates( + compare: ( + nextExec: ExecItem, + prevExec: ExecItem | undefined, + prevExecs: ExecItem[] + ) => void + ) { + let prevExecs: ExecItem[] = []; const abortController = new AbortController(); - let prevExecs: ExecItem[] | undefined; - streamExecsList({ + client: this.apiClient, signal: abortController.signal, headers: { headers: { Accept: "text/event-stream" }, }, }).then(async ({ stream }) => { for await (const evt of stream) { - const execs = parseStreamEvent(evt); + const execs = (evt as unknown as ExecListResponse).execs; // parseStreamEvent(evt); if (prevExecs) { execs.forEach((exec) => { @@ -105,21 +114,7 @@ export class PintShellsClient implements IAgentClientShells { (execItem) => execItem.id === exec.id ); - if (!prevExec) { - return; - } - - console.log("WTF", exec); - - if ( - prevExec.status === "RUNNING" && - (exec.status === "STOPPED" || exec.status === "FINISHED") - ) { - fire({ - shellId: exec.id, - exitCode: 0, - }); - } + compare(exec, prevExec, prevExecs); }); } @@ -130,21 +125,48 @@ export class PintShellsClient implements IAgentClientShells { return Disposable.create(() => { abortController.abort(); }); - }); + } + private onShellExitedEmitter = new EmitterSubscription<{ + shellId: string; + exitCode: number; + }>((fire) => + this.subscribeAndEvaluateExecsUpdates((exec, prevExec) => { + if (!prevExec) { + return; + } + + if (prevExec.status === "RUNNING" && exec.status === "EXITED") { + fire({ + shellId: exec.id, + exitCode: exec.exitCode, + }); + } + }) + ); onShellExited = this.onShellExitedEmitter.event; - private onShellOutEmitter = new EmitterSubscription<{ + + private onShellOutEmitter = new Emitter<{ shellId: ShellId; out: string; - }>(() => { - return Disposable.create(() => {}); - }); + }>(); onShellOut = this.onShellOutEmitter.event; private onShellTerminatedEmitter = new EmitterSubscription<{ - shellId: ShellId; + shellId: string; author: string; - }>(() => { - return Disposable.create(() => {}); - }); + }>((fire) => + this.subscribeAndEvaluateExecsUpdates((exec, prevExec) => { + if (!prevExec) { + return; + } + + if (prevExec.status === "RUNNING" && exec.status === "STOPPED") { + fire({ + shellId: exec.id, + author: "", + }); + } + }) + ); onShellTerminated = this.onShellTerminatedEmitter.event; constructor(private apiClient: Client, private sandboxId: string) {} private convertExecToShellDTO(exec: ExecItem) { @@ -187,7 +209,7 @@ export class PintShellsClient implements IAgentClientShells { if (!exec.data) { console.log(exec); - throw new Error("Nooooooooo"); + throw new Error(exec.error.message); } console.log("Gotz shell", exec.data); @@ -216,26 +238,52 @@ export class PintShellsClient implements IAgentClientShells { this.openShells[shellId] = abortController; - getExecOutput({ + const exec = await getExec({ + client: this.apiClient, + path: { + id: shellId, + }, + }); + + if (!exec.data) { + throw new Error(exec.error.message); + } + + const { stream } = await getExecOutput({ + client: this.apiClient, path: { id: shellId }, + query: { lastSequence: 0 }, signal: abortController.signal, headers: { - headers: { Accept: "text/event-stream" }, + Accept: "text/event-stream", }, - }).then(async ({ stream }) => { - for await (const evt of stream) { - const data = parseStreamEvent(evt); + }); - fire( - data.map((pintPort) => ({ - port: pintPort.port, - url: pintPort.address, - })) - ); + const buffer: string[] = []; + + console.log("Waiting for IO"); + for await (const evt of stream) { + const data = parseStreamEvent<{ + type: "stdout" | "stderr"; + output: ""; + sequence: number; + timestamp: string; + }>(evt); + + console.log("GOTZ IO", data); + + if (!buffer.length) { + buffer.push(data.output); + break; } - }); + } + + console.log("No IO", buffer); - return {}; + return { + buffer, + ...this.convertExecToShellDTO(exec.data), + }; } async rename(shellId: ShellId, name: string): Promise { return null; diff --git a/src/SandboxClient/commands.ts b/src/SandboxClient/commands.ts index 56552e8..7c85140 100644 --- a/src/SandboxClient/commands.ts +++ b/src/SandboxClient/commands.ts @@ -96,7 +96,7 @@ export class SandboxCommands { const passedEnv = Object.assign(opts?.env ?? {}); // Build bash args array - const args = ["-c", "source $HOME/.private/.env 2>/dev/null || true"]; + const args = ["source $HOME/.private/.env 2>/dev/null || true"]; if (Object.keys(passedEnv).length) { Object.entries(passedEnv).forEach(([key, value]) => { @@ -112,7 +112,7 @@ export class SandboxCommands { const shell = await this.agentClient.shells.create({ command: "bash", - args, + args: ["-c", args.join(" ")], projectPath: this.agentClient.workspacePath, size: opts?.dimensions ?? DEFAULT_SHELL_SIZE, type: opts?.asGlobalSession ? "COMMAND" : "TERMINAL", diff --git a/src/agent-client-interface.ts b/src/agent-client-interface.ts index 403c65f..2f84815 100644 --- a/src/agent-client-interface.ts +++ b/src/agent-client-interface.ts @@ -129,6 +129,7 @@ export type IAgentClientState = | "HIBERNATED"; export interface IAgentClient { + type: "pitcher" | "pint"; sandboxId: string; workspacePath: string; isUpToDate: boolean; diff --git a/src/api-clients/client/types.gen.ts b/src/api-clients/client/types.gen.ts index ee3c79a..e30ac60 100644 --- a/src/api-clients/client/types.gen.ts +++ b/src/api-clients/client/types.gen.ts @@ -590,6 +590,8 @@ export type VmStartResponse = { cluster: string; id: string; latest_pitcher_version: string; + pint_token?: string; + pint_url?: string; pitcher_manager_version: string; pitcher_token: string; pitcher_url: string; @@ -953,6 +955,8 @@ export type SandboxForkResponse = { cluster: string; id: string; latest_pitcher_version: string; + pint_token?: string; + pint_url?: string; pitcher_manager_version: string; pitcher_token: string; pitcher_url: string; diff --git a/src/api-clients/pint/types.gen.ts b/src/api-clients/pint/types.gen.ts index 4b5489a..61f2499 100644 --- a/src/api-clients/pint/types.gen.ts +++ b/src/api-clients/pint/types.gen.ts @@ -123,6 +123,10 @@ export type ExecItem = { * Whether the exec is interactive */ interactive: boolean; + /** + * Exit code of the process (only present when process has exited) + */ + exitCode: number; }; export type ExecListResponse = { @@ -165,25 +169,6 @@ export type ExecDeleteResponse = { message: string; }; -export type ExecStdout = { - /** - * Type of the exec output - */ - type: 'stdout' | 'stderr'; - /** - * Data associated with the exec output - */ - output: string; - /** - * Sequence number of the output message - */ - sequence: number; - /** - * Timestamp of when the output was generated - */ - timestamp?: string; -}; - export type ExecStdin = { /** * Type of the exec input @@ -313,6 +298,29 @@ export type PortsListResponse = { ports: Array; }; +export type ExecStdout = { + /** + * Type of the exec output + */ + type: 'stdout' | 'stderr'; + /** + * Data associated with the exec output + */ + output: string; + /** + * Sequence number of the output message + */ + sequence: number; + /** + * Timestamp of when the output was generated + */ + timestamp?: string; + /** + * Exit code of the process (only present when process has exited) + */ + exitCode?: number; +}; + export type Task = TaskItem; export type DeleteFileData = { @@ -837,7 +845,12 @@ export type GetExecOutputData = { */ id: string; }; - query?: never; + query?: { + /** + * Last sequence number received by the client. Used to fetch only new output since that sequence or before if it is negative. + */ + lastSequence?: number; + }; url: '/api/v1/execs/{id}/io'; }; diff --git a/test.ts b/test.ts index d9e2955..4a2f916 100644 --- a/test.ts +++ b/test.ts @@ -1,12 +1,5 @@ import { CodeSandbox } from "@codesandbox/sdk"; -const sdk = new CodeSandbox(); - -const sandbox = await sdk.sandboxes.create(); -const client = await sandbox.connect(); -console.log(await client.commands.run('echo "Hello World"')); - -/* const sdk = new CodeSandbox( "csb_v1_uv6a0J6nM43YEMkqJ2RE3T8fuFGmPKsnkZEUkhCBbcU", { @@ -14,11 +7,11 @@ const sdk = new CodeSandbox( } ); console.log("Getting sandbox..."); -let sandbox = await sdk.sandboxes.resume("cyshxw"); +let sandbox = await sdk.sandboxes.resume("kf55nx"); -sandbox["pitcherManagerResponse"].pitcherURL = "https://cyshxw-57468.csb.dev"; +sandbox["pitcherManagerResponse"].pitcherURL = "https://kf55nx-57468.csb.dev"; sandbox["pitcherManagerResponse"].pitcherToken = - "1ee18f9b607570eedfe4f809f2f66511fb4ba0a9c1008e60a769a3e9d81496d5"; + "4b1d4b53771541daf9280b085774bb8f401e881f999b9cb700ab40b0c6b6a55d"; console.log("Connecting..."); @@ -26,5 +19,4 @@ const client = await sandbox.connect(); console.log("Running command..."); -console.log(await client.commands.run("echo Hello World")); -*/ +console.log(await client.commands.run("echo 'Hello World'")); From 23fb42edc54b2ad6513c0158f134a633046bca7a Mon Sep 17 00:00:00 2001 From: mohaimen Date: Mon, 10 Nov 2025 23:19:20 +0100 Subject: [PATCH 235/241] init fsclient imp --- src/PintClient/index.ts | 142 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 6 deletions(-) diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index 33c125e..d9cc3cc 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -1,10 +1,17 @@ + +import { Port } from "../pitcher-protocol/messages/port"; +import { SandboxSession } from "../types"; +import { Emitter, EmitterSubscription, Event } from "../utils/event"; +import { Disposable } from "../utils/disposable"; +import { Client, createClient, createConfig } from "../api-clients/pint/client"; import { IAgentClient, IAgentClientPorts, IAgentClientShells, IAgentClientState, + IAgentClientFS, + PickRawFsResult, } from "../agent-client-interface"; -import { Port } from "../pitcher-protocol/messages/port"; import { createExec, ExecItem, @@ -16,11 +23,16 @@ import { PortInfo, streamExecsList, streamPortsList, + createFile, + readFile, + deleteFile, + performFileAction, + listDirectory, + createDirectory, + deleteDirectory, + } from "../api-clients/pint"; -import { SandboxSession } from "../types"; -import { Emitter, EmitterSubscription, Event } from "../utils/event"; -import { Disposable } from "../utils/disposable"; -import { Client, createClient, createConfig } from "../api-clients/pint/client"; + import { ShellSize, ShellProcessType, @@ -31,6 +43,15 @@ import { ShellDTO, ShellProcessStatus, } from "../pitcher-protocol/messages/shell"; +import { + FSReadFileParams, + FSReadFileResult, + FSReadDirParams, + FSReadDirResult, + FSReadDirMessage, + FSWriteFileParams, + FSWRiteFileResult, +} from "../pitcher-protocol/messages/fs"; function parseStreamEvent(evt: unknown): T { if (typeof evt !== "string") { @@ -296,11 +317,119 @@ export class PintShellsClient implements IAgentClientShells { } } +export class PintFsClient implements IAgentClientFS { + constructor(private apiClient: Client, private sandboxId: string) {} + + async readFile(path: string): Promise> { + try { + const response = await readFile({ + client: this.apiClient, + path: { + path: path, + }, + }); + + if (response.data) { + // Convert string content to Uint8Array to match FSReadFileResult type + const encoder = new TextEncoder(); + const content = encoder.encode(response.data.content); + + return { + type: "ok", + result: { + content: content, + }, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to read file", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } + } + + // TODO: Implement other IAgentClientFS methods + async readdir(path: string): Promise> { + throw new Error("Not implemented"); + } + + async writeFile( + path: string, + content: Uint8Array, + create?: boolean, + overwrite?: boolean + ): Promise> { + throw new Error("Not implemented"); + } + + async stat(path: string): Promise> { + throw new Error("Not implemented"); + } + + async copy( + from: string, + to: string, + recursive?: boolean, + overwrite?: boolean + ): Promise> { + throw new Error("Not implemented"); + } + + async rename( + from: string, + to: string, + overwrite?: boolean + ): Promise> { + throw new Error("Not implemented"); + } + + async remove( + path: string, + recursive?: boolean + ): Promise> { + throw new Error("Not implemented"); + } + + async mkdir( + path: string, + recursive?: boolean + ): Promise> { + throw new Error("Not implemented"); + } + + async watch( + path: string, + options: { + readonly recursive?: boolean; + readonly excludes?: readonly string[]; + }, + onEvent: (watchEvent: any) => void + ): Promise< + | (PickRawFsResult<"fs/watch"> & { type: "error" }) + | { type: "success"; dispose(): void } + > { + throw new Error("Not implemented"); + } + + async download(path?: string): Promise<{ downloadUrl: string }> { + throw new Error("Not implemented"); + } +} export class PintClient implements IAgentClient { static async create(session: SandboxSession) { return new PintClient(session); } + readonly type = "pint" as const; + // Since there is no websocket connection or internal hibernation, the state // will always be CONNECTED. No state change events will be triggered readonly state = "CONNECTED"; @@ -313,7 +442,7 @@ export class PintClient implements IAgentClient { ports: IAgentClientPorts; shells: IAgentClientShells; - fs: any = null; // TODO: Implement + fs: IAgentClientFS; setup: any = null; // TODO: Implement tasks: any = null; // TODO: Implement system: any = null; // TODO: Implement @@ -334,6 +463,7 @@ export class PintClient implements IAgentClient { this.ports = new PintPortsClient(apiClient, this.sandboxId); this.shells = new PintShellsClient(apiClient, this.sandboxId); + this.fs = new PintFsClient(apiClient, this.sandboxId); } ping(): void {} From 2a9404a92741edaf6fbef00a7f38a0b7a061f949 Mon Sep 17 00:00:00 2001 From: mohaimen Date: Tue, 11 Nov 2025 22:32:59 +0100 Subject: [PATCH 236/241] finish implementing the basic files and directories apis --- package-lock.json | 2 +- package.json | 2 +- src/AgentClient/index.ts | 1 + src/PintClient/index.ts | 155 ++++++++++++++++++++++++++++++++++----- test.ts | 18 ++++- 5 files changed, 157 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 05cc710..bad48e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "react": "^19.1.1", "rimraf": "^6.0.1", "semver": "^6.3.0", - "tslib": "^2.1.0", + "tslib": "^2.8.1", "typescript": "^5.7.2", "vitest": "^3.2.4", "why-is-node-running": "^2.3.0" diff --git a/package.json b/package.json index 617338b..c8d6bf6 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "react": "^19.1.1", "rimraf": "^6.0.1", "semver": "^6.3.0", - "tslib": "^2.1.0", + "tslib": "^2.8.1", "typescript": "^5.7.2", "vitest": "^3.2.4", "why-is-node-running": "^2.3.0" diff --git a/src/AgentClient/index.ts b/src/AgentClient/index.ts index c0c23ea..b456afc 100644 --- a/src/AgentClient/index.ts +++ b/src/AgentClient/index.ts @@ -398,6 +398,7 @@ class AgentClientSystem implements IAgentClientSystem { } export class AgentClient implements IAgentClient { + readonly type = "pitcher" as const; static async create({ session, getSession, diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index d9cc3cc..a457f53 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -356,9 +356,42 @@ export class PintFsClient implements IAgentClientFS { } } - // TODO: Implement other IAgentClientFS methods async readdir(path: string): Promise> { - throw new Error("Not implemented"); + try { + const response = await listDirectory({ + client: this.apiClient, + path: { + path: path, + }, + }); + + if (response.data) { + const entries = response.data.files.map((fileInfo) => ({ + name: fileInfo.name, + type: fileInfo.isDir ? (1 as const) : (0 as const), // 1 = directory, 0 = file + isSymlink: false, // API doesn't provide symlink info, defaulting to false + })); + + return { + type: "ok", + result: { + entries: entries, + }, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to read directory", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } } async writeFile( @@ -367,7 +400,109 @@ export class PintFsClient implements IAgentClientFS { create?: boolean, overwrite?: boolean ): Promise> { - throw new Error("Not implemented"); + try { + // Convert Uint8Array content to string for the API + const decoder = new TextDecoder(); + const contentString = decoder.decode(content); + + const response = await createFile({ + client: this.apiClient, + path: { + path: path, + }, + body: { + content: contentString, + }, + }); + + if (response.data) { + // FSWriteFileResult is an empty object (Record) + return { + type: "ok", + result: {}, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to write file", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } + } + + async remove( + path: string, + recursive?: boolean + ): Promise> { + try { + const response = await deleteDirectory({ + client: this.apiClient, + path: { + path: path, + }, + }); + + if (response.data) { + // FSRemoveResult is an empty object (Record) + return { + type: "ok", + result: {}, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to remove directory", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } + } + + async mkdir( + path: string, + recursive?: boolean + ): Promise> { + try { + const response = await createDirectory({ + client: this.apiClient, + path: { + path: path, + }, + }); + + if (response.data) { + // FSMkdirResult is an empty object (Record) + return { + type: "ok", + result: {}, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to create directory", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } } async stat(path: string): Promise> { @@ -391,20 +526,6 @@ export class PintFsClient implements IAgentClientFS { throw new Error("Not implemented"); } - async remove( - path: string, - recursive?: boolean - ): Promise> { - throw new Error("Not implemented"); - } - - async mkdir( - path: string, - recursive?: boolean - ): Promise> { - throw new Error("Not implemented"); - } - async watch( path: string, options: { diff --git a/test.ts b/test.ts index 4a2f916..1448b76 100644 --- a/test.ts +++ b/test.ts @@ -17,6 +17,20 @@ console.log("Connecting..."); const client = await sandbox.connect(); -console.log("Running command..."); +//console.log("Running command..."); +//console.log(await client.commands.run("echo 'Hello World'")); -console.log(await client.commands.run("echo 'Hello World'")); + +console.log("creating directory..."); +console.log(await client.fs.mkdir("/workspace/newdir")); +console.log("creating file in new dir..."); +console.log(await client.fs.writeFile("/workspace/newdir/text.txt", new TextEncoder().encode("Hello World"))); +console.log("read file..."); +console.log(await client.fs.readTextFile("/workspace/newdir/text.txt")); + +console.log("Reading directory after adding newdir and new file..."); +console.log(await client.fs.readdir("/workspace")); +console.log("Removing directory..."); +console.log(await client.fs.remove("/workspace/newdir")); +console.log("Reading directory after deleting newdir..."); +console.log(await client.fs.readdir("/workspace")); From a5bcf9d5c5cf3ea331213f6201e8a9f20392979c Mon Sep 17 00:00:00 2001 From: mohaimen Date: Tue, 11 Nov 2025 23:08:44 +0100 Subject: [PATCH 237/241] add delete exec api --- src/PintClient/index.ts | 43 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index a457f53..d95228c 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -26,6 +26,7 @@ import { createFile, readFile, deleteFile, + deleteExec, performFileAction, listDirectory, createDirectory, @@ -242,8 +243,46 @@ export class PintShellsClient implements IAgentClientShells { buffer: [], }; } - delete(shellId: ShellId): Promise { - throw new Error("Not implemented"); + async delete(shellId: ShellId): Promise { + try { + // First get the exec details before deleting it + const exec = await getExec({ + client: this.apiClient, + path: { + id: shellId, + }, + }); + + if (!exec.data) { + return null; // Exec doesn't exist + } + + // Convert to shell DTO before deletion + const shellDTO = this.convertExecToShellDTO(exec.data); + + // Delete the exec + const deleteResponse = await deleteExec({ + client: this.apiClient, + path: { + id: shellId, + }, + }); + + if (deleteResponse.data) { + // Clean up any open shells reference + if (this.openShells[shellId]) { + this.openShells[shellId].abort(); + delete this.openShells[shellId]; + } + + return shellDTO as CommandShellDTO | TerminalShellDTO; + } else { + return null; + } + } catch (error) { + console.error("Failed to delete shell:", error); + return null; + } } async getShells(): Promise { const execs = await listExecs({ From a5fafeef4dbb55809117e1e25bcbacf577136e53 Mon Sep 17 00:00:00 2001 From: mohaimen Date: Wed, 12 Nov 2025 13:20:46 +0100 Subject: [PATCH 238/241] implement restart shell and send stdin apis --- src/PintClient/index.ts | 41 +++++++++++++++++++++++++++++++++++++---- test.ts | 1 - 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index d95228c..103a430 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -27,6 +27,8 @@ import { readFile, deleteFile, deleteExec, + updateExec, + execExecStdin, performFileAction, listDirectory, createDirectory, @@ -348,11 +350,42 @@ export class PintShellsClient implements IAgentClientShells { async rename(shellId: ShellId, name: string): Promise { return null; } - restart(shellId: ShellId): Promise { - throw new Error("Not implemented"); + async restart(shellId: ShellId): Promise { + try { + await updateExec({ + client: this.apiClient, + path: { + id: shellId, + }, + body: { + status: 'running', + }, + }); + + return null; + } catch (error) { + console.error("Failed to restart shell:", error); + return null; + } } - send(shellId: ShellId, input: string, size: ShellSize): Promise { - throw new Error("Not implemented"); + async send(shellId: ShellId, input: string, size: ShellSize): Promise { + try { + await execExecStdin({ + client: this.apiClient, + path: { + id: shellId, + }, + body: { + type: 'stdin', + input: input, + }, + }); + + return null; + } catch (error) { + console.error("Failed to send input to shell:", error); + return null; + } } } diff --git a/test.ts b/test.ts index 1448b76..a5c0815 100644 --- a/test.ts +++ b/test.ts @@ -20,7 +20,6 @@ const client = await sandbox.connect(); //console.log("Running command..."); //console.log(await client.commands.run("echo 'Hello World'")); - console.log("creating directory..."); console.log(await client.fs.mkdir("/workspace/newdir")); console.log("creating file in new dir..."); From 9d93f9185515affa4da2997827718e2c0360a5af Mon Sep 17 00:00:00 2001 From: mohaimen Date: Thu, 13 Nov 2025 13:39:24 +0100 Subject: [PATCH 239/241] add rename file implementation --- src/PintClient/index.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/PintClient/index.ts b/src/PintClient/index.ts index 103a430..8a68ec1 100644 --- a/src/PintClient/index.ts +++ b/src/PintClient/index.ts @@ -595,7 +595,38 @@ export class PintFsClient implements IAgentClientFS { to: string, overwrite?: boolean ): Promise> { - throw new Error("Not implemented"); + try { + const response = await performFileAction({ + client: this.apiClient, + path: { + path: from, + }, + body: { + action: 'move', + destination: to, + }, + }); + + if (response.data) { + // FSRenameResult is an empty object (Record) + return { + type: "ok", + result: {}, + }; + } else { + return { + type: "error", + error: response.error?.message || "Failed to rename/move file", + errno: null, + }; + } + } catch (error) { + return { + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + errno: null, + }; + } } async watch( From 803a99172c4b74385255ab9bd6f45e8f70743846 Mon Sep 17 00:00:00 2001 From: mohaimen Date: Thu, 13 Nov 2025 13:47:12 +0100 Subject: [PATCH 240/241] test rename file --- test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test.ts b/test.ts index a5c0815..d9be854 100644 --- a/test.ts +++ b/test.ts @@ -27,9 +27,14 @@ console.log(await client.fs.writeFile("/workspace/newdir/text.txt", new TextEnco console.log("read file..."); console.log(await client.fs.readTextFile("/workspace/newdir/text.txt")); +console.log("Reading directory after adding newdir and new file..."); +console.log(await client.fs.readdir("/workspace")); +console.log("rename file....."); +console.log(await client.fs.rename("/workspace/newdir/text.txt", "/workspace/newdir2/renamed.txt")); + console.log("Reading directory after adding newdir and new file..."); console.log(await client.fs.readdir("/workspace")); console.log("Removing directory..."); -console.log(await client.fs.remove("/workspace/newdir")); +console.log(await client.fs.remove("/workspace/newdir2")); console.log("Reading directory after deleting newdir..."); -console.log(await client.fs.readdir("/workspace")); +console.log(await client.fs.readdir("/workspace")); \ No newline at end of file From 661857542cef698c4db3ab9b235dcfa5653a0b9c Mon Sep 17 00:00:00 2001 From: Joji Augustine Date: Fri, 14 Nov 2025 17:34:14 +0100 Subject: [PATCH 241/241] add e2e pint <> pitcher compat tests --- package.json | 1 + src/Sandbox.ts | 4 +--- tests/e2e/api.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/api.test.ts diff --git a/package.json b/package.json index c8d6bf6..5a38932 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "build-openapi-pint": "node_modules/.bin/openapi-ts -i ./pint-openapi-bundled.json -o src/api-clients/pint -c @hey-api/client-fetch", "clean": "rimraf ./dist", "test": "vitest", + "test:e2e": "vitest run tests/e2e/", "typecheck": "tsc --noEmit", "format": "prettier '**/*.{md,js,jsx,json,ts,tsx}' --write", "postbuild": "rimraf {lib,es}/**/__tests__ {lib,es}/**/*.{spec,test}.{js,d.ts,js.map}", diff --git a/src/Sandbox.ts b/src/Sandbox.ts index bf9e8fb..0b61890 100644 --- a/src/Sandbox.ts +++ b/src/Sandbox.ts @@ -180,9 +180,7 @@ export class Sandbox { customSession?: SessionCreateOptions ): Promise { // HACK: we currently do not get a flag for pint, but this is a check we can use for now - const isPint = - pitcherManagerResponse.userWorkspacePath === - pitcherManagerResponse.workspacePath; + const isPint = pitcherManagerResponse.pitcherManagerVersion.endsWith('bartender'); if (!customSession || !customSession.id) { return { diff --git a/tests/e2e/api.test.ts b/tests/e2e/api.test.ts new file mode 100644 index 0000000..7ddee83 --- /dev/null +++ b/tests/e2e/api.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest' +import { CodeSandbox } from '../../src/index' + +process.env.CSB_BASE_URL = process.env.CSB_BASE_URL ?? 'https://api.codesandbox.dev' +process.env.CSB_API_KEY = process.env.CSB_API_KEY ?? 'csb_v1_devbox' + +// Stream +// Pitcher: 7ngcrf +// Pint + Bartender: 8rf5py +// process.env.CSB_TEMPLATE_ID = process.env.CSB_TEMPLATE_ID ?? '7ngcrf' +process.env.CSB_TEMPLATE_ID = process.env.CSB_TEMPLATE_ID ?? '8rf5py' + + +describe('Sandbox Creation', () => { + it('should successfully create and start a sandbox', async () => { + const sdk = new CodeSandbox() + let sandbox: any = null + + try { + console.log('Creating sandbox...') + sandbox = await sdk.sandboxes.create({ + id: process.env.CSB_TEMPLATE_ID, + }) + console.log('Created sandbox with ID:', sandbox.id) + + + const client = await sandbox.connect() + console.log('Connected to sandbox, fetching root files...') + + const files = await client.fs.readdir('/') + expect(sandbox).not.toBeNull() + expect(Array.isArray(files)).toBe(true) + expect(files.length).toBeGreaterThan(0) + expect(files.some((file: any) => file.name === 'etc' && file.type === 'directory' && !file.isSymlink)).toBe(true) + + } finally { + if (sandbox) { + console.log('Shutting down sandbox...') + await sdk.sandboxes.shutdown(sandbox.id) + } + } + }, 30000) +})