UNPKG

@bytecodealliance/jco

Version:

JavaScript tooling for working with WebAssembly Components

165 lines (149 loc) 4.73 kB
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 { getTmpDir, styleText } from "../common.js"; import { transpile } from "./transpile.js"; const DEFAULT_SERVE_HOST = "localhost"; 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]; host = host ?? DEFAULT_SERVE_HOST; 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 @ ${host}:${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 (err) { let msg = `${styleText(["red", "bold"], "error")} Failed to resolve ${styleText("bold", "@bytecodealliance/preview2-shim")}, ensure it is installed.`; msg += `\nERROR:\n${err.toString()}`; throw new Error(msg); } 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 } } }