-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-upstream-version-task.yml
More file actions
164 lines (146 loc) · 8.07 KB
/
Copy pathcheck-upstream-version-task.yml
File metadata and controls
164 lines (146 loc) · 8.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
name: Check upstream version task
# Upstream-version tracker for a wrapper repo, hosted here once.
# A required resolve-upstream hook sets a versions step output, a JSON object of name -> version, one key or several for a multi-component pin.
# The task writes that object to a committed state file beside version.json, diffs it against the prior state, and opens a rolling App-signed bump PR per branch that the merge-bot auto-merges.
# Call it from a scheduled entry-point workflow, and matrix only the branches that ship the version, since a CI-only version uses ["develop"].
# A tracker whose bump waits for a human sets auto-merge false.
# The head then carries a marker no merge-bot rule matches, whatever bump-branch-prefix names, so the pull request needs a maintainer decision instead of merging on its own.
# With auto-merge true the default bump-branch-prefix, upstream-version, matches the merge-bot task's built-in upstream-version-main and upstream-version-develop pairs.
on:
workflow_call:
inputs:
state-file:
description: Committed version-state file, a JSON object of name -> version, at the repo root beside version.json.
required: false
type: string
default: upstream-version.json
branches:
description: JSON array of base branches to open bump PRs against.
required: false
type: string
default: '["main", "develop"]'
bump-branch-prefix:
description: >-
Head-branch prefix used when auto-merge is true.
Keep the default unless the caller also passes a matching merge-bot rules entry, or auto-merge will not fire.
required: false
type: string
default: upstream-version
auto-merge:
description: >-
Whether the bump pull request auto-merges.
False routes the bump to a human by prefixing the head with a marker no merge-bot rule matches.
required: false
type: boolean
default: true
secrets:
CODEGEN_APP_CLIENT_ID:
required: true
CODEGEN_APP_PRIVATE_KEY:
required: true
jobs:
# No job-level permissions declared: every write here (the checkout, the pull request) uses the App token, so GITHUB_TOKEN needs no scope and the caller can grant permissions: {}.
check-upstream-version:
name: Check upstream version job
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: ${{ fromJSON(inputs.branches) }}
steps:
# App token so the merge commit fires downstream workflows and the PR is signed.
- name: Generate GitHub App token step
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}
private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}
- name: Checkout code step
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ matrix.branch }}
token: ${{ steps.app-token.outputs.token }}
- name: Run resolve-upstream hook step
id: resolve-upstream
if: ${{ hashFiles('.github/actions/resolve-upstream/action.yml') != '' }}
uses: ./.github/actions/resolve-upstream
- name: Missing resolve-upstream hook step
if: ${{ hashFiles('.github/actions/resolve-upstream/action.yml') == '' }}
run: |
set -Eeuo pipefail
echo "::error::Required hook missing: .github/actions/resolve-upstream/action.yml"
exit 1
# Normalize the resolved JSON with sorted keys, so the committed file and its diff are stable.
# The old-vs-new key diff drives the PR title and body, naming only components that moved.
# The head carries a no-auto-merge marker when the caller opted out, so a merge-bot rule can never match it by accident.
- name: Compute upstream version diff step
id: resolve
env:
NEW_VERSIONS: ${{ steps.resolve-upstream.outputs.versions }}
STATE_FILE: ${{ inputs.state-file }}
BUMP_BRANCH_PREFIX: ${{ inputs.bump-branch-prefix }}
AUTO_MERGE: ${{ inputs.auto-merge }}
BRANCH: ${{ matrix.branch }}
run: |
set -Eeuo pipefail
# Require a non-empty JSON object of single-line name -> version strings.
# A CR/LF would corrupt the single-line GITHUB_OUTPUT, so reject it here instead of committing unconsumable state.
if ! new="$(printf '%s' "$NEW_VERSIONS" | jq -S '.' 2>/dev/null)" \
|| [ "$(printf '%s' "$new" | jq -r 'type == "object" and length > 0 and all(.[]; type == "string" and (test("[\r\n]") | not)) and (keys | all(test("[\r\n]") | not))')" != "true" ]; then
echo "::error::resolve-upstream must output a non-empty JSON object of single-line name -> version strings, no CR/LF; got: $NEW_VERSIONS"
exit 1
fi
# Missing, non-JSON, or non-object state becomes an empty object.
# The first run and any unusable prior file then both diff cleanly against the resolved object instead of failing.
if [ -f "$STATE_FILE" ] && old="$(jq -S 'if type == "object" then . else empty end' "$STATE_FILE" 2>/dev/null)" && [ -n "$old" ]; then :; else old='{}'; fi
# Write the canonical state file as jq emits it, LF, matching .editorconfig's .json rule.
# Unchanged content means no diff, and therefore no PR.
printf '%s\n' "$new" > "$STATE_FILE"
# Diff across the union of old+new keys so an added, moved, or removed key is caught.
# Removals carry a null .new.
# These drive the PR title and body, naming only the keys that moved.
changed="$(jq -n --argjson old "$old" --argjson new "$new" '
[ (($old + $new) | keys[]) | { key: ., new: $new[.] } | select($old[.key] != .new) ]')"
summary="$(printf '%s' "$changed" | jq -r '
map(if .new == null then "\(.key) removed" else "\(.key) to \(.new)" end) | join(", ")')"
# Title: a canonicalization-only change, meaning the state was reserialized with no key moved.
# That happens when the prior file was valid but differently formatted.
# The trivial single-version case renders bare, otherwise each moved component is named.
if [ "$(printf '%s' "$changed" | jq 'length == 0')" = "true" ]; then
title="Canonicalize upstream version state file"
elif [ "$(printf '%s' "$new" | jq -r 'keys == ["version"]')" = "true" ]; then
title="Update upstream version to $(printf '%s' "$new" | jq -r '.version')"
else
title="Update upstream versions: $summary"
fi
if [ "$AUTO_MERGE" = "true" ]; then
head="${BUMP_BRANCH_PREFIX}-${BRANCH}"
else
head="no-auto-merge-${BUMP_BRANCH_PREFIX}-${BRANCH}"
fi
{
echo "head=$head"
echo "title=$title"
echo "body<<EOF"
if [ "$(printf '%s' "$changed" | jq 'length == 0')" = "true" ]; then
echo "Re-serialize the upstream-version state file to canonical form; no version changed."
else
echo "Rolling upstream-version bump opened by the version tracker."
echo
printf '%s' "$changed" | jq -r '.[] | if .new == null then "- \(.key): removed" else "- \(.key): \(.new)" end'
fi
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Rolling PR: signed by the API, satisfies Require signed commits.
# Auto-merged by the merge-bot when the head matches one of its rules.
- name: Open bump pull request step
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ steps.app-token.outputs.token }}
base: ${{ matrix.branch }}
branch: ${{ steps.resolve.outputs.head }}
title: ${{ steps.resolve.outputs.title }}
commit-message: ${{ steps.resolve.outputs.title }}
body: ${{ steps.resolve.outputs.body }}
sign-commits: true
delete-branch: true