fix(ci): make npm scripts Windows-safe #148
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/CD Pipeline | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| jobs: | |
| release-notes: | |
| name: Release Notes | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Init required submodules | |
| run: | | |
| git submodule update --init --depth 1 external/vscode-test-playwright | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20.x | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Determine next version | |
| id: version | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const latestRelease = await github.rest.repos.getLatestRelease({ owner: context.repo.owner, repo: context.repo.repo }).catch(()=>null); | |
| let nextVersion; | |
| let prevTagRaw = ''; | |
| if (latestRelease) { | |
| prevTagRaw = latestRelease.data.tag_name || ''; | |
| const prev = prevTagRaw.replace(/^v/, ''); | |
| const parts = prev.split('.'); | |
| if (parts.length !== 3 || parts.some(p => isNaN(Number(p)))) { | |
| core.setFailed(`Latest release tag '${prevTagRaw}' is not semver x.y.z`); | |
| return; | |
| } | |
| parts[2] = String(Number(parts[2]) + 1); | |
| nextVersion = parts.join('.'); | |
| } else { | |
| nextVersion = '0.0.1'; | |
| } | |
| core.info(`Computed next version: ${nextVersion}`); | |
| core.setOutput('version', nextVersion); | |
| core.setOutput('previous_tag', prevTagRaw); | |
| - name: Generate Context | |
| id: context | |
| run: | | |
| PREV_TAG="${{ steps.version.outputs.previous_tag }}" | |
| if [ -n "$PREV_TAG" ]; then | |
| RANGE="$PREV_TAG..HEAD" | |
| echo "Generating context for range: $RANGE" | |
| else | |
| RANGE="" | |
| echo "Generating context for initial release (all history)" | |
| fi | |
| { | |
| echo 'commits<<EOF' | |
| git log --pretty=format:"- %s (%an) [%h]" $RANGE | |
| echo '' | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo 'stats<<EOF' | |
| if [ -n "$RANGE" ]; then | |
| git diff --stat $RANGE | |
| else | |
| git show --stat | |
| fi | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| - name: Generate Release Notes | |
| run: npm run release:notes | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| PREV_TAG: ${{ steps.version.outputs.previous_tag }} | |
| COMMITS: ${{ steps.context.outputs.commits }} | |
| STATS: ${{ steps.context.outputs.stats }} | |
| - name: Verify RELEASE_NOTES.md | |
| run: | | |
| test -s RELEASE_NOTES.md | |
| echo "--- RELEASE_NOTES.md (first 40 lines) ---" | |
| head -n 40 RELEASE_NOTES.md | |
| - name: Upload release notes artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-notes | |
| path: | | |
| RELEASE_NOTES.md | |
| build-and-test: | |
| name: Build and Test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| node-version: [20.x] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Init required submodules | |
| run: | | |
| git submodule update --init --depth 1 external/vscode-test-playwright | |
| - name: Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run lint | |
| run: npm run lint | |
| - name: Compile TypeScript | |
| run: npm run compile | |
| - name: Setup virtual display (Linux only) | |
| if: matrix.os == 'ubuntu-latest' | |
| run: sudo apt-get update && sudo apt-get install -y xvfb | |
| - name: Run tests | |
| shell: bash | |
| run: | | |
| if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then | |
| xvfb-run --auto-servernum --server-args='-screen 0 1280x1024x24' npm test | |
| else | |
| npm test | |
| fi | |
| env: | |
| CI: true | |
| auto-release: | |
| name: Auto Release | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test, release-notes] | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| env: | |
| USE_CODEX: 'false' # Set to 'true' to use OpenAI Codex instead of Claude | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Init required submodules | |
| run: | | |
| git submodule update --init --depth 1 external/vscode-test-playwright | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20.x | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install system dependencies for Playwright demo | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb ffmpeg | |
| - name: Determine next version | |
| id: version | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const latestRelease = await github.rest.repos.getLatestRelease({ owner: context.repo.owner, repo: context.repo.repo }).catch(()=>null); | |
| let nextVersion; | |
| let prevTagRaw = ''; | |
| if (latestRelease) { | |
| prevTagRaw = latestRelease.data.tag_name || ''; | |
| const prev = prevTagRaw.replace(/^v/, ''); | |
| const parts = prev.split('.'); | |
| if (parts.length !== 3 || parts.some(p => isNaN(Number(p)))) { | |
| core.setFailed(`Latest release tag '${prevTagRaw}' is not semver x.y.z`); | |
| return; | |
| } | |
| parts[2] = String(Number(parts[2]) + 1); | |
| nextVersion = parts.join('.'); | |
| } else { | |
| nextVersion = '0.0.1'; | |
| } | |
| core.info(`Computed next version: ${nextVersion}`); | |
| core.setOutput('version', nextVersion); | |
| core.setOutput('previous_tag', prevTagRaw); | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Generate Context | |
| id: context | |
| run: | | |
| PREV_TAG="${{ steps.version.outputs.previous_tag }}" | |
| if [ -n "$PREV_TAG" ]; then | |
| RANGE="$PREV_TAG..HEAD" | |
| echo "Generating context for range: $RANGE" | |
| else | |
| RANGE="" | |
| echo "Generating context for initial release (all history)" | |
| fi | |
| { | |
| echo 'commits<<EOF' | |
| git log --pretty=format:"- %s (%an) [%h]" $RANGE | |
| echo '' | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo 'stats<<EOF' | |
| if [ -n "$RANGE" ]; then | |
| git diff --stat $RANGE | |
| else | |
| git show --stat | |
| fi | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| - name: Generate Release Notes | |
| run: npm run release:notes | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| PREV_TAG: ${{ steps.version.outputs.previous_tag }} | |
| COMMITS: ${{ steps.context.outputs.commits }} | |
| STATS: ${{ steps.context.outputs.stats }} | |
| - name: Init demo video submodule | |
| run: | | |
| git submodule update --init --depth 1 external/playwright-test-videos | |
| - name: Update README demo video | |
| shell: bash | |
| run: | | |
| xvfb-run --auto-servernum --server-args='-screen 0 1280x1024x24' npm run demo:pw:update-readme | |
| - name: Commit and Push Changes | |
| run: | | |
| git add CHANGELOG.md README.md package.json docs/pw-videos/demo.mp4 | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| else | |
| git commit -m "docs: update for release ${{ steps.version.outputs.version }} [skip ci]" | |
| git push origin main | |
| fi | |
| - name: Create Tag | |
| run: | | |
| git tag ${{ steps.version.outputs.version }} | |
| git push origin ${{ steps.version.outputs.version }} | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ steps.version.outputs.version }} | |
| body_path: RELEASE_NOTES.md | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| package: | |
| name: Package Extension | |
| runs-on: ubuntu-latest | |
| needs: build-and-test | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Init required submodules | |
| run: | | |
| git submodule update --init --depth 1 external/vscode-test-playwright | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20.x | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install vsce | |
| run: npm install -g @vscode/vsce | |
| - name: Package extension | |
| run: vsce package | |
| - name: Upload VSIX artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: vsix-package | |
| path: '*.vsix' | |
| retention-days: 30 | |
| publish: | |
| name: Publish to VS Code Marketplace | |
| runs-on: ubuntu-latest | |
| needs: [package, auto-release] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Init required submodules | |
| run: | | |
| git submodule update --init --depth 1 external/vscode-test-playwright | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20.x | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install vsce | |
| run: npm install -g @vscode/vsce | |
| - name: Sync package.json version to tag (no commit) | |
| env: | |
| TAG_VERSION: ${{ needs.auto-release.outputs.version }} | |
| run: | | |
| node -e "const fs=require('fs'); const tag=process.env.TAG_VERSION; const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); if(pkg.version!==tag){ pkg.version=tag; fs.writeFileSync('package.json', JSON.stringify(pkg,null,2)+'\n'); console.log('Synced package.json version to tag', tag); } else { console.log('package.json version already matches tag', tag); }" | |
| - name: Publish to VS Code Marketplace | |
| env: | |
| VSCE_PAT: ${{ secrets.VSCE_PAT }} | |
| run: vsce publish -p $VSCE_PAT --allow-proposed-apis terminalDataWriteEvent | |
| - name: Download VSIX artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: vsix-package | |
| - name: Upload VSIX to GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: '*.vsix' | |
| tag_name: ${{ needs.auto-release.outputs.version }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |