You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
2.8 KiB
JavaScript
109 lines
2.8 KiB
JavaScript
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().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])
|
|
}
|