import * as pty from 'node-pty' import { getContainerPath, runCommand } from './utils.js' import fs from 'fs/promises' import { randomUUID } from 'crypto' const stat = path => new Promise(resolve => fs .access(path) .then(() => resolve(true)) .catch(() => resolve(false)) ) const containerIds = new Set() await cleanContainers() async function storeContainers() { await fs.writeFile('containers-db.local.json', JSON.stringify([...containerIds])) } async function cleanContainers() { if (!(await stat('containers-db.local.json'))) { await storeContainers() } const raw = JSON.parse(await fs.readFile('containers-db.local.json', 'utf8')) await Promise.all(raw.map(id => destroyContainer(id))) } async function createContainer(tag) { const id = randomUUID().split('-').at(-1) await fs.mkdir(getContainerPath('phc', id), { recursive: true }) await runCommand('docker', [ 'run', '-d', '--name', id, '--volume', `${getContainerPath('phc', id)}:/project`, tag, 'sleep', 'infinity', ]) containerIds.add(id) await storeContainers() await runCommandContainer(id, `echo "echo hello world" > example.sh; chmod +x example.sh`) // Example file tree await runCommandContainer(id, `mkdir -p /project/src /project/bin`) await runCommandContainer(id, `echo 'console.log("Hello from index.js")' > /project/src/index.js`) await runCommandContainer(id, `echo 'console.log("Hello from app.js")' > /project/src/app.js`) await runCommandContainer(id, `echo 'echo "Hello from script.sh"' > /project/bin/script.sh`) await runCommandContainer(id, `chmod +x /project/bin/script.sh`) await runCommandContainer(id, `echo 'This is the README file.' > /project/README.md`) await runCommandContainer(id, `echo '{"name": "project", "version": "1.0.0"}' > /project/package.json`) return id } export async function createContainerPty( tag, options = { onExit, onData, shellCommand: ['/bin/sh'], } ) { const shellCommand = options.shellCommand ?? ['/bin/sh'] const { onData, onExit } = options const id = await createContainer(tag) const container = pty.spawn('docker', ['exec', '-it', '-w', '/project', id, ...shellCommand]) container.onExit(async e => { onExit?.(e) await destroyContainer(id) }) container.onData(data => { onData?.(data) }) return { id, pty: container } } async function destroyContainer(id) { await runCommand('docker', ['rm', '-f', id]) containerIds.delete(id) await storeContainers() } async function runCommandContainer(uuid, command) { return await runCommand('docker', ['exec', '-w', '/project', uuid, '/bin/sh', '-c', command]) }