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
70 changes: 63 additions & 7 deletions .github/workflows/sdk-release-finalize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ on:
description: "The origin release tag created or verified by this workflow, or the unverified input when origin tag reconciliation is disabled."
value: ${{ jobs.finalize.outputs.origin_tag }}
origin_tag_created:
description: "true when a new origin release tag was pushed; false when it already existed or origin tag reconciliation is disabled."
description: "true when the origin release tag was created or moved; false when it already existed or origin tag reconciliation is disabled."
value: ${{ jobs.finalize.outputs.origin_tag_created }}
fern_generation_tag:
description: "The Fern replay generation tag created or verified by this workflow, or empty when replay.lock is absent or Fern generation preservation is disabled."
Expand Down Expand Up @@ -115,6 +115,10 @@ jobs:
echo "::error::$label must not begin with '-': $tag"
exit 1
fi
if [[ "$tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then
echo "::error::$label must not begin with a 40-character SHA: $tag"
exit 1
fi
Comment thread
cursor[bot] marked this conversation as resolved.
if ! git check-ref-format "refs/tags/$tag"; then
echo "::error::Invalid $label: $tag"
exit 1
Expand Down Expand Up @@ -219,7 +223,23 @@ jobs:
set -euo pipefail

target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}")
existing_commit=$(git rev-parse -q --verify "refs/tags/${ORIGIN_TAG}^{commit}" || true)
existing_ref=""
existing_commit=""
if git ls-remote --exit-code --refs origin "refs/tags/${ORIGIN_TAG}" >/dev/null; then
git fetch --force --no-tags origin "refs/tags/${ORIGIN_TAG}:refs/tags/${ORIGIN_TAG}"
existing_ref=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}")
existing_commit=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}^{commit}")
else
status=$?
if [[ "$status" -ne 2 ]]; then
echo "::error::Could not read remote tag $ORIGIN_TAG from origin."
exit "$status"
fi
git tag -d "$ORIGIN_TAG" 2>/dev/null || true
fi

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

if [[ -n "$existing_commit" ]]; then
if [[ "$existing_commit" == "$target_commit" ]]; then
Expand All @@ -228,13 +248,25 @@ jobs:
exit 0
fi

echo "::error::Tag $ORIGIN_TAG already points at $existing_commit, not $target_commit."
if git merge-base --is-ancestor "$existing_commit" "$target_commit"; then
echo "Moving tag $ORIGIN_TAG from $existing_commit to $target_commit."
git tag -f -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT"
git push --force-with-lease="refs/tags/$ORIGIN_TAG:$existing_ref" origin "refs/tags/$ORIGIN_TAG"
echo "created=true" >> "$GITHUB_OUTPUT"
exit 0
fi

if git merge-base --is-ancestor "$target_commit" "$existing_commit"; then
echo "Tag $ORIGIN_TAG already points at newer release commit $existing_commit; leaving it in place."
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "::error::Tag $ORIGIN_TAG already points at divergent commit $existing_commit, not $target_commit."
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$ORIGIN_TAG" "$target_commit" -m "Release $VERSION_TAG for Fern originGitCommit $ORIGIN_COMMIT"
git tag -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT"
git push origin "refs/tags/$ORIGIN_TAG"

echo "created=true" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -334,6 +366,30 @@ jobs:
echo "$label tag $tag points at $expected_commit."
}

verify_moving_tag() {
local label="$1"
local tag="$2"
local expected_commit="$3"
local actual_commit

actual_commit=$(git rev-parse -q --verify "refs/tags/${tag}^{commit}" || true)
if [[ -z "$actual_commit" ]]; then
echo "::error::$label tag $tag does not exist."
exit 1
fi
if [[ "$actual_commit" == "$expected_commit" ]]; then
echo "$label tag $tag points at $expected_commit."
return
fi
if git merge-base --is-ancestor "$expected_commit" "$actual_commit"; then
echo "$label tag $tag points at newer release commit $actual_commit."
return
fi

echo "::error::$label tag $tag points at $actual_commit, not $expected_commit or a newer release commit."
exit 1
}

target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}")
git fetch --force --tags origin

Expand All @@ -349,7 +405,7 @@ jobs:
fi

if [[ "$CREATE_ORIGIN_TAG" == "true" ]]; then
verify_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit"
verify_moving_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit"
fi

if [[ "$PRESERVE_FERN_GENERATION" == "true" ]]; then
Expand Down
13 changes: 5 additions & 8 deletions .github/workflows/sdk-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ on:
description: "Optional prefix to prepend to the SDK version tag."
type: string
default: ""
origin-tag-prefix:
description: "Optional prefix to prepend to the origin tag, before originGitCommit/version_tag."
type: string
default: ""
outputs:
should_publish:
description: "true only when the version tag did not already exist and callers should publish."
Expand Down Expand Up @@ -81,7 +77,6 @@ jobs:
SPEC_PATH: ${{ inputs.spec-path }}
METADATA_PATH: ${{ inputs.metadata-path }}
VERSION_TAG_PREFIX: ${{ inputs.version-tag-prefix }}
ORIGIN_TAG_PREFIX: ${{ inputs.origin-tag-prefix }}
steps:
- name: Checkout release commit
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand Down Expand Up @@ -133,6 +128,10 @@ jobs:
echo "::error::Version tag must not begin with '-': $version_tag"
exit 1
fi
if [[ "$version_tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then
echo "::error::Version tag must not begin with a 40-character SHA: $version_tag"
exit 1
fi
if ! git check-ref-format "refs/tags/$version_tag"; then
echo "::error::Invalid version tag name: $version_tag"
exit 1
Expand All @@ -145,8 +144,6 @@ jobs:

- name: Read originGitCommit
id: origin
env:
VERSION_TAG: ${{ steps.version.outputs.version_tag }}
shell: bash
run: |
set -euo pipefail
Expand All @@ -162,7 +159,7 @@ jobs:
exit 1
fi

origin_tag="${ORIGIN_TAG_PREFIX}${origin_commit}/${VERSION_TAG}"
origin_tag="backend-origin/${origin_commit}"
if [[ "$origin_tag" == -* ]]; then
echo "::error::Origin tag must not begin with '-': $origin_tag"
exit 1
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ Shared GitHub Actions and reusable workflows for the Phenoml SDK repos.
shared pre-publish release gate for generated SDKs: extract the SDK version,
report whether the release should publish, repair metadata, or skip, read
Fern's `originGitCommit`, verify the bundled OpenAPI spec for new publishes,
and validate the origin release tag name. Version tags, origin release tags,
GitHub releases, package builds, and registry publish jobs stay in each SDK
repo.
and validate the prefixed `backend-origin/<originGitCommit>` release tag
name. Version tags, origin release tags, GitHub releases, package builds,
and registry publish jobs stay in each SDK repo.
- [`sdk-release-finalize`](.github/workflows/sdk-release-finalize.yml) —
reconciles release metadata after an SDK package publish succeeds: it ensures
the normal SDK version tag/release, the Fern origin release tag, and, when
Expand Down