feat(cli): global -C flag for working-directory switching #11173
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| permissions: | |
| # Doing it explicitly because the default permission only includes metadata: read. | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| types: [opened, synchronize, labeled] | |
| push: | |
| branches: | |
| - main | |
| # `paths` with negations instead of paths-ignore: PTY snapshot baselines | |
| # are .md files (crates/vite_cli_snapshots/**/snapshots/*.md) and a | |
| # baseline-only push must still run CI. Later patterns win. | |
| paths: | |
| - '**' | |
| - '!**/*.md' | |
| - 'crates/vite_cli_snapshots/**' | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.ref_name != 'main' }} | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| optimize-ci: | |
| runs-on: ubuntu-latest # or whichever runner you use for your CI | |
| outputs: | |
| skip: ${{ steps.check_skip.outputs.skip }} | |
| steps: | |
| - name: Optimize CI | |
| id: check_skip | |
| # v0.0.9 still declares `using: node20`; emits a Node 20 deprecation warning until upstream ships a node24 release. | |
| # https://github.com/withgraphite/graphite-ci-action | |
| uses: withgraphite/graphite-ci-action@402a89bc8aa6db18ae1f9c61953d001d5f9d828f # v0.0.11 | |
| with: | |
| graphite_token: ${{ secrets.GRAPHITE_CI_OPTIMIZER_TOKEN }} | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| needs: optimize-ci | |
| if: needs.optimize-ci.outputs.skip == 'false' | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| # snapshots is OR-ed in because the PTY harness stores its assertions | |
| # as .md files (fixtures/**/snapshots/*.md), which the code filter's | |
| # markdown exclusion would otherwise classify as docs-only. | |
| code-changed: ${{ steps.filter.outputs.code == 'true' || steps.filter.outputs.snapshots == 'true' }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| id: filter | |
| with: | |
| predicate-quantifier: every | |
| filters: | | |
| code: | |
| - '!**/*.md' | |
| - '!docs/**' | |
| - '!.github/workflows/deploy-docs-preview.yml' | |
| - '!.github/workflows/deploy-docs.yml' | |
| - '!netlify.toml' | |
| snapshots: | |
| - 'crates/vite_cli_snapshots/**' | |
| docs-fmt: | |
| name: Docs format check | |
| needs: detect-changes | |
| # Docs-only changes skip the full CI matrix, so nothing else format-checks | |
| # them (code changes get `vp check` in cli-e2e-test). Non-blocking: | |
| # intentionally not listed in the `done` job's needs. | |
| if: needs.detect-changes.outputs.code-changed == 'false' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: voidzero-dev/setup-vp@35171c92dd08b67d5a9d3f2a4327800e58396f2a # v1.13.0 | |
| with: | |
| # The workspace `vite-plus` version can be ahead of the registry, so | |
| # pin the released build instead of auto-detecting from package.json. | |
| version: latest | |
| run-install: false | |
| - name: Run vp fmt --check | |
| run: | | |
| # `vp fmt` loads the root vite.config.ts, whose `import ... from 'vite-plus'` | |
| # must resolve at runtime. Link the released package out of the global | |
| # install instead of installing the whole workspace. | |
| mkdir -p node_modules | |
| ln -s "$HOME/.vite-plus/current/node_modules/vite-plus" node_modules/vite-plus | |
| vp fmt --check | |
| download-previous-rolldown-binaries: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/download-rolldown-binaries | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| test: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| name: Test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: test | |
| tools: just | |
| - run: cargo check --all-targets --all-features | |
| env: | |
| RUSTFLAGS: '-D warnings --cfg tokio_unstable' # also update .cargo/config.toml | |
| - run: just test | |
| # Windows tests are cross-compiled on a fast Linux runner with cargo-xwin | |
| # (clang-cl + lld-link against the xwin-downloaded MSVC CRT/Windows SDK) | |
| # and packed into a portable nextest archive. The test-windows job then | |
| # only downloads the archive and runs it: no Rust toolchain, dev drive, or | |
| # compilation on the slow Windows runner. Same approach as | |
| # voidzero-dev/vite-task#443. | |
| build-windows-tests: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| name: Build Windows tests | |
| runs-on: namespace-profile-linux-x64-default | |
| env: | |
| XWIN_ACCEPT_LICENSE: '1' | |
| # The MSVC STL from xwin's VS17 manifest static-asserts a minimum Clang | |
| # version newer than what runner images ship. Microsoft's documented | |
| # escape hatch lets Detours (C++, via the fspy git dependency) compile | |
| # with the runner's clang-cl; cargo-xwin folds a pre-set CXXFLAGS into | |
| # the per-target CXXFLAGS it generates. | |
| CXXFLAGS: -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: ./.github/actions/setup-xwin | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: windows-cross | |
| tools: cargo-nextest | |
| # `cargo xwin env` resolves the rustflags configured in | |
| # .cargo/config.toml (including the cfg-based Windows section), appends | |
| # its -Lnative Windows SDK paths, and re-exports the result as | |
| # CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS, so plain cargo | |
| # invocations cross-compile exactly like `cargo xwin` would. It renders | |
| # its intended *removal* of RUSTFLAGS as `export RUSTFLAGS="";`, which | |
| # must become a real unset: a set RUSTFLAGS takes precedence over the | |
| # target-specific rustflags and would shadow them. | |
| - name: Check Windows target | |
| run: | | |
| eval "$(cargo xwin env --target x86_64-pc-windows-msvc | grep '^export ')" | |
| unset RUSTFLAGS | |
| # Fail fast if cargo-xwin ever stops folding the repo's cfg-based | |
| # Windows rustflags (--cfg tokio_unstable, /STACK:8388608) into the | |
| # target env var; a dropped /STACK would be a silent runtime | |
| # difference in the shipped test binaries. | |
| case "$CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS" in | |
| *tokio_unstable*STACK:8388608*) ;; | |
| *) | |
| echo "::error::cargo-xwin dropped the .cargo/config.toml Windows rustflags: $CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS" | |
| exit 1 | |
| ;; | |
| esac | |
| export CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="$CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS -D warnings" | |
| cargo check --all-targets --all-features --target x86_64-pc-windows-msvc | |
| # Keep the package selection in sync with the `test` recipe in justfile. | |
| # vite_cli_snapshots is excluded there too: its snapshot suite needs a | |
| # built vp and node at runtime and joins the Windows archive later. | |
| - name: Build test archive | |
| run: | | |
| eval "$(cargo xwin env --target x86_64-pc-windows-msvc | grep '^export ')" | |
| unset RUSTFLAGS | |
| cargo nextest archive $(for d in crates/*/; do n=$(basename $d); [ "$n" = "vite_cli_snapshots" ] || echo -n "-p $n "; done) -p vite-plus-cli \ | |
| --target x86_64-pc-windows-msvc --archive-file windows-tests.tar.zst | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-test-archive | |
| path: windows-tests.tar.zst | |
| retention-days: 7 | |
| # Separate archive for the PTY snapshot harness (excluded from the | |
| # unit-test archive above): its suite needs vp, node, and a built | |
| # packages/cli/dist at runtime, which only cli-snapshot-test-windows | |
| # provides. The archive carries the cli_snapshots test binary plus the | |
| # vpt helper; nextest rewrites CARGO_MANIFEST_DIR/CARGO_BIN_EXE_vpt at | |
| # run time so the relocated binaries find fixtures and helpers. | |
| - name: Build snapshot test archive | |
| run: | | |
| eval "$(cargo xwin env --target x86_64-pc-windows-msvc | grep '^export ')" | |
| unset RUSTFLAGS | |
| cargo nextest archive -p vite_cli_snapshots \ | |
| --target x86_64-pc-windows-msvc --archive-file windows-snapshot-tests.tar.zst | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-snapshot-test-archive | |
| path: windows-snapshot-tests.tar.zst | |
| retention-days: 7 | |
| test-windows: | |
| needs: build-windows-tests | |
| name: Test (Windows) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-test-archive | |
| - uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10 | |
| with: | |
| tool: cargo-nextest | |
| # `cargo-nextest` is invoked directly so the job never depends on the | |
| # runner's Rust toolchain. | |
| # --test-threads=1: nextest runs each test in its own process, so | |
| # serial_test's in-process locks no longer serialize tests that contend | |
| # on cross-process shared state (fixed temp paths, e.g. | |
| # `vp-test-custom-home` in vite_shared::home tests). The whole suite is | |
| # only ~1 minute of test time, so full serialization is cheap | |
| # insurance; revisit if it becomes the bottleneck. | |
| - name: Run tests | |
| run: cargo-nextest nextest run --archive-file windows-tests.tar.zst --workspace-remap . --test-threads 1 | |
| env: | |
| RUST_MIN_STACK: '8388608' | |
| # Keep Windows env parity with the `test` recipe in justfile. | |
| __COMPAT_LAYER: RunAsInvoker | |
| # Snapshot assertions resolve paths from the Linux build machine's | |
| # workspace root baked in at compile time; point insta at this | |
| # checkout instead (without it, insta shells out to a `cargo` that | |
| # is not installed here). | |
| INSTA_WORKSPACE_ROOT: ${{ github.workspace }} | |
| test-musl: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| name: Test (Linux x64 musl) | |
| runs-on: namespace-profile-linux-x64-default | |
| container: | |
| image: node:22-alpine3.21 | |
| env: | |
| # GitHub Actions sets HOME=/github/home in containers, but the euid home is /root. | |
| # Pin Rust tooling paths to avoid $HOME mismatch issues. | |
| CARGO_HOME: /root/.cargo | |
| RUSTUP_HOME: /root/.rustup | |
| # `-crt-static`: vite-task's `fspy_preload_unix` cdylib (unconditional | |
| # build-dep since voidzero-dev/vite-task#344) can't link against a | |
| # static musl libc. vite+ ships as a NAPI module that links musl libc | |
| # dynamically anyway, so matching here is correct. | |
| # Must mirror `.cargo/config.toml` rustflags — RUSTFLAGS env overrides | |
| # both [build] and [target.*] levels. | |
| RUSTFLAGS: --cfg tokio_unstable -C link-args=-Wl,--warn-unresolved-symbols -C target-feature=-crt-static | |
| steps: | |
| - name: Install Alpine dependencies | |
| shell: sh {0} | |
| run: apk add --no-cache bash curl git just musl-dev gcc g++ python3 cmake make | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - name: Install rustup | |
| run: | | |
| # GitHub Actions sets HOME=/github/home in containers, but rustup expects euid home (/root) | |
| export HOME=/root | |
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none | |
| echo "/root/.cargo/bin" >> "$GITHUB_PATH" | |
| - name: Install Rust toolchain | |
| run: rustup show | |
| # Skip separate cargo check — cargo test already compiles everything. | |
| - run: just test | |
| lint: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| name: Lint | |
| runs-on: namespace-profile-linux-x64-default | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: lint | |
| tools: just,cargo-shear | |
| components: clippy rust-docs rustfmt | |
| - run: | | |
| cargo shear | |
| cargo fmt --check | |
| just lint | |
| # RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items | |
| - uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 | |
| with: | |
| files: . | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - name: Install docs dependencies | |
| run: pnpm -C docs install --frozen-lockfile | |
| # Retry once because `pnpm dedupe --check` re-resolves all dependencies and | |
| # can non-deterministically flag optional transitive peers (e.g. oxc-resolver | |
| # via dts-resolver) as dedup-able depending on async resolution order. | |
| - name: Deduplicate dependencies | |
| run: pnpm dedupe --check || pnpm dedupe --check | |
| # Cross-compiles the Windows release NAPI binding (vite-plus .node) and CLI | |
| # binaries (vp.exe, vp-shim.exe, vp-setup.exe) on a fast Linux runner with | |
| # cargo-xwin, then publishes them as the windows-cli-binaries artifact. The | |
| # Windows entries of cli-e2e-test, cli-snap-test, and install-e2e-test-sfw | |
| # download that artifact instead of each spending ~24 minutes rebuilding the | |
| # same binaries on slow windows-latest runners whenever build-upstream's | |
| # NAPI binding cache misses. Release builds are unaffected: they keep | |
| # building natively on Windows via reusable-release-build.yml (their cache | |
| # keys differ through RELEASE_BUILD/VERSION). | |
| build-windows-cli: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.code-changed == 'true' | |
| name: Build Windows CLI binaries | |
| runs-on: namespace-profile-linux-x64-default | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: ./.github/actions/build-windows-cli | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cli-e2e-test: | |
| name: CLI E2E test | |
| needs: | |
| - download-previous-rolldown-binaries | |
| - build-windows-cli | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| # Windows runs against the prebuilt binaries from build-windows-cli, | |
| # so it needs no Rust toolchain or dev drive here. | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| if: runner.os != 'Windows' | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: cli-e2e-test-${{ matrix.target }} | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - name: Install docs dependencies | |
| run: pnpm -C docs install --frozen-lockfile | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| # Prebuilt by build-windows-cli (Linux + cargo-xwin); the artifact | |
| # paths are workspace-relative, so this places the binding into | |
| # packages/cli/binding/ and the CLI binaries into | |
| # target/x86_64-pc-windows-msvc/release/ where | |
| # `tool install-global-cli` finds them. | |
| - name: Download prebuilt Windows binaries | |
| if: runner.os == 'Windows' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-cli-binaries | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| skip-native: ${{ runner.os == 'Windows' }} | |
| - name: Ensure no unexpected file changes after build | |
| if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} | |
| run: | | |
| if [[ -n "$(git status --porcelain)" ]]; then | |
| echo "::error::Unexpected file changes detected after build" | |
| git status | |
| git diff | |
| exit 1 | |
| fi | |
| - name: Check TypeScript types | |
| if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} | |
| run: pnpm tsgo | |
| - name: Install Global CLI vp | |
| run: | | |
| pnpm bootstrap-cli:ci | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH | |
| else | |
| echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH | |
| fi | |
| - name: Verify vp installation | |
| run: | | |
| which vp | |
| vp --version | |
| vp -h | |
| - name: Run vp check | |
| run: vp check | |
| - name: Run unit tests | |
| run: RUST_BACKTRACE=1 pnpm test:unit | |
| env: | |
| RUST_MIN_STACK: 8388608 | |
| - name: Test global package install (powershell) | |
| if: ${{ matrix.os != 'namespace-profile-linux-x64-default' }} | |
| shell: pwsh | |
| run: | | |
| $vpHome = Join-Path $HOME ".vite-plus" | |
| $vpBin = Join-Path $vpHome "bin" | |
| . (Join-Path $vpHome 'env.ps1') | |
| Write-Host "PATH: $env:Path" | |
| Get-Command node | |
| Get-Command npm | |
| Get-Command npx | |
| Get-Command vp | |
| vp env doctor | |
| # Test 1: Install a JS-based CLI (typescript) | |
| vp install -g typescript | |
| tsc --version | |
| Get-Command tsc | |
| # Test 2: Verify the package was installed correctly | |
| Get-ChildItem $vpBin | |
| # Test 3: Uninstall | |
| vp uninstall -g typescript | |
| # Test 4: Verify uninstall removed shim | |
| Write-Host "Checking bin dir after uninstall:" | |
| Get-ChildItem $vpBin | |
| $shimPath = if ($IsWindows) { Join-Path $vpBin "tsc.cmd" } else { Join-Path $vpBin "tsc" } | |
| if (Test-Path $shimPath) { | |
| Write-Error "tsc shim file still exists at $shimPath" | |
| exit 1 | |
| } | |
| Write-Host "tsc shim removed successfully" | |
| # Test 5: use session | |
| vp env use 18 | |
| node --version | |
| vp env doctor | |
| vp env use --unset | |
| node --version | |
| - name: Test global package install (cmd) | |
| if: ${{ matrix.os == 'windows-latest' }} | |
| shell: cmd | |
| run: | | |
| echo "PATH: %PATH%" | |
| where.exe node | |
| where.exe npm | |
| where.exe npx | |
| where.exe vp | |
| vp env use 18 | |
| node --version | |
| vp env use --unset | |
| node --version | |
| vp env doctor | |
| REM Test 1: Install a JS-based CLI (typescript) | |
| vp install -g typescript | |
| tsc --version | |
| where.exe tsc | |
| REM Test 2: Verify the package was installed correctly | |
| dir "%USERPROFILE%\.vite-plus\packages\typescript\" | |
| dir "%USERPROFILE%\.vite-plus\bin\" | |
| REM Test 3: Uninstall | |
| vp uninstall -g typescript | |
| REM Test 4: Verify uninstall removed shim (.cmd wrapper) | |
| echo Checking bin dir after uninstall: | |
| dir "%USERPROFILE%\.vite-plus\bin\" | |
| if exist "%USERPROFILE%\.vite-plus\bin\tsc.cmd" ( | |
| echo Error: tsc.cmd shim file still exists | |
| exit /b 1 | |
| ) | |
| echo tsc.cmd shim removed successfully | |
| REM Test 5: Verify shell script was also removed (for Git Bash) | |
| if exist "%USERPROFILE%\.vite-plus\bin\tsc" ( | |
| echo Error: tsc shell script still exists | |
| exit /b 1 | |
| ) | |
| echo tsc shell script removed successfully | |
| REM Test 6: use session | |
| vp env use 18 | |
| node --version | |
| vp env doctor | |
| vp env use --unset | |
| node --version | |
| - name: Test global package install (bash) | |
| run: | | |
| echo "PATH: $PATH" | |
| ls -la ~/.vite-plus/ | |
| ls -la ~/.vite-plus/bin/ | |
| which node | |
| which npm | |
| which npx | |
| which vp | |
| vp env doctor | |
| # Test 1: Install a JS-based CLI (typescript) | |
| vp install -g typescript | |
| tsc --version | |
| which tsc | |
| # Test 2: Verify the package was installed correctly | |
| ls -la ~/.vite-plus/bin/ | |
| # Test 3: Uninstall | |
| vp uninstall -g typescript | |
| # Test 4: Verify uninstall removed shim | |
| echo "Checking bin dir after uninstall:" | |
| ls -la ~/.vite-plus/bin/ | |
| if [ -f ~/.vite-plus/bin/tsc ]; then | |
| echo "Error: tsc shim file still exists at ~/.vite-plus/bin/tsc" | |
| exit 1 | |
| fi | |
| echo "tsc shim removed successfully" | |
| # Test 5: use session | |
| vp env use 18 | |
| node --version | |
| vp env doctor | |
| vp env use --unset | |
| node --version | |
| # Upgrade tests (merged from separate job to avoid duplicate build) | |
| - name: Test upgrade (bash) | |
| shell: bash | |
| run: | | |
| # Resolve `current` (symlink on Unix, junction on Windows) and return | |
| # the install dir basename — "local-dev-<timestamp>" for the dev | |
| # build, "<version>" for a downloaded release. Node's realpathSync | |
| # handles symlinks and junctions uniformly. | |
| get_current_dirname() { | |
| node -p "require('path').basename(require('fs').realpathSync(require('path').resolve(process.env.USERPROFILE || process.env.HOME, '.vite-plus', 'current')))" | |
| } | |
| # Assert on the `current` target dir, not the version: right after a | |
| # release commit on main, the dev build's version equals npm latest, | |
| # so `vp upgrade --force` succeeds but the version is unchanged. | |
| # The dir flip (dev → release → dev) is the real signal that the | |
| # download/extract/swap and rollback flows ran end-to-end. | |
| INITIAL_DIR=$(get_current_dirname) | |
| echo "Initial install dir: $INITIAL_DIR" | |
| # --check queries npm registry and prints update status | |
| vp upgrade --check | |
| # full upgrade: download, extract, swap | |
| vp upgrade --force | |
| vp --version | |
| vp env doctor | |
| ls -la ~/.vite-plus/ | |
| UPDATED_DIR=$(get_current_dirname) | |
| echo "Updated install dir: $UPDATED_DIR" | |
| if [ "$UPDATED_DIR" == "$INITIAL_DIR" ]; then | |
| echo "Error: current install dir should have changed after upgrade (still $INITIAL_DIR)" | |
| exit 1 | |
| fi | |
| # rollback to the previous install | |
| vp upgrade --rollback | |
| vp --version | |
| vp env doctor | |
| ROLLBACK_DIR=$(get_current_dirname) | |
| echo "Rollback install dir: $ROLLBACK_DIR" | |
| if [ "$ROLLBACK_DIR" != "$INITIAL_DIR" ]; then | |
| echo "Error: current install dir should have been restored after rollback (expected $INITIAL_DIR, got $ROLLBACK_DIR)" | |
| exit 1 | |
| fi | |
| - name: Test upgrade (powershell) | |
| if: ${{ matrix.os != 'namespace-profile-linux-x64-default' }} | |
| shell: pwsh | |
| run: | | |
| $vpHome = Join-Path $HOME ".vite-plus" | |
| . (Join-Path $vpHome 'env.ps1') | |
| Get-ChildItem $vpHome | |
| # See bash block above for why we assert on the install dir basename. | |
| function Get-CurrentDirname { | |
| node -p "require('path').basename(require('fs').realpathSync(require('path').resolve(process.env.USERPROFILE || process.env.HOME, '.vite-plus', 'current')))" | |
| } | |
| $initialDir = Get-CurrentDirname | |
| Write-Host "Initial install dir: $initialDir" | |
| # --check queries npm registry and prints update status | |
| vp upgrade --check | |
| # full upgrade: download, extract, swap | |
| vp upgrade --force | |
| vp --version | |
| vp env doctor | |
| Get-ChildItem $vpHome | |
| $updatedDir = Get-CurrentDirname | |
| Write-Host "Updated install dir: $updatedDir" | |
| if ($updatedDir -eq $initialDir) { | |
| Write-Error "Error: current install dir should have changed after upgrade (still $initialDir)" | |
| exit 1 | |
| } | |
| # rollback to the previous install | |
| vp upgrade --rollback | |
| vp --version | |
| vp env doctor | |
| $rollbackDir = Get-CurrentDirname | |
| Write-Host "Rollback install dir: $rollbackDir" | |
| if ($rollbackDir -ne $initialDir) { | |
| Write-Error "Error: current install dir should have been restored after rollback (expected $initialDir, got $rollbackDir)" | |
| exit 1 | |
| } | |
| - name: Test upgrade (cmd) | |
| if: ${{ matrix.os == 'windows-latest' }} | |
| shell: cmd | |
| run: | | |
| REM See bash block above for why we assert on the install dir basename. | |
| for /f "usebackq delims=" %%v in (`node -p "require('path').basename(require('fs').realpathSync(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current')))"`) do set INITIAL_DIR=%%v | |
| echo Initial install dir: %INITIAL_DIR% | |
| REM --check queries npm registry and prints update status | |
| vp upgrade --check | |
| REM full upgrade: download, extract, swap | |
| vp upgrade --force | |
| vp --version | |
| vp env doctor | |
| dir "%USERPROFILE%\.vite-plus\" | |
| for /f "usebackq delims=" %%v in (`node -p "require('path').basename(require('fs').realpathSync(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current')))"`) do set UPDATED_DIR=%%v | |
| echo Updated install dir: %UPDATED_DIR% | |
| if "%UPDATED_DIR%"=="%INITIAL_DIR%" ( | |
| echo Error: current install dir should have changed after upgrade, still %INITIAL_DIR% | |
| exit /b 1 | |
| ) | |
| REM rollback to the previous install | |
| vp upgrade --rollback | |
| vp --version | |
| vp env doctor | |
| for /f "usebackq delims=" %%v in (`node -p "require('path').basename(require('fs').realpathSync(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current')))"`) do set ROLLBACK_DIR=%%v | |
| echo Rollback install dir: %ROLLBACK_DIR% | |
| if not "%ROLLBACK_DIR%"=="%INITIAL_DIR%" ( | |
| echo Error: current install dir should have been restored after rollback, expected %INITIAL_DIR%, got %ROLLBACK_DIR% | |
| exit /b 1 | |
| ) | |
| - name: Test same-version forced upgrade (Windows) | |
| if: ${{ matrix.os == 'windows-latest' }} | |
| shell: pwsh | |
| run: | | |
| # Force-reinstall the published `latest` version rather than the local | |
| # build's version: mid-release-cycle the build can be ahead of the | |
| # registry, and `vp upgrade <version>` must download <version> from npm. | |
| $version = "$(npm view vite-plus version)".Trim() | |
| if (-not $version) { | |
| Write-Error "Failed to resolve latest published vite-plus version" | |
| exit 1 | |
| } | |
| $realVpHome = Join-Path $HOME ".vite-plus" | |
| $currentVp = Join-Path $realVpHome "current/bin/vp.exe" | |
| $tempVpHome = Join-Path $env:RUNNER_TEMP "vp-same-version-force" | |
| if (Test-Path $tempVpHome) { | |
| Remove-Item $tempVpHome -Recurse -Force | |
| } | |
| $initialVersionDir = Join-Path $tempVpHome $version | |
| $initialBinDir = Join-Path $initialVersionDir "bin" | |
| New-Item -ItemType Directory -Path $initialBinDir | Out-Null | |
| Copy-Item $currentVp (Join-Path $initialBinDir "vp.exe") | |
| New-Item -ItemType Junction -Path (Join-Path $tempVpHome "current") -Target $initialVersionDir | Out-Null | |
| $env:VP_HOME = $tempVpHome | |
| & (Join-Path $tempVpHome "current/bin/vp.exe") upgrade $version --force | |
| $currentTarget = (Get-Item (Join-Path $tempVpHome "current")).Target | |
| if ($currentTarget -is [array]) { | |
| $currentTarget = $currentTarget[0] | |
| } | |
| $updatedDir = Split-Path -Leaf $currentTarget | |
| Write-Host "Updated install dir: $updatedDir" | |
| if (-not $updatedDir.StartsWith("$version+force.")) { | |
| Write-Error "Expected current to point at a forced reinstall dir for $version, got $updatedDir" | |
| exit 1 | |
| } | |
| if (-not (Test-Path (Join-Path $tempVpHome "$version/bin/vp.exe"))) { | |
| Write-Error "Original same-version install directory was unexpectedly removed" | |
| exit 1 | |
| } | |
| $previousVersion = Get-Content (Join-Path $tempVpHome ".previous-version") -Raw | |
| if ($previousVersion.Trim() -ne $version) { | |
| Write-Error "Expected .previous-version to be $version, got $previousVersion" | |
| exit 1 | |
| } | |
| - name: Test same-version forced upgrade (Unix) | |
| if: ${{ matrix.os != 'windows-latest' }} | |
| shell: bash | |
| run: | | |
| # Unix mirror of the Windows same-version forced-upgrade test above. | |
| # Overwriting the running binary in place does not fail on macOS or | |
| # modern Linux the way it does on Windows, but the fix still changes | |
| # the on-disk layout on every platform (current must repoint at a | |
| # `<version>+force.*` dir, the original version tree must survive, and | |
| # `.previous-version` must be recorded), so we assert that here too. | |
| # | |
| # Force-reinstall the published `latest` version rather than the local | |
| # build's version: mid-release-cycle the build can be ahead of the | |
| # registry, and `vp upgrade <version>` must download <version> from npm. | |
| version=$(npm view vite-plus version) | |
| if [ -z "$version" ]; then | |
| echo "Failed to resolve latest published vite-plus version" | |
| exit 1 | |
| fi | |
| real_vp_home="$HOME/.vite-plus" | |
| current_vp="$real_vp_home/current/bin/vp" | |
| temp_vp_home="${RUNNER_TEMP:?RUNNER_TEMP must be set}/vp-same-version-force" | |
| rm -rf "$temp_vp_home" | |
| initial_bin_dir="$temp_vp_home/$version/bin" | |
| mkdir -p "$initial_bin_dir" | |
| cp "$current_vp" "$initial_bin_dir/vp" | |
| chmod +x "$initial_bin_dir/vp" | |
| # Relative symlink, matching swap_current_link's `current -> <version>`. | |
| ln -s "$version" "$temp_vp_home/current" | |
| VP_HOME="$temp_vp_home" "$temp_vp_home/current/bin/vp" upgrade "$version" --force | |
| updated_dir=$(basename "$(readlink "$temp_vp_home/current")") | |
| echo "Updated install dir: $updated_dir" | |
| case "$updated_dir" in | |
| "$version+force."*) ;; | |
| *) | |
| echo "Expected current to point at a forced reinstall dir for $version, got $updated_dir" | |
| exit 1 | |
| ;; | |
| esac | |
| if [ ! -f "$temp_vp_home/$version/bin/vp" ]; then | |
| echo "Original same-version install directory was unexpectedly removed" | |
| exit 1 | |
| fi | |
| previous_version=$(tr -d '[:space:]' < "$temp_vp_home/.previous-version") | |
| if [ "$previous_version" != "$version" ]; then | |
| echo "Expected .previous-version to be $version, got $previous_version" | |
| exit 1 | |
| fi | |
| - name: Test implode (bash) | |
| shell: bash | |
| run: | | |
| # Retry on Windows — file locks can cause transient "Access is denied" errors | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| for i in 1 2 3; do | |
| vp implode --yes && break | |
| echo "Retry $i: vp implode failed, waiting 5s..." | |
| sleep 5 | |
| done | |
| else | |
| vp implode --yes | |
| fi | |
| ls -la ~/ | |
| VP_HOME="${USERPROFILE:-$HOME}/.vite-plus" | |
| if [ -d "$VP_HOME" ]; then | |
| echo "Error: $VP_HOME still exists after implode" | |
| exit 1 | |
| fi | |
| # Reinstall | |
| pnpm bootstrap-cli:ci | |
| vp --version | |
| - name: Test implode (powershell) | |
| if: ${{ matrix.os != 'namespace-profile-linux-x64-default' }} | |
| shell: pwsh | |
| run: | | |
| $vpHome = Join-Path $HOME ".vite-plus" | |
| . (Join-Path $vpHome 'env.ps1') | |
| if ($IsWindows) { | |
| # Retry — Windows file locks can cause transient "Access is denied" errors | |
| foreach ($i in 1..3) { | |
| vp implode --yes | |
| if ($LASTEXITCODE -eq 0) { break } | |
| Write-Host "Retry $i`: vp implode failed, waiting 5s..." | |
| Start-Sleep -Seconds 5 | |
| } | |
| Start-Sleep -Seconds 5 | |
| } else { | |
| vp implode --yes | |
| } | |
| Get-ChildItem $HOME | |
| if (Test-Path $vpHome) { | |
| Write-Error "$vpHome still exists after implode" | |
| exit 1 | |
| } | |
| pnpm bootstrap-cli:ci | |
| vp --version | |
| - name: Test implode (cmd) | |
| if: ${{ matrix.os == 'windows-latest' }} | |
| shell: cmd | |
| run: | | |
| REM Retry — Windows file locks can cause transient "Access is denied" errors | |
| REM vp.exe renames its own parent directory; cmd.exe may report | |
| REM "The system cannot find the path specified" on exit — ignore it. | |
| for /L %%i in (1,1,3) do ( | |
| vp implode --yes || ver >NUL | |
| if not exist "%USERPROFILE%\.vite-plus" goto implode_done | |
| echo Retry %%i: vp implode failed, waiting 5s... | |
| timeout /T 5 /NOBREAK >NUL | |
| ) | |
| :implode_done | |
| timeout /T 5 /NOBREAK >NUL | |
| dir "%USERPROFILE%\" | |
| if exist "%USERPROFILE%\.vite-plus" ( | |
| echo Error: .vite-plus still exists after implode | |
| exit /b 1 | |
| ) | |
| pnpm bootstrap-cli:ci | |
| vp --version | |
| cli-snap-test: | |
| name: CLI snap test (${{ matrix.target }}, ${{ matrix.shard }}/${{ matrix.shardTotal }}) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| - build-windows-cli | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| shard: 1 | |
| shardTotal: 3 | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| shard: 2 | |
| shardTotal: 3 | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| shard: 3 | |
| shardTotal: 3 | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| shard: 1 | |
| shardTotal: 3 | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| shard: 2 | |
| shardTotal: 3 | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| shard: 3 | |
| shardTotal: 3 | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| shard: 1 | |
| shardTotal: 3 | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| shard: 2 | |
| shardTotal: 3 | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| shard: 3 | |
| shardTotal: 3 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - name: Setup Dev Drive | |
| if: runner.os == 'Windows' | |
| uses: samypr100/setup-dev-drive@30f0f98ae5636b2b6501e181dfb3631b9974818d # v4.0.0 | |
| with: | |
| drive-size: 12GB | |
| drive-format: ReFS | |
| # Route TEMP/TMP onto the Dev Drive (ReFS) so `tmpdir()` from | |
| # `packages/tools/src/snap-test.ts` lives on a filesystem that | |
| # cleanly resolves pnpm's junction reparse points. NTFS-on-C: | |
| # preserves the junction-target backslashes during resolution, | |
| # which produces mixed `\` / `/` paths that break Node's ESM | |
| # subpath-import walk-up (`#module-sync-enabled` inside | |
| # `@voidzero-dev/vite-plus-core` → `ERR_PACKAGE_IMPORT_NOT_DEFINED` | |
| # during `vp fmt --write` in the | |
| # `new-create-vite-migrates-eslint-prettier` snap test). | |
| env-mapping: | | |
| TEMP,{{ DEV_DRIVE }}/Temp | |
| TMP,{{ DEV_DRIVE }}/Temp | |
| - name: Create TEMP/TMP on Dev Drive | |
| if: runner.os == 'Windows' | |
| # `setup-dev-drive` only mounts the drive; it doesn't create the | |
| # dir we point TEMP/TMP at. Anything that calls `os.tmpdir()` / | |
| # `lstat($TEMP)` before this dir exists fails (e.g. the bootstrap | |
| # CLI installer's `lstat 'E:\Temp'` ENOENT). | |
| shell: bash | |
| run: mkdir -p "$TEMP" "$TMP" | |
| # Windows runs against the prebuilt binaries from build-windows-cli, | |
| # so it needs no Rust toolchain here (the Dev Drive above stays for | |
| # the TEMP/TMP routing). | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| if: runner.os != 'Windows' | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: cli-snap-test-${{ matrix.target }} | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - name: Install docs dependencies | |
| run: pnpm -C docs install --frozen-lockfile | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| # See the cli-e2e-test job for details on the prebuilt binaries. | |
| - name: Download prebuilt Windows binaries | |
| if: runner.os == 'Windows' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-cli-binaries | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| skip-native: ${{ runner.os == 'Windows' }} | |
| - name: Install Global CLI vp | |
| run: | | |
| pnpm bootstrap-cli:ci | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH | |
| else | |
| echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH | |
| fi | |
| - name: Run CLI snapshot tests (shard ${{ matrix.shard }}/${{ matrix.shardTotal }}) | |
| run: | | |
| RUST_BACKTRACE=1 pnpm -F ./packages/cli snap-test-local --shard=${{ matrix.shard }}/${{ matrix.shardTotal }} | |
| RUST_BACKTRACE=1 pnpm -F ./packages/cli snap-test-global --shard=${{ matrix.shard }}/${{ matrix.shardTotal }} | |
| bash .github/scripts/retry-failed-snap-tests.sh | |
| env: | |
| RUST_MIN_STACK: 8388608 | |
| # Runs the PTY snapshot harness (crates/vite_cli_snapshots) with BOTH vp | |
| # flavors on Linux and macOS: the local flavor uses the packages/cli build | |
| # from this job, the global flavor reuses the installed release binary via | |
| # VP_SNAP_GLOBAL_VP (no vite_global_cli compile; build-upstream builds or | |
| # cache-restores it). One leg per OS: the suite is not sharded. Windows | |
| # runs in cli-snapshot-test-windows via the cross-compiled archive. | |
| cli-snapshot-test: | |
| name: CLI snapshot test (${{ matrix.target }}) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: cli-snapshot-test-${{ matrix.target }} | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| - name: Install Global CLI vp | |
| run: pnpm bootstrap-cli:ci | |
| # Provision the managed runtime once into the real home so cases can | |
| # seed from it (seed-runtime) instead of each downloading ~50MB. | |
| # Best-effort: without a seed, cases that need the runtime download it | |
| # themselves. | |
| - name: Prewarm managed runtime | |
| run: | | |
| "$HOME/.vite-plus/bin/vp" node --version \ | |
| || echo "prewarm failed; cases will download the runtime on demand" | |
| - name: Run PTY snapshot tests | |
| run: | | |
| VP_SNAP_GLOBAL_VP="$HOME/.vite-plus/bin/vp" \ | |
| VP_SNAP_JS_RUNTIME_DIR="$HOME/.vite-plus/js_runtime" \ | |
| cargo test -p vite_cli_snapshots | |
| env: | |
| RUST_BACKTRACE: '1' | |
| # Runs the PTY snapshot harness (crates/vite_cli_snapshots) on Windows with | |
| # BOTH vp flavors, without a Rust toolchain on the runner: the test binary | |
| # and vpt arrive cross-compiled in the nextest archive from | |
| # build-windows-tests, the global vp comes prebuilt from build-windows-cli, | |
| # and the JS CLI is built here (skip-native) for the local flavor. | |
| cli-snapshot-test-windows: | |
| name: CLI snapshot test (Windows) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| - build-windows-cli | |
| - build-windows-tests | |
| runs-on: windows-latest | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| # Same Dev Drive TEMP routing as cli-snap-test: staged fixture | |
| # workspaces live under os.tmpdir() and must resolve pnpm junction | |
| # reparse points cleanly (see that job for the full story). | |
| - name: Setup Dev Drive | |
| uses: samypr100/setup-dev-drive@30f0f98ae5636b2b6501e181dfb3631b9974818d # v4.0.0 | |
| with: | |
| drive-size: 12GB | |
| drive-format: ReFS | |
| env-mapping: | | |
| TEMP,{{ DEV_DRIVE }}/Temp | |
| TMP,{{ DEV_DRIVE }}/Temp | |
| - name: Create TEMP/TMP on Dev Drive | |
| shell: bash | |
| run: mkdir -p "$TEMP" "$TMP" | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| # See the cli-e2e-test job for details on the prebuilt binaries. | |
| - name: Download prebuilt Windows binaries | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-cli-binaries | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: x86_64-pc-windows-msvc | |
| skip-native: true | |
| - name: Install Global CLI vp | |
| shell: bash | |
| run: pnpm bootstrap-cli:ci | |
| # Provision the managed runtime once into the real home so cases can | |
| # seed from it (seed-runtime) instead of each downloading ~50MB. | |
| # Best-effort: without a seed, cases that need the runtime download it | |
| # themselves. | |
| - name: Prewarm managed runtime | |
| shell: bash | |
| run: | | |
| "$USERPROFILE/.vite-plus/bin/vp.exe" node --version \ | |
| || echo "prewarm failed; cases will download the runtime on demand" | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-snapshot-test-archive | |
| - uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10 | |
| with: | |
| tool: cargo-nextest | |
| # `cargo-nextest` is invoked directly so the job never depends on the | |
| # runner's Rust toolchain. --workspace-remap makes nextest rewrite | |
| # CARGO_MANIFEST_DIR and CARGO_BIN_EXE_vpt to this checkout, which is | |
| # how the relocated test binary finds fixtures and helpers. | |
| - name: Run PTY snapshot tests | |
| shell: bash | |
| run: | | |
| # current/bin, not bin: on Windows bin\vp.exe is the trampoline, | |
| # which re-execs %VP_HOME%\current\bin\vp.exe; the harness gives | |
| # each case an isolated VP_HOME with no install inside, so the | |
| # trampoline would fail. current\bin\vp.exe is the real CLI. | |
| export VP_SNAP_GLOBAL_VP="$USERPROFILE/.vite-plus/current/bin/vp.exe" | |
| export VP_SNAP_JS_RUNTIME_DIR="$USERPROFILE/.vite-plus/js_runtime" | |
| # --no-fail-fast: on a snapshot suite every diff is diagnostic | |
| # signal; cancelling on the first failure hides the rest. | |
| cargo-nextest nextest run --archive-file windows-snapshot-tests.tar.zst --workspace-remap . --no-fail-fast | |
| env: | |
| RUST_BACKTRACE: '1' | |
| # Keep Windows env parity with the `test` recipe in justfile. | |
| __COMPAT_LAYER: RunAsInvoker | |
| cli-e2e-test-musl: | |
| name: CLI E2E test (Linux x64 musl) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| runs-on: namespace-profile-linux-x64-default | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: cli-e2e-test-musl | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| # Cross-compile for musl on the glibc host | |
| - name: Build with upstream (musl) | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: x86_64-unknown-linux-musl | |
| # Run bootstrap-cli:ci and E2E tests inside an Alpine container where musl is native. | |
| # Can't run on the glibc host because NAPI .node files are musl-linked. | |
| - name: Run E2E in Alpine container | |
| run: | | |
| docker run --rm \ | |
| -e CI=true \ | |
| -e GITHUB_ACTIONS=true \ | |
| -v "${{ github.workspace }}:/workspace" \ | |
| -w /workspace \ | |
| node:22-alpine3.21 sh -c " | |
| apk add --no-cache bash curl ca-certificates git | |
| # Install pnpm and re-resolve optional dependencies for musl. | |
| # The host node_modules has glibc bindings; pnpm store holds both | |
| # but we need to re-link the musl variants. | |
| corepack enable | |
| pnpm install --frozen-lockfile --force | |
| # Download musl rolldown binding (host downloaded glibc variant) | |
| ROLLDOWN_VERSION=\$(node -p \"require('./rolldown/packages/rolldown/package.json').version\") | |
| npm pack \"@rolldown/binding-linux-x64-musl@\${ROLLDOWN_VERSION}\" | |
| tar -xzf \"rolldown-binding-linux-x64-musl-\${ROLLDOWN_VERSION}.tgz\" | |
| cp ./package/rolldown-binding.linux-x64-musl.node ./packages/core/dist/rolldown/shared/ | |
| cp ./package/rolldown-binding.linux-x64-musl.node ./rolldown/packages/rolldown/dist/shared/ | |
| rm -rf package *.tgz | |
| pnpm bootstrap-cli:ci | |
| export PATH=\"/root/.vite-plus/bin:\$PATH\" | |
| vp --version | |
| vp -h | |
| vp env doctor | |
| # Verify shims work | |
| which node | |
| which npm | |
| node --version | |
| # Test global package install | |
| vp install -g typescript | |
| tsc --version | |
| vp uninstall -g typescript | |
| git config --global --add safe.directory /workspace | |
| RUST_BACKTRACE=1 pnpm test | |
| bash .github/scripts/retry-failed-snap-tests.sh | |
| " | |
| install-e2e-test: | |
| name: Local CLI `vp install` E2E test | |
| needs: | |
| - download-previous-rolldown-binaries | |
| runs-on: namespace-profile-linux-x64-default | |
| # Run if: not a PR, OR PR has 'test: install-e2e' label | |
| if: >- | |
| github.event_name != 'pull_request' || | |
| contains(github.event.pull_request.labels.*.name, 'test: install-e2e') | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: install-e2e-test | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: x86_64-unknown-linux-gnu | |
| - name: Build CLI | |
| run: | | |
| pnpm bootstrap-cli:ci | |
| echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH | |
| - name: Run local CLI `vp install` | |
| run: | | |
| export PATH=$PWD/node_modules/.bin:$PATH | |
| vp -h | |
| # Test vp install on various repositories with different package managers | |
| repos=( | |
| # pnpm workspace | |
| "pnpm/pnpm:pnpm" | |
| "vitejs/vite:vite" | |
| # yarn workspace | |
| "napi-rs/napi-rs:napi-rs" | |
| "toeverything/AFFiNE:AFFiNE" | |
| # npm workspace | |
| "npm/cli:npm" | |
| "redhat-developer/vscode-extension-tester:vscode-extension-tester" | |
| ) | |
| for repo_info in "${repos[@]}"; do | |
| IFS=':' read -r repo dir_name <<< "$repo_info" | |
| echo "Testing vp install on $repo…" | |
| # remove the directory if it exists | |
| if [ -d "$RUNNER_TEMP/$dir_name" ]; then | |
| rm -rf "$RUNNER_TEMP/$dir_name" | |
| fi | |
| git clone --depth 1 "https://github.com/$repo.git" "$RUNNER_TEMP/$dir_name" | |
| cd "$RUNNER_TEMP/$dir_name" | |
| vp install --no-frozen-lockfile | |
| # run again to show install cache increase by time | |
| time vp install | |
| echo "✓ Successfully installed dependencies for $repo" | |
| echo "" | |
| done | |
| install-e2e-test-sfw: | |
| name: Local CLI `vp install` E2E test (Socket Firewall Free) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| - build-windows-cli | |
| # Run if: not a PR (push-to-main / workflow_dispatch), OR PR has 'test: sfw' label. | |
| # Heavy job (3 OSes × real registry traffic) — gated to avoid running on every PR. | |
| if: >- | |
| github.event_name != 'pull_request' || | |
| contains(github.event.pull_request.labels.*.name, 'test: sfw') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| sfw_asset: sfw-free-linux-x86_64 | |
| vp_bin: vp | |
| # Only Linux needs the TLS-bypass: the ubuntu-latest runner | |
| # doesn't preinstall Node 22.18 into vp's cache, so vp's HttpClient | |
| # (rustls) fetches `nodejs.org/.../SHASUMS256.txt` through sfw and | |
| # hits the upstream EKU bug. macOS/Windows runners ship Node in | |
| # vp's cache already, so vp never calls its HttpClient through sfw | |
| # in this test — leave the verification on there. | |
| vp_insecure_tls: '1' | |
| - os: namespace-profile-mac-default | |
| target: aarch64-apple-darwin | |
| sfw_asset: sfw-free-macos-arm64 | |
| vp_bin: vp | |
| vp_insecure_tls: '' | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| sfw_asset: sfw-free-windows-x86_64.exe | |
| # On Windows vp ships as `vp.exe`; sfw spawns its child process | |
| # directly without applying PATHEXT, so the bare `vp` lookup fails. | |
| vp_bin: vp.exe | |
| vp_insecure_tls: '' | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: ./.github/actions/clone | |
| # Windows runs against the prebuilt binaries from build-windows-cli, | |
| # so it needs no Rust toolchain or dev drive here. | |
| - uses: oxc-project/setup-rust@68c3199c5339f965e6e163924c3c450773eba42b # main (pending v1.0.17 — Swatinem/rust-cache v2.9.1 for node24) | |
| if: runner.os != 'Windows' | |
| with: | |
| save-cache: ${{ github.ref_name == 'main' }} | |
| cache-key: install-e2e-test-sfw-${{ matrix.os }} | |
| - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: rolldown-binaries | |
| path: ./rolldown/packages/rolldown/src | |
| merge-multiple: true | |
| # See the cli-e2e-test job for details on the prebuilt binaries. | |
| - name: Download prebuilt Windows binaries | |
| if: runner.os == 'Windows' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: windows-cli-binaries | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| skip-native: ${{ runner.os == 'Windows' }} | |
| - name: Build CLI | |
| run: | | |
| pnpm bootstrap-cli:ci | |
| # Mirror the per-OS path form used by the `test` job (ci.yml ~L266): | |
| # GITHUB_PATH on Windows needs a Windows-style path, otherwise child | |
| # processes spawned by tools like sfw (which use CreateProcess and | |
| # not bash's PATH munging) can't resolve `vp.exe`. | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH | |
| else | |
| echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH | |
| fi | |
| - name: Download sfw | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUNNER_TEMP/sfw-bin" | |
| # `--remove-on-error` (curl 7.83+) prevents leaving a zero-byte file | |
| # on failure so the next step's `sfw --version` fails clearly rather | |
| # than with "exec format error" on an empty binary. | |
| curl --fail --location --remove-on-error --retry 3 --retry-delay 2 \ | |
| --output "$RUNNER_TEMP/sfw-bin/sfw${{ runner.os == 'Windows' && '.exe' || '' }}" \ | |
| "https://github.com/SocketDev/sfw-free/releases/latest/download/${{ matrix.sfw_asset }}" | |
| if [[ "${{ runner.os }}" != "Windows" ]]; then | |
| chmod +x "$RUNNER_TEMP/sfw-bin/sfw" | |
| fi | |
| echo "$RUNNER_TEMP/sfw-bin" >> "$GITHUB_PATH" | |
| - name: Verify sfw on PATH | |
| run: sfw --version | |
| - name: Run `sfw vp install` against a real repo | |
| # TODO(SocketDev/sfw-free#30, SocketDev/sfw-free#43): drop `vp_insecure_tls` | |
| # from the Linux matrix entry once sfw ships the EKU fix. Verified | |
| # against sfw v1.11.0 (releases/latest as of 2026-05-28): on Linux, | |
| # vp's HTTPS request to nodejs.org through sfw still fails with | |
| # "invalid peer certificate: UnknownIssuer" because sfw's CA carries a | |
| # present-but-empty Extended Key Usage extension that rustls rejects. | |
| # macOS/Windows runners cache Node 22.18 in vp's directory, so vp | |
| # doesn't call its HttpClient through sfw — leaving TLS verification | |
| # enabled there gives us coverage that the bypass *isn't* used on | |
| # those platforms. | |
| env: | |
| VP_INSECURE_TLS: ${{ matrix.vp_insecure_tls }} | |
| run: | | |
| set -euo pipefail | |
| # Force the registry-fetch path: install a pinned pnpm globally so | |
| # vp downloads it (and therefore traverses sfw) rather than reusing | |
| # whatever's preinstalled on the runner. | |
| sfw "${{ matrix.vp_bin }}" i -g pnpm@9.15.0 | |
| # Then exercise `vp install` inside a real repo, also through sfw. | |
| git clone --depth 1 https://github.com/vitejs/vite.git "$RUNNER_TEMP/vite" | |
| cd "$RUNNER_TEMP/vite" | |
| sfw "${{ matrix.vp_bin }}" install --no-frozen-lockfile | |
| done: | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: | |
| - test | |
| - build-windows-tests | |
| - test-windows | |
| - build-windows-cli | |
| - test-musl | |
| - lint | |
| - cli-e2e-test | |
| - cli-e2e-test-musl | |
| - cli-snap-test | |
| - cli-snapshot-test | |
| - cli-snapshot-test-windows | |
| # Skipped on unlabeled PRs; counted on push-to-main and labeled PRs. | |
| # `contains(needs.*.result, 'failure')` ignores "skipped" results. | |
| - install-e2e-test-sfw | |
| steps: | |
| - run: exit 1 | |
| # Thank you, next https://github.com/vercel/next.js/blob/canary/.github/workflows/build_and_test.yml#L379 | |
| if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} |