Add programmatic start() API alongside CLI#57
Conversation
Split jspod into:
- lib/start.js — exports start(options) returning a handle
{ url, port, host, root, ready, exit, stop }. Pod-startup logic,
no process.exit, no banner, no signal handlers.
- lib/index.js — barrel export ({ start, formatUrl }).
- index.js — thin CLI shell. argv parsing, banner, install subcommand,
SIGINT/SIGTERM handlers that call handle.stop() then process.exit.
Behaviour identical to pre-refactor CLI.
package.json gets main + exports map pointing at lib/index.js, so
`import { start } from 'jspod'` works for host programs (e.g.
solid-desktop) that want to embed jspod in-process instead of
spawning the CLI as a child.
Smoke-tested both surfaces:
- CLI: node index.js --port 5455 --no-bootstrap → ready, served GET /,
SIGINT → clean SIGTERM to JSS → exit 0.
- API: start({ port, root, open:false, bootstrap:false }) → ready
resolves true, fetch returns 200, stop() returns {code:0,signal:null}
and is idempotent.
- Bare-specifier import via `file:` install → imports 'jspod' and gets
{ start, formatUrl } as expected.
|
Folded in a small shutdown-ordering fix (closes #62). On a terminal Ctrl+C the JSS child shares our process group and gets SIGINT too — it already prints This drops the parent's redundant line and lets @copilot-pull-request-reviewer please re-review — diff has grown by one commit. |
23eaaf6 to
a27ae7d
Compare
|
Correction to my earlier comment: I've backed the shutdown-ordering fix out of this PR to keep #57 scoped to just the programmatic |
Closes #56.
Summary
Split jspod into a library and a CLI shell so host programs can embed jspod in-process instead of spawning the CLI as a child.
lib/start.js— exportsstart(options)returning a handle{ url, port, host, root, ready, exit, stop }. Pod-startup logic only: noprocess.exit, no banner, no signal handlers.lib/index.js— barrel export ({ start, formatUrl }).index.js— thin CLI shell. argv parsing, banner,installsubcommand, SIGINT/SIGTERM handlers that callhandle.stop()thenprocess.exit. Behaviour identical to pre-refactor CLI.package.json—main+exportsmap point atlib/index.js;binstays atindex.js.lib/added tofiles.API shape
Errors throw (invalid port, no free port in range, etc.) rather than calling
process.exit. Thereadypromise rejects if JSS fails to come up;exitresolves when the underlying JSS child exits for any reason (so callers can react).Why a stop() method and not just signals?
A library must not install signal handlers — the host program owns those.
stop()lets the host bind its own SIGINT/lifecycle hooks and explicitly tear down the pod when it's ready. The CLI shell binds SIGINT/SIGTERM and callshandle.stop()from inside them, preserving the prior CLI behaviour.Bootstrap-spawn fix
The first-run "install default bundle" path used to spawn
process.execPath, [process.argv[1], 'install', ...]. Under library use,process.argv[1]is the host program — that would fail. Now resolved viaimport.meta.urlto the CLI script in the same package.Smoke tests
node index.js --port 5455 --no-bootstrap→ banner, JSS spawn, ready, GET / 200, SIGINT triggers the graceful shutdown handler, JSS gets SIGTERM, exits 0, prints "Goodbye!".start({ port:5456, root, open:false, bootstrap:false })→readyresolvestrue,fetch(pod.url)returns 200,stop()returns{ code:0, signal:null }, secondstop()is idempotent.file:install of this branch into a temp package, thenimport { start } from 'jspod'→ exports{ formatUrl, start }as expected.Downstream
solid-desktop currently spawns jspod as a child process (with
ELECTRON_RUN_AS_NODE=1plumbing and a manual readiness probe). With this merged, that collapses to:Separate PR — out of scope for this one.
Out of scope
ready/exit/stop— can add alogoption or event emitter incrementally as consumers ask.jspod installas a library function — kept CLI-only for now; can be lifted later.Test plan
npx jspodon a clean checkout — banner, ready, browser opensnpx jspod install chrome(with a running pod) — unchangedimport { start } from 'jspod'from a host program — handle works, stop terminates JSS