Files
OpenSpec/test/cli-e2e
Tabish Bidiwale bfa670eda9 perf(cli): load each command's implementation only when it runs (#2025)
* perf(cli): load each command's implementation only when it runs

src/cli/index.ts statically imported every command module, so every
invocation, even `openspec --version`, loaded 485 modules (zod, yaml,
fast-glob, ora, diff and every command) before commander ran. Callers
that run the CLI many times, such as editors and agents, paid for that on
each call, most on Windows where Node loads modules slowly.

Command definitions (names, options, help) stay eager; implementations
move behind `await import()` in their actions, the pattern `init` already
used. The `register*Command` modules that mixed both are split: the
definitions live in src/cli/commands/, and each action body moves
unchanged into an exported function in src/commands/. Telemetry, the
completion tip, ora in failWithError, and the config profile's drift
check and update load on demand too.

`--version` and `--help` now load 24 modules (commander and the
definitions); median wall time on macOS drops from ~150 ms to ~33 ms
(`node -e 0` is 18 ms). Help for every command, completion scripts,
exit codes, `--json` output, error messages and telemetry events are
byte-identical before and after.

test/cli-e2e/startup-modules.test.ts runs the built CLI with a
module-recording hook and asserts that `--version` and `--help` load no
package but commander and no command implementation, and that a command
loads only its own implementation. It fails on main.

* docs(contributing): keep the CLI's startup fast

* test(cli): compare loaded modules against the real dist/ path

Node reports loaded modules by their real paths, so a checkout reached
through a symlink or a Windows short name made every module look foreign
and the absence checks pass without checking anything. Resolve dist/ with
realpathSync.native, fail when no CLI module was recorded, and require
--version and --help to exit 0.
2026-10-02 04:38:30 +00:00
..