From bba9476474eea961b86c774b62f492d83792e0ec Mon Sep 17 00:00:00 2001 From: decaf-ts-admin Date: Wed, 2 Sep 2026 22:06:43 +0100 Subject: [PATCH] ci: adopt reusable-actions shared workflows - all shared workflows become thin callers of decaf-ts/reusable-actions/.github/workflows/* @master (codeql, jest-coverage, nodejs-build-prod, pages, publish-on-release, release-on-merge-pr, release-on-tag, snyk, trivy, renovate) - security scanning enforced at release time: snyk + trivy gates run inside release-on-tag and block the release (and npm publish) on HIGH/CRITICAL findings; standalone trivy keeps the weekly dep pass and manual dispatch; standalone snyk keeps manual dispatch - pages deploys exactly once per release (tag push) - prepare-it-tests script boots integration-test infra; called by every shared workflow that runs tests - repo-specific workflows stay local --- .github/workflows/codeql-analysis.yml | 63 ++----------------- .github/workflows/jest-coverage.yaml | 75 +++-------------------- .github/workflows/nodejs-build-prod.yaml | 45 ++------------ .github/workflows/pages.yaml | 54 ++-------------- .github/workflows/publish-on-release.yaml | 49 +++------------ .github/workflows/release-on-merge-pr.yml | 54 +++------------- .github/workflows/release-on-tag.yaml | 33 +++------- .github/workflows/renovate.yml | 38 +++++++----- .github/workflows/snyk-analysis.yaml | 29 +++------ .github/workflows/trivy-scan.yml | 74 +++++++++------------- package.json | 1 + 11 files changed, 108 insertions(+), 407 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f64fbce2..6a2dddf6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,23 +1,13 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL" +# Thin caller of decaf-ts/reusable-actions/.github/workflows/codeql-analysis.yml. + on: push: tags: - 'v?[0-9]+.[0-9]+.[0-9]+' - '!*-no-ci' pull_request: - # The branches below must be a subset of the branches above branches: [ master, main ] schedule: - cron: '41 0 * * 4' @@ -25,50 +15,5 @@ on: jobs: analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: decaf-ts/reusable-actions/.github/workflows/codeql-analysis.yml@master + secrets: inherit diff --git a/.github/workflows/jest-coverage.yaml b/.github/workflows/jest-coverage.yaml index d93f0cd9..af9ccafa 100644 --- a/.github/workflows/jest-coverage.yaml +++ b/.github/workflows/jest-coverage.yaml @@ -1,76 +1,17 @@ name: 'Test Coverage' +# Thin caller of decaf-ts/reusable-actions/.github/workflows/jest-coverage.yaml. +# Boots integration-test infra via 'npm run prepare-it-tests' before testing. + on: pull_request: - # The branches below must be a subset of the branches above branches: [ master, main ] workflow_dispatch: jobs: coverage: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [ 22 ] - if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - # Cache dependencies (npm) - - name: Cache dependencies - id: cache-npm - uses: actions/cache@v4 - with: - path: | - ~/.npm - node_modules - key: deps-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - deps-${{ runner.os }}-${{ matrix.node-version }}- - - # Install dependencies only if cache missed - - run: npm ci - - - name: Cache Build - id: cache-build - uses: actions/cache@v4 - with: - path: | - lib - dist - key: build-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - build-${{ runner.os }}-${{ matrix.node-version }}- - - - if: ${{ steps.cache-build.outputs.cache-hit != 'true' }} - run: npm run build:prod # Replace with your build command - - - run: npm run coverage - - name: Tests ✅ - if: ${{ success() }} - run: | - curl --request POST --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' --header 'content-type: application/json' --data '{ - "context": "tests", - "state": "success", - "description": "Tests passed", - "target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - }' - - name: Tests 🚨 - if: ${{ failure() }} - run: | - curl --request POST --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' --header 'content-type: application/json' --data '{ - "context": "tests", - "state": "failure", - "description": "Tests failed", - "target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - }' - - - uses: artiomtr/jest-coverage-report-action@v2.0-rc.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - threshold: 80 # optional parameter \ No newline at end of file + uses: decaf-ts/reusable-actions/.github/workflows/jest-coverage.yaml@master + with: + # mirrors the repo's own jest coverageThreshold (statements), with slack + coverage-threshold: 24 + secrets: inherit diff --git a/.github/workflows/nodejs-build-prod.yaml b/.github/workflows/nodejs-build-prod.yaml index 061e5d3f..33ad22b5 100644 --- a/.github/workflows/nodejs-build-prod.yaml +++ b/.github/workflows/nodejs-build-prod.yaml @@ -1,47 +1,12 @@ name: "Build & Test" +# Thin caller of decaf-ts/reusable-actions/.github/workflows/nodejs-build-prod.yaml. +# Boots integration-test infra via 'npm run prepare-it-tests' before testing. + on: workflow_dispatch: # on button click jobs: test: - strategy: - matrix: - node-version: [22] # Add your desired versions - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Cache dependencies - id: cache-npm - uses: actions/cache@v4 - with: - path: | - ~/.npm - node_modules - key: deps-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - deps-${{ runner.os }}-${{ matrix.node-version }}- - - # Install dependencies only - - run: npm ci - - - name: Cache Build - id: cache-build - uses: actions/cache@v4 - with: - path: | - lib - dist - key: build-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - build-${{ runner.os }}-${{ matrix.node-version }}- - - - if: ${{ steps.cache-build.outputs.cache-hit != 'true' }} - run: npm run build:prod # Replace with your build command - - - run: npm run test:all \ No newline at end of file + uses: decaf-ts/reusable-actions/.github/workflows/nodejs-build-prod.yaml@master + secrets: inherit diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 7f9aa734..c0dcd0a9 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -1,58 +1,16 @@ name: "Pages Builder" +# Docs deploy exactly once per release: every merge to master produces a +# release (release-on-merge-pr), whose tag push triggers this deployment. +# Thin caller of decaf-ts/reusable-actions/.github/workflows/pages.yaml. + on: workflow_dispatch: # on button click push: tags: - 'v?[0-9]+.[0-9]+.[0-9]+' - branches: [ master, main ] - paths: - - workdocs/** - - .github/workflows/pages.yaml jobs: - # Single deploy job no building deploy: - permissions: - contents: read - pages: write - id-token: write - strategy: - matrix: - node-version: [ 22 ] - environment: - name: github-pages - # url: ${{steps.deployment.outputs.page_url}} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - # Cache dependencies (npm) - - name: Cache dependencies - id: cache-npm - uses: actions/cache@v4 - with: - path: ~/.npm - key: deps-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - deps-${{ runner.os }}-${{ matrix.node-version }}- - - # Install dependencies only if cache missed - - run: npm ci - - run: npm run build:prod # Replace with your build command - - run: npm run coverage - - run: npm run docs - - name: Setup Pages - uses: actions/upload-pages-artifact@v3 - with: - name: docs - path: docs - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - with: - artifact_name: docs + uses: decaf-ts/reusable-actions/.github/workflows/pages.yaml@master + secrets: inherit diff --git a/.github/workflows/publish-on-release.yaml b/.github/workflows/publish-on-release.yaml index 633b7081..fb536857 100644 --- a/.github/workflows/publish-on-release.yaml +++ b/.github/workflows/publish-on-release.yaml @@ -1,5 +1,10 @@ name: "Publish on Release" +# Triggered by the GitHub Release created in release-on-tag.yaml. Thin caller of +# decaf-ts/reusable-actions/.github/workflows/publish-on-release.yaml +# (build, test, publish to npm). Boots integration-test infra via +# 'npm run prepare-it-tests' before testing. + on: release: types: @@ -8,45 +13,5 @@ on: jobs: build: - strategy: - matrix: - node-version: [ 22 ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org/ - # Cache dependencies (npm) - - name: Cache dependencies - id: cache-npm - uses: actions/cache@v4 - with: - path: | - ~/.npm - node_modules - key: deps-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - deps-${{ runner.os }}-${{ matrix.node-version }}- - - run: npm ci - - name: Cache Build - id: cache-build - uses: actions/cache@v4 - with: - path: | - lib - dist - key: build-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - build-${{ runner.os }}-${{ matrix.node-version }}- - - - if: ${{ steps.cache-build.outputs.cache-hit != 'true' }} - run: npm run build:prod # Replace with your build command - - run: npm run test:all - - run: npm publish --access public - env: - GITHUB_TOKEN: ${{ secrets.CONSECUTIVE_ACTION_TRIGGER }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + uses: decaf-ts/reusable-actions/.github/workflows/publish-on-release.yaml@master + secrets: inherit diff --git a/.github/workflows/release-on-merge-pr.yml b/.github/workflows/release-on-merge-pr.yml index f8d72bba..2b0fbe02 100644 --- a/.github/workflows/release-on-merge-pr.yml +++ b/.github/workflows/release-on-merge-pr.yml @@ -1,5 +1,11 @@ name: "Run on PR Merge to Master" +# Triggers the release cycle on any merged PR (including renovate security PRs): +# bump version/tag -> build -> test -> push tag +# The pushed tag then triggers release-on-tag (security gates + GitHub Release) +# and publish-on-release (npm publish). +# Thin caller of decaf-ts/reusable-actions/.github/workflows/release-on-merge-pr.yml. + on: pull_request: types: @@ -8,49 +14,5 @@ on: jobs: merge: if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - permissions: - actions: read - contents: write - security-events: write - strategy: - fail-fast: false - matrix: - node-version: [22.x] - language: ["javascript"] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_PAT }} - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org/ - - - name: Install dependencies - run: npm ci - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git remote set-url origin https://decaf-ts-admin:${{ secrets.GH_PAT }}@github.com/${{ github.repository }}.git - - - name: Bump TAG - run: npm version patch - - - name: Build - run: npm run build:prod - - - name: Test - run: npm run coverage - - - name: Commit changes - run: | - TAG=$(git describe --tags --exact-match || echo "no-tag") - git add . - git commit -m "Github Action automatic release: ${TAG_NAME}" || echo "No changes to commit" - git push origin HEAD:master --follow-tags \ No newline at end of file + uses: decaf-ts/reusable-actions/.github/workflows/release-on-merge-pr.yml@master + secrets: inherit diff --git a/.github/workflows/release-on-tag.yaml b/.github/workflows/release-on-tag.yaml index 664d8794..f0d69210 100644 --- a/.github/workflows/release-on-tag.yaml +++ b/.github/workflows/release-on-tag.yaml @@ -1,5 +1,10 @@ name: "Release on Tag" +# Triggered by the tag pushed from release-on-merge-pr.yml. Runs the snyk + +# trivy release gates, then creates a GitHub Release, which in turn triggers +# publish-on-release (npm publish). A failing gate blocks the release. +# Thin caller of decaf-ts/reusable-actions/.github/workflows/release-on-tag.yaml. + on: push: tags: @@ -10,30 +15,8 @@ on: jobs: deploy: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [ 22 ] + uses: decaf-ts/reusable-actions/.github/workflows/release-on-tag.yaml@master permissions: contents: write - env: - GITHUB_TOKEN: ${{ secrets.CONSECUTIVE_ACTION_TRIGGER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - name: Build Release - run: echo ${{ github.sha }} > Release.txt - - name: Test - run: cat Release.txt - - name: Release - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - Release.txt - README.md - LICENSE.md + security-events: write + secrets: inherit diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 5f543683..db168d70 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -1,20 +1,30 @@ -name: Renovate +name: "Renovate (on-demand)" + +# Thin caller of decaf-ts/reusable-actions/.github/workflows/renovate.yml. +# Triggers: +# - workflow_dispatch: manual / forced renovate run +# - repository_dispatch (renovate-trigger, renovate-dep-trigger): fired by the +# release gates / weekly trivy dep pass when vulns / dep updates are found. on: + workflow_dispatch: + inputs: + pr-strategy: + description: "overrides | bump-dependents | both" + required: false + default: "bump-dependents" + clear-stale-overrides: + description: "Remove package.json overrides that no longer match a known vuln (weekly dep run)." + required: false + default: false repository_dispatch: - types: [renovate-trigger] + types: [renovate-trigger, renovate-dep-trigger] jobs: renovate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Run Renovate - uses: renovatebot/github-action@v46.1.14 - with: - configurationFile: renovate.json - env: - RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} - RENOVATE_REPOSITORIES: ${{ github.repository}} + uses: decaf-ts/reusable-actions/.github/workflows/renovate.yml@master + with: + pr-strategy: ${{ inputs.pr-strategy || 'bump-dependents' }} + clear-stale-overrides: ${{ github.event.action == 'renovate-dep-trigger' }} + renovate-config-file: renovate.json + secrets: inherit diff --git a/.github/workflows/snyk-analysis.yaml b/.github/workflows/snyk-analysis.yaml index e3b2f517..00cafcd2 100644 --- a/.github/workflows/snyk-analysis.yaml +++ b/.github/workflows/snyk-analysis.yaml @@ -1,12 +1,11 @@ name: "Snyk Analysis" + +# Snyk enforcement happens at RELEASE time: the shared release-on-tag workflow +# runs the snyk release gate (severity-threshold=high) before creating the +# GitHub Release. This standalone workflow is kept for manual/on-demand scans. +# Thin caller of decaf-ts/reusable-actions/.github/workflows/snyk-analysis.yaml. + on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - '!*-alpha' - - '!*-no-ci' - pull_request: - branches: ["master"] workflow_dispatch: permissions: @@ -16,17 +15,5 @@ permissions: jobs: security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Run Snyk to check for vulnerabilities - uses: snyk/actions/node@master - continue-on-error: true # To make sure that SARIF upload gets called - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - args: --sarif-file-output=snyk.sarif - - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: snyk.sarif \ No newline at end of file + uses: decaf-ts/reusable-actions/.github/workflows/snyk-analysis.yaml@master + secrets: inherit diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 47f9f597..2b03fb4f 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -1,51 +1,35 @@ -name: Trivy Security Scan +name: "Trivy Security Scan" + +# Security scanning is enforced at RELEASE time: the shared release-on-tag +# workflow runs the trivy release gate before creating the GitHub Release. +# This standalone workflow is kept only for the weekly dependency-update pass +# (which drives the weekly renovate dep run) and manual diagnostics. +# Thin caller of decaf-ts/reusable-actions/.github/workflows/trivy-scan.yml. + on: workflow_dispatch: schedule: - - cron: '0 9 * * *' - push: - branches: [master] - pull_request: - branches: [master] + # Weekly dependency-update pass (Monday 02:00 UTC). + - cron: "0 2 * * 1" jobs: - trivy-scan: - name: Trivy Vulnerability Scan - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Generate Trivy Vulnerability Report - uses: aquasecurity/trivy-action@v0.36.0 - with: - scan-type: "fs" - output: trivy-report.json - format: json - scan-ref: . - exit-code: 0 - - - name: Upload Vulnerability Scan Results - uses: actions/upload-artifact@v4 - with: - name: trivy-report - path: trivy-report.json - retention-days: 30 - - - name: Check vulnerabilities - id: check - run: | - COUNT=$(jq '[.Results[]?.Vulnerabilities[]?] | length' trivy-report.json) - if [ "$COUNT" -gt 0 ]; then - echo "vulnerabilities=true" >> $GITHUB_OUTPUT - else - echo "vulnerabilities=false" >> $GITHUB_OUTPUT - fi + vuln: + name: Trivy vuln scan + if: github.event_name != 'schedule' + uses: decaf-ts/reusable-actions/.github/workflows/trivy-scan.yml@master + with: + scan-type: vuln + severity: HIGH,CRITICAL + ignore-unfixed: true + exit-code: "0" + upload-artifact: true + secrets: inherit - - name: Trigger Renovate - if: steps.check.outputs.vulnerabilities == 'true' - env: - GH_TOKEN: ${{ secrets.GH_PAT }} - run: | - gh api repos/${{ github.repository }}/dispatches \ - -f event_type=renovate-trigger + dep: + name: Trivy dep scan + if: github.event_name == 'schedule' + uses: decaf-ts/reusable-actions/.github/workflows/trivy-scan.yml@master + with: + scan-type: dep + upload-artifact: true + secrets: inherit diff --git a/package.json b/package.json index 5c37044f..42a9daf1 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "repo:setup": "codex exec \"$(cat ./.codex/prompts/repo-setup.md)\nbase_path is ./\"", "repo:doc": "codex exec \"$(cat ./.codex/prompts/doc.md) $(cat ./.codex/prompts/bulk-docs.md)\nbase_path is ./\"", "repo:tests": "codex exec \"$(cat ./.codex/prompts/bulk-tests.md)\nbase_path is ./ and coverage is 95%\" -s workspace-write", + "prepare-it-tests": "echo \"prepare-it-tests: no infra to boot\"", "repo:readme": "codex exec \"$(cat ./.codex/prompts/update-readme.md)\nbase_path is ./\"", "repo:pr": "npm run repo:doc && npm run repo:tests && npm run repo:readme", "sync-codex": "./bin/sync-codex.sh"