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'))) { 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() 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() 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', 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() }