Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
185 commits
Select commit Hold shift + click to select a range
b662966
docs(rfc): vp migrate upgrade path for existing Vite+ projects
fengmk2 Jun 10, 2026
8998baf
docs(rfc): cover real 0.1.24->0.2.0 upgrade failure in vp migrate
fengmk2 Jun 18, 2026
94aa32f
docs(rfc): align vp migrate upgrade with the v0.2.1 prompt spec
fengmk2 Jun 18, 2026
3d30ccc
fix(migrate): make vp migrate upgrade v0.1.x projects to v0.2.x
fengmk2 Jun 18, 2026
8eb837d
feat(migrate): manage vitest only when the project uses it directly
fengmk2 Jun 18, 2026
e82435a
feat(migrate): align the full @vitest/* ecosystem to the bundled vitest
fengmk2 Jun 19, 2026
26968b3
docs(rfc): revise migrate RFC for vitest provisioning and ecosystem r…
fengmk2 Jun 19, 2026
f2b0fda
fix(migrate): make upgrade provisioning peer-safe
fengmk2 Jun 19, 2026
744e9d8
fix(migrate): validate upgrade scenarios in snapshots
fengmk2 Jun 19, 2026
c8b175b
test(migrate): update default vitest snapshots
fengmk2 Jun 21, 2026
15ca945
fix(migrate): handle peer and override edge cases
fengmk2 Jun 21, 2026
04c57a6
fix(migrate): cover remaining vitest upgrade cases
fengmk2 Jun 21, 2026
f443a5b
fix(test): normalize snapshot file endings
fengmk2 Jun 21, 2026
4e3e6e1
test(migrate): sync idempotency snapshots
fengmk2 Jun 21, 2026
bfc958b
test(create): update standalone Yarn catalog snapshot
fengmk2 Jun 21, 2026
e282708
fix(migrate): preserve vitest imports for Nuxt tests
fengmk2 Jun 23, 2026
774bc8f
test(ecosystem-ci): update npmx.dev fixture
fengmk2 Jun 23, 2026
461bbc6
test(cli): stabilize Nuxt lint snapshot
fengmk2 Jun 23, 2026
92c88a0
fix(migrate): preserve Vitest across Nuxt packages
fengmk2 Jun 23, 2026
3606535
fix(migrate): convert Yarn PnP projects
fengmk2 Jun 23, 2026
b2b6d6e
test(ecosystem): install Playwright for npmx.dev
fengmk2 Jun 23, 2026
b814002
test(migrate): cover conservative monorepo retention
fengmk2 Jun 23, 2026
d7ea7f9
fix(migrate): pin pkg.pr.new targets in test helper
fengmk2 Jun 23, 2026
63a0861
fix(test): keep pkg.pr.new overrides minimal
fengmk2 Jun 23, 2026
d199e90
fix(migrate): allow pkg.pr.new pnpm subdependencies
fengmk2 Jun 23, 2026
9f245c9
fix(test): refresh mutable pkg.pr.new installs
fengmk2 Jun 24, 2026
4a0bac7
fix(migrate): preserve Vitest ecosystem catalogs
fengmk2 Jun 24, 2026
c88d00d
fix(migrate): pin vite-plus toolchain versions
fengmk2 Jun 24, 2026
23832e1
fix(test): reuse unchanged pkg.pr.new install
fengmk2 Jun 24, 2026
26343f3
fix(test): run pkg.pr.new migration from project root
fengmk2 Jun 24, 2026
cbc03da
fix(migrate): isolate config compatibility checks
fengmk2 Jun 24, 2026
3292590
fix(test): pin pkg.pr.new migration builds by commit
fengmk2 Jun 24, 2026
a593aa3
fix(migrate): move pnpm settings to workspace config
fengmk2 Jun 25, 2026
94937cd
docs(migrate): document user-facing migration rules
fengmk2 Jun 25, 2026
27034b4
test(migrate): update migration snapshots
fengmk2 Jun 25, 2026
ed11ef3
docs(migrate): clarify pnpm vite dependency rule
fengmk2 Jun 25, 2026
bea36b2
fix(migrate): format projects after migration
fengmk2 Jun 25, 2026
a960740
fix(migrate): preserve unmigrated Prettier projects
fengmk2 Jun 25, 2026
98fa5c8
fix(migrate): detect legacy browser providers
fengmk2 Jun 25, 2026
18a6a56
fix(ci): upgrade affected pnpm 11 versions for pkg.pr.new tests
fengmk2 Jun 26, 2026
d71857e
fix(migrate): preserve Bun config arrays
fengmk2 Jun 26, 2026
b579b81
fix(migrate): avoid Bun peer suppression
fengmk2 Jun 26, 2026
2a9225a
fix(ci): support Bun pkg.pr.new migration tests
fengmk2 Jun 26, 2026
aa5a0c7
fix(ci): satisfy lint in Bun pkg helper
fengmk2 Jun 26, 2026
3ff95bb
fix(migrate): rewrite tools invoked through bunx
fengmk2 Jun 26, 2026
95ea87f
fix(migrate): limit automatic formatting to changed files
fengmk2 Jun 26, 2026
1194a1d
fix(migrate): defer supported formats to oxfmt
fengmk2 Jun 26, 2026
c8432dc
fix(ci): upgrade affected pnpm 10 pkg-pr-new tests
fengmk2 Jun 26, 2026
fa32021
fix(migrate): address review feedback
fengmk2 Jun 26, 2026
b3f6c55
fix(migrate): preserve pnpm catalog layouts
fengmk2 Jun 26, 2026
7da35ef
test(migrate): use release version for default catalog
fengmk2 Jun 26, 2026
bd63c0d
fix(migrate): address remaining review feedback
fengmk2 Jun 27, 2026
c70b63c
fix(migrate): address upgrade-path review findings
fengmk2 Jun 27, 2026
19f44cc
ci(migrate): verify pkg.pr.new builds through the registry bridge
fengmk2 Jun 27, 2026
1cde19e
chore(migrate): fix oxfmt formatting and refresh global migration snaps
fengmk2 Jun 27, 2026
d13148f
ci(migrate): persist bridge registry into project .npmrc and .yarnrc.yml
fengmk2 Jun 27, 2026
7d73cd9
fix(migrate): do not duplicate vite-plus into devDependencies
fengmk2 Jun 27, 2026
094397c
refactor(migrate): split migrator.ts into category modules
fengmk2 Jun 27, 2026
64b580a
docs(agents): point to the migrator/ structure guide
fengmk2 Jun 27, 2026
089f609
chore(skills): add test-pkg-pr-new-migrate skill
fengmk2 Jun 27, 2026
f359808
fix(migrate): normalize protocol-pinned vite-plus under force-override
fengmk2 Jun 27, 2026
142e5e9
fix(migrate): address PR review feedback
fengmk2 Jun 27, 2026
87b376d
test(migrate): drop pkg.pr.new force-override in favor of the bridge
fengmk2 Jun 27, 2026
d83d6fd
test: regenerate command-upgrade-check for the bounded normalizer
fengmk2 Jun 27, 2026
a1f7495
ci(pkg-pr-new): comment the bridge version on the PR after registration
fengmk2 Jun 27, 2026
b06c350
style: run oxfmt on bridge-comment-template.md
fengmk2 Jun 27, 2026
0e26437
refactor(migrate): group Rolldown compat files under compat/
fengmk2 Jun 27, 2026
c0970dc
fix(migrate): preserve the bunx --bun flag when rewriting to vp
fengmk2 Jun 27, 2026
823decd
fix(migrate): clear the progress spinner before the upgrade prompts
fengmk2 Jun 27, 2026
5e3e95e
feat(migrate): upgrade below-range Node versions to the latest supported
fengmk2 Jun 28, 2026
104bf6d
fix(migrate): make the rolldown-compat worker-path test Windows-safe
fengmk2 Jun 28, 2026
d9c74ff
chore(skills): add verify-interactive-cli
fengmk2 Jun 28, 2026
941383b
refactor(ci): build the pkg.pr.new bridge comment with github-script
fengmk2 Jun 28, 2026
fbe8d5c
refactor(migrate): simplify node-version binding, format, and vitest …
fengmk2 Jun 28, 2026
26a7c5d
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 28, 2026
8ec6428
docs(migrate): drop the interactive-confirm note from Node.js version…
fengmk2 Jun 28, 2026
c0a3a57
ci(pkg-pr-new): publish to the bridge via its reusable action
fengmk2 Jun 28, 2026
b1ae30a
refactor(ci): consume the bridge action's version output in the PR co…
fengmk2 Jun 28, 2026
4f61f92
ci(pkg-pr-new): point the registry bridge at voidzero-dev/void.app
fengmk2 Jun 28, 2026
22ef2d6
fix(ci): force-stage .npmrc in the pkg.pr.new migrate test helper
fengmk2 Jun 28, 2026
4fd5955
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 28, 2026
f92f6a0
fix(migrate): inject the direct vite edge on the pnpm upgrade path
fengmk2 Jun 28, 2026
affd11e
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 28, 2026
c3ac6b7
fix(migrate): raise Node pins whose floor is below the supported range
fengmk2 Jun 29, 2026
901dfcc
fix(migrate): pause the progress spinner before upgrade-path confirms
fengmk2 Jun 29, 2026
4055b45
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 29, 2026
19d4375
fix(ci): whitelist "unparseable" for typos; gate spinner-clear on int…
fengmk2 Jun 29, 2026
c869d31
test(migrate): regenerate upgrade-path snaps for the direct vite edge
fengmk2 Jun 29, 2026
bf052fe
fix(migrate): use catalog: for the bun direct vite edge in catalog pr…
fengmk2 Jun 29, 2026
e4bb804
refactor(migrate): pass supportCatalog to ensureDirectViteForPnpm in …
fengmk2 Jun 29, 2026
c8c060b
refactor(migrate): unify direct-vite injection through one sorted helper
fengmk2 Jun 29, 2026
b6dac3c
fix(migrate): rewrite GitHub workflow node-version-file refs when mig…
fengmk2 Jun 29, 2026
5b214b7
refactor(migrate): simplify nvmrc workflow-rewrite replacement
fengmk2 Jun 29, 2026
04a73e5
chore(migrate): reword comment to satisfy typos lint
fengmk2 Jun 29, 2026
b1f776c
ci: bump pkg-pr-registry-bridge action to latest main
fengmk2 Jun 29, 2026
a552baf
fix(migrate): keep standalone bun projects on concrete specs (no cata…
fengmk2 Jun 29, 2026
fd20cac
test(migrate): make migration-upgrade-bun-catalog a real bun workspace
fengmk2 Jun 29, 2026
ad9f8a4
fix(migrate): address PR review feedback
fengmk2 Jun 29, 2026
36a8160
ci: grant issues:write to the pkg.pr.new bridge-comment step
fengmk2 Jun 29, 2026
1318359
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 29, 2026
333e612
fix(migrate): preserve vite imports in plugin packages
fengmk2 Jun 29, 2026
29cadcc
test(migrate): remove redundant import-rewriter test
fengmk2 Jun 29, 2026
df600f9
fix(migrate): address PR review feedback
fengmk2 Jun 29, 2026
ebada86
test(migrate): add yarn-below-catalog snap for the catalog gating
fengmk2 Jun 29, 2026
d9dc2fb
refactor(migrate): require supportCatalog in yarn.ts helpers
fengmk2 Jun 29, 2026
f170521
fix(deps): bump anyhow to 1.0.103 for RUSTSEC-2026-0190
fengmk2 Jun 29, 2026
1321678
ci: clear lockfiles and node_modules before migrate in pkg.pr.new test
fengmk2 Jun 29, 2026
185cedf
fix(migrate): address PR review feedback
fengmk2 Jun 29, 2026
5952808
refactor(migrate): simplify union no-rewrite check
fengmk2 Jun 29, 2026
567da86
fix(migrate): strip the full bundled set from package.json on upgrade
fengmk2 Jun 29, 2026
fb2e405
fix(migrate): address Codex review feedback
fengmk2 Jun 29, 2026
16b1eba
refactor(test): use globSync to walk dist in versions.spec
fengmk2 Jun 29, 2026
60dbdea
fix(migrate): repoint node-version-file in composite actions
fengmk2 Jun 29, 2026
4be8d51
refactor(migrate): use globSync to collect GitHub Actions files
fengmk2 Jun 29, 2026
6896821
fix(binding): declare native addon engines.node >=20.0.0
fengmk2 Jun 29, 2026
442519c
fix(migrate): do not rewrite supported Node version pins
fengmk2 Jun 29, 2026
738aff4
refactor(binding): drop orphaned resolveProjectNodeVersion
fengmk2 Jun 30, 2026
96b3d1c
chore(migrate): address review feedback on node-pin docs and snap
fengmk2 Jun 30, 2026
6ecf7d0
fix(migrate): address Codex review (peer jsPlugins, workspace catalog)
fengmk2 Jun 30, 2026
88e404d
fix(ci): only clear root node_modules in pkg.pr.new migrate helper
fengmk2 Jun 30, 2026
833a12b
test(migrate): oxfmt the preserve-pins snap fixture package.json
fengmk2 Jun 30, 2026
a0e4154
refactor(migrate): share the bootstrap workspace package.json scanner
fengmk2 Jun 30, 2026
2a892d7
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 30, 2026
2cfce6b
ci(pkg-pr-new): update registry bridge action and canonical URL
fengmk2 Jun 30, 2026
f1a5604
feat(migrate): add --full flag for existing Vite+ projects
fengmk2 Jun 30, 2026
81dee3a
feat(migrate): show toolchain version changes in the summary
fengmk2 Jun 30, 2026
898c785
refactor(migrate): tidy the version-change table renderer
fengmk2 Jun 30, 2026
50c7445
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 30, 2026
c75cbc3
feat(install): install preview builds through the registry bridge
fengmk2 Jun 30, 2026
43ae260
feat(migrate): run the build under test when routing local vs global
fengmk2 Jun 30, 2026
616a8c2
feat(migrate): configure the registry bridge for preview builds
fengmk2 Jun 30, 2026
b97616d
refactor(ci): resolve preview commits via x-commit-key, let migrate o…
fengmk2 Jun 30, 2026
979ed9e
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 30, 2026
c895779
refactor(migrate): small cleanups from review
fengmk2 Jun 30, 2026
7c5aca0
fix(migrate): stamp the test version into pkg.pr.new builds
fengmk2 Jun 30, 2026
d31f7b6
docs(skill): align test-pkg-pr-new-migrate with migrate-owned registry
fengmk2 Jun 30, 2026
cec2f8a
chore(ci): show resolved toolchain versions after migrate test
fengmk2 Jun 30, 2026
03f243c
fix(ci): only pass `vp why -r` for pnpm in the migrate test
fengmk2 Jun 30, 2026
1b3643b
fix(ci): query `vp why` one package at a time in the migrate test
fengmk2 Jun 30, 2026
bcc979b
chore(ci): detect the package manager via `vp env current --json`
fengmk2 Jun 30, 2026
fb12898
chore(ci): parse `vp env current --json` with node, not python3
fengmk2 Jun 30, 2026
d7a6c4b
docs(ci): add CLI install + `vp migrate` to the pkg.pr.new PR comment
fengmk2 Jun 30, 2026
f5b62b4
docs(ci): use the PR number for the pkg.pr.new install command
fengmk2 Jun 30, 2026
2ed32d0
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jun 30, 2026
d2bf490
fix(ci): harden package-manager detection in the migrate test
fengmk2 Jun 30, 2026
bf21c4f
feat(migrate): show per-package progress during the config compatibil…
fengmk2 Jun 30, 2026
52e453b
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 1, 2026
dff920d
feat(migrate): show progress during the toolchain import rewrite
fengmk2 Jul 1, 2026
7d848b3
fix(migrate): route to the global CLI when either side is a preview b…
fengmk2 Jul 1, 2026
fb0fa39
refactor(migrate): reuse getWorkspaceProjectPaths in the config-compa…
fengmk2 Jul 1, 2026
cc69225
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 1, 2026
8a802d8
fix(migrate): drop redundant casts flagged by oxlint-tsgolint 0.24.0
fengmk2 Jul 1, 2026
fcd4a97
fix(migrate): clear the spinner before the --full setup prompts
fengmk2 Jul 1, 2026
a3e3079
docs(migrate): note the Rolldown-compat check and trim a duplicated rule
fengmk2 Jul 1, 2026
b749ef8
fix(migrate): reference the catalog for injected browser providers
fengmk2 Jul 1, 2026
15f8e1e
feat(migrate): catalog-ize aligned @vitest/* when the toolchain is ca…
fengmk2 Jul 1, 2026
6ad2ffb
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 1, 2026
4b4c228
test(migrate): regenerate @vitest/* catalog snaps and add varlet #10 …
fengmk2 Jul 1, 2026
d36064e
test(migrate): add varlet-cli #10 regression for vite-plus consumer c…
fengmk2 Jul 1, 2026
1081f16
fix(migrate): gate catalog support on pnpm >= 9.5.0, not the 10.6.2 s…
fengmk2 Jul 1, 2026
8498463
refactor(migrate): dedupe the provider-reconcile ternary and supportC…
fengmk2 Jul 1, 2026
6dc08ff
fix(migrate): pin browser-provider framework peers instead of writing *
fengmk2 Jul 1, 2026
452cbf6
fix(migrate): rewrite the catalog below pnpm 10.6.2 and keep catalog:…
fengmk2 Jul 1, 2026
ca3c13c
refactor(migrate): dedup pnpm-workspace.yaml reads; guard bun catalog…
fengmk2 Jul 1, 2026
3f51afb
feat(migrate): scope the vite to vite-plus rewrite to config entry files
fengmk2 Jul 1, 2026
c006a76
refactor(migrate): drop dead extra_config_files path, preserve tsconf…
fengmk2 Jul 1, 2026
7da2ece
style(migrate): apply rustfmt to the #2004 import_rewriter tests
fengmk2 Jul 1, 2026
63eab83
fix(ci): run vp migrate via the Rust binary in test-pkg-pr-new-migrat…
fengmk2 Jul 1, 2026
001df0b
fix(migrate): clear the compat-check spinner before the existing-Vite…
fengmk2 Jul 1, 2026
ad4b708
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 1, 2026
14f1328
fix(migrate): defer legacy git-hooks setup to --full on a version-update
fengmk2 Jul 1, 2026
d963215
fix(ci): robust pnpm detection + combined vp why in test-pkg-pr-new-m…
fengmk2 Jul 1, 2026
5cf9447
fix(migrate): honor vitest exclusions + inject vitest for Nuxt packages
fengmk2 Jul 1, 2026
63f877e
ci: bump pkg-pr-registry-bridge to f8677960 (tar end-of-archive fix)
fengmk2 Jul 1, 2026
ab9a0b3
fix(migrate): 4 PR-review fixes (bridge resolve, force-stage, prettie…
fengmk2 Jul 1, 2026
0fecc75
fix(migrate): write pnpm catalog for standalone pnpm 9.5-10.6.1 projects
fengmk2 Jul 1, 2026
da6cd2c
docs(migrate): restructure migrate-rules.md for user clarity
fengmk2 Jul 2, 2026
9b2f64e
fix(migrate): apply 13 code-review fixes across migrate paths
fengmk2 Jul 2, 2026
ce1bd40
ci: bump pkg-pr-registry-bridge to a8dc0539 (per-package atomic publish)
fengmk2 Jul 2, 2026
e29973c
test(migrate): declare module 'vite' augmentations follow the config …
fengmk2 Jul 2, 2026
6f99ee3
fix(migrate): keep declare module 'vite' augmentations on 'vite'
fengmk2 Jul 2, 2026
7a214c2
ci: bump pkg-pr-registry-bridge to 4072b814 (dedicated /-/register en…
fengmk2 Jul 2, 2026
3ee9ab7
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 2, 2026
900c2e2
ci: bump setup-vp to 13e7afb9 (version auto-resolution)
fengmk2 Jul 2, 2026
3028a34
Merge remote-tracking branch 'origin/main' into rfc/migrate-upgrade-path
fengmk2 Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 32 additions & 0 deletions .claude/skills/test-pkg-pr-new-migrate/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: test-pkg-pr-new-migrate
description: Verify a pkg.pr.new build of vite-plus against a real project before release — run `vp migrate` from the pkg.pr.new commit against a local project, deps resolved through the registry bridge. Use when asked to verify/e2e-test a pkg.pr.new build against a project, "test PR #<N> on <project>", or check a prerelease against a repo.
allowed-tools: Bash, Read
---

# Verify a pkg.pr.new build against one project

Installs an isolated global `vp` built from a registry bridge commit build and runs `vp migrate` on a specified local project. The global CLI and the migrated project both pin `vite-plus`/`vite` to the clearly-defined `0.0.0-commit.<sha>` build. `vp migrate` itself writes the bridge registry into the project's `.npmrc` (or `.yarnrc.yml` for Yarn Berry) so the deps resolve, during this run and in the project's own CI; this script only force-stages that file past `.gitignore`.

Required inputs: a `<PR-or-SHA>` (the build to verify) and a `<project-path>`. If either is missing from the request, **ask the user** for it — never guess the PR/SHA, as the build under test is the user's choice.

```bash
.github/scripts/test-pkg-pr-new-migrate.sh <PR-or-SHA> <project-path> [migrate-options...]
# e.g.
.github/scripts/test-pkg-pr-new-migrate.sh 1891 /path/to/npmx.dev --no-interactive
```

- First arg is a PR number or commit SHA; the script resolves the immutable commit via the bridge `x-commit-key` header and verifies the bridge serves it (the pkg.pr.new publish workflow registers each commit).
- Never touches `~/.vite-plus`; clears only the workspace ROOT lockfile + `node_modules` before migrating; refuses a dirty worktree unless `ALLOW_DIRTY=1`; prints the project's `git status`/`diff` at the end — inspect that to confirm the migration result.

**The build under test must include the "migrate writes the bridge registry" feature** (this session's work / current branch head onward). The harness no longer writes the registry itself — it relies on `vp migrate` doing it. Testing an older build with this harness would leave the project with no bridge registry, so its deps resolve from npmjs (`ERR_PNPM_NO_MATCHING_VERSION` on the `0.0.0-commit.<sha>` version). Always verify a fresh build of the branch, not a stale published commit.

The script prints the resolved versions at the end, querying one package at a time (npm/yarn/bun `why` only accept a single package; `-r` is pnpm-only). Each must resolve to exactly ONE version: `vite-plus` and `vite` at the expected `0.0.0-commit.<sha>` (vite via the `@voidzero-dev/vite-plus-core` alias), `vitest` at the bundled upstream version. Multiple versions, or a stale/wrong version, means the migration or install is broken. To re-check by hand:

```bash
cd <project-path>
# pnpm: one call, recursive across workspaces
vp why -r vite-plus vite vitest
# npm / yarn / bun: one package per call, no -r
vp why vite-plus && vp why vite && vp why vitest
```
55 changes: 55 additions & 0 deletions .claude/skills/verify-interactive-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
name: verify-interactive-cli
description: Drive and capture vp's interactive (clack) prompts in a tmux session to verify interactive UX and catch spinner-over-prompt bugs that snap tests (which run non-interactively) miss. Use when asked to test/verify/capture an interactive vp command's prompts (vp migrate, vp create, ...), reproduce a prompt-rendering bug, or show the real interactive CLI output for a PR.
allowed-tools: Bash, Read
---

# Verify vp's interactive CLI prompts

Snap tests run `vp` non-interactively, so interactive clack prompts (hooks / agent / editor confirms, the Node-version upgrade confirm) and TTY-only rendering bugs are never exercised. This skill drives the real prompts in a TTY (via tmux), captures clean output, and can catch a spinner animating underneath an active prompt (a real, recurring UX bug class, e.g. the "Preparing migration" and "Checking Node.js version support" spinners).

## Prerequisites

- `tmux` (macOS has none by default): `brew install tmux`.
- The global `vp` must contain the code under test. To exercise working-tree changes, rebuild first: `pnpm bootstrap-cli` (~5 min), then confirm with `vp --version`.

## Driver

`interactive-cli-tmux-driver.sh` is bundled in this skill's directory.

```bash
# Run to completion, auto-accepting every prompt's DEFAULT; prints a clean transcript.
.claude/skills/verify-interactive-cli/interactive-cli-tmux-driver.sh <project-dir> "vp migrate"

# STOP at a specific prompt (do NOT answer it) and check for a spinner animating under it.
.claude/skills/verify-interactive-cli/interactive-cli-tmux-driver.sh <project-dir> "vp migrate" "Upgrade Node.js"
```

How it works:

- Runs the command in a detached tmux session; `tmux capture-pane -p` yields clean text (clack's in-place redraws overwrite, so only resolved lines remain), far cleaner than `expect`'s raw ANSI capture.
- Auto-accepts each prompt's default by sending Enter when the pane goes STABLE. A waiting prompt is static; animating spinners keep the pane changing, so Enter never fires mid-work.
- End-detection uses `; echo "$M1$M2 exit=$?"` where M1/M2 are split vars, so the literal end marker is not in the typed command line (otherwise a grep matches the echoed command, not the program output).
- With a STOP_AT regex it halts at the target prompt and captures twice ~3s apart: identical captures = static prompt (OK); differing captures (or a `Checking ... (Xs)` line) = a spinner is animating under the prompt = a UX bug.

## Setting up a target project

Create a throwaway project that triggers the prompts you want to see, e.g. a fresh Vite app whose `.node-version` is below the supported range to exercise the Node-upgrade confirm:

```bash
mkdir -p /tmp/vp-demo && cd /tmp/vp-demo
printf '{\n "name":"d","private":true,"type":"module","packageManager":"pnpm@10.18.0",\n "scripts":{"build":"vite build","test":"vitest run"},\n "devDependencies":{"vite":"^8.0.0","vitest":"^4.1.0"}\n}\n' > package.json
echo "24.3.0" > .node-version
printf 'import { defineConfig } from "vite";\nexport default defineConfig({});\n' > vite.config.ts
git init -q && git add -A && git -c user.email=x@x -c user.name=x commit -qm init
```

## When you find a spinner-over-prompt bug

Fix it test-first: the prompt's code must pause the migration progress spinner before the confirm renders. Assert the call order is `['pause', 'confirm']`. Reference fix: `upgradeUnsupportedNodeVersions` (in `packages/cli/src/migration/migrator/setup.ts`) takes a `pauseProgress` callback that `bin.ts` wires to `clearMigrationProgress`, called right before `prompts.confirm`.

## Gotchas

- clack's active-prompt marker here is `›` (not always `◆`); detect a waiting prompt by pane stability, not a specific glyph.
- `VP_SKIP_INSTALL=1` skips the dependency install but breaks steps that load `vite.config.ts` (e.g. the prettier auto-migration) because `vite` isn't installed; use it only when you stop before the install step.
- A `&` inside a background task detaches the script, so the task reports "completed" early while it keeps running; poll a status file or `capture-pane` to track real progress.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/bash
# Drive and capture an interactive `vp` (clack) prompt flow inside tmux, to verify
# interactive UX that snap tests (which run non-interactively) never cover.
#
# Usage:
# interactive-cli-tmux-driver.sh <project-dir> "<command>" [STOP_AT_REGEX]
# interactive-cli-tmux-driver.sh /tmp/demo "vp migrate"
# interactive-cli-tmux-driver.sh /tmp/demo "vp migrate" "Upgrade Node.js"
#
# No STOP_AT : run to completion, auto-accepting every prompt's DEFAULT (Enter),
# then print a clean transcript.
# With STOP_AT: drive prompts until one matching the regex appears, then STOP
# (do NOT answer it) and capture the pane twice 3s apart. Identical
# captures => prompt is static (good). Differing captures (or a
# "Checking ... (Xs)" line) => a spinner is animating UNDER the
# prompt -- a real UX bug (this is how the Node-upgrade confirm
# spinner-overlap was found).
#
# Why tmux and not `expect`: expect's raw capture is full of ANSI cursor/redraw
# noise; `tmux capture-pane -p` returns clean text because in-place redraws
# overwrite and only the resolved lines remain in scrollback.
# macOS has no tmux/timeout by default: `brew install tmux`.
set -u
DIR="${1:?project dir}"; CMD="${2:?command, e.g. \"vp migrate\"}"; STOP_AT="${3:-}"
command -v tmux >/dev/null || { echo "need tmux: brew install tmux" >&2; exit 1; }
S="clicap_$$"
cap1="$(mktemp)"; cap2="$(mktemp)"

tmux kill-session -t "$S" 2>/dev/null
tmux new-session -d -s "$S" -x 100 -y 50
tmux set-option -t "$S" history-limit 50000
# M1/M2 are split so the end marker never appears in the TYPED command line --
# otherwise a grep for it matches the echoed command, not the program output.
tmux send-keys -t "$S" 'export PS1="$ " PROMPT="%% " M1=CLI M2=CAPDONE' Enter; sleep 1
tmux send-keys -t "$S" "cd '$DIR'" Enter; sleep 1
tmux send-keys -t "$S" 'clear' Enter; sleep 1
tmux send-keys -t "$S" "$CMD"'; echo "$M1$M2 exit=$?"' Enter

prev=""; stable=0; sent=0
for i in $(seq 1 180); do
sleep 2
pane="$(tmux capture-pane -t "$S" -p -S -120 2>/dev/null)"
# reached the prompt we want to inspect -> stop without answering it
if [ -n "$STOP_AT" ] && printf '%s' "$pane" | grep -q "$STOP_AT"; then
tmux capture-pane -t "$S" -p -S -60 > "$cap1"; sleep 3
tmux capture-pane -t "$S" -p -S -60 > "$cap2"
if diff -q "$cap1" "$cap2" >/dev/null; then
echo "STATIC prompt (nothing animating underneath) -- OK"
else
echo "ANIMATING under the prompt (likely spinner-over-prompt bug):"
diff "$cap1" "$cap2"
fi
echo "--- prompt ---"; sed -n "/$STOP_AT/,\$p" "$cap1"
tmux kill-session -t "$S" 2>/dev/null; rm -f "$cap1" "$cap2"; exit 0
fi
# program finished
printf '%s' "$pane" | grep -q "CLICAPDONE exit=" && break
# A waiting prompt makes the pane STABLE; animating spinners keep it changing,
# so this won't fire mid-work. Accept the default with Enter, once per state.
if [ "$pane" = "$prev" ]; then
stable=$((stable+1))
if [ "$stable" -ge 2 ] && [ "$sent" -eq 0 ]; then tmux send-keys -t "$S" Enter; sent=1; fi
else
prev="$pane"; stable=0; sent=0
fi
done

echo "=== clean transcript ==="
tmux capture-pane -t "$S" -p -S -3000
tmux kill-session -t "$S" 2>/dev/null; rm -f "$cap1" "$cap2"
Loading
Loading