112 lines
4 KiB
JavaScript
112 lines
4 KiB
JavaScript
import { async_run } from 'framerock'
|
|
const run_process_gui = (runtime_conf, cwd_for_spawn, args_for_spawn) => {
|
|
const jsbuild_app_frontend = async function () {
|
|
return `
|
|
const streams = {}
|
|
const elems_terminals = {}
|
|
const async_process_stream = async function (stream_id, stream_key, callback) {
|
|
for await (let chunk of streams[stream_id][stream_key].readable) {
|
|
callback(chunk)
|
|
}
|
|
return
|
|
}
|
|
const setup_dom = function () {
|
|
document.body.style = 'height: 100%;'
|
|
document.head.insertAdjacentHTML('beforeend', '<style type="text/css">html, body{ height: 100%; width: 100%; margin: 0px; padding: 0px;}</style>')
|
|
const elem_root = document.createElement('div')
|
|
elem_root.style = 'height: 100%; width: 100%; display: flex; font-size: 16px;'
|
|
for (let [ stream_key, style ] of [
|
|
['stderr', { backgroundColor: '#333' }],
|
|
['stdout', { backgroundColor: '#000' }]
|
|
]) {
|
|
const elem_terminal = document.createElement('div')
|
|
elems_terminals[stream_key] = elem_terminal
|
|
Object.assign(elem_terminal.style, {
|
|
flex: '1', fontFamily: 'monospace', color: '#FFF', height: '100%', overflow: 'auto', padding: '20px', boxSizing: 'border-box', whiteSpace: 'pre',
|
|
...style
|
|
})
|
|
elem_root.appendChild(elem_terminal)
|
|
}
|
|
document.body.appendChild(elem_root)
|
|
return
|
|
}
|
|
|
|
const on_open = function () {
|
|
const main_streamid = '1'
|
|
streams[main_streamid] = {
|
|
prev_message_idx: -1,
|
|
stdout: new TextDecoderStream(),
|
|
stderr: new TextDecoderStream(),
|
|
}
|
|
for (let stream_key of ['stdout', 'stderr']) {
|
|
async_process_stream(main_streamid, stream_key, (chunk) => {elems_terminals[stream_key].textContent += chunk;}).then(()=>{}).catch(console.error)
|
|
}
|
|
FRAMEROCK_UTILS.transport_send_bytes(new TextEncoder().encode(JSON.stringify([ 'SPAWN_PROCESS_INIT', { stream_id: main_streamid } ])))
|
|
console.info(['Process Spawned'])
|
|
return
|
|
}
|
|
const on_message = function (event) {
|
|
const [ evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(event.data))
|
|
if (evt_type === 'SPAWN_PROCESS_EVENT') {
|
|
const [ stream_id, msg_idx, payload ] = evt_data
|
|
const stream = streams[stream_id]
|
|
if (stream === undefined) {
|
|
console.error(['Unexpected: missing ref to stream:', stream_id])
|
|
return
|
|
}
|
|
if (msg_idx !== stream.prev_message_idx+1) {
|
|
console.error('received message out of order, skipping')
|
|
return
|
|
}
|
|
stream.prev_message_idx = msg_idx*1
|
|
const [ evt_subtype, evt_subdata ] = payload
|
|
if (evt_subtype === 'stream_chunk') {
|
|
const [ stream_key, stream_data ] = evt_subdata
|
|
if (stream_data === null) {
|
|
stream[stream_key].writable.close()
|
|
}
|
|
else {
|
|
const writer = stream[stream_key].writable.getWriter()
|
|
writer.write(new Uint8Array(stream_data))
|
|
writer.releaseLock()
|
|
}
|
|
}
|
|
else if (evt_subtype === 'exited') {
|
|
console.info(['Process Exited', evt_subdata])
|
|
}
|
|
}
|
|
return
|
|
}
|
|
setup_dom()
|
|
FRAMEROCK_UTILS.setup_transport({ on_open, on_message })
|
|
`.trim()
|
|
}
|
|
const handle_transport_bytes = function (utils, message) {
|
|
const [ evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(message))
|
|
if (evt_type === 'SPAWN_PROCESS_INIT') {
|
|
let msg_idx = 0
|
|
const wrap_send = (payload) => {
|
|
utils.transport_send_bytes(new TextEncoder().encode(JSON.stringify([ 'SPAWN_PROCESS_EVENT', [ evt_data.stream_id, msg_idx*1, payload ] ])))
|
|
msg_idx++
|
|
return
|
|
}
|
|
const onExit = (p, exitCode, signalCode, error) => {
|
|
wrap_send(['exited', { exitCode, signalCode }])
|
|
return
|
|
}
|
|
const proc = Bun.spawn(args_for_spawn, { cwd: cwd_for_spawn, stdout: 'pipe', stderr: 'pipe', onExit })
|
|
const async_handle_chunks = async (stream_key) => {
|
|
for await (const chunk of proc[stream_key]) {
|
|
wrap_send(['stream_chunk', [ stream_key, [ ...chunk ] ]])
|
|
}
|
|
wrap_send(['stream_chunk', [ stream_key, null ]])
|
|
return
|
|
}
|
|
Promise.all([ async_handle_chunks('stdout'), async_handle_chunks('stderr') ]).then(()=>{}).catch(console.error)
|
|
}
|
|
return
|
|
}
|
|
async_run({ config: { ...runtime_conf }, jsbuild_app_frontend, handle_transport_bytes }).then(()=>{}).catch(console.error)
|
|
return
|
|
}
|
|
export { run_process_gui }
|