chore: make unix just test runnable
#9529
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: 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 | |
| - 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 | |
| # Allow new clippy lints from the toolchain that fire in upstream | |
| # rolldown crates without a `[lints]` table. | |
| cargo clippy --all-targets --all-features -- -D warnings \ | |
| -A clippy::byte_char_slices \ | |
| -A clippy::manual_assert_eq \ | |
| -A clippy::needless_return_with_question_mark \ | |
| -A clippy::useless_borrows_in_formatting | |
| # RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items | |
| - uses: crate-ci/typos@f8a58b6b53f2279f71eb605f03a4ae4d10608f45 # v1.47.0 | |
| 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: 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 | |
| - 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 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 | |
| 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: | | |
| 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 | |
| install-e2e-test-sfw: | |
| name: Local CLI `vp install` E2E test (Socket Firewall Free) | |
| needs: | |
| - download-previous-rolldown-binaries | |
| # 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 | |
| - 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: install-e2e-test-sfw-${{ matrix.os }} | |
| target-dir: ${{ runner.os == 'Windows' && format('{0}/target', env.DEV_DRIVE) || '' }} | |
| - 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: ${{ matrix.target }} | |
| - 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 | |
| - test-musl | |
| - lint | |
| - cli-e2e-test | |
| - cli-e2e-test-musl | |
| - cli-snap-test | |
| # 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') }} |