Skip to content

Commit 6e114b3

Browse files
rochdevclaude
andauthored
ci(release): consolidate publish jobs into a matrix-driven single job (#9071)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0e0b182 commit 6e114b3

2 files changed

Lines changed: 119 additions & 59 deletions

File tree

.github/workflows/release.yml

Lines changed: 106 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13,64 +13,56 @@ concurrency:
1313
cancel-in-progress: true
1414

1515
jobs:
16-
publish-v3:
17-
if: github.event_name == 'push' && github.event.ref == 'refs/heads/v3.x'
16+
setup:
17+
if: github.event_name == 'push'
1818
runs-on: ubuntu-latest
19-
environment:
20-
name: npm
21-
url: https://npmjs.com/package/dd-trace
22-
permissions:
23-
id-token: write
19+
# Space-separated npm dist-tags for each release line.
20+
# The latest branch always gets "latest" prepended automatically.
21+
# Add a line here when creating a new release branch.
2422
env:
25-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
DIST_TAGS: |
24+
v3.x latest-node14
25+
v4.x latest-node16
26+
v5.x latest-node18 latest-node20
27+
outputs:
28+
npm_tags: ${{ steps.compute.outputs.npm_tags }}
29+
is_latest: ${{ steps.compute.outputs.is_latest }}
2630
steps:
27-
- uses: DataDog/dd-octo-sts-action@96a25462dbcb10ebf0bfd6e2ccc917d2ab235b9a # v1.0.4
28-
id: octo-sts
29-
with:
30-
scope: DataDog/dd-trace-js
31-
policy: self.github.release.push-tags
32-
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33-
- uses: ./.github/actions/node
34-
- run: npm publish --tag latest-node14
35-
- id: pkg
31+
- id: compute
32+
env:
33+
GH_TOKEN: ${{ github.token }}
3634
run: |
37-
content=`cat ./package.json | tr '\n' ' '`
38-
echo "json=$content" >> $GITHUB_OUTPUT
39-
- run: |
40-
git tag v${{ fromJson(steps.pkg.outputs.json).version }}
41-
git push https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git v${{ fromJson(steps.pkg.outputs.json).version }}
42-
- run: node scripts/release/notes
35+
branch="${{ github.ref_name }}"
4336
44-
publish-v4:
45-
if: github.event_name == 'push' && github.event.ref == 'refs/heads/v4.x'
46-
runs-on: ubuntu-latest
47-
environment:
48-
name: npm
49-
url: https://npmjs.com/package/dd-trace
50-
permissions:
51-
id-token: write
52-
env:
53-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54-
steps:
55-
- uses: DataDog/dd-octo-sts-action@96a25462dbcb10ebf0bfd6e2ccc917d2ab235b9a # v1.0.4
56-
id: octo-sts
57-
with:
58-
scope: DataDog/dd-trace-js
59-
policy: self.github.release.push-tags
60-
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
61-
- uses: ./.github/actions/node
62-
- run: npm publish --tag latest-node16
63-
- id: pkg
64-
run: |
65-
content=`cat ./package.json | tr '\n' ' '`
66-
echo "json=$content" >> $GITHUB_OUTPUT
67-
- run: |
68-
git tag v${{ fromJson(steps.pkg.outputs.json).version }}
69-
git push https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git v${{ fromJson(steps.pkg.outputs.json).version }}
70-
- run: node scripts/release/notes
37+
# Find the latest release line by intersecting the GitHub branch
38+
# list with DIST_TAGS. This ensures a branch that exists in the
39+
# repo but is not yet wired into the config cannot steal the
40+
# latest tag before triggers and dist-tag mappings are updated.
41+
configured=$(echo "$DIST_TAGS" | awk '{print $1}')
42+
latest=$(gh api "repos/${{ github.repository }}/branches" \
43+
--paginate \
44+
--jq '.[].name' \
45+
| grep -E '^v[0-9]+\.x$' \
46+
| grep -Fx -f <(echo "$configured") \
47+
| sort -V \
48+
| tail -1)
49+
50+
# Collect the tags defined for this branch (everything after the branch name)
51+
branch_tags=$(echo "$DIST_TAGS" | awk -v b="$branch" '$1==b{$1=""; sub(/^ /,""); print}')
7152
72-
publish-latest:
73-
if: github.event_name == 'push' && github.event.ref == 'refs/heads/v5.x'
53+
if [ "$branch" = "$latest" ]; then
54+
npm_tags="latest ${branch_tags:-latest-${branch%.x}}"
55+
is_latest="true"
56+
else
57+
npm_tags="${branch_tags:-latest-${branch%.x}}"
58+
is_latest="false"
59+
fi
60+
61+
echo "npm_tags=$npm_tags" >> "$GITHUB_OUTPUT"
62+
echo "is_latest=$is_latest" >> "$GITHUB_OUTPUT"
63+
64+
publish:
65+
needs: setup
7466
runs-on: ubuntu-latest
7567
environment:
7668
name: npm
@@ -89,20 +81,76 @@ jobs:
8981
with:
9082
persist-credentials: false
9183
- uses: ./.github/actions/node
92-
- run: npm publish
9384
- id: pkg
9485
run: |
9586
content=`cat ./package.json | tr '\n' ' '`
9687
echo "json=$content" >> $GITHUB_OUTPUT
97-
- run: |
98-
git tag v${{ fromJson(steps.pkg.outputs.json).version }}
99-
git push https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git v${{ fromJson(steps.pkg.outputs.json).version }}
100-
- run: node scripts/release/notes --latest
88+
- name: Publish
89+
run: |
90+
version="${{ fromJson(steps.pkg.outputs.json).version }}"
91+
tags=(${{ needs.setup.outputs.npm_tags }})
92+
93+
# Exchange the GitHub OIDC token for an npm registry token so
94+
# that npm publish and npm dist-tag add share the same auth.
95+
# npm only runs this exchange internally for publish (oidc.js:141)
96+
# and does not persist the token, so dist-tag add would fail
97+
# without doing it explicitly here.
98+
# Audience format and exchange endpoint from npm/lib/utils/oidc.js.
99+
oidc_token=$(curl -sS -fH \
100+
"Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
101+
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org" \
102+
| jq -r '.value')
103+
npm_token=$(curl -sS -fX POST \
104+
-H "Authorization: Bearer $oidc_token" \
105+
-H "Content-Type: application/json" \
106+
"https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/dd-trace" \
107+
| jq -r '.token')
108+
export NODE_AUTH_TOKEN="$npm_token"
109+
110+
# Idempotent: skip publish if this version already exists so
111+
# that a rerun after a partial failure can still repair dist-tags,
112+
# git tags, release notes, and docs.
113+
if npm view "dd-trace@$version" version 2>/dev/null | grep -qF "$version"; then
114+
local_integrity=$(npm pack --dry-run --json 2>/dev/null | jq -r '.[0].integrity')
115+
published_integrity=$(npm view "dd-trace@$version" dist.integrity 2>/dev/null)
116+
if [ "$local_integrity" != "$published_integrity" ]; then
117+
echo "ERROR: package content at current HEAD differs from dd-trace@$version on the registry" >&2
118+
echo " Published: $published_integrity" >&2
119+
echo " Current: $local_integrity" >&2
120+
exit 1
121+
fi
122+
echo "Version $version already published with identical content, skipping npm publish"
123+
else
124+
npm publish --tag "${tags[0]}"
125+
fi
126+
127+
# Always reconcile all expected dist-tags.
128+
for tag in "${tags[@]:1}"; do
129+
npm dist-tag add "dd-trace@$version" "$tag"
130+
done
131+
- name: Tag release
132+
run: |
133+
version="${{ fromJson(steps.pkg.outputs.json).version }}"
134+
remote="https://x-access-token:${{ steps.octo-sts.outputs.token }}@github.com/${{ github.repository }}.git"
135+
if git ls-remote --tags "$remote" "v$version" | grep -q "v$version"; then
136+
echo "Tag v$version already exists, skipping"
137+
else
138+
git tag "v$version"
139+
git push "$remote" "v$version"
140+
fi
141+
- name: Release notes
142+
run: |
143+
if [ "${{ needs.setup.outputs.is_latest }}" = "true" ]; then
144+
node scripts/release/notes --latest
145+
else
146+
node scripts/release/notes
147+
fi
101148
env:
102149
GH_TOKEN: ${{ steps.octo-sts.outputs.token }}
103150

104151
docs:
105-
needs: "publish-latest"
152+
needs: [setup, publish]
153+
if: needs.setup.outputs.is_latest == 'true'
106154
runs-on: ubuntu-latest
107155
permissions:
108156
id-token: write

scripts/release/notes.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ if (version.includes('-')) {
2525
fs.mkdirSync(folder, { recursive: true })
2626
fs.writeFileSync(file, body)
2727

28-
run(`gh release create ${tag} --target v${major}.x --title ${version} -F ${file} ${flags.join(' ')}`)
28+
let releaseExists = false
29+
try {
30+
capture(`gh release view ${tag} --json tagName`)
31+
releaseExists = true
32+
} catch {
33+
// Release does not exist yet
34+
}
35+
36+
if (releaseExists) {
37+
run(`gh release edit ${tag} --title ${version} -F ${file} ${flags.join(' ')}`)
38+
} else {
39+
run(`gh release create ${tag} --target v${major}.x --title ${version} -F ${file} ${flags.join(' ')}`)
40+
}

0 commit comments

Comments
 (0)