startApp
uses node:net
Start app name as tcm dev <name> does (it's run, on a free port, on the branch the studio serves), its output
kept in .tcm/studio/logs/<name>.log; it runs on after the studio (Stop, or tcm ps, ends it). Where it's
listening, once it is (it says so in .tcm/run), or that it's still starting.
import { liveRunning } from "#codebase/tcmts/cli/running"; import { HttpError } from "#codebase/tcmts/server"; import { editableCodebase, studioApps, studioCodebase, tcmEnv } from "#codebase/tcmts/studio"; import { spawn } from "node:child_process"; import fs from "node:fs"; import net from "node:net"; import path from "node:path"; export async function startApp(name: string): Promise<string> { const cb = editableCodebase(); const { cache } = studioCodebase(); if (!studioApps(cb, cache.placement(cb)).some((a) => a.path === name)) throw new HttpError( 404, `${name} isn't an app: tcm dev runs a browser entry point nothing calls.`, ); const root = cb.lay.root; if (liveRunning(root).some((r) => r.app === name && r.command === "dev")) throw new HttpError(409, `${name} is running already.`); const port = await new Promise<number>((resolve, reject) => { const s = net.createServer(); s.once("error", reject); s.listen(0, "127.0.0.1", () => { const p = (s.address() as net.AddressInfo).port; s.close(() => resolve(p)); }); }); const logs = path.join(cb.lay.tcmDir, "studio", "logs"); fs.mkdirSync(logs, { recursive: true }); const log = fs.openSync(path.join(logs, `${name}.log`), "w"); const env = tcmEnv(); const child = spawn(process.execPath, [process.argv[1]!, "dev", name, "--port", String(port)], { cwd: root, env, detached: true, stdio: ["ignore", log, log], }); child.unref(); fs.closeSync(log); for (let i = 0; i < 120; i++) { const r = liveRunning(root).find((x) => x.pid === child.pid); if (r?.url) return r.url; if (child.exitCode !== null) { const said = fs .readFileSync(path.join(logs, `${name}.log`), "utf8") .trim() .split("\n") .slice(-8) .join("\n"); throw new HttpError(400, said || `tcm dev ${name} ended`); } await new Promise((r) => setTimeout(r, 500)); } return `still starting: .tcm/studio/logs/${name}.log says how it's going`; }
uses
- tcmts.cli.running.liveRunning
- tcmts.server.HttpError
- tcmts.studio.editableCodebase
- tcmts.studio.studioApps
- tcmts.studio.studioCodebase
- tcmts.studio.tcmEnv