From 4cc202c687a5be4183fea83f34d61474195f9526 Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Thu, 11 Dec 2025 19:28:48 +0200 Subject: [PATCH] ci: improve workflow configuration and reliability - Replace global read-all permissions with scoped permissions (contents: read, actions: write) - Fix cache configuration to exclude node_modules and include package-lock.json - Improve CI workflow resolution with flexible path matching and pagination - Verify version instead of committing version bumps from CI - Detect prereleases and publish with appropriate npm tags (next vs latest) - Use generic test suite description in release notes to avoid drift --- .github/workflows/release.yml | 75 ++++++++++++++++++----------------- .github/workflows/test.yml | 9 +++-- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cadf43c..811b343 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,36 +71,28 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | - // Find CI workflow by name or filename - const { data: allWorkflows } = await github.rest.actions.listRepoWorkflows({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - const ciWorkflow = allWorkflows.workflows.find(w => - w.name === 'CI' || - w.path === '.github/workflows/test.yml' - ); - - if (!ciWorkflow) { - core.setFailed('Could not find CI workflow'); - return; - } - - const { data: workflows } = await github.rest.actions.listWorkflowRuns({ + const wfList = await github.rest.actions.listRepoWorkflows({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: ciWorkflow.id, - head_sha: context.sha, - status: 'completed' }); - - const latestRun = workflows.workflow_runs[0]; - if (!latestRun || latestRun.conclusion !== 'success') { - core.setFailed(`CI workflow has not passed for this commit. Status: ${latestRun?.conclusion || 'not found'}`); + const wf = + wfList.data.workflows.find(w => w.path.endsWith('/test.yml')) || + wfList.data.workflows.find(w => (w.name || '').toLowerCase() === 'ci'); + if (!wf) core.setFailed('CI workflow not found (test.yml or CI).'); + const { data } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: wf.id, + head_sha: context.sha, + status: 'completed', + per_page: 1 + }); + const latestRun = data.workflow_runs?.[0]; + if (!latestRun) core.setFailed('No completed CI runs found for this commit.'); + if (latestRun.conclusion !== 'success') { + core.setFailed(`CI workflow conclusion: ${latestRun.conclusion}`); } - - console.log(`CI workflow ID: ${ciWorkflow.id}, Status: ${latestRun?.conclusion || 'not found'}`) + console.log(`CI status: ${latestRun.conclusion}`) security: name: 🔒 Security Scan @@ -197,22 +189,24 @@ jobs: - name: 🏗️ Build Parser run: npm run build - - name: 📋 Update Package Version + - name: 🔎 Verify package.json version matches input if: github.event_name == 'workflow_dispatch' run: | - VERSION="${{ github.event.inputs.version }}" - npm version ${VERSION} --no-git-tag-version - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add package.json package-lock.json - git commit -m "chore: bump version to ${VERSION}" || true + INPUT="${{ github.event.inputs.version }}" + EXPECTED="v${INPUT#v}" + PKG="v$(node -p "require('./package.json').version")" + if [ "$PKG" != "$EXPECTED" ]; then + echo "package.json version ($PKG) does not match requested release ($EXPECTED)." + echo "Bump package.json in a PR before running workflow_dispatch." + exit 1 + fi - name: 🏷️ Create Tag if: github.event_name == 'workflow_dispatch' run: | VERSION="v${{ github.event.inputs.version }}" - git tag ${VERSION} - git push origin ${VERSION} + git tag "${VERSION#v}" + git push origin "${VERSION#v}" - name: 📝 Generate Release Notes id: release_notes @@ -237,7 +231,7 @@ jobs: echo "" echo "- Initial release of tree-sitter-shellspec" echo "- Complete ShellSpec grammar support" - echo "- 59 comprehensive test cases" + echo "- Comprehensive test suite with broad coverage" echo "- Real-world compatibility with official ShellSpec examples" } >> release_notes.md fi @@ -262,7 +256,14 @@ jobs: generate_release_notes: false - name: 📦 Publish to npm - run: npm publish --access public + run: | + if [[ "${{ needs.validate.outputs.version }}" == *"-"* ]]; then + PUBLISH_TAG="next" + else + PUBLISH_TAG="latest" + fi + echo "Publishing with tag: $PUBLISH_TAG" + npm publish --access public --tag "$PUBLISH_TAG" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0bb30c9..a1e9561 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: read-all +permissions: + contents: read + actions: write jobs: test: @@ -89,9 +91,8 @@ jobs: id: cache-parser with: path: | - src/ - node_modules/ - key: ${{ runner.os }}-parser-${{ matrix.node-version }}-${{ hashFiles('src/parser.c', 'binding.gyp', 'package.json') }} + build/ + key: ${{ runner.os }}-parser-${{ matrix.node-version }}-${{ hashFiles('package-lock.json', 'src/parser.c', 'binding.gyp', 'src/**/*.cc', 'src/**/*.h') }} - name: Build Parser if: steps.cache-parser.outputs.cache-hit != 'true'