feat(cli): prompt for node mismatch reinstall #9031
Workflow file for this run
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-ignore: | |
| - '**/*.md' | |
| 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@9bc969adfd43bb790da3b64b543c78c75cef9689 # v0.0.9 | |
| 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: | |
| code-changed: ${{ steps.filter.outputs.code }} | |
| steps: | |
| - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 | |
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| id: filter | |
| with: | |
| filters: | | |
| code: | |
| - '!**/*.md' | |
| 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: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| 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 | |
| env-mapping: | | |
| CARGO_HOME,{{ DEV_DRIVE }}/.cargo | |
| RUSTUP_HOME,{{ DEV_DRIVE }}/.rustup | |
| - 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 | |
| target-dir: ${{ runner.os == 'Windows' && format('{0}/target', env.DEV_DRIVE) || '' }} | |
| - run: cargo check --all-targets --all-features | |
| env: | |
| RUSTFLAGS: '-D warnings --cfg tokio_unstable' # also update .cargo/config.toml | |
| # Test all crates/* packages. New crates are automatically included. | |
| # Also test vite-plus-cli (lives outside crates/) to catch type sync issues. | |
| - run: cargo test $(for d in crates/*/; do echo -n "-p $(basename $d) "; done) -p vite-plus-cli | |
| env: | |
| RUST_MIN_STACK: 8388608 | |
| 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 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 | |
| # Test all crates/* packages. New crates are automatically included. | |
| # Also test vite-plus-cli (lives outside crates/) to catch type sync issues. | |
| # Skip separate cargo check — cargo test already compiles everything. | |
| - run: cargo test $(for d in crates/*/; do echo -n "-p $(basename $d) "; done) -p vite-plus-cli | |
| env: | |
| RUST_MIN_STACK: 8388608 | |
| 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: cargo-shear | |
| components: clippy rust-docs rustfmt | |
| - run: | | |
| cargo shear | |
| cargo fmt --check | |
| cargo clippy --all-targets --all-features -- -D warnings | |
| # RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items | |
| - uses: crate-ci/typos@aca895bf05aec0cb7dffa6f94495e923224d9f17 # v1.46.2 | |
| with: | |
| files: . | |
| - uses: oxc-project/setup-node@ab97f03642370d79a7e96dd286bd02a1be40e0ba # v1.3.0 | |
| - 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 | |
| cli-e2e-test: | |
| name: CLI E2E test | |
| needs: | |
| - download-previous-rolldown-binaries | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: namespace-profile-linux-x64-default | |
| target: x86_64-unknown-linux-gnu | |
| - os: macos-latest | |
| 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 | |
| - name: Setup Dev Drive | |
| if: runner.os == 'Windows' | |
| uses: samypr100/setup-dev-drive@30f0f98ae5636b2b6501e181dfb3631b9974818d # v4.0.0 | |
| with: | |
| drive-size: 12GB | |
| drive-format: ReFS | |
| env-mapping: | | |
| CARGO_HOME,{{ DEV_DRIVE }}/.cargo | |
| RUSTUP_HOME,{{ DEV_DRIVE }}/.rustup | |
| - 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-${{ matrix.target }} | |
| target-dir: ${{ runner.os == 'Windows' && format('{0}/target', env.DEV_DRIVE) || '' }} | |
| - uses: oxc-project/setup-node@ab97f03642370d79a7e96dd286bd02a1be40e0ba # v1.3.0 | |
| - 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 | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| - 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 (Join-Path $vpHome "packages/typescript") | |
| 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/packages/typescript/ | |
| 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 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 | |
| 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: macos-latest | |
| target: aarch64-apple-darwin | |
| shard: 1 | |
| shardTotal: 3 | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| shard: 2 | |
| shardTotal: 3 | |
| - os: macos-latest | |
| 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: | | |
| CARGO_HOME,{{ DEV_DRIVE }}/.cargo | |
| RUSTUP_HOME,{{ DEV_DRIVE }}/.rustup | |
| 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" | |
| - 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-snap-test-${{ matrix.target }} | |
| target-dir: ${{ runner.os == 'Windows' && format('{0}/target', env.DEV_DRIVE) || '' }} | |
| - uses: oxc-project/setup-node@ab97f03642370d79a7e96dd286bd02a1be40e0ba # v1.3.0 | |
| - 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 | |
| - name: Build with upstream | |
| uses: ./.github/actions/build-upstream | |
| with: | |
| target: ${{ matrix.target }} | |
| - 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 | |
| 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@ab97f03642370d79a7e96dd286bd02a1be40e0ba # v1.3.0 | |
| - 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@ab97f03642370d79a7e96dd286bd02a1be40e0ba # v1.3.0 | |
| - 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 | |
| done: | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: | |
| - test | |
| - test-musl | |
| - lint | |
| - cli-e2e-test | |
| - cli-e2e-test-musl | |
| - cli-snap-test | |
| 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') }} |