UNPKG

@bytecodealliance/jco

Version:

JavaScript tooling for working with WebAssembly Components

134 lines (120 loc) 3.93 kB
import { getTmpDir } from '../common.js'; import { transpile } from './transpile.js'; import { rm, mkdir, writeFile, symlink } from 'node:fs/promises'; import { basename, resolve, extname } from 'node:path'; import { spawn } from 'node:child_process'; import process from 'node:process'; import { fileURLToPath, pathToFileURL } from 'node:url'; import c from 'chalk-template'; export async function run (componentPath, args, opts) { // Ensure that `args` is an array args = [...args]; return runComponent(componentPath, args, opts, ` if (!mod.run || !mod.run.run) { console.error('Not a valid command component to execute.'); process.exit(1); } try { mod.run.run(); // for stdout flushing await new Promise(resolve => setTimeout(resolve)); process.exit(0); } catch (e) { console.error(e); process.exit(1); } `); } export async function serve (componentPath, args, opts) { let tryFindPort = false; let { port, host } = opts; if (port === undefined) { tryFindPort = true; port = '8000'; } // Ensure that `args` is an array args = [...args]; return runComponent(componentPath, args, opts, ` import { HTTPServer } from '@bytecodealliance/preview2-shim/http'; const server = new HTTPServer(mod.incomingHandler); let port = ${port}; ${tryFindPort ? ` while (true) { try { server.listen(port, ${JSON.stringify(host)}); break; } catch (e) { if (e.code !== 'EADDRINUSE') throw e; } port++; } ` : `server.listen(port, ${JSON.stringify(host)})`} console.error(\`Server listening on port...\`); `); } async function runComponent (componentPath, args, opts, executor) { const jcoImport = opts.jcoImport ? resolve(opts.jcoImport) : null; const name = basename(componentPath.slice(0, -extname(componentPath).length || Infinity)); const outDir = opts.jcoDir || await getTmpDir(); if (opts.jcoDir) { await mkdir(outDir, { recursive: true }); } try { try { await transpile(componentPath, { name, quiet: true, noTypescript: true, wasiShim: true, outDir, tracing: opts.jcoTrace, map: opts.jcoMap, importBindings: opts.jcoImportBindings, }); } catch (e) { throw new Error('Unable to transpile command for execution', { cause: e }); } await writeFile(resolve(outDir, 'package.json'), JSON.stringify({ type: 'module' })); let preview2ShimPath; try { preview2ShimPath = resolve(fileURLToPath(import.meta.resolve('@bytecodealliance/preview2-shim')), '../../../'); } catch { throw c`Unable to locate the {bold @bytecodealliance/preview2-shim} package, make sure it is installed.`; } const modulesDir = resolve(outDir, 'node_modules', '@bytecodealliance'); await mkdir(modulesDir, { recursive: true }); try { await symlink(preview2ShimPath, resolve(modulesDir, 'preview2-shim'), 'dir'); } catch (e) { if (e.code !== 'EEXIST') throw e; } const runPath = resolve(outDir, '_run.js'); await writeFile(runPath, ` ${jcoImport ? `import ${JSON.stringify(pathToFileURL(jcoImport))}` : ''} import process from 'node:process'; try { process.argv[1] = "${name}"; } catch {} const mod = await import('./${name}.js'); ${executor} `); const nodePath = process.env.JCO_RUN_PATH || process.argv[0]; process.exitCode = await new Promise((resolve, reject) => { const cp = spawn(nodePath, [...process.env.JCO_RUN_ARGS ? process.env.JCO_RUN_ARGS.split(' ') : [], runPath, ...args], { stdio: 'inherit' }); cp.on('error', reject); cp.on('exit', resolve); }); } finally { try { if (!opts.jcoDir) await rm(outDir, { recursive: true }); } catch { // empty } } }