diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5990d9c..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 409a1ef..293cd2f 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,11 +1,11 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages - name: Node.js Package (npm) on: release: types: [created] + push: + branches: + - working permissions: contents: write @@ -13,29 +13,7 @@ permissions: id-token: write jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Install dependencies - run: | - # Use public npm registry (package-lock.json is gitignored) - npm config set registry https://registry.npmjs.org/ - # Clean install to ensure platform-specific optional deps (rollup native bindings) are resolved correctly - # See: https://github.com/npm/cli/issues/4828 - rm -rf node_modules package-lock.json - npm install --force - timeout-minutes: 10 - - run: npm run lint - - run: npm run build - - run: npm run test - publish-npm: - needs: build runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -44,16 +22,44 @@ jobs: with: node-version: 24 registry-url: https://registry.npmjs.org/ - - name: Update npm - run: npm install -g npm@latest - - name: Install dependencies + - name: Determine npm tag and publish strategy + id: npm-tag run: | - # Use public npm registry (package-lock.json is gitignored) - npm config set registry https://registry.npmjs.org/ - # Clean install to ensure platform-specific optional deps (rollup native bindings) are resolved correctly - # See: https://github.com/npm/cli/issues/4828 - rm -rf node_modules package-lock.json - npm install --force - timeout-minutes: 10 - - run: npm publish --access public + VERSION=$(node -p "require('./package.json').version") + IS_RELEASE="${{ github.event_name == 'release' }}" + SHORT_SHA=$(git rev-parse --short=7 HEAD) + TIMESTAMP=$(date +%Y%m%d%H%M%S) + if [[ "$VERSION" == *"-"* ]]; then + echo "tag=dev" >> $GITHUB_OUTPUT + echo "should_publish=true" >> $GITHUB_OUTPUT + # Add timestamp and SHA to pre-release version (e.g., 1.5.5-dev.0 -> 1.5.5-dev.20260131210612.ab169e2) + BASE_VERSION="${VERSION%%-*}" + NEW_VERSION="${BASE_VERSION}-dev.${TIMESTAMP}.${SHORT_SHA}" + echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT + echo "📦 Publishing pre-release version: ${NEW_VERSION}" + else + echo "tag=latest" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> $GITHUB_OUTPUT + # Only publish production versions on release events + if [[ "$IS_RELEASE" == "true" ]]; then + echo "should_publish=true" >> $GITHUB_OUTPUT + echo "📦 Publishing production version: ${VERSION}" + else + echo "should_publish=false" >> $GITHUB_OUTPUT + echo "⚠️ Skipping publish: Production version detected on non-release push" + fi + fi + - run: npm install -g npm@latest + if: steps.npm-tag.outputs.should_publish == 'true' + - run: npm install --verbose --foreground-scripts + if: steps.npm-tag.outputs.should_publish == 'true' + timeout-minutes: 10 + - name: Update package.json version for pre-release + if: steps.npm-tag.outputs.should_publish == 'true' && steps.npm-tag.outputs.tag == 'dev' + run: | + node -e "const pkg = require('./package.json'); pkg.version = '${{ steps.npm-tag.outputs.version }}'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');" + echo "Updated package.json to version ${{ steps.npm-tag.outputs.version }}" + - name: Publish to npm + if: steps.npm-tag.outputs.should_publish == 'true' + run: npm publish --access public --tag ${{ steps.npm-tag.outputs.tag }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78f68ae..0211ea8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,10 @@ on: branches: - main +concurrency: + group: test-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + permissions: contents: write diff --git a/package.json b/package.json index d09098b..1436d55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grunnverk/github-tools", - "version": "1.5.4", + "version": "1.5.5", "description": "GitHub API utilities for automation - PR management, issue tracking, workflow monitoring", "main": "dist/index.js", "type": "module", @@ -45,7 +45,7 @@ "node": ">=24.0.0" }, "dependencies": { - "@grunnverk/git-tools": "^1.5.4", + "@grunnverk/git-tools": "^1.5.14", "@octokit/rest": "^22.0.0" }, "peerDependencies": { diff --git a/src/github.ts b/src/github.ts index 748f931..d6f0baf 100644 --- a/src/github.ts +++ b/src/github.ts @@ -642,9 +642,24 @@ export const waitForPullRequestChecks = async (prNumber: number, options: { time consecutiveNoChecksCount = 0; // ... rest of the while loop logic ... + // Filter for actual failures, excluding cancelled checks + // Cancelled checks are typically from workflows that cancel themselves (e.g., concurrency groups) + // and should not be treated as failures const failingChecks = checkRuns.filter( - (cr) => cr.conclusion && ['failure', 'timed_out', 'cancelled'].includes(cr.conclusion) + (cr) => cr.conclusion && ['failure', 'timed_out'].includes(cr.conclusion) ); + + // Track cancelled checks separately for informational purposes + const cancelledChecks = checkRuns.filter( + (cr) => cr.conclusion === 'cancelled' + ); + + if (cancelledChecks.length > 0) { + logger.info(`PR #${prNumber}: ${cancelledChecks.length} check${cancelledChecks.length > 1 ? 's' : ''} cancelled (not treated as failure):`); + for (const check of cancelledChecks) { + logger.info(` 🚫 ${check.name}: cancelled`); + } + } if (failingChecks.length > 0) { const { owner, repo } = await getRepoDetails(options.cwd); diff --git a/tests/github.test.ts b/tests/github.test.ts index ded640b..e12769d 100644 --- a/tests/github.test.ts +++ b/tests/github.test.ts @@ -604,16 +604,13 @@ describe('GitHub Utilities', () => { }, }); - // Mock check details for each failed check + // Mock check details for each failed check (only failure and timed_out, not cancelled) mockOctokit.checks.get .mockResolvedValueOnce({ data: { output: { title: 'Linting errors found', summary: 'Code style violations detected' } } }) .mockResolvedValueOnce({ data: { output: { title: 'Build timeout', summary: 'Build took too long to complete' } } - }) - .mockResolvedValueOnce({ - data: { output: { title: 'Tests cancelled', summary: 'Test run was cancelled' } } }); try { @@ -623,8 +620,9 @@ describe('GitHub Utilities', () => { const { PullRequestCheckError } = await import('../src/errors'); expect(error).toBeInstanceOf(PullRequestCheckError); expect(error.prNumber).toBe(123); - expect(error.failedChecks).toHaveLength(3); - expect(error.message).toContain('3 checks failed'); + // Only failure and timed_out are treated as failures, cancelled is not + expect(error.failedChecks).toHaveLength(2); + expect(error.message).toContain('2 checks failed'); const instructions = error.getRecoveryInstructions(); expect(instructions).toBeTruthy(); @@ -3958,7 +3956,8 @@ jobs: for (const conclusionType of testConclusionTypes) { mockOctokit.pulls.get.mockResolvedValue({ data: { head: { sha: 'test-sha' } } }); - if (['failure', 'cancelled', 'timed_out'].includes(conclusionType)) { + // Only failure and timed_out should cause failures, cancelled is not treated as failure + if (['failure', 'timed_out'].includes(conclusionType)) { // These should cause failures vi.spyOn(GitHub, 'getRepoDetails').mockResolvedValue({ owner: 'test-owner', repo: 'test-repo' }); mockRun.mockImplementation(async (command: string) => { @@ -3995,7 +3994,7 @@ jobs: expect(error.failedChecks[0].conclusion).toBe(conclusionType); } } else { - // These should pass + // These should pass (including 'cancelled') mockOctokit.checks.listForRef.mockResolvedValue({ data: { check_runs: [{ diff --git a/vitest.config.ts b/vitest.config.ts index fbfbbd3..6dd1571 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,12 +10,8 @@ export default defineConfig({ }, // Add pool configuration to prevent memory issues pool: 'forks', - poolOptions: { - forks: { - maxForks: 2, - minForks: 1 - } - }, + maxForks: 2, + minForks: 1, // Add test timeout and memory limits testTimeout: 30000, hookTimeout: 10000,