diff --git a/.github/inditextech-ci-sync-manifest.json b/.github/inditextech-ci-sync-manifest.json index a19abb6..62944f9 100644 --- a/.github/inditextech-ci-sync-manifest.json +++ b/.github/inditextech-ci-sync-manifest.json @@ -17,7 +17,7 @@ "creation_year": 2026, "integrity": { "algorithm": "hmac-sha256", - "signature": "1726fed4e2095a5d6fffdd98e61fc426f5137247194239d143c19c32a382395f" + "signature": "30da579cb160780970680b47a0265f360eb311100c20e8d0d3948bf03c146577" }, "managed_by": "InditexTech CI governance", "managed_paths": { @@ -28,11 +28,11 @@ ".github/PULL_REQUEST_TEMPLATE.md": "23a0b0ac79a9020ccac9c013582a01f86e2df2ae7f914673583b9a3368313041", ".github/inditextech-ci-node.json": "72449d1243d714b1ef9bd615e6dc67d0ec0b25f0c73440a08fa34d1e498864ac", ".github/workflows/code-npm_node-PR_verify.yml": "2d4430d765f4a7539ae64fc75cb8254cc09fdcaa7a2a805faada3865887262ce", - ".github/workflows/code-npm_node-publish-release-and-snapshot.yml": "ad2241467f491c6d158a1dcb85f41f31a76d2cf786f4ea5b198c75e4b8fa8a11", - ".github/workflows/code-npm_node-release-core.yml": "da11bed8544814e98d34ce4a20aeec3818fa3e2ab4f738027a64163d9de19709", - ".github/workflows/code-npm_node-sonarcloud-analysis.yml": "d2fb005c98eb0b68081dcce9611b4cf70e905847119839316fa9034bcac73213", + ".github/workflows/code-npm_node-publish-release-and-snapshot.yml": "b8743248b8882fc5c3de16acc455c9660fde8bcf686784f0a21e7989cb66ccf7", + ".github/workflows/code-npm_node-release-core.yml": "4d049809fb1d88f41b15c28cbe62b164dfe1be297ab6d7db38a685e9f693bcaa", + ".github/workflows/code-npm_node-sonarcloud-analysis.yml": "e86284867ae0bb193af66233d87f3f78ea7e7a0c9d33bd0e80432486245bf12b", ".github/workflows/code-release_preview.yml": "4e2e95d60a498eb12d97ba5c8330b1173f04b02c807140beddad06a6e225b166", - ".github/workflows/codeql.yml": "94084661946332025fbc91374dba51e0c82ab53f95de717c491ba63f384fad75", + ".github/workflows/codeql.yml": "3deedabd1c2469f05588157631078d85528e538e094f3e603e26c04894303d38", ".github/workflows/pr-verify.yml": "5628d1d93c09cb761602fcfc3857c325e2dc542cadc159a1edad58d1818d0de0", ".github/workflows/push-verify.yml": "643f6905fa30284af4a2eabac752293b2e785a5e82449bb6d97950624386829c", ".github/workflows/scorecard-analysis.yml": "b45c2a0f87801796cefb4363d9f1414891b0734bc07de3f50d4a880e22faea10", @@ -57,6 +57,6 @@ "schema_version": 2, "source_digests": { "base": "d0ba4f054c8e0a0d6c828d02718efc01251f7897cfcbe864d3c76ef304dc4a13", - "node": "da5aefad643c8b4ce2a07646e6216366993ab6296603af9bc9165b195f193c95" + "node": "f96646bb1913b7965d0e5def0800aa90b68e36ce89487f555f6e9a0d1162119b" } } diff --git a/.github/workflows/code-npm_node-publish-release-and-snapshot.yml b/.github/workflows/code-npm_node-publish-release-and-snapshot.yml index 5715ebc..35af958 100644 --- a/.github/workflows/code-npm_node-publish-release-and-snapshot.yml +++ b/.github/workflows/code-npm_node-publish-release-and-snapshot.yml @@ -146,7 +146,10 @@ jobs: exit 0 fi head_committer_email="$(git show -s --format=%ce HEAD)" - if [[ "$GITHUB_EVENT_NAME" == "push" && "$head_committer_email" == *"[bot]@users.noreply.github.com" ]]; then + head_subject="$(git show -s --format=%s HEAD)" + # Release commits are signed by the organization release identity, + # not a [bot] account, so the release lane is also recognized by subject. + if [[ "$GITHUB_EVENT_NAME" == "push" && ( "$head_committer_email" == *"[bot]@users.noreply.github.com" || "$head_subject" == "[node-release] "* ) ]]; then echo "::notice title=Snapshot skipped::Next-dev commit does not need a snapshot." echo "should_publish=false" >> "$GITHUB_OUTPUT" exit 0 @@ -296,7 +299,7 @@ jobs: path: ${{ runner.temp }}/publish-dist - name: Publish via npm trusted publishing - uses: InditexTech/gh-actions/npm@8a719baa6e8d514ccdfe87cff2baae9007a8b168 # v1.1.5 + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: @@ -321,6 +324,8 @@ jobs: release_type: ${{ inputs.release_type }} secrets: APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + CI_GPG_SECRET_KEY: ${{ secrets.CI_GPG_SECRET_KEY }} + CI_GPG_SECRET_KEY_PASSWORD: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} publish-npm: name: Publish ${{ matrix.release.tag }} to npm @@ -349,7 +354,7 @@ jobs: path: ${{ runner.temp }}/publish-dist - name: Publish via npm trusted publishing - uses: InditexTech/gh-actions/npm@8a719baa6e8d514ccdfe87cff2baae9007a8b168 # v1.1.5 + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: @@ -568,7 +573,7 @@ jobs: git -C "$source" push --atomic origin "$RELEASE_COMMIT:$EXPECTED_REF" "${tag_refs[@]}" - name: Publish verified delegated tarballs via npm trusted publishing - uses: InditexTech/gh-actions/npm@8a719baa6e8d514ccdfe87cff2baae9007a8b168 # v1.1.5 + uses: InditexTech/gh-actions/npm@80ca79bcb04379e4fed8b91a55aa15dc6b8b638a # v1.1.6 env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: @@ -649,6 +654,13 @@ jobs: tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ github.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + - name: Prepare next development source env: RELEASES: ${{ needs.release-core.outputs.releases }} @@ -657,8 +669,6 @@ jobs: shell: bash run: | set -euo pipefail - git -C "$SOURCE_ROOT" config user.name "github-actions[bot]" - git -C "$SOURCE_ROOT" config user.email "41898282+github-actions[bot]@users.noreply.github.com" release_bump="$(jq -er '.[0].release_bump | select(type == "string" and length > 0)' <<< "$RELEASES")" case "${PACKAGE_MANAGER:-npm}" in npm) RELEASE_BUMP="$release_bump" npm run version:development ;; diff --git a/.github/workflows/code-npm_node-release-core.yml b/.github/workflows/code-npm_node-release-core.yml index e33cb0b..6dd1af6 100644 --- a/.github/workflows/code-npm_node-release-core.yml +++ b/.github/workflows/code-npm_node-release-core.yml @@ -24,6 +24,12 @@ on: secrets: APP_PRIVATE_KEY: required: true + CI_GPG_SECRET_KEY: + description: Armored GPG secret key signing release commits and tags. + required: false + CI_GPG_SECRET_KEY_PASSWORD: + description: Passphrase protecting CI_GPG_SECRET_KEY. + required: false permissions: contents: read @@ -465,6 +471,21 @@ jobs: } if [[ "$resume" == "false" ]]; then + # A workflow_dispatch never passes through the Release Preview + # CHANGELOG gate, so a fresh cut must prove there is something to + # release: refuse an Unreleased section without a content entry. + if [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then + if [[ ! -f CHANGELOG.md ]] || ! awk ' + $0 ~ /^##[[:space:]]+\[Unreleased\][[:space:]]*$/ { unreleased = 1; next } + unreleased && $0 ~ /^##[[:space:]]/ { exit } + unreleased && $0 ~ /^[[:space:]]*([-*+]|[0-9]+\.)[[:space:]]+/ { entry = 1 } + END { exit !(unreleased && entry) } + ' CHANGELOG.md; then + echo "::error title=No CHANGELOG changes::A dispatched release needs a substantive entry under ## [Unreleased]." + exit 1 + fi + fi + write_records releases="$(jq -cs . "$records_file")" @@ -527,6 +548,11 @@ jobs: primary_version="$(jq -r '.[0].version' <<< "$releases")" echo "RELEASE_VERSION=$primary_version" >> "$GITHUB_ENV" + # CHANGELOG links must name real tags: independent workspaces tag + # -, so the prefix is the primary tag minus its + # version (empty for single and locked-step releases). + primary_tag="$(jq -r '.[0].tag' <<< "$releases")" + echo "CHANGELOG_TAG_PREFIX=${primary_tag%"$primary_version"}" >> "$GITHUB_ENV" echo "RELEASE_BUMP=$release_bump" >> "$GITHUB_ENV" else write_records @@ -557,12 +583,13 @@ jobs: changelog: ${{ env.WORKING_DIRECTORY }}/CHANGELOG.md fail-on-empty-release-notes: false keep-unreleased-section: true - tag-prefix: "" + tag-prefix: ${{ env.CHANGELOG_TAG_PREFIX }} - name: Reconcile CHANGELOG version with release if: vars.RELEASE_LIFECYCLE != 'delegated' && steps.release-plan.outputs.resume != 'true' env: RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + TAG_PREFIX: ${{ env.CHANGELOG_TAG_PREFIX }} working-directory: ${{ env.WORKING_DIRECTORY }} shell: bash run: | @@ -576,16 +603,25 @@ jobs: if [[ "$cl_ver" != "$RELEASE_VERSION" ]]; then echo "::notice title=CHANGELOG version reconciled::keep-a-changelog wrote ${cl_ver}; re-anchoring to release ${RELEASE_VERSION}." cl_ver_re="${cl_ver//./\\.}" + tag_prefix="${TAG_PREFIX:-}" + tp_re="${tag_prefix//./\\.}" tmp="$(mktemp)" sed -E \ -e "s@^## \[${cl_ver_re}\]( - )@## [${RELEASE_VERSION}]\1@" \ - -e "s@^(\[Unreleased\]: .*/compare/)${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ - -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${RELEASE_VERSION}@" \ + -e "s@^(\[Unreleased\]: .*/compare/${tp_re})${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ + -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${tp_re}${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${tag_prefix}${RELEASE_VERSION}@" \ "$cl" > "$tmp" cat "$tmp" > "$cl" rm -f "$tmp" fi + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ steps.app-token.outputs.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + - name: Commit and stage release if: vars.RELEASE_LIFECYCLE != 'delegated' && steps.release-plan.outputs.resume != 'true' id: release-commit @@ -604,8 +640,6 @@ jobs: echo "::error title=Missing release metadata::Versioning did not produce changes to commit." exit 1 fi - git config user.name "${APP_SLUG}[bot]" - git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" git commit -m "[node-release] Prepare release" while IFS= read -r record; do @@ -627,9 +661,6 @@ jobs: run: | set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - case ",$RELEASE_LABELS," in *",release-type/major,"*) release_bump="major" ;; *",release-type/minor,"*) release_bump="minor" ;; @@ -693,6 +724,11 @@ jobs: echo "RELEASE_BUMP=$release_bump" >> "$GITHUB_ENV" echo "RELEASE_VERSION=$primary_version" >> "$GITHUB_ENV" + # CHANGELOG links must name real tags: independent workspaces tag + # -, so the prefix is the primary tag minus its + # version (empty for single and locked-step releases). + primary_tag="$(jq -r '.[0].tag' <<< "$releases")" + echo "CHANGELOG_TAG_PREFIX=${primary_tag%"$primary_version"}" >> "$GITHUB_ENV" { echo "expected_ref=refs/heads/$BASELINE_BRANCH" @@ -713,12 +749,13 @@ jobs: changelog: ${{ env.WORKING_DIRECTORY }}/CHANGELOG.md fail-on-empty-release-notes: false keep-unreleased-section: true - tag-prefix: "" + tag-prefix: ${{ env.CHANGELOG_TAG_PREFIX }} - name: Reconcile CHANGELOG version with delegated release if: vars.RELEASE_LIFECYCLE == 'delegated' && steps.delegated-plan.outputs.changelog_pending == 'true' env: RELEASE_VERSION: ${{ env.RELEASE_VERSION }} + TAG_PREFIX: ${{ env.CHANGELOG_TAG_PREFIX }} working-directory: ${{ env.WORKING_DIRECTORY }} shell: bash run: | @@ -732,11 +769,13 @@ jobs: if [[ "$cl_ver" != "$RELEASE_VERSION" ]]; then echo "::notice title=CHANGELOG version reconciled::keep-a-changelog wrote ${cl_ver}; re-anchoring to release ${RELEASE_VERSION}." cl_ver_re="${cl_ver//./\\.}" + tag_prefix="${TAG_PREFIX:-}" + tp_re="${tag_prefix//./\\.}" tmp="$(mktemp)" sed -E \ -e "s@^## \[${cl_ver_re}\]( - )@## [${RELEASE_VERSION}]\1@" \ - -e "s@^(\[Unreleased\]: .*/compare/)${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ - -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${RELEASE_VERSION}@" \ + -e "s@^(\[Unreleased\]: .*/compare/${tp_re})${cl_ver_re}(\.\.\.HEAD)@\1${RELEASE_VERSION}\2@" \ + -e "s@^\[${cl_ver_re}\]: (.*/)(compare/[^ ]*\.\.\.|releases/tag/)${tp_re}${cl_ver_re}@[${RELEASE_VERSION}]: \1\2${tag_prefix}${RELEASE_VERSION}@" \ "$cl" > "$tmp" cat "$tmp" > "$cl" rm -f "$tmp" @@ -1284,6 +1323,13 @@ jobs: tool_versions: ${{ steps.tool-versions.outputs.tool_versions }} asdf_version: ${{ env.ASDF_BRANCH_VERSION }} + - name: Configure release git signing + uses: InditexTech/gh-actions/configure-release-git@3dac9bbff47d1e983604671191a08e4d345d9064 + with: + token: ${{ steps.app-token.outputs.token }} + gpg-secret-key: ${{ secrets.CI_GPG_SECRET_KEY }} + gpg-passphrase: ${{ secrets.CI_GPG_SECRET_KEY_PASSWORD }} + - name: Promote the release to the default branch id: promote if: vars.RELEASE_LIFECYCLE != 'delegated' && (needs.build-distributions.result == 'success' || needs.build-distributions.result == 'skipped') @@ -1299,8 +1345,6 @@ jobs: run: | set -euo pipefail gh auth setup-git - git config user.name "${APP_SLUG}[bot]" - git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" git fetch --no-tags origin "+${STAGING_REF}:refs/ci-governance/staged" 2>/dev/null || true if [[ "$(git ls-remote origin "$EXPECTED_REF" | cut -f1)" == "$RELEASE_COMMIT" ]]; then @@ -1324,7 +1368,7 @@ jobs: echo "::error title=Tag conflict::$tag already exists." exit 1 fi - git tag -a "$tag" -m "Release $version" + git tag -s "$tag" -m "Release $version" tag_refs+=("refs/tags/$tag") done < <(jq -c '.[]' <<< "$RELEASES") @@ -1344,8 +1388,6 @@ jobs: run: | set -euo pipefail gh auth setup-git - git config user.name "${APP_SLUG}[bot]" - git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" descriptor="$GITHUB_WORKSPACE/.github/inditextech-ci-node.json" diff --git a/.github/workflows/code-npm_node-sonarcloud-analysis.yml b/.github/workflows/code-npm_node-sonarcloud-analysis.yml index 1971228..fedf79f 100644 --- a/.github/workflows/code-npm_node-sonarcloud-analysis.yml +++ b/.github/workflows/code-npm_node-sonarcloud-analysis.yml @@ -141,7 +141,7 @@ jobs: } >> "$GITHUB_ENV" - name: Run SonarCloud analysis - uses: SonarSource/sonarqube-scan-action@ba9859eae8dd6bd29e412f25ddbbef3d032000f4 # v8.2.2 + uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0 env: SONAR_HOST_URL: https://sonarcloud.io SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 48fb08c..e2801ce 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -93,7 +93,7 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 with: build-mode: ${{ matrix.build-mode }} languages: ${{ matrix.language }} @@ -104,6 +104,6 @@ jobs: id: actions/cache-poisoning/poisonable-step - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 with: category: /language:${{ matrix.language }}