Skip to content

refactor: migrate shell layer to tinyexec + per-command e2e tests#210

Open
rqbazan wants to merge 4 commits into
mainfrom
refactor/shell-quoting-and-vland-init
Open

refactor: migrate shell layer to tinyexec + per-command e2e tests#210
rqbazan wants to merge 4 commits into
mainfrom
refactor/shell-quoting-and-vland-init

Conversation

@rqbazan
Copy link
Copy Markdown
Member

@rqbazan rqbazan commented May 12, 2026

Summary

Three intertwined improvements landing together because they share the same shell layer rewrite:

  1. @vlandoss/clibuddy (minor, breaking pre-1.0): replace zx with tinyexec. New ShellService API:

    • run(cmd, args, opts?) — verbose, streams stdio, prints $ <cmd> <args> prefix.
    • runCaptured(cmd, args, opts?) — silent, returns Output { stdout, stderr, exitCode }.
    • Both throw NonZeroExitError on non-zero exit by default; opt out with { throwOnError: false }.
    • at(cwd) / child(opts) for inherited config.
    • New resolveBinPath(pkg, { from, binPath?, binName? }) helper that finds an installed package's bin even when exports is restrictive (oxlint) or absent (@biomejs/biome).
    • isNonZeroExitError replaces isProcessOutput.
    • Removed: $\...`template-literal API,quote/isRaw/defaultQuote, getPreferLocal/localBaseBinPath, mute()/quiet(), ./test-helpers` export.
    • tinyexec auto-prepends every parent node_modules/.bin to PATH, so localBaseBinPath is no longer needed.
  2. @vlandoss/run-run (patch): all ToolService subclasses (biome, oxlint, oxfmt, tsdown, tsc) resolve their bin via resolveBinPath and pass the absolute path with display: <friendly-name>. This bypasses the node_modules/.bin/<bin> shims that run-run itself publishes (tools/biome etc.), which would otherwise loop back through rr tools <bin> indefinitely. ToolService.exec accepts only string[]. tscheck runs pretsc/pretypecheck with shell: true so they can use &&, pipes, env-var substitution. End-user CLI behaviour unchanged.

  3. @vlandoss/vland (minor): vland init prompts (default-yes) for installing dependencies and initialising git when --install/--no-install/--git/--no-git aren't passed. Non-interactive defaults: both true. Also: the git-commit-failure (pathspec 'initial' did not match) caused by the old custom quote() not wrapping whitespace strings is gone — array-based exec makes each token survive.

Tests reorganised

  • Dropped clibuddy/test-helpers (the zx mock). e2e is the strategy now.
  • run-run integration suite split into one file per command: cli, jsc, lint, format, tsc, build-lib. Each spawns the real rr binary against a temp fixture (makeFixture helper) and asserts on observable output.
  • vland init keeps its existing integration coverage (commit message exact-match, no pathspec errors, prompt-default behaviour in non-TTY).

Test plan

  • `pnpm test` — 22/22 across 7 files
  • `pnpm test:types` — 4/4 packages clean
  • Manual smoke from `packages/vland`: `rr jsc`, `rr lint`, `rr format`, `rr tsc` all exit 0 with each flag separated in the verbose trace
  • Manual smoke: `vland init my-app -t library --no-install --git` produces a single `chore: initial commit from vland`
  • CI green
  • Bot opens the Version PR with the expected bumps:
    • `@vlandoss/clibuddy` 0.5.0 → 0.6.0 (minor, breaking pre-1.0)
    • `@vlandoss/vland` 0.1.1 → 0.2.0 (minor)
    • `@vlandoss/run-run` 0.5.2 → 0.5.3 (patch)

🤖 Generated with Claude Code

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

🦋 Changeset detected

Latest commit: f368be9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@vlandoss/clibuddy Minor
@vlandoss/run-run Patch
@vlandoss/vland Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Big single landing for three intertwined improvements that were
prepped on this branch:

1. **clibuddy: replace zx with tinyexec.** The new `ShellService` API
   is `run(cmd, args, opts?)` (verbose, streams stdio) and
   `runCaptured(cmd, args, opts?)` (silent, returns Output), both
   throwing `NonZeroExitError` by default. Drops `quote/isRaw/
   defaultQuote/getPreferLocal/localBaseBinPath/mute()/quiet()/
   isProcessOutput` and the `./test-helpers` export. Adds
   `resolveBinPath(pkg, { from, binPath?, binName? })` to find the
   absolute path to a package's bin even when its `exports` map is
   restrictive (oxlint) or absent (@biomejs/biome).

2. **run-run: use the new shell + tighten exec to string[].** Every
   `ToolService` now resolves its bin path with `resolveBinPath` and
   passes it to `ShellService.run` with `display: <friendly-name>`,
   bypassing the `node_modules/.bin/<bin>` shims that run-run itself
   publishes (tools/biome, tools/oxlint, tools/oxfmt, tools/tsdown)
   which would otherwise loop. `ToolService.exec` now accepts only
   `string[]`. `tscheck` runs pre-scripts with `shell: true` so they
   can use `&&`, pipes, env-var substitution.

3. **vland init: prompt for install/git + fix git commit.** Adds
   default-yes confirms when the user doesn't pass --install/--no-
   install / --git/--no-git on the CLI. The git-commit failure
   (`pathspec 'initial' did not match`) caused by missing $'…'
   wrapping in the old custom quote() goes away naturally with array-
   based exec.

**Tests reorganised.** Drop the clibuddy/test-helpers mock entirely.
run-run integration tests are split into one file per command
(cli, jsc, lint, format, tsc, build-lib), each spawning the real
`rr` binary against a temp fixture (`makeFixture` helper) and
asserting on observable output. vland init keeps its existing
integration coverage (commit message exact-match, no pathspec errors,
prompt-default behaviour in non-TTY).

Verification: pnpm test (22 tests across 7 files, all green) and
pnpm test:types (4 packages clean).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rqbazan rqbazan force-pushed the refactor/shell-quoting-and-vland-init branch from d662e9c to 174d876 Compare May 12, 2026 04:35
`BiomeService.check()` calls `biome ci` instead of `biome check` when
`isCI` is true (std-env). The jsc tests asserted on `check` only,
which made them green locally but red on GitHub Actions. Relax the
regex to match either subcommand — the test's intent is that biome
receives properly tokenized flags, not which subcommand it's invoked
with.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rqbazan rqbazan added the preview Ready to run CI/CD label May 12, 2026
@vland-bot
Copy link
Copy Markdown
Contributor

vland-bot Bot commented May 12, 2026

Preview release

Latest commit: f368be9

Some packages have been released:

Package Version Install
@vlandoss/clibuddy v0.5.1-git-f368be9.0 @vlandoss/clibuddy@v0.5.1-git-f368be9.0
@vlandoss/run-run v0.5.3-git-f368be9.0 @vlandoss/run-run@v0.5.3-git-f368be9.0
@vlandoss/vland v0.1.2-git-f368be9.0 @vlandoss/vland@v0.1.2-git-f368be9.0

Note

Use the PR number as tag to install any package. For instance:

pnpm add @vlandoss/clibuddy@pr-210

rqbazan and others added 2 commits May 11, 2026 23:46
The clack spinner already says "Installing dependencies with pnpm…"
but `installDependencies` from nypm was running with stdio inherited,
so deprecation warnings (DEP0169 url.parse), upstream `unrun` ENOENT
warnings, and pnpm's `Ignored build scripts` notice all leaked into
the terminal — confusing UX next to the spinner.

nypm exposes `silent: true` which pipes stdio. If install fails the
spinner already flips to "Failed to install dependencies" and we
debug-log the captured error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The project-name prompt always suggested `my-app` regardless of which
template was picked, and the outro always recommended `pnpm dev` even
for the `library` template (which has no `dev` script in its
package.json).

Add a `TEMPLATE_META` map keyed by template name with:

- `placeholder` — used in the project-name `text()` prompt
- `runScript`   — appended to the outro's next-steps line

Mappings:
- library  → `my-lib`,  next: `pnpm test`  (libraries don't have a dev server)
- backend  → `my-api`,  next: `pnpm dev`
- monorepo → `my-mono`, next: `pnpm dev`   (turbo run dev)

Add a backend-specific install-resolution test alongside the existing
library one to lock in both branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Ready to run CI/CD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant