Skip to content

feat(cli): global -C flag for working-directory switching#2031

Draft
fengmk2 wants to merge 16 commits into
graphite-base/2031from
feat/cwd-flag
Draft

feat(cli): global -C flag for working-directory switching#2031
fengmk2 wants to merge 16 commits into
graphite-base/2031from
feat/cwd-flag

Conversation

@fengmk2

@fengmk2 fengmk2 commented Jul 3, 2026

Copy link
Copy Markdown
Member

Implements the approved RFC, now included in this PR as rfcs/cwd-flag.md (moved from #2022).

  • vp -C <dir> <cmd> behaves exactly like cd <dir> && vp <cmd> for every command, parsed by the global Rust binary (crates/vite_global_cli) and by the local bin (packages/cli/src/bin.ts); the tool child process is spawned with <dir> as cwd, no process.chdir in the Rust layer.
  • Bare vp dev/build/preview/pack at a workspace root now resolve a target instead of silently running against the root: defaultPackage from the root config (statically extracted, so it works at roots without a vite-plus install), single-runnable auto-select in interactive terminals, otherwise a ranked package listing with -C hints and exit 1.
  • Positionals keep upstream semantics (Vite [root], tsdown entries); a parity regression snap test locks that in.

Tests live on the PTY snapshot harness (stacked on #2052), with real pass/fail assertions in both local and global vp flavors: cwd_flag (-C vs cd byte-identical for pack and run, missing-dir error, positional entry-semantics parity), app_root_listing (picker select via real keystrokes, Ctrl+C cancel, non-TTY listing for build and dev), app_root_auto_select, and app_root_default_package (note line, dist assertion, missing-directory error). The full old snap-test-local suite still passes unchanged; clippy, unit tests, and a manual global-binary run verified.

The interactive package picker is now implemented on vite_select (fuzzy filter, Enter = implicit -C, Ctrl+C = exit 130), with PTY snapshot coverage driving real keystrokes in both flavors. Requires voidzero-dev/vite-task#510 (configurable selector prompt); the vite-task pin points at that PR's commit and needs a re-bump to vite-task main after it merges.

@fengmk2 fengmk2 self-assigned this Jul 3, 2026
@netlify

netlify Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 8f73602
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a49b84f4df08c0008bb1077

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

✅ Staging deployment successful!

Preview: https://viteplus-staging.void.app/
Commit: 04de6c3

@fengmk2

fengmk2 commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Manual verification steps

Setup

git fetch origin && git checkout feat/cwd-flag
pnpm install && pnpm bootstrap-cli   # installs this branch as the global vp
vp --help | grep -- '-C'             # the new global flag is listed

(pnpm bootstrap-cli replaces the installed ~/.vite-plus CLI; re-run it from main afterwards to restore.)

Create a scratch monorepo with no local vite-plus install, so the branch's bundled CLI runs end to end:

mkdir -p /tmp/vp-c-demo/apps/web /tmp/vp-c-demo/apps/admin /tmp/vp-c-demo/packages/ui/src && cd /tmp/vp-c-demo
echo '{ "name": "demo", "private": true, "workspaces": ["apps/*", "packages/*"] }' > package.json
for a in web admin; do
  echo "{ \"name\": \"$a\", \"private\": true }" > apps/$a/package.json
  echo "<!doctype html><h1>$a</h1>" > apps/$a/index.html
done
echo '{ "name": "ui", "private": true, "type": "module", "main": "src/index.ts" }' > packages/ui/package.json
echo 'export const ui = 1' > packages/ui/src/index.ts

1. -C runs any command as if started in the directory

vp -C packages/ui pack        # packs ui; dist lands in packages/ui/dist
vp -C apps/web dev            # dev server for web, from the root
vp -C apps/web build
vp -C nope build              # error: directory not found: nope (exit 1)

Compare with cd packages/ui && vp pack: identical output. The positional form is unchanged: vp pack packages/ui still treats the path as a tsdown entry resolved from the invocation directory (upstream semantics).

2. Bare app commands at the workspace root

vp build         # error + package listing (apps ranked first) + -C hints, exit 1
vp dev           # same; no server starts against the root anymore
vp dev --help    # still prints vite's dev help, never the listing
vp build | cat   # non-TTY: same listing, deterministic

3. Single-app auto-select (interactive terminal only)

rm -rf apps/admin
vp dev    # prints "Selected package: web (apps/web)" + a Tip line, then starts web

4. defaultPackage

echo 'export default { defaultPackage: "./apps/web" }' > vite.config.ts
vp build            # note: vp build: using ./apps/web (defaultPackage)
vp -C packages/ui pack   # explicit -C still wins over the config
echo 'export default { defaultPackage: "./missing" }' > vite.config.ts
vp build            # error: defaultPackage points to a missing directory (exit 1)

5. The original cwd-divergence bug

The reproduction repo demonstrates the process.cwd() config read that motivated -C:

git clone https://github.com/why-reproductions-are-required/vite-plus-monorepo-app-commands-repro
cd vite-plus-monorepo-app-commands-repro   # do NOT pnpm install: keep the branch CLI in charge
vp dev apps/admin      # still fails with ENOENT (positional = root only, unchanged)
vp -C apps/admin dev   # works: cert loads, server starts

6. Snap tests

pnpm -F vite-plus snap-test-local command-cwd-flag
pnpm -F vite-plus snap-test-local command-app-root-listing
pnpm -F vite-plus snap-test-local command-default-package
git diff -- packages/cli/snap-tests   # should be empty

@fengmk2 fengmk2 changed the base branch from main to rfc/interactive-snapshot-tests July 5, 2026 01:50

fengmk2 commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label auto-merge to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 02:20
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 04:36
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 04:41
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 04:56
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 05:17
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 05:37
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 05:46
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 05:50
@fengmk2 fengmk2 force-pushed the rfc/interactive-snapshot-tests branch from fefbe9d to e6ed290 Compare July 5, 2026 06:10
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 06:44
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 06:52
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 07:03
fengmk2 added 16 commits July 5, 2026 16:56
…pp commands

Implements rfcs/cwd-flag.md (RFC #2022):

- vp -C <dir> <cmd> runs any command as if started in <dir> (git/make/pnpm
  convention), parsed by both the global binary and the local bin; the
  spawned tool gets <dir> as its working directory, no process.chdir
- bare dev/build/preview/pack at a workspace root now resolve a target:
  defaultPackage from the root config (static extraction, works without a
  vite-plus install), single-runnable auto-select in interactive terminals,
  otherwise a package listing with -C hints and exit 1
- positional semantics are untouched: vite [root] and tsdown entries behave
  exactly as before (covered by a parity regression snap test)
- interactive picker is a follow-up pending vite_select prompt support
…malize -C paths

Post-review cleanup:

- hoist the CI/interactive-terminal gate into vite_shared and reuse it from
  the command picker, vite_install, and app-target elicitation (the new copy
  had drifted to 6 of 11 CI vars)
- expose vite_static_config::has_config_file and use it for runnable ranking
  (the hand-rolled list missed vite.config.cjs/.cts)
- normalize -C and defaultPackage joins with clean() so upward workspace
  walks never see ./ or .. components
- read defaultPackage before the workspace lookup so package.json-less
  framework roots (the RFC's motivating shape) work
- exempt -h/--help/-V/--version from elicitation so vp dev --help always
  reaches the tool
- accept -C=dir in bin.ts to match the clap grammar; simplify row
  construction, single-runnable check, and the target binding in mod.rs
…and defaultPackage

Review follow-ups:

- reuse vite_powershell::is_stdin_terminal (memoized) and add memoized
  stdout/stderr wrappers in vite_shared; migrate all 19 direct
  is_terminal() call sites and enforce via disallowed-methods in
  .clippy.toml
- keep the CI env check in is_interactive_terminal: it is not covered by
  the TTY checks because some CI systems run jobs in a PTY (Buildkite
  does by default); documented in the code
- document -C and the workspace-root behavior in docs/guide/monorepo.md
  and defaultPackage in docs/config/index.md
…xtures

- cargo fmt: alphabetize the interactivity module/exports in vite_shared
  (the Lint CI failure) and reflow touched files
- drop `|| echo` from the app-root-listing, default-package-missing, and
  cwd-flag fixtures so the snapshots capture the real exit code (via the
  runner's [N] prefix) and full error output directly
Expand the App Commands section into subsections with real terminal output:
single-app auto-select, the multi-app listing with -C hints, -C targeting,
and defaultPackage (including the non-workspace framework case).
Uses the interactive snapshot harness to cover the TTY-gated branches the
old snap tests cannot reach: single-app auto-select (Selected/Tip lines),
the multi-candidate listing under a real terminal, and the defaultPackage
note, each in both local and global vp flavors.
…rkspace root

Replaces the interim TTY listing with the fuzzy vite_select picker (the vp
run selector component): typing filters packages, Enter runs the selection
as an implicit -C, Ctrl+C cancels with exit 130. Single-runnable auto-select
and the non-interactive listing are unchanged. Every picker render emits a
package-select:<query>:<index> milestone so PTY snapshot tests synchronize
on real keystrokes (picker_select / picker_cancel / non-TTY listing cases,
both flavors).

Requires the configurable prompt from voidzero-dev/vite-task#510; the
vite-task pin points at that PR's commit and needs a re-bump to vite-task
main after it merges.
…arness

Moves the four old-harness fixtures added by this PR (command-cwd-flag,
command-app-root-listing, command-default-package,
command-default-package-missing) into crates/vite_cli_snapshots:

- cwd_flag: -C vs cd byte-identical for pack and run, missing-dir error,
  positional entry-semantics parity, with vpt list-dir/rm assertions
- app_root_listing: non-TTY dev listing joins the build step
- app_root_default_package: dist assertion plus a missing-directory case
  via a nested non-workspace root

Real pass/fail assertions and recorded exit codes replace the
regenerate-and-diff model for these cases.
…efault entry

A Vite config without a pack block does not make bare vp pack succeed, so
it no longer counts as pack-runnable; document the per-command runnable
rules normatively in the RFC and docs.
Locks in the corrected pack-runnable rule: an app's vite.config.ts without
a pack block does not count, so bare vp pack at the root auto-selects the
library whose only signal is tsdown's default src/index.ts entry and packs
it with no pack config at all.
Bare vp build and vp pack in a standalone repo (no workspaces field) run
in place even in an interactive terminal: no picker, no auto-select line,
no listing. Guards resolution-order step 6 against regressions.
@fengmk2 fengmk2 changed the base branch from graphite-base/2031 to rfc/interactive-snapshot-tests July 5, 2026 08:58
@fengmk2 fengmk2 changed the base branch from rfc/interactive-snapshot-tests to graphite-base/2031 July 5, 2026 09:10
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.

1 participant