diff --git a/package.json b/package.json index e06e9e5..5c65f13 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "0.2.1", "type": "module", "bin": { - "vgrok": "./dist/src/vgrok.js" + "vgrok": "./dist/vgrok.js" }, - "main": "./dist/src/client.js", + "main": "./dist/client.js", "files": [ "dist" ], @@ -22,11 +22,11 @@ }, "packageManager": "pnpm@10.14.0", "dependencies": { - "@vercel/sandbox": "^0.0.16" + "@vercel/sandbox": "2.0.0-beta.9" }, "devDependencies": { "@types/node": "^24.1.0", "@types/ws": "^8.18.1", - "@typescript/native-preview": "7.0.0-dev.20260124.1" + "@typescript/native-preview": "7.0.0-dev.20260329.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc3cfb4..eaaf914 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@vercel/sandbox': - specifier: ^0.0.16 - version: 0.0.16 + specifier: 2.0.0-beta.9 + version: 2.0.0-beta.9 devDependencies: '@types/node': specifier: ^24.1.0 @@ -19,8 +19,8 @@ importers: specifier: ^8.18.1 version: 8.18.1 '@typescript/native-preview': - specifier: 7.0.0-dev.20260124.1 - version: 7.0.0-dev.20260124.1 + specifier: 7.0.0-dev.20260329.1 + version: 7.0.0-dev.20260329.1 packages: @@ -30,51 +30,51 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-hFA0vQyyrmTwRLfZnC03QtCCwg/6kyM5qfOjEoIqKlAi7TllP4eLFSJz38X9fh/3Geh0krkZHeIh6h6QP0Jjfw==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-zS1thDk7luD82nXVwvMd97F7FgxAE6jGtSmnHeXdaQ+6hJQcQLOVkfUdaehhdodqKDapWA2jEURxQAYjDGvv3g==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-WvhTJ0YucAQpTQwy54tj8c8rzsPFXJeXAk04vYQSBBq7gv3k8CoLuVzghAYG2zGzAFEHA9FW/rT9NACqNJ8Iww==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-3IJ2qmpjQ1OXpZNUhJRjF1+SbDuqGC14Ug8DjWJlPBp06isi1fcJph90f5qW//FxEsNnJPYRcNwpP0A2RbTASg==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-YjhWXiQdCOMbWhZgOy4eYs5l6i6lKxtI8rsmzLiGyM7sgD7Uzq8hh9z1DCSpGwvDR7Bky9sjkjGHJ2r+KGaU2Q==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-gQb6SjB5JlUKDaDuz6mv/m+/OBWVDlcjHINFOykBZZYZtgxBx6nEDjLrT8TiJRjmHEG6hSbv+yisUL9IThWycA==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-fJGwwQYldfeSO/rzXJMgRR+YhfBdGZgQN+n3vBX5/lCUWS1dtXI/8yWpBs/mc0UtYm0w2oY912eG4Xd0kJMElw==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-WKSSJrH611DFFAg6YCkgbnkdy0a4RRpzvDpNXtPzLTbMYC5oJdq3Dpvncx5nrJvGh4J4yvzXoMxraGPyygqGLw==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-ReEuzIqgwydFCsRUmlPERfgt38m7Z+Lyw3MddOCT6mJFArZ4J+XxOFlPL3PLI1pSXJHeHc3oTSQGToODLR1bnw==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-kg4r+ssxoEWruBynUg9bFMdcMpo5NupzAPqNBlV8uWbmYGZjaPLonFWAi9ZZMiVJY/x5ZQ9GBl6xskwLdd3PJQ==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-4jwjoKlsapGj0wFTxI/d9TLBK+kVHwn+qSdPIcuE2t8OsjpEXSvjvjHMX64ysyf6VGVKd9m/qJs8708qo4RYmg==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-Qi4lddVxl5MG7Tk67gYhCFnoqqLGd4TvaI8RN4qHFjt3GV8s6c+0cQGsJXJnVgMx27qbyDTdsyAa2pvb42rYcQ==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-dtUucoRDMi0bUbM2GkSF3qMbNRwE+K9udc6lTUj6+akXLqatuXm7PHVcqjrdXCyarc3CSNJE5Uq6GDHWzjDxyw==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-+k5+usuB8HZ6Xc+enLdb95ZJd25bQqsnI1zXxfRCHP+RS9mxs70Mi9ezQz3lKOLZFFXShSH7iW9iulm8KwVzCQ==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260124.1': - resolution: {integrity: sha512-VRIKr9caNPE8zZqQKyPaRB+3HZQVy5AECW2B8GMRqp2LQvE5eDKXsilXAcLc0LaaHDwXIKxMlvIVC1sWkJOYhw==} + '@typescript/native-preview@7.0.0-dev.20260329.1': + resolution: {integrity: sha512-v5lJ0TgSt2m9yVk2xoj9+NH/gTDeWTLaWGPx6MJsUKOYd6bmCJhHbMcWmb8d/zlfhE9ffpixUKYj62CdYfriqA==} hasBin: true - '@vercel/oidc@2.0.0': - resolution: {integrity: sha512-U0hncpXof7gC9xtmSrjz6vrDqdwJXi8z/zSc9FyS80AoXKfCZtpkBz9gtL3x30Agmpxpwe35P1W2dP9Epa/RGA==} - engines: {node: '>= 18'} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} - '@vercel/sandbox@0.0.16': - resolution: {integrity: sha512-CaCWk9E1N9k4rTE6ArowVOOWDAIE/vUekDzRyBJumaMDwU9tNR0o6bRSNVlBycKmQPqKm0wSrHiQwn4CQbTvFw==} + '@vercel/sandbox@2.0.0-beta.9': + resolution: {integrity: sha512-BTxplf5Mcqp0H0KA5gCmV5nROgXcH7MudGqhrDKqWjKNiib4CV2fzF5zmIIaN/tVuZjHX73PUESIlKLPVMXZcQ==} async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -94,6 +94,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + os-paths@4.4.0: + resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==} + engines: {node: '>= 6.0'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -110,6 +117,18 @@ packages: undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + undici@7.24.6: + resolution: {integrity: sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==} + engines: {node: '>=20.18.1'} + + xdg-app-paths@5.1.0: + resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==} + engines: {node: '>=6'} + + xdg-portable@7.3.0: + resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==} + engines: {node: '>= 6.0'} + zod@3.24.4: resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} @@ -123,46 +142,49 @@ snapshots: dependencies: '@types/node': 24.1.0 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260124.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260124.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260124.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260124.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260124.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260124.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260124.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260329.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260124.1': + '@typescript/native-preview@7.0.0-dev.20260329.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260124.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260124.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260124.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260124.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260124.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260124.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260124.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260329.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260329.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260329.1 - '@vercel/oidc@2.0.0': {} + '@vercel/oidc@3.2.0': {} - '@vercel/sandbox@0.0.16': + '@vercel/sandbox@2.0.0-beta.9': dependencies: - '@vercel/oidc': 2.0.0 + '@vercel/oidc': 3.2.0 async-retry: 1.3.3 jsonlines: 0.1.1 ms: 2.1.3 + picocolors: 1.1.1 tar-stream: 3.1.7 + undici: 7.24.6 + xdg-app-paths: 5.1.0 zod: 3.24.4 async-retry@1.3.3: @@ -180,6 +202,10 @@ snapshots: ms@2.1.3: {} + os-paths@4.4.0: {} + + picocolors@1.1.1: {} + retry@0.13.1: {} streamx@2.22.1: @@ -201,4 +227,14 @@ snapshots: undici-types@7.8.0: {} + undici@7.24.6: {} + + xdg-app-paths@5.1.0: + dependencies: + xdg-portable: 7.3.0 + + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + zod@3.24.4: {} diff --git a/src/client.ts b/src/client.ts index 2b13d2b..f2751b6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,16 +1,13 @@ import { CommandFinished, Sandbox } from '@vercel/sandbox'; -import { readFile, writeFile } from 'node:fs/promises'; +import { readFile } from 'node:fs/promises'; import http from 'node:http'; import { join } from 'node:path'; -import { tmpdir } from 'node:os'; +import { hostname } from 'node:os'; import type { TunnelRequest, TunnelResponse } from './server.js'; import { vercelCliAuth } from './vercel-cli-auth.js'; -type VgrokConfig = { localPortToSandbox: Record }; - const SANDBOX_PORT = 3000; const WS_PATH = '/_vgrok_ws'; -const VGROK_CONFIG_PATH = join(tmpdir(), './vgrok-config.json'); let shuttingDown = false; async function shutdown(sandbox: Sandbox | null) { @@ -34,34 +31,19 @@ async function writeLogs(cmd: CommandFinished) { export async function client({ port, timeout }: { port: number, timeout: number }) { const localPort = port; - const config = await readFile(VGROK_CONFIG_PATH, 'utf8') - .then(str => JSON.parse(str) as VgrokConfig) - .catch(() => null); - const localPortToSandbox = config ? config.localPortToSandbox : {}; + const name = `vgrok-${hostname().replaceAll(/[^\w-]/g, '_')}-${localPort}`; const { token, teamId, projectId } = vercelCliAuth(); - let sandbox: Sandbox | null = null; - - if (localPortToSandbox[localPort]) { - const { id, createdAt } = localPortToSandbox[localPort]; - console.log(`Reusing existing sandbox for port ${localPort} with ID ${id}`); - sandbox = await Sandbox.get({ teamId, projectId, token, sandboxId: id }).catch(() => null); - if (sandbox && sandbox.status !== 'running') { - console.log(`Sandbox with ID ${id} is not running`); - sandbox = null; - } - if (Date.now() - createdAt > timeout) { - // assume sandbox is gone now since it's expired - sandbox = null; - } - } - + console.log(`Checking existence of sandbox for port ${localPort}`); + let sandbox = await Sandbox.get({ teamId, projectId, token, name }).catch(() => null); let sandboxUrl: string; if (sandbox) { + console.log(`Found sandbox ${sandbox.name} but it is not running. Current status: ${sandbox.status}`); sandboxUrl = sandbox.domain(SANDBOX_PORT); } else { console.log(`Creating new sandbox for port ${localPort}`); sandbox = await Sandbox.create({ + name, teamId, projectId, token, @@ -73,15 +55,11 @@ export async function client({ port, timeout }: { port: number, timeout: number timeout, }); sandboxUrl = sandbox.domain(SANDBOX_PORT); - localPortToSandbox[localPort] = { id: sandbox.sandboxId, createdAt: Date.now() }; - await writeFile( - VGROK_CONFIG_PATH, - JSON.stringify({ localPortToSandbox } satisfies VgrokConfig) - ); // TODO: can we spawn a new sandbox automatically when timeout reached? // setTimeout(async () => { newSandbox() }, timeout); // Alternatively we can kill the ngrok process to avoid confusion. + console.log(`Writing files...`); await sandbox.writeFiles([ { content: Buffer.from(JSON.stringify({ @@ -103,20 +81,21 @@ export async function client({ port, timeout }: { port: number, timeout: number env: { NPM_CONFIG_UPDATE_NOTIFIER: 'false' } }); await writeLogs(pnpm); - - await sandbox.runCommand({ - cmd: 'node', - args: ['server.js'], - detached: true, - env: { - SANDBOX_PORT: String(SANDBOX_PORT), - WS_PATH: WS_PATH, - }, - //stderr: process.stderr, // TODO: hide - //stdout: process.stdout, // TODO: hide - }); } + console.log('Starting server in sandbox...'); + await sandbox.runCommand({ + cmd: 'node', + args: ['server.js'], + detached: true, + env: { + SANDBOX_PORT: String(SANDBOX_PORT), + WS_PATH: WS_PATH, + }, + //stderr: process.stderr, // TODO: hide + //stdout: process.stdout, // TODO: hide + }); + console.log('Starting local client...'); const socket = new WebSocket(sandboxUrl + WS_PATH); const connected = Promise.withResolvers(); diff --git a/tsconfig.json b/tsconfig.json index 976f31a..26b7f41 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { + "rootDir": "src", "outDir": "dist", "module": "nodenext", "target": "esnext",