diff --git a/.github/workflows/post-release-test.yml b/.github/workflows/post-release-test.yml new file mode 100644 index 0000000000..31b96ba26c --- /dev/null +++ b/.github/workflows/post-release-test.yml @@ -0,0 +1,469 @@ +name: Post-Release Test + +permissions: {} + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to test (e.g., 0.0.0-g12345678.20260228-1200)' + required: true + type: string + workflow_run: + workflows: ['Release'] + types: [completed] + +defaults: + run: + shell: bash + +jobs: + extract-version: + name: Extract release version + runs-on: ubuntu-latest + # For workflow_run: only run if the release succeeded + # For workflow_dispatch: always run (manual trigger with explicit version) + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + permissions: + contents: read + outputs: + version: ${{ steps.version.outputs.version }} + npm_tag: ${{ steps.version.outputs.npm_tag }} + steps: + - name: Resolve version + id: version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + MANUAL_VERSION: ${{ inputs.version }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + if [ -n "$MANUAL_VERSION" ]; then + # Manual trigger: use the provided version directly + echo "version=$MANUAL_VERSION" >> "$GITHUB_OUTPUT" + echo "npm_tag=test" >> "$GITHUB_OUTPUT" + echo "Using manually specified version: $MANUAL_VERSION" + else + # Automatic trigger: find the release by commit SHA + RELEASE_JSON=$(gh api repos/$GH_REPO/releases --jq ".[] | select(.target_commitish == \"$HEAD_SHA\") | {tag_name, prerelease}" | head -1) + if [ -z "$RELEASE_JSON" ]; then + echo "Error: No GitHub release found for commit $HEAD_SHA" + exit 1 + fi + + TAG_NAME=$(echo "$RELEASE_JSON" | jq -r '.tag_name') + PRERELEASE=$(echo "$RELEASE_JSON" | jq -r '.prerelease') + + # Strip 'v' prefix to get version + VERSION="${TAG_NAME#v}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + if [ "$PRERELEASE" = "true" ]; then + echo "npm_tag=test" >> "$GITHUB_OUTPUT" + else + echo "npm_tag=latest" >> "$GITHUB_OUTPUT" + fi + + echo "Found release: tag=$TAG_NAME version=$VERSION prerelease=$PRERELEASE" + fi + + wait-for-npm-propagation: + name: Wait for npm propagation + runs-on: ubuntu-latest + needs: extract-version + permissions: + contents: read + env: + VERSION: ${{ needs.extract-version.outputs.version }} + steps: + - name: Wait for all packages to be available on npm + run: | + PACKAGES=( + "vite-plus" + "@voidzero-dev/vite-plus-core" + "@voidzero-dev/vite-plus-test" + "@voidzero-dev/vite-plus-cli-darwin-arm64" + "@voidzero-dev/vite-plus-cli-darwin-x64" + "@voidzero-dev/vite-plus-cli-linux-x64-gnu" + "@voidzero-dev/vite-plus-cli-linux-arm64-gnu" + "@voidzero-dev/vite-plus-cli-win32-x64-msvc" + "@voidzero-dev/vite-plus-cli-win32-arm64-msvc" + ) + + MAX_ATTEMPTS=30 + INTERVAL=10 + + for pkg in "${PACKAGES[@]}"; do + echo "Waiting for $pkg@$VERSION on npm..." + for attempt in $(seq 1 $MAX_ATTEMPTS); do + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/$pkg/$VERSION") + if [ "$HTTP_STATUS" = "200" ]; then + echo " $pkg@$VERSION is available (attempt $attempt)" + break + fi + if [ "$attempt" = "$MAX_ATTEMPTS" ]; then + echo "Error: $pkg@$VERSION not found on npm after $MAX_ATTEMPTS attempts" + exit 1 + fi + echo " Attempt $attempt/$MAX_ATTEMPTS: HTTP $HTTP_STATUS, retrying in ${INTERVAL}s..." + sleep $INTERVAL + done + done + + echo "All packages are available on npm." + + verify-npm-packages: + name: Verify npm package metadata + runs-on: ubuntu-latest + needs: [extract-version, wait-for-npm-propagation] + permissions: + contents: read + env: + VERSION: ${{ needs.extract-version.outputs.version }} + steps: + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: 22 + registry-url: 'https://registry.npmjs.org' + + - name: Verify vite-plus package + run: | + echo "Verifying vite-plus@$VERSION..." + PKG_JSON=$(npm view "vite-plus@$VERSION" --json) + + # Verify version + ACTUAL_VERSION=$(echo "$PKG_JSON" | jq -r '.version') + if [ "$ACTUAL_VERSION" != "$VERSION" ]; then + echo "Error: Expected version $VERSION, got $ACTUAL_VERSION" + exit 1 + fi + echo " Version: $ACTUAL_VERSION ✓" + + # Verify bin.vp exists + VP_BIN=$(echo "$PKG_JSON" | jq -r '.bin.vp // empty') + if [ -z "$VP_BIN" ]; then + echo "Error: bin.vp not found in package.json" + exit 1 + fi + echo " bin.vp: $VP_BIN ✓" + + # Verify dependencies exist + DEPS=$(echo "$PKG_JSON" | jq -r '.dependencies | keys[]' 2>/dev/null || echo "") + echo " Dependencies: $DEPS" + + - name: Verify core and test packages + run: | + for pkg in "@voidzero-dev/vite-plus-core" "@voidzero-dev/vite-plus-test"; do + echo "Verifying $pkg@$VERSION..." + ACTUAL_VERSION=$(npm view "$pkg@$VERSION" version --json 2>/dev/null | jq -r '.' 2>/dev/null || npm view "$pkg@$VERSION" version) + if [ "$ACTUAL_VERSION" != "$VERSION" ]; then + echo "Error: $pkg expected version $VERSION, got $ACTUAL_VERSION" + exit 1 + fi + echo " $pkg@$VERSION ✓" + done + + - name: Verify CLI platform packages + run: | + PLATFORMS=( + "darwin-arm64" + "darwin-x64" + "linux-x64-gnu" + "linux-arm64-gnu" + "win32-x64-msvc" + "win32-arm64-msvc" + ) + for platform in "${PLATFORMS[@]}"; do + pkg="@voidzero-dev/vite-plus-cli-$platform" + echo "Verifying $pkg@$VERSION..." + ACTUAL_VERSION=$(npm view "$pkg@$VERSION" version --json 2>/dev/null | jq -r '.' 2>/dev/null || npm view "$pkg@$VERSION" version) + if [ "$ACTUAL_VERSION" != "$VERSION" ]; then + echo "Error: $pkg expected version $VERSION, got $ACTUAL_VERSION" + exit 1 + fi + echo " $pkg@$VERSION ✓" + done + + test-standalone-install-sh: + name: Test install.sh (${{ matrix.name }}) + runs-on: ${{ matrix.os }} + needs: [extract-version, wait-for-npm-propagation] + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + name: Linux x64 glibc + - os: macos-15-intel + name: macOS x64 + - os: namespace-profile-mac-default + name: macOS ARM64 + env: + VITE_PLUS_VERSION: ${{ needs.extract-version.outputs.version }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Run install.sh + run: cat packages/cli/install.sh | bash + + - name: Verify installation + working-directory: ${{ runner.temp }} + run: | + # Source shell config to get PATH updated + if [ -f ~/.zshenv ]; then + source ~/.zshenv + elif [ -f ~/.zshrc ]; then + source ~/.zshrc + elif [ -f ~/.bash_profile ]; then + source ~/.bash_profile + elif [ -f ~/.bashrc ]; then + source ~/.bashrc + else + export PATH="$HOME/.vite-plus/bin:$PATH" + fi + echo "PATH: $PATH" + + # Verify version + VP_VERSION=$(vp --version) + echo "vp --version: $VP_VERSION" + if [[ "$VP_VERSION" != *"$VITE_PLUS_VERSION"* ]]; then + echo "Error: Expected version to contain $VITE_PLUS_VERSION, got $VP_VERSION" + exit 1 + fi + + - name: Set PATH + run: | + echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH + + - name: Verify bin setup + run: | + BIN_PATH="$HOME/.vite-plus/bin" + ls -al "$BIN_PATH" + + # Verify shim executables exist + for shim in node npm npx; do + if [ ! -f "$BIN_PATH/$shim" ]; then + echo "Error: Shim not found: $BIN_PATH/$shim" + exit 1 + fi + echo "Found shim: $BIN_PATH/$shim" + done + + vp env doctor + + - name: Test create and build + working-directory: ${{ runner.temp }} + run: | + vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla + cd hello && vp run build + + - name: Test env install + run: | + vp env install 22 + node --version + + - name: Test global install + run: | + vp install -g typescript + tsc --version + vp uninstall -g typescript + + - name: Test dlx + run: | + vp dlx print-current-version + + - name: Test upgrade check + run: | + vp upgrade --check + + test-standalone-install-ps1: + name: Test install.ps1 (Windows x64) + runs-on: windows-latest + needs: [extract-version, wait-for-npm-propagation] + permissions: + contents: read + env: + VITE_PLUS_VERSION: ${{ needs.extract-version.outputs.version }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Run install.ps1 + shell: pwsh + run: | + & ./packages/cli/install.ps1 + + - name: Set PATH + shell: bash + run: | + echo "$USERPROFILE\.vite-plus\bin" >> $GITHUB_PATH + + - name: Verify installation + shell: pwsh + working-directory: ${{ runner.temp }} + run: | + Write-Host "PATH: $env:Path" + + # Verify version + $vpVersion = vp --version + Write-Host "vp --version: $vpVersion" + if ($vpVersion -notmatch [regex]::Escape($env:VITE_PLUS_VERSION)) { + Write-Error "Expected version to contain $env:VITE_PLUS_VERSION, got $vpVersion" + exit 1 + } + + - name: Verify bin setup + shell: pwsh + run: | + $binPath = "$env:USERPROFILE\.vite-plus\bin" + Get-ChildItem -Force $binPath + + $expectedShims = @("node.cmd", "npm.cmd", "npx.cmd") + foreach ($shim in $expectedShims) { + $shimFile = Join-Path $binPath $shim + if (-not (Test-Path $shimFile)) { + Write-Error "Shim not found: $shimFile" + exit 1 + } + Write-Host "Found shim: $shimFile" + } + + $env:Path = "$env:USERPROFILE\.vite-plus\bin;$env:Path" + vp env doctor + + - name: Test create and build + shell: pwsh + working-directory: ${{ runner.temp }} + run: | + vp create vite --no-interactive --no-agent -- hello --no-interactive -t vanilla + cd hello + vp run build + + - name: Test env install + shell: pwsh + run: | + vp env install 22 + node --version + + - name: Test global install + shell: pwsh + run: | + vp install -g typescript + tsc --version + vp uninstall -g typescript + + - name: Test dlx + shell: pwsh + run: | + vp dlx print-current-version + + - name: Test upgrade check + shell: pwsh + run: | + vp upgrade --check + + test-npm-install: + name: Test npm install (${{ matrix.name }}) + runs-on: ${{ matrix.os }} + needs: [extract-version, wait-for-npm-propagation] + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + name: Linux x64 + - os: windows-latest + name: Windows x64 + env: + VERSION: ${{ needs.extract-version.outputs.version }} + steps: + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: 22 + registry-url: 'https://registry.npmjs.org' + + - name: Install vite-plus from npm + working-directory: ${{ runner.temp }} + run: | + mkdir test-npm-install && cd test-npm-install + npm init -y + npm install "vite-plus@$VERSION" + + - name: Verify NAPI binding loads + working-directory: ${{ runner.temp }}/test-npm-install + run: | + node -e "require('vite-plus/binding'); console.log('NAPI binding loaded successfully')" + + - name: Verify npx vp works + working-directory: ${{ runner.temp }}/test-npm-install + run: | + VP_VERSION=$(npx vp --version) + echo "npx vp --version: $VP_VERSION" + npx vp --help + + notify-failure: + name: Notify on failure + runs-on: ubuntu-latest + needs: + - verify-npm-packages + - test-standalone-install-sh + - test-standalone-install-ps1 + - test-npm-install + if: failure() + permissions: + contents: read + issues: write + steps: + - name: Create or update GitHub issue on failure + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + ISSUE_TITLE="Post-Release Test Failed" + ISSUE_LABEL="post-release-failure" + RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + RELEASE_SHA="${{ github.event.workflow_run.head_sha }}" + + # Create label if it doesn't exist + if ! gh label list --json name --jq '.[].name' | grep -q "^${ISSUE_LABEL}$"; then + CREATE_LABEL_OUTPUT=$(gh label create "$ISSUE_LABEL" --color "d73a4a" --description "Post-release test failure" 2>&1) + if [ $? -eq 0 ]; then + echo "Created label: $ISSUE_LABEL" + elif echo "$CREATE_LABEL_OUTPUT" | grep -qi "already exists"; then + echo "Label '$ISSUE_LABEL' already exists, continuing." + else + echo "Error: Failed to create label '$ISSUE_LABEL':" + echo "$CREATE_LABEL_OUTPUT" >&2 + exit 1 + fi + fi + + # Search for existing open issue with the label + EXISTING_ISSUE=$(gh issue list --label "$ISSUE_LABEL" --state open --json number --jq '.[0].number') + + if [ -z "$EXISTING_ISSUE" ]; then + # Create new issue if none exists + gh issue create \ + --title "$ISSUE_TITLE" \ + --label "$ISSUE_LABEL" \ + --body "The post-release smoke test has failed for published npm packages. + + **Failed Run:** $RUN_URL + **Release Commit:** $RELEASE_SHA + **Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC') + + Please investigate whether the published packages are functional." + echo "Created new issue" + else + # Add comment to existing issue + gh issue comment "$EXISTING_ISSUE" \ + --body "The post-release smoke test has failed again. + + **Failed Run:** $RUN_URL + **Release Commit:** $RELEASE_SHA + **Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "Added comment to issue #$EXISTING_ISSUE" + fi