Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/upstream-release-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,10 @@ jobs:
# `renovate[bot]`, while `gh pr view`/`gh release view`
# return `app/toolhive-release-app` for the same class of
# identity.
# GitHub logins are case-insensitive; preserve their spelling
# in outputs while comparing bot names without case sensitivity.
is_bot() {
printf '%s\n' "$1" | grep -qE '(\[bot\]$|^app/|^github-actions|^stacklokbot$|^dependabot|^renovate|^copilot)'
printf '%s\n' "$1" | grep -qiE '(\[bot\]$|^app/|^github-actions|^stacklokbot$|^dependabot|^renovate|^copilot)'
}

# ---------- compare the release range ----------
Expand Down Expand Up @@ -557,8 +559,15 @@ jobs:
COMMIT_MESSAGE=$(printf '%s' "$COMMIT_JSON" | jq -r '.commit.message // ""' 2>/dev/null || true)
# Squash merges can separate this trailer from Co-authored-by
# with a blank line, so inspect the complete commit message.
INITIATOR=$(printf '%s\n' "$COMMIT_MESSAGE" \
| sed -n 's/^Release-Triggered-By:[[:blank:]]*//p')
# Count before extraction: command substitution strips empty
# trailing values, which could hide an empty duplicate trailer.
INITIATOR_COUNT=$(printf '%s\n' "$COMMIT_MESSAGE" \
| grep -c '^Release-Triggered-By:' || true)
INITIATOR=""
if [ "$INITIATOR_COUNT" -eq 1 ]; then
INITIATOR=$(printf '%s\n' "$COMMIT_MESSAGE" \
| sed -n 's/^Release-Triggered-By:[[:blank:]]*//p')
fi
# Accept a single GitHub login. Missing, malformed, duplicate,
# or bot trailers fall through to the existing owner sources.
if [[ "$INITIATOR" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$ ]] && ! is_bot "$INITIATOR"; then
Expand Down
25 changes: 25 additions & 0 deletions scripts/upstream-release/workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,15 @@ for (const annotated of [true, false]) {
for (const message of [
'Release v0.60.0',
'Release v0.60.0\n\nRelease-Triggered-By: renovate',
'Release v0.60.0\n\nRelease-Triggered-By: Renovate',
'Release v0.60.0\n\nRelease-Triggered-By: StacklokBot',
'Release v0.60.0\n\nRelease-Triggered-By: toolhive-release-app[bot]',
'Release v0.60.0\n\nRelease-Triggered-By: app/toolhive-release-app',
'Release v0.60.0\n\nRelease-Triggered-By: invalid login',
'Release v0.60.0\n\nRelease-Triggered-By: alice\nRelease-Triggered-By: bob',
'Release v0.60.0\n\nRelease-Triggered-By: alice\nRelease-Triggered-By:',
'Release v0.60.0\n\nRelease-Triggered-By:\nRelease-Triggered-By: alice',
'Release v0.60.0\n\nRelease-Triggered-By: alice\nRelease-Triggered-By: \t',
'Release v0.60.0\n\nDocumentation mentions Release-Triggered-By: alice',
]) {
test(`falls back to the release PR merger for unusable metadata: ${JSON.stringify(message)}`, (t) => {
Expand Down Expand Up @@ -244,3 +249,23 @@ test('leaves the owner unresolved when all sources are bots', (t) => {
assert.equal(meta.owner, null);
assert.equal(meta.owner_source, null);
});

for (const source of ['releaseAuthor', 'merger', 'commitAuthor']) {
test(`rejects mixed-case bots in the ${source} fallback`, (t) => {
const meta = resolveOwner(t, {
message: '',
releaseAuthor: 'app/release-bot',
merger: 'app/release-bot',
commitAuthor: 'app/release-bot',
[source]: 'Renovate',
});
assert.equal(meta.owner, null);
});
}

test('preserves the case of a human initiator login', (t) => {
const meta = resolveOwner(t, {
message: 'Release v0.60.0\n\nRelease-Triggered-By: ChrisJBurns',
});
assert.equal(meta.owner, 'ChrisJBurns');
});
Loading