Skip to content

test: Add CI for npm-installed CLI#5542

Open
backspace wants to merge 5 commits into
mainfrom
cli-parse-tests-cs-12083
Open

test: Add CI for npm-installed CLI#5542
backspace wants to merge 5 commits into
mainfrom
cli-parse-tests-cs-12083

Conversation

@backspace

@backspace backspace commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

boxel parse is silently broken in the npm release of Boxel CLI. The test suite didn’t catch this because it runs using monorepo conventions, not the npm package.

This makes several changes:

  1. a runBoxel test helper to test CLI behaviour, not in-code behaviour
  2. which can run with dist/index.js as before or path to an npm-installed package
  3. the existing CI job now runs against a tarred version of the package
  4. new CI jobs run after unstable and stable npm releases to exercise the just-released package

Deliberate test failure

The failed CI job is known, to demonstrate the problem of undetected brokenness, and that this new CI structure would fix it. The fix that causes the test to pass is in #5551, separated for demonstration and ease of review.

The boxel-cli suite only ever called command functions in-process inside
the monorepo, where pnpm's nested node_modules masks how the package
behaves once installed from npm. `boxel parse` shipped broken — glint
silently resolved nothing under npm's hoisted node_modules layout —
despite a fully green suite.

Add a subprocess-driven harness whose only per-context variable is the
binary under test (BOXEL_CLI_BIN):

- tests/helpers/run-boxel.ts drives the CLI as a subprocess (argv + env +
  stdin -> stdout/stderr/exit).
- tests/helpers/integration.ts seeds a JWT profile on disk so a spawned
  CLI authenticates via a temp HOME with no Matrix round-trip.
- scripts/run-cli-suite.ts installs the target and runs a test command
  against it: `pnpm pack` (resolves catalog:/workspace: specifiers) then
  `npm install` the tarball for real hoisting, or `npm install` from the
  registry for the published artifact.

Wire two contexts into CI: the PR gate runs the suite against the packed
npm install (test:cli:tarball), and the publish workflow smoke-tests the
just-published registry artifact (verify-unstable / verify-stable).

Add parse fixtures covering the reported resolution gaps plus a
deliberate type error that proves glint actually ran, and port
smoke.test.ts onto the harness.

This is the test/CI half of CS-12083. The parse fix is a follow-up, so
the parse fixtures are expected to fail against the installed binary
until it lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Realm Server Test Results

    1 files  ± 0      1 suites  ±0   12m 10s ⏱️ - 2m 16s
1 913 tests +30  1 913 ✅ +30  0 💤 ±0  0 ❌ ±0 
1 992 runs  +30  1 992 ✅ +30  0 💤 ±0  0 ❌ ±0 

Results for commit 70bb97f. ± Comparison against earlier commit d874d06.

Rewrite the tests/integration suite to drive the installed `boxel` binary
via runBoxel instead of calling command functions in-process, so the same
tests exercise the CLI as installed from npm (the tarball PR gate and the
published post-release smoke).

- Add createTestRealmViaCli(home): create a realm through `boxel realm
  create` and read its URL back from the profile the CLI wrote to disk.
- Each test seeds a profile on disk (createTestHome + setupTestProfile /
  setupJwtTestProfile), runs the command under test via runBoxel, and
  verifies realm/DB/checkpoint state in-process via reloadProfile(home).
- Text content rides stdin; binary content is staged to a temp --file.
- Commands with --json are asserted via res.json(); others via stdout
  (the CLI drops ANSI colors when stdout is not a TTY) and exit code.

A few tests stay in-process by necessity, coexisting with the subprocess
tests in the same files: white-box cases that mock/spy the internal fetch
(request-body shape, injected non-2xx responses) can't cross the process
boundary, and realm-watch is the long-running `watch start` daemon plus
the RealmWatcher internal API, which a subprocess can't drive without
weakening assertions.

Not run locally (needs the realm-server + Postgres + Matrix stack);
eslint clean across the suite. Expect CI iteration to settle argv and
output-assertion details.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@backspace
backspace marked this pull request as ready for review July 20, 2026 19:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a77f4dc3f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +115
if (result.status === 0 && result.stdout.trim()) {
console.log(`Resolved ${PKG_NAME}@${version}${result.stdout.trim()}`);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wait for the exact version that was just published

When unstable or latest already points to an older release, this condition succeeds immediately even if the newly published dist-tag has not propagated, because the workflows pass those tags at .github/workflows/boxel-cli-publish.yml:550 and :565. npmInstall() can consequently install the previous artifact and report a green post-publish verification for code that was never tested; propagate the concrete published version from the publish jobs and wait until that exact version resolves.

Useful? React with 👍 / 👎.

@backspace backspace Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in d874d06

name: 'tracked-format-class',
covers: '@tracked inside a static isolated format class',
},
{ name: 'runtime-common', covers: 'bare @cardstack/runtime-common import' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make runtime-common resolvable before expecting a clean parse

The packed-install integration run cannot pass this fixture: @cardstack/runtime-common is only a devDependency in packages/boxel-cli/package.json, so npm does not install it for the packed CLI, and the generated parse tsconfig in src/commands/parse.ts has no path for the bare package. Glint therefore reports a module-resolution diagnostic for runtime-common/home.gts, contradicting the errors === [] assertion and making the new Boxel CLI CI job fail until this dependency is shipped or vendored/mapped.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is recorded in CS-12254

covers: '@tracked inside a static isolated format class',
},
{ name: 'runtime-common', covers: 'bare @cardstack/runtime-common import' },
{ name: 'boxel-host-tools', covers: '@cardstack/boxel-host/tools/* import' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Vendor the tools path before requiring its fixture to pass

This clean fixture imports @cardstack/boxel-host/tools/save-card, but src/commands/parse.ts only maps @cardstack/boxel-host/commands/*, and scripts/build-types.ts excludes the host app's tools directory from HOST_APP_REACHABLE_SUBDIRS. Thus the npm-installed CLI has neither a path mapping nor bundled source for this import, so the fixture necessarily produces a resolution error and fails the newly enabled integration job.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the failure is deliberate, and fixed in #5551

The post-publish verify jobs installed @cardstack/boxel-cli@unstable /
@latest. A dist-tag always resolves to some prior release, so
waitForPublishedVersion returned on the first poll regardless of whether
the version just published had propagated, and npmInstall could pull the
previous artifact — a green smoke-test of code this run never installed.

Thread the concrete version out of the publish jobs (a `version` job
output, read from package.json in the unstable publish step and from the
strip step for stable) and pass it to run-cli-suite, so the poll waits
for that exact version to resolve and the install fetches it. Also warn
in run-cli-suite when --version is a dist-tag rather than a concrete
version, so this can't silently regress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@backspace
backspace requested a review from a team July 21, 2026 16:31

@habdelra habdelra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖]

Lens. This reviews the new test-execution machinery — the runBoxel subprocess helper and the run-cli-suite.ts install-and-run harness — the CI wiring that drives it, and whether the ~30 in-process→subprocess test conversions preserve behavior. I checked out the head branch and traced the module-resolution and packaging claims against the actual parse tsconfig and bundled-types/.

Bottom line: the design is sound and I found no blocking correctness bug in the new machinery. The one thing to get right before merge is sequencing — the suite is deliberately red and can only go green once two separate follow-ups land, not one (see thread 2 below). Two non-blocking cleanups are in inline threads.

What lands right.

  • The split between which binary runs (BOXEL_CLI_BIN, read once in the parent) and how the suite drives it (identical runBoxel calls) is the right seam: one suite, three execution contexts (local dist, packed tarball, published registry), no per-context test forks.
  • sanitizedParentEnv() stripping BOXEL_* is a real correctness win, not just hygiene — it makes a developer's shell match CI, and the smoke test asserting a leaked BOXEL_ENVIRONMENT can't shift behavior pins it.
  • The conversions strengthen coverage rather than merely relocating it: the error-path tests now assert exitCode alongside the JSON payload, which the in-process calls couldn't observe. parse.test.ts's plain-glimmer fixture is a faithful superset of the removed parse-glimmer-types.test.ts, and the deliberate-type-error fixture that proves glint actually ran (guarding the "produced no TS diagnostics" false-pass) is the crux of the whole PR and is well-constructed.
  • pnpm pack (not npm pack) to rewrite catalog: / workspace:*, then npm install for hoisting, is the correct combination to reproduce a real user's node_modules layout.

On the open review threads (verified against the branch):

  1. "Wait for the exact published version" (P1) — fixed correctly. boxel-cli-publish.yml now exposes a version output from each publish job (read from package.json), and both verify jobs install needs.<job>.outputs.version — a concrete semver, not the moving unstable/latest dist-tag. waitForPublishedVersion only meaningfully polls for a concrete version and loudly warns on a dist-tag. The chain is airtight. (As a bonus confirmation: verify-stable correctly omits the published guard that verify-unstable needs — the stable job always publishes when it succeeds, whereas the unstable push path can no-op.)

  2. "runtime-common fixture can't resolve" (P1) — confirmed, and this is the sequencing point. @cardstack/runtime-common is a devDependency (workspace:*), so it isn't installed in the npm layout. The generated parse tsconfig maps @cardstack/base/*, @cardstack/host/*, @cardstack/boxel-host/commands/*, @cardstack/boxel-ui/*, and a '*' catch-all → bundled-types/host-types/. The bare @cardstack/runtime-common in fixtures/parse/runtime-common/home.gts falls through to that catch-all, and bundled-types/host-types/@cardstack/ ships only host/, not runtime-common/ — so that fixture's expect(errors).toEqual([]) fails today. Per your own thread notes, the boxel-host-tools gap is fixed in the companion PR but runtime-common is tracked separately, so the parse suite needs both fixes to go green; the companion PR alone leaves the runtime-common case red.

  3. "boxel-host-tools fixture" (P1) — deliberate, no action here. Same mechanism (no path mapping, and tools is excluded from the bundled host subdirs); addressed by the companion PR.

Recommendations.

  1. Sequence the merge so main is never left with a red CLI job. This flips the existing green "Boxel CLI Tests" job to run the packed install, which fails until both the boxel-host-tools fix and the runtime-common follow-up land — turning that job red on every unrelated PR and failing verify-unstable on every subsequent unstable publish. Land them together, or, if they can't ship together, mark the two known-failing fixtures pending (a documented it.fails/skip keyed to the missing mappings) so the demonstration is preserved without reddening main. (Non-blocking on code; blocking on merge order.)
  2. run-boxel.ts: set the stream encoding to avoid multi-byte UTF-8 corruption on chunk boundaries — see thread. (Non-blocking.)
  3. Drop the dead new ProfileManager(...) statement duplicated across ~15 converted tests — see thread. (Non-blocking.)

Adjacent, out of scope. waitForPublishedVersion shells out to sleep via execFileSync('sleep', ...), which is POSIX-only. Harmless because it runs only on the Linux publish runners, but it's the one spot where the otherwise cross-platform helpers (the rest honors USERPROFILE for Windows) wouldn't run on Windows. Not asking for a change.


Generated by Claude Code

Comment thread packages/boxel-cli/tests/helpers/run-boxel.ts
Comment thread packages/boxel-cli/tests/integration/file-read.test.ts Outdated
backspace and others added 2 commits July 21, 2026 13:39
Set the stdout/stderr encoding so Node's StringDecoder buffers partial
multi-byte sequences across `data` events. Accumulating `Buffer` chunks
with `+=` decoded each chunk independently, so a multi-byte character
split across a pipe boundary became U+FFFD — a latent flake for realm
content with non-ASCII text read back through `file read` and `--json`
payloads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ests

The `new ProfileManager(emptyHome/.boxel-cli)` line in the no-active-
profile tests is never used — the behavior comes entirely from spawning
the CLI with HOME pointed at an empty dir, which has no profiles.json.
Remove the statement (and its now-unused import, or narrow it to `import
type` where ProfileManager is still referenced as a type) so these tests
read as exactly what they check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@backspace

Copy link
Copy Markdown
Contributor Author

Sequence the merge so main is never left with a red CLI job.

The fix PR is already approved. My plan is to merge this once approved, then rebase the fix in #5551, then merge it. I know this will result in main briefly having a failing test, but I think that’s fine, as the fix will come just after.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants