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
124 changes: 124 additions & 0 deletions .github/workflows/release-tags.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Move release tags

on:
push:
branches:
- main
paths:
- .release-source.json

permissions:
contents: write

concurrency:
group: release-tags-main
cancel-in-progress: false

jobs:
move-release-tags:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Skip non-release commits
id: release-metadata
run: |
set -euo pipefail

if [[ ! -f .release-source.json ]]; then
echo "release_commit=false" >> "$GITHUB_OUTPUT"
exit 0
fi

version="$(jq -r '.version' .release-source.json)"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid release version in .release-source.json: $version"
exit 1
fi

echo "release_commit=true" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "source_sha=$(jq -r '.sourceSha' .release-source.json)" >> "$GITHUB_OUTPUT"
echo "major=${version%%.*}" >> "$GITHUB_OUTPUT"
echo "minor=${version%.*}" >> "$GITHUB_OUTPUT"

- name: Update floating and exact version tags
if: ${{ steps.release-metadata.outputs.release_commit == 'true' }}
env:
VERSION: ${{ steps.release-metadata.outputs.version }}
SOURCE_SHA: ${{ steps.release-metadata.outputs.source_sha }}
MAJOR: ${{ steps.release-metadata.outputs.major }}
MINOR: ${{ steps.release-metadata.outputs.minor }}
run: |
set -euo pipefail

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git tag -fa "v${VERSION}" -m "Release v${VERSION}"
git tag -fa "v${MINOR}" -m "Release v${MINOR}"
git tag -fa "v${MAJOR}" -m "Release v${MAJOR}"

git fetch origin main
main_version="$(git show origin/main:.release-source.json | jq -r '.version')"
main_source_sha="$(git show origin/main:.release-source.json | jq -r '.sourceSha')"
if [[ "$VERSION" != "$main_version" || "$SOURCE_SHA" != "$main_source_sha" ]]; then
echo "Skipping stale tag update for release $VERSION; origin/main now points to $main_version from $main_source_sha"
exit 0
fi

git push --force origin "refs/tags/v${VERSION}" "refs/tags/v${MINOR}" "refs/tags/v${MAJOR}"
Comment thread
jbeckwith-oai marked this conversation as resolved.

- name: Create or update GitHub release
if: ${{ steps.release-metadata.outputs.release_commit == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release-metadata.outputs.version }}
SOURCE_SHA: ${{ steps.release-metadata.outputs.source_sha }}
run: |
set -euo pipefail

notes_file="$RUNNER_TEMP/release-notes.md"
awk -v version="$VERSION" '
$0 ~ "^## \\[" version "\\]" {
in_section=1
next
}
in_section && /^## \[/ {
exit
}
in_section {
print
}
' CHANGELOG.md > "$notes_file"

if [[ ! -s "$notes_file" ]]; then
printf 'Release v%s\n' "$VERSION" > "$notes_file"
fi

git fetch origin main
main_version="$(git show origin/main:.release-source.json | jq -r '.version')"
main_source_sha="$(git show origin/main:.release-source.json | jq -r '.sourceSha')"
if [[ "$VERSION" != "$main_version" || "$SOURCE_SHA" != "$main_source_sha" ]]; then
echo "Skipping stale release update for release $VERSION; origin/main now points to $main_version from $main_source_sha"
exit 0
fi

if gh release view "v${VERSION}" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release edit "v${VERSION}" \
--repo "$GITHUB_REPOSITORY" \
--title "v${VERSION}" \
--notes-file "$notes_file" \
--target "$GITHUB_SHA" \
--verify-tag
else
gh release create "v${VERSION}" \
--repo "$GITHUB_REPOSITORY" \
--title "v${VERSION}" \
--notes-file "$notes_file" \
--target "$GITHUB_SHA" \
--verify-tag
fi
116 changes: 116 additions & 0 deletions .github/workflows/validate-release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Validate release PR

on:
pull_request:
branches:
- main

permissions:
contents: read

jobs:
validate-release-pr:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Validate maintenance PR scope
if: ${{ !startsWith(github.head_ref, 'release/code-scan-action-v') }}
run: |
set -euo pipefail

while IFS= read -r file; do
[[ -z "$file" ]] && continue
if [[ ! "$file" =~ ^\.github/ ]]; then
echo "Maintenance PRs may only change .github/* files. Unexpected file: $file"
exit 1
fi
done < <(git diff --name-only origin/main...HEAD)

echo "Branch ${{ github.head_ref }} is not a generated release branch; artifact mirror files are unchanged."

- name: Validate generated release payload
if: ${{ startsWith(github.head_ref, 'release/code-scan-action-v') }}
env:
HEAD_REF: ${{ github.head_ref }}
run: |
set -euo pipefail

if [[ ! -f .release-source.json ]]; then
echo "Missing .release-source.json in generated release PR"
exit 1
fi

jq -e '
.repository == "promptfoo/promptfoo"
and .packagePath == "code-scan-action"
and (.sourceSha | test("^[0-9a-f]{40}$"))
and (.sourceTag | type == "string" and length > 0)
and (.version | test("^[0-9]+\\.[0-9]+\\.[0-9]+$"))
' .release-source.json > /dev/null

version="$(jq -r '.version' .release-source.json)"
source_sha="$(jq -r '.sourceSha' .release-source.json)"
source_tag="$(jq -r '.sourceTag' .release-source.json)"

if [[ "$HEAD_REF" != "release/code-scan-action-v${version}" ]]; then
echo "Branch name $HEAD_REF does not match release version $version"
exit 1
fi

if [[ "$source_tag" != "code-scan-action-${version}" ]]; then
echo "Source tag $source_tag does not match release version $version"
exit 1
fi

while IFS= read -r file; do
[[ -z "$file" ]] && continue
if [[ ! "$file" =~ ^(action\.yml|README\.md|CHANGELOG\.md|\.release-source\.json|dist(/.*)?)$ ]]; then
echo "Unexpected generated release file: $file"
exit 1
fi
done < <(git diff --name-only origin/main...HEAD)

source_dir="$RUNNER_TEMP/promptfoo-source"
expected_dir="$RUNNER_TEMP/code-scan-action-expected"

rm -rf "$source_dir" "$expected_dir"
git clone https://github.com/promptfoo/promptfoo.git "$source_dir"

cd "$source_dir"
git checkout --detach "$source_sha"
git merge-base --is-ancestor "$source_sha" origin/main

resolved_source_tag_sha="$(git rev-parse "${source_tag}^{commit}")"
if [[ "$resolved_source_tag_sha" != "$source_sha" ]]; then
echo "Source tag $source_tag points to $resolved_source_tag_sha, expected $source_sha"
exit 1
fi

npm install -g npm@latest
npm ci
npm ci --prefix code-scan-action
npm run tsc --prefix code-scan-action
npm run build --prefix code-scan-action

mkdir -p "$expected_dir"
cp code-scan-action/action.yml "$expected_dir/action.yml"
cp code-scan-action/README.md "$expected_dir/README.md"
cp code-scan-action/CHANGELOG.md "$expected_dir/CHANGELOG.md"
cp -R code-scan-action/dist "$expected_dir/dist"
cp "$GITHUB_WORKSPACE/.release-source.json" "$expected_dir/.release-source.json"

for file in action.yml README.md CHANGELOG.md .release-source.json; do
if ! diff -u "$expected_dir/$file" "$GITHUB_WORKSPACE/$file"; then
echo "Generated mirror file $file does not match promptfoo/promptfoo@$source_sha"
exit 1
fi
done

if ! diff -qr "$expected_dir/dist" "$GITHUB_WORKSPACE/dist"; then
echo "Generated mirror dist/ does not match promptfoo/promptfoo@$source_sha"
exit 1
fi
Loading