From 7d5602176cc75daef042d7c9e85d6f9c94588f9d Mon Sep 17 00:00:00 2001 From: Justus-at-Tazama Date: Wed, 8 Apr 2026 00:41:51 +0200 Subject: [PATCH 1/2] ci: sync workflows from tazama-lf/workflows Sync 17 canonical workflow files from tazama-lf/workflows. branch-target-check.yml was already up to date. Changes applied across all synced files: - Pin all action SHAs (checkout v6.0.2, setup-node v6.3.0, codeql v4.35.1, scorecard-action v2.4.3, upload-artifact v7.0.0, etc.) - gpg-verify.yml: fix inverted git log range, replace circular check-runs API call with commit verification endpoint, add github-actions[bot] exclusion, guard empty commit range - scorecard.yml: remove dev from push.branches trigger (ossf/scorecard-action enforces default branch only) - package-rule-rc.yml / package-rule.yml: pull latest reusable workflow logic - node.js.yml, conventional-commits.yml, dco-check.yml, etc.: pin SHAs publish.yml / release-train.yml synced with improvements only (scope and org references kept as @frmscoe): - Pin checkout and setup-node SHAs - publish.yml: use node -p instead of jq, add continue-on-error to Slack step, use curl --fail -sS - release-train.yml: add GH_USERNAME env var, use \ instead of inline secret reference (prevents injection) Signed-off-by: Justus-at-Tazama --- .github/workflows/codacy.yml | 6 +- .github/workflows/codeql.yml | 14 ++--- .github/workflows/conventional-commits.yml | 8 ++- .github/workflows/dco-check.yml | 9 ++- .github/workflows/dependency-review.yml | 4 +- .github/workflows/gpg-verify.yml | 15 +++-- .github/workflows/milestone.yml | 18 +----- .github/workflows/njsscan.yml | 8 ++- .github/workflows/node.js.yml | 12 ++-- .github/workflows/package-rule-rc.yml | 29 ++++++--- .github/workflows/package-rule.yml | 29 ++++++--- .github/workflows/publish.yml | 11 ++-- .github/workflows/release-train.yml | 7 ++- .github/workflows/release.yml | 70 ++++++++-------------- .github/workflows/sbom.yml | 4 +- .github/workflows/version-check.yml | 12 +++- 16 files changed, 132 insertions(+), 124 deletions(-) diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index 2de0966..0591e76 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -41,9 +41,7 @@ jobs: steps: # Checkout the repository to the GitHub Actions runner - name: Checkout code - uses: actions/checkout@v4 - - # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Codacy Analysis CLI uses: codacy/codacy-analysis-cli-action@562ee3e92b8e92df8b67e0a5ff8aa8e261919c08 # v4.4.7 with: @@ -61,6 +59,6 @@ jobs: # Upload the SARIF file generated in the previous step - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 with: sarif_file: results.sarif diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6212b29..7a5b0e9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,7 +29,7 @@ jobs: analyze: if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' name: Analyze - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + runs-on: ubuntu-latest permissions: actions: read contents: read @@ -46,11 +46,9 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -64,9 +62,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, 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. + uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. @@ -77,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index 6aee786..89506c4 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -16,16 +16,20 @@ on: pull_request: types: [opened, synchronize, reopened, edited] +permissions: + contents: read + pull-requests: write + jobs: validate-pr-title: if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' runs-on: ubuntu-latest # Use the latest Ubuntu runner for the job steps: - name: Checkout code - uses: actions/checkout@v4 # Checkout the repository code using the actions/checkout action + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: PR Conventional Commit Validation - uses: ytanikin/PRConventionalCommits@1.1.0 # Use the PRConventionalCommits action to validate PR titles + uses: ytanikin/PRConventionalCommits@b7be9213c4fa33260646db6c9b905332dc90b310 # 1.1.0 with: # Define the task types that are valid for conventional commits task_types: '["build","ci","docs","feat","fix","perf","refactor","style","test","feat!"]' diff --git a/.github/workflows/dco-check.yml b/.github/workflows/dco-check.yml index 45d9c26..c5c04ff 100644 --- a/.github/workflows/dco-check.yml +++ b/.github/workflows/dco-check.yml @@ -11,13 +11,16 @@ on: [pull_request] jobs: dco: - if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' + if: | + github.actor != 'dependabot[bot]' && + github.actor != 'dependabot-preview[bot]' && + github.actor != 'github-actions[bot]' # Define the runner environment runs-on: ubuntu-latest steps: # Step to check out the repository - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Fetch all history for all branches to ensure complete commit history is available @@ -43,7 +46,7 @@ jobs: # Loop through each commit and check for the Signed-off-by line for commit in $commits; do # Check if the commit message contains the Signed-off-by line - if ! git show --quiet --format=%B $commit | grep -q "^Signed-off-by: "; then + if ! git show --quiet --format=%B "$commit" | grep -q "^Signed-off-by: "; then # If not, add the commit hash to the list of non-compliant commits non_compliant_commits="$non_compliant_commits $commit" fi diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 577adb6..5b1f706 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -21,6 +21,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/gpg-verify.yml b/.github/workflows/gpg-verify.yml index 30f34d3..79e78d2 100644 --- a/.github/workflows/gpg-verify.yml +++ b/.github/workflows/gpg-verify.yml @@ -16,7 +16,7 @@ jobs: github.actor != 'github-actions[bot]' runs-on: ubuntu-latest # Use the latest Ubuntu runner for the job steps: - - uses: actions/checkout@v4 # Checkout the repository code using the actions/checkout action + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Fetch all history for all branches to ensure we have the full commit history @@ -44,11 +44,18 @@ jobs: # Check the GPG verification status of each commit via the GitHub commit API for commit in $commits; do - verified=$(curl -s \ + response=$(curl -s --max-time 10 --fail \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ - https://api.github.com/repos/$GITHUB_REPOSITORY/commits/$commit \ - | jq -r '.commit.verification.verified') + https://api.github.com/repos/$GITHUB_REPOSITORY/commits/$commit) + curl_exit=$? + + if [[ $curl_exit -ne 0 ]]; then + echo "GitHub API request failed for commit $commit (curl exit $curl_exit). Check network or API availability." + exit 1 + fi + + verified=$(echo "$response" | jq -r '.commit.verification.verified') # If the commit is not verified, list it and exit with a non-zero status if [[ "$verified" != "true" ]]; then diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml index 269a26c..04dea2c 100644 --- a/.github/workflows/milestone.yml +++ b/.github/workflows/milestone.yml @@ -21,25 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - # Step to check out the repository code. - - name: Checkout Repository - uses: actions/checkout@v4 - - # Step to set up environment variables required for the script. - - name: Set up environment variables - run: | - # Set the GitHub token for authentication. - echo "ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV - # Set the milestone number based on the workflow input. - echo "MILESTONE_NUMBER=${{ github.event.inputs.milestoneId }}" >> $GITHUB_ENV - # Set the base API URL for GitHub. - echo "API_URL=https://api.github.com" >> $GITHUB_ENV - # Step to close the specified milestone using GitHub API. - name: Close Milestone run: | # Use the environment variables set up earlier to make the API call. - curl -X PATCH \ + curl --fail -s -X PATCH \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -d '{"state": "closed"}' \ @@ -51,7 +37,7 @@ jobs: # Step to trigger another workflow for releasing, passing the milestone number. - name: Trigger Release Workflow - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@ce5485de42c9b2622d2ed064be479e8ed65e76f4 # v1 with: token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.repository }} diff --git a/.github/workflows/njsscan.yml b/.github/workflows/njsscan.yml index d1bddf2..8c6584b 100644 --- a/.github/workflows/njsscan.yml +++ b/.github/workflows/njsscan.yml @@ -35,13 +35,15 @@ jobs: name: njsscan code scanning steps: - name: Checkout the code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: nodejsscan scan id: njsscan + continue-on-error: true uses: ajinabraham/njsscan-action@d58d8b2f26322cd35a9efb8003baac517f226d81 with: - args: '. --sarif --output results.sarif || true' + args: '. --sarif --output results.sarif' - name: Upload njsscan report - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3 + if: hashFiles('results.sarif') != '' with: sarif_file: results.sarif diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 1c8d182..b6fe740 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -28,9 +28,9 @@ jobs: matrix: node-version: [20] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -50,9 +50,9 @@ jobs: node-version: [20] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -72,9 +72,9 @@ jobs: node-version: [20] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} cache: 'npm' diff --git a/.github/workflows/package-rule-rc.yml b/.github/workflows/package-rule-rc.yml index 18b82cf..07234a3 100644 --- a/.github/workflows/package-rule-rc.yml +++ b/.github/workflows/package-rule-rc.yml @@ -55,10 +55,10 @@ jobs: steps: - name: Checkout rule repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20' @@ -80,8 +80,11 @@ jobs: - name: Clone Rule Executer repository (dev branch) run: | + git config --global url."https://x-access-token:${GH_TOKEN_LIB}@github.com/".insteadOf "https://github.com/" git clone https://github.com/tazama-lf/rule-executer -b dev rule-executer echo "Rule Executer clone complete." + env: + GH_TOKEN_LIB: ${{ secrets.GH_TOKEN_LIB }} - name: Prepare rule-executer-${{ inputs.rule_number }} run: | @@ -101,14 +104,22 @@ jobs: # "rule": "npm:@tazama-lf/rule-901@X.Y.Z" # → for tazama-lf: keep @tazama-lf, change rule number and version # → for frmscoe: switch namespace to @frmscoe, change rule number and version - if [ "$RULE_ORG" = "frmscoe" ]; then - sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@frmscoe/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" - else - sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@tazama-lf/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" - fi + case "$RULE_ORG" in + frmscoe) + EXPECTED_SCOPE="@frmscoe" + sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@frmscoe/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" + ;; + tazama-lf) + EXPECTED_SCOPE="@tazama-lf" + sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@tazama-lf/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" + ;; + *) + echo "::error::Unknown RULE_ORG value: '${RULE_ORG}'. Expected 'frmscoe' or 'tazama-lf'." + exit 1 + ;; + esac # Validate rule dependency rewrite succeeded — fail if pattern didn't match - EXPECTED_SCOPE=$( [ "$RULE_ORG" = "frmscoe" ] && echo "@frmscoe" || echo "@tazama-lf" ) if ! grep -q "npm:${EXPECTED_SCOPE}/rule-${RULE_NUM}@${VERSION}" "${RULE_DIR}/package.json"; then echo "❌ Failed to update rule dependency in package.json — pattern may have changed" echo " Expected: npm:${EXPECTED_SCOPE}/rule-${RULE_NUM}@${VERSION}" @@ -138,7 +149,7 @@ jobs: - name: Install dependencies run: | cd "rule-executer-${{ inputs.rule_number }}" - npm ci + npm install env: GH_TOKEN: ${{ secrets.GH_TOKEN_LIB }} diff --git a/.github/workflows/package-rule.yml b/.github/workflows/package-rule.yml index fdc0a5c..344e798 100644 --- a/.github/workflows/package-rule.yml +++ b/.github/workflows/package-rule.yml @@ -58,10 +58,10 @@ jobs: steps: - name: Checkout rule repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20' @@ -83,8 +83,11 @@ jobs: - name: Clone Rule Executer repository (main branch) run: | + git config --global url."https://x-access-token:${GH_TOKEN_LIB}@github.com/".insteadOf "https://github.com/" git clone https://github.com/tazama-lf/rule-executer -b main rule-executer echo "Rule Executer clone complete." + env: + GH_TOKEN_LIB: ${{ secrets.GH_TOKEN_LIB }} - name: Prepare rule-executer-${{ inputs.rule_number }} run: | @@ -101,14 +104,22 @@ jobs: echo "Applying substitutions for rule-${RULE_NUM} (org: ${RULE_ORG}, version: ${VERSION})" # Update rule dependency in package.json - if [ "$RULE_ORG" = "frmscoe" ]; then - sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@frmscoe/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" - else - sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@tazama-lf/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" - fi + case "$RULE_ORG" in + frmscoe) + EXPECTED_SCOPE="@frmscoe" + sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@frmscoe/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" + ;; + tazama-lf) + EXPECTED_SCOPE="@tazama-lf" + sed -i "s|npm:@tazama-lf/rule-[^@]*@[^\"]*|npm:@tazama-lf/rule-${RULE_NUM}@${VERSION}|g" "${RULE_DIR}/package.json" + ;; + *) + echo "::error::Unknown RULE_ORG value: '${RULE_ORG}'. Expected 'frmscoe' or 'tazama-lf'." + exit 1 + ;; + esac # Validate rule dependency rewrite succeeded — fail if pattern didn't match - EXPECTED_SCOPE=$( [ "$RULE_ORG" = "frmscoe" ] && echo "@frmscoe" || echo "@tazama-lf" ) if ! grep -q "npm:${EXPECTED_SCOPE}/rule-${RULE_NUM}@${VERSION}" "${RULE_DIR}/package.json"; then echo "❌ Failed to update rule dependency in package.json — pattern may have changed" echo " Expected: npm:${EXPECTED_SCOPE}/rule-${RULE_NUM}@${VERSION}" @@ -138,7 +149,7 @@ jobs: - name: Install dependencies run: | cd "rule-executer-${{ inputs.rule_number }}" - npm ci + npm install env: GH_TOKEN: ${{ secrets.GH_TOKEN_LIB }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f4254c5..29c2233 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,12 +30,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ secrets.GH_TOKEN_LIB }} - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20' registry-url: 'https://npm.pkg.github.com/' @@ -54,7 +54,7 @@ jobs: - name: Publish package run: | - VERSION=$(jq -r '.version' package.json) + VERSION=$(node -p "require('./package.json').version") if [[ "$VERSION" == *-* ]]; then # Prerelease version (e.g. 1.2.3-rc.1): publish under the 'rc' dist-tag # so it does not become the default 'latest' install target. @@ -67,10 +67,11 @@ jobs: # Send Slack notification — requires SLACK_WEBHOOK_URL org/repo secret - name: Send Slack notification + continue-on-error: true env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} run: | - curl -s -X POST -H 'Content-type: application/json' --data '{ + curl --fail -sS -X POST -H 'Content-type: application/json' --data '{ "blocks": [ { "type": "header", @@ -94,4 +95,4 @@ jobs: ] } ] - }' "$SLACK_WEBHOOK_URL" || true + }' "$SLACK_WEBHOOK_URL" diff --git a/.github/workflows/release-train.yml b/.github/workflows/release-train.yml index 28c3403..d8128b7 100644 --- a/.github/workflows/release-train.yml +++ b/.github/workflows/release-train.yml @@ -60,13 +60,13 @@ jobs: echo "✅ Version input '$VERSION' is valid." - name: Checkout dev branch - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: dev token: ${{ secrets.GH_TOKEN_LIB }} - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20' registry-url: 'https://npm.pkg.github.com/' @@ -237,6 +237,7 @@ jobs: - name: Open PR to main env: GH_TOKEN: ${{ secrets.GH_TOKEN_LIB }} + GH_USERNAME: ${{ secrets.GH_USERNAME }} run: | VERSION="$VERSION_INPUT" BRANCH="${{ steps.push_branch.outputs.branch }}" @@ -250,7 +251,7 @@ jobs: --body-file /tmp/pr-body.md \ --base main \ --head "$BRANCH" \ - --reviewer "${{ secrets.GH_USERNAME }}"; then + --reviewer "$GH_USERNAME"; then EXISTING=$(gh pr list --head "$BRANCH" --base main --json number --jq '.[0].number // empty') if [ -n "$EXISTING" ]; then echo "PR #${EXISTING} already exists for $BRANCH — skipping." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e79256..571015e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,9 +7,6 @@ name: Release Workflow on: repository_dispatch: types: [release] - properties: - milestone_number: - type: string jobs: release: @@ -18,38 +15,20 @@ jobs: steps: # Checkout the main branch with all history - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 (upgraded from v2) with: ref: main fetch-depth: 0 # Fetch all tags - # Fetch merged pull request and determine release labels - - uses: actions-ecosystem/action-get-merged-pull-request@v1 - id: get-merged-pull-request - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions-ecosystem/action-release-label@v1 - id: release-label - if: ${{ steps.get-merged-pull-request.outputs.title != null }} - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - # Get the latest tag in the repository - - uses: actions-ecosystem/action-get-latest-tag@v1 - id: get-latest-tag - if: ${{ steps.release-label.outputs.level != null }} - with: - semver_only: true - # Determine the release type (major, minor, patch) based on commit messages - name: Determine Release Type id: determine_release run: | - PREV_VERSION=$(git describe --abbrev=0 --tags) - echo "Previous Version: $PREV_VERSION" - - COMMIT_MESSAGES=$(git log $PREV_VERSION^..HEAD --format=%B) + PREV_VERSION=$(git describe --abbrev=0 --tags 2>/dev/null || echo "") + echo "Previous Version: ${PREV_VERSION:-none (first release)}" + GIT_LOG_RANGE="${PREV_VERSION:+${PREV_VERSION}..}HEAD" + + COMMIT_MESSAGES=$(git log $GIT_LOG_RANGE --format=%B) echo "Commit Messages: $COMMIT_MESSAGES" # Determine release type based on commit messages and labels @@ -61,8 +40,6 @@ jobs: RELEASE_TYPE="major" elif echo "$COMMIT_MESSAGES" | grep -q -e "feat:"; then RELEASE_TYPE="minor" - elif echo "$COMMIT_MESSAGES" | grep -q -e "feat:" && (echo "$COMMIT_MESSAGES" | grep -q -e "fix:" || echo "$COMMIT_MESSAGES" | grep -q -e "enhancement:" || echo "$COMMIT_MESSAGES" | grep -q -e "docs:" || echo "$COMMIT_MESSAGES" | grep -q -e "refactor:" || echo "$COMMIT_MESSAGES" | grep -q -e "chore:"); then - RELEASE_TYPE="minor" elif echo "$COMMIT_MESSAGES" | grep -q -e "fix:" -e "enhancement:" -e "docs:" -e "refactor:" -e "chore:" -e "build:" -e "ci:" -e "perf:" -e "style:" -e "test:" -e "chore(deps):" -e "chore(deps-dev):"; then RELEASE_TYPE="patch" fi @@ -74,7 +51,7 @@ jobs: - name: Bump Version id: bump_version run: | - PREV_VERSION=$(git describe --abbrev=0 --tags) + PREV_VERSION=$(git describe --abbrev=0 --tags 2>/dev/null || echo "v0.0.0") echo "Previous Version: $PREV_VERSION" RELEASE_TYPE=${{ steps.determine_release.outputs.release_type }} @@ -103,10 +80,14 @@ jobs: # Get the milestone details - name: Get Milestone Details id: get_milestone + env: + MILESTONE_NUMBER: ${{ github.event.client_payload.milestone_number }} run: | - # Retrieve the milestone ID from the workflow input - MILESTONE_ID=${{ github.event.client_payload.milestone_number }} - MILESTONE_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/milestones/${MILESTONE_ID}") + if [[ -z "$MILESTONE_NUMBER" ]]; then + echo "::error::MILESTONE_NUMBER is not set in client_payload" + exit 1 + fi + MILESTONE_RESPONSE=$(curl --fail -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/milestones/${MILESTONE_NUMBER}") MILESTONE_TITLE=$(echo "$MILESTONE_RESPONSE" | jq -r '.title') MILESTONE_DESCRIPTION=$(echo "$MILESTONE_RESPONSE" | jq -r '.description') MILESTONE_DATE=$(echo "$MILESTONE_RESPONSE" | jq -r '.due_on') @@ -141,8 +122,9 @@ jobs: LABEL_DEPS="chore(deps):" LABEL_DEPS_DEV="chore(deps-dev):" # Get the last release tag - LAST_RELEASE_TAG=$(git describe --abbrev=0 --tags) - echo "Last Release Tag: $LAST_RELEASE_TAG" + LAST_RELEASE_TAG=$(git describe --abbrev=0 --tags 2>/dev/null || echo "") + GIT_LOG_RANGE="${LAST_RELEASE_TAG:+${LAST_RELEASE_TAG}..}HEAD" + echo "Last Release Tag: ${LAST_RELEASE_TAG:-none (first release)}" # Get the milestone details from the output of the previous step MILESTONE_TITLE="${{ steps.get_milestone.outputs.milestone_title }}" MILESTONE_DESCRIPTION="${{ steps.get_milestone.outputs.milestone_description }}" @@ -158,7 +140,7 @@ jobs: local section_label="$2" local section_icon="$3" # Get the commit messages with the specified label between the last release and the current release - local commit_messages=$(git log --pretty=format:"- %s (Linked Issues: %C(yellow)%H%Creset)" "$LAST_RELEASE_TAG..HEAD" --grep="$section_label" --no-merges --decorate --decorate-refs=refs/issues) + local commit_messages=$(git log --pretty=format:"- %s (Linked Issues: %C(yellow)%H%Creset)" $GIT_LOG_RANGE --grep="$section_label" --no-merges --decorate --decorate-refs=refs/issues) # If there are commit messages, append the section to the changelog file if [ -n "$commit_messages" ]; then # Remove duplicate commit messages @@ -189,7 +171,7 @@ jobs: # Function to append non-labeled commits to the changelog file append_non_labeled_commits() { # Get the commit messages that do not match any conventional commit labels between the last release and the current release - local non_labeled_commit_messages=$(git log --pretty=format:"- %s (Linked Issues: %C(yellow)%H%Creset)" "$LAST_RELEASE_TAG..HEAD" --invert-grep --grep="^fix:\|^feat:\|^enhancement:\|^docs:\|^refactor:\|^chore:\|^build:\|^ci:\|^perf:\|^style:\|^test:\|^BREAKING CHANGE:\|^feat!:\|^chore(deps):\|^chore(deps-dev):") + local non_labeled_commit_messages=$(git log --pretty=format:"- %s (Linked Issues: %C(yellow)%H%Creset)" $GIT_LOG_RANGE --invert-grep --grep="^fix:\|^feat:\|^enhancement:\|^docs:\|^refactor:\|^chore:\|^build:\|^ci:\|^perf:\|^style:\|^test:\|^BREAKING CHANGE:\|^feat!:\|^chore(deps):\|^chore(deps-dev):") # If there are non-labeled commit messages, append the section to the changelog file if [ -n "$non_labeled_commit_messages" ]; then # Remove duplicate commit messages @@ -221,23 +203,19 @@ jobs: # Attach changelog as an artifact - name: Attach Changelog to Release - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: Changelog path: /home/runner/work/changelog.txt # Create release while including the changelog - name: Create Release - id: create_release - uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.bump_version.outputs.new_version }} - release_name: ${{ steps.bump_version.outputs.new_version }} - body_path: /home/runner/work/changelog.txt - draft: false - prerelease: false + run: | + gh release create "${{ steps.bump_version.outputs.new_version }}" \ + --title "${{ steps.bump_version.outputs.new_version }}" \ + --notes-file /home/runner/work/changelog.txt - name: Send Slack Notification continue-on-error: true diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 1abab3f..3a184e6 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -33,10 +33,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build the Docker image + if: hashFiles('Dockerfile') != '' run: docker build . --file Dockerfile --tag localbuild/testimage:latest - name: Scan the image and upload dependency results + if: hashFiles('Dockerfile') != '' uses: anchore/sbom-action@bb716408e75840bbb01e839347cd213767269d4a with: image: "localbuild/testimage:latest" diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 5567037..30ac703 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -26,11 +26,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Reject prerelease version on main PR run: | - VERSION=$(jq -r '.version' package.json) + if [ ! -f package.json ]; then + echo "❌ package.json not found" + exit 1 + fi + VERSION=$(jq -r '.version // empty' package.json) + if [ -z "$VERSION" ]; then + echo "❌ No version field in package.json" + exit 1 + fi if [[ "$VERSION" == *-* ]]; then echo "❌ package.json version '$VERSION' contains a prerelease suffix." echo " Strip the suffix (e.g. change '$VERSION' → '${VERSION%-*}') before merging to main." From 207eb60ef7fd46d23160c60024c9fab00ed10670 Mon Sep 17 00:00:00 2001 From: Justus-at-Tazama Date: Wed, 8 Apr 2026 00:53:53 +0200 Subject: [PATCH 2/2] ci: overhaul sync-workflows.yml - Add concurrency block: cancel-in-progress prevents parallel sync runs - Pin actions/checkout to SHA de0fac2e4500dabe0009e67214ff5f5447ce83dd (v6.0.2) - Remove manual gh CLI install step (pre-installed on ubuntu-latest; v2.14.7 was 2 years out of date) - Add SSH commit signing (base64-encoded SSH_SIGNING_KEY secret, same Justus-at-Tazama identity as tazama-lf/workflows). Fails fast if secret is missing or key is invalid. - Fix Get actor details: previous version called pulls API on push/ workflow_dispatch events where pull_request.number is empty; add fallback to github.actor for non-PR triggers - Fix branch strategy: delete and recreate sync-workflows-update each run instead of checkout+pull (avoids accumulating stale changes) - Add dev branch existence check: create from default branch if absent - Add missing cd .. at end of loop (latent bug: loop was broken after first repo) - Fix PR token handling: remove echo-to-file + gh auth login + unset pattern; GH_TOKEN env var used directly (gh CLI picks it up automatically) - Fix PR body: use variable instead of inline interpolation; use two-arg commit message (-m msg -m body) instead of concatenation - Change PR_REVIEWERS source: vars.PR_REVIEWERS -> secrets.GH_USERNAME (consistent with tazama-lf/workflows) - Add publish.yml / version-check.yml / release-train.yml exclusion: these are library-only and should not be copied to rule repos Signed-off-by: Justus-at-Tazama --- .github/workflows/sync-workflows.yml | 117 +++++++++++++++++---------- 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/.github/workflows/sync-workflows.yml b/.github/workflows/sync-workflows.yml index b712869..8ef81eb 100644 --- a/.github/workflows/sync-workflows.yml +++ b/.github/workflows/sync-workflows.yml @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -# This GitHub Actions workflow syncs workflows from a central repository to other repositories when a pull request is merged. +# This GitHub Actions workflow syncs workflows from a central repository to other repositories when changes are pushed to dev. # Please do not attempt to edit this flow without the direct consent from the DevOps team. This file is managed centrally. @@ -8,42 +8,65 @@ name: Sync Workflows on: push: - branches: - - dev # The branches below must be a subset of the branches above + branches: [ "dev" ] workflow_dispatch: +concurrency: + group: sync-workflows-${{ github.ref }} + cancel-in-progress: true + jobs: Sync_All_Repos_Common_Workflows: if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' runs-on: ubuntu-latest # Use the latest Ubuntu runner for the job steps: - name: Checkout Central Workflows Repo # Step to checkout the repository containing the central workflows - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Git # Step to configure Git with necessary details run: | - git config --global user.name 'github-actions' - git config --global user.email 'github-actions@github.com' + git config --global user.name 'Justus-at-Tazama' + git config --global user.email '123470803+Justus-at-Tazama@users.noreply.github.com' git config --global credential.helper store - - - name: Install GitHub CLI # Step to install GitHub CLI for creating pull requests - run: | - sudo apt-get update - sudo apt-get install -y curl - curl -fsSL https://github.com/cli/cli/releases/download/v2.14.7/gh_2.14.7_linux_amd64.tar.gz -o ghcli.tar.gz - tar -xzf ghcli.tar.gz - sudo cp gh_2.14.7_linux_amd64/bin/gh /usr/local/bin/ - - - name: Get PR author details # Step to get the details of the pull request author + # Configure SSH commit signing so sync commits show as Verified on GitHub. + # SSH_SIGNING_KEY must be stored base64-encoded (single line, no wrapping) to avoid + # line-ending corruption. Set with: base64 -w 0 signing_key | gh secret set SSH_SIGNING_KEY + # The corresponding public key must be registered as a Signing Key on the GitHub account + # that GH_TOKEN belongs to: Settings -> SSH and GPG keys -> New signing key. + if [ -z "${{ secrets.SSH_SIGNING_KEY }}" ]; then + echo "::error::SSH_SIGNING_KEY secret is not set. Commits cannot be signed. Add the secret before running the sync." >&2 + exit 1 + fi + mkdir -p ~/.ssh + # Decode the base64 secret directly to the key file -- no line-ending issues possible. + echo "${{ secrets.SSH_SIGNING_KEY }}" | base64 -d > ~/.ssh/signing_key + chmod 600 ~/.ssh/signing_key + # Validate the key is a usable Ed25519/RSA private key before enabling signing. + ssh-keygen -y -f ~/.ssh/signing_key > /dev/null || { + echo "::error::SSH_SIGNING_KEY is not a valid SSH private key or is passphrase-protected. Fix the secret before running the sync." >&2 + exit 1 + } + git config --global gpg.format ssh + git config --global user.signingkey ~/.ssh/signing_key + git config --global commit.gpgsign true + + - name: Get actor details # Step to get the triggering actor details for the Signed-off-by line id: pr_author env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PR_AUTHOR_NAME=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.user.login') - PR_AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae') - PR_AUTHOR_NAME_FULL=$(git log -1 --pretty=format:'%an') + # For pull_request events use the PR author login; for push/workflow_dispatch fall back to the triggering actor. + if [ "${{ github.event_name }}" = "pull_request" ]; then + PR_AUTHOR_NAME=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.user.login') + PR_AUTHOR_NAME_FULL=$(git log -1 --pretty=format:'%an') + PR_AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae') + else + PR_AUTHOR_NAME="${{ github.actor }}" + PR_AUTHOR_NAME_FULL="${{ github.actor }}" + PR_AUTHOR_EMAIL="${{ github.actor }}@users.noreply.github.com" + fi echo "PR_AUTHOR_NAME=${PR_AUTHOR_NAME}" >> $GITHUB_ENV echo "PR_AUTHOR_EMAIL=${PR_AUTHOR_EMAIL}" >> $GITHUB_ENV echo "PR_AUTHOR_NAME_FULL=${PR_AUTHOR_NAME_FULL}" >> $GITHUB_ENV @@ -84,7 +107,7 @@ jobs: rule-084 rule-090 rule-091 - PR_REVIEWERS: ${{ vars.PR_REVIEWERS }} # List of reviewers + PR_REVIEWERS: ${{ secrets.GH_USERNAME }} # List of reviewers GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} run: | SIGNED_OFF_BY="Signed-off-by: ${{ env.PR_AUTHOR_NAME_FULL }} <${{ env.PR_AUTHOR_EMAIL }}>" @@ -103,32 +126,46 @@ jobs: cd $repo git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/frmscoe/$repo.git - if git ls-remote --heads origin sync-workflows-update | grep sync-workflows-update; then - git checkout sync-workflows-update - git pull origin sync-workflows-update + # Ensure dev branch exists in target repo -- create from default branch if absent. + if ! git ls-remote --heads origin dev | grep -q dev; then + DEFAULT_BRANCH=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p') + echo "Branch 'dev' not found in $repo; creating from '${DEFAULT_BRANCH}'" + git checkout -b dev "origin/${DEFAULT_BRANCH}" + git push origin dev else - git checkout -b sync-workflows-update + git checkout dev + fi + + # Always delete and recreate sync-workflows-update to avoid accumulating stale changes. + # RESERVED: sync-workflows-update is managed by this workflow -- do not use it for regular development contributions. + if git ls-remote --heads origin sync-workflows-update | grep -q sync-workflows-update; then + git push origin --delete sync-workflows-update || true fi + git checkout -b sync-workflows-update cd .. - mkdir -p $repo/.github/workflows + mkdir -p $repo/.github/workflows # Create the workflows directory if it does not exist - # Copy all common workflow files; skip canonical package-rule*.yml definitions - # (rule repos receive caller stubs only — stamped below) + # Copy all workflow files; apply per-file rules before copying for file in temp-workflows/*; do filename=$(basename "$file") + # package-rule*.yml canonical definitions: never copy -- stubs are stamped below if [[ "${filename}" == package-rule*.yml ]]; then continue fi + # publish.yml, version-check.yml, release-train.yml: library-only -- skip for rule repos + if [[ "${filename}" == "publish.yml" || "${filename}" == "version-check.yml" || "${filename}" == "release-train.yml" ]]; then + continue + fi cp -r "$file" "$repo/.github/workflows/" done - # Stamp caller stubs (replaces the full per-repo package-rule copies) - RULE_NUM="${repo#rule-}" # strip "rule-" prefix → "001", "044", etc. + # Stamp caller stubs for all frmscoe rule repos + RULE_NUM="${repo#rule-}" # strip "rule-" prefix -> "001", "044", etc. # RC caller stub printf '%s\n' \ '# SPDX-License-Identifier: Apache-2.0' \ - '# This file is managed centrally — do not edit manually.' \ + '# This file is managed centrally -- do not edit manually.' \ '# To change build behaviour, update the reusable workflow in frmscoe/workflows.' \ 'on:' \ ' push:' \ @@ -145,7 +182,7 @@ jobs: # Stable caller stub printf '%s\n' \ '# SPDX-License-Identifier: Apache-2.0' \ - '# This file is managed centrally — do not edit manually.' \ + '# This file is managed centrally -- do not edit manually.' \ '# To change build behaviour, update the reusable workflow in frmscoe/workflows.' \ 'on:' \ ' push:' \ @@ -161,22 +198,20 @@ jobs: > "$repo/.github/workflows/package-rule.yml" echo "Stamped caller stubs for rule-${RULE_NUM} (frmscoe)" - cd $repo - git add . - git commit -m "ci: sync workflows from central-workflows ${SIGNED_OFF_BY}" || echo "No changes to commit" + cd "$repo" + git add .github/workflows/ + git commit -m "ci: sync workflows from central-workflows" -m "${SIGNED_OFF_BY}" || echo "No changes to commit" git push origin sync-workflows-update || git push origin sync-workflows-update --force - # Authenticate gh CLI with scoped token - echo "${{ secrets.GH_TOKEN }}" > /tmp/gh_token - unset GITHUB_TOKEN - gh auth login --with-token < /tmp/gh_token - + # Create the PR with reviewers IFS=',' read -ra REVIEWERS <<< "${PR_REVIEWERS}" REVIEWERS_ARGS="" for reviewer in "${REVIEWERS[@]}"; do REVIEWERS_ARGS+="--reviewer $reviewer " done - gh pr create --title "ci: sync workflows from central-workflows" --body "This PR syncs workflows from the central-workflows repository. ${SIGNED_OFF_BY}" --base dev --head sync-workflows-update $REVIEWERS_ARGS || echo "PR already exists, updating existing PR" + PR_BODY="This PR syncs workflows from the central-workflows repository."$'\n\n'"${SIGNED_OFF_BY}" + gh pr create --title "ci: sync workflows from central-workflows" --body "$PR_BODY" --base dev --head sync-workflows-update $REVIEWERS_ARGS || echo "PR already exists, updating existing PR" - rm /tmp/gh_token + cd .. + done \ No newline at end of file