Skip to content

release: import CommonGround Kernel v3 public baseline #1

release: import CommonGround Kernel v3 public baseline

release: import CommonGround Kernel v3 public baseline #1

Workflow file for this run

name: Publish Package and Release Artifacts
on:
push:
branches:
- main
paths:
- VERSION
workflow_dispatch:
inputs:
tag:
description: Existing release tag to reconcile, for example v3.1.0.
required: true
type: string
publish_to_pypi:
description: Publish artifacts to PyPI before syncing the GitHub Release
required: true
default: false
type: boolean
sync_github_release:
description: Create or repair the GitHub Release and release assets
required: true
default: true
type: boolean
concurrency:
group: publish-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.sha }}
cancel-in-progress: false
permissions:
contents: read
jobs:
resolve:
name: Resolve Release Target
runs-on: ubuntu-latest
outputs:
release_version: ${{ steps.target.outputs.release_version }}
release_tag: ${{ steps.target.outputs.release_tag }}
target_commit: ${{ steps.target.outputs.target_commit }}
checkout_ref: ${{ steps.target.outputs.checkout_ref }}
create_tag: ${{ steps.target.outputs.create_tag }}
publish_to_pypi: ${{ steps.target.outputs.publish_to_pypi }}
sync_github_release: ${{ steps.target.outputs.sync_github_release }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Resolve release target
id: target
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
RAW_TAG="${{ inputs.tag }}"
RELEASE_TAG="${RAW_TAG#refs/tags/}"
RELEASE_TAG="v${RELEASE_TAG#v}"
if ! git ls-remote --exit-code --refs --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
echo "::error::Tag $RELEASE_TAG does not exist on origin."
exit 1
fi
git fetch --tags origin
TARGET_COMMIT="$(git rev-list -n 1 "$RELEASE_TAG")"
RELEASE_VERSION="${RELEASE_TAG#v}"
CREATE_TAG="false"
PUBLISH_TO_PYPI="${{ inputs.publish_to_pypi }}"
SYNC_GITHUB_RELEASE="${{ inputs.sync_github_release }}"
else
RELEASE_VERSION="$(sed -n '1{s/[[:space:]]*$//;p;q}' VERSION)"
RELEASE_TAG="v$RELEASE_VERSION"
TARGET_COMMIT="${GITHUB_SHA}"
EXISTING_SHA="$(git ls-remote --tags origin "refs/tags/$RELEASE_TAG^{}" | awk 'NR==1 {print $1}')"
if [ -z "$EXISTING_SHA" ]; then
EXISTING_SHA="$(git ls-remote --refs --tags origin "refs/tags/$RELEASE_TAG" | awk 'NR==1 {print $1}')"
fi
if [ -n "$EXISTING_SHA" ] && [ "$EXISTING_SHA" != "$TARGET_COMMIT" ]; then
echo "::error::Tag $RELEASE_TAG already exists at $EXISTING_SHA, expected $TARGET_COMMIT."
exit 1
fi
if [ -n "$EXISTING_SHA" ]; then
CREATE_TAG="false"
else
CREATE_TAG="true"
fi
PUBLISH_TO_PYPI="true"
SYNC_GITHUB_RELEASE="true"
fi
if [ -z "$RELEASE_VERSION" ]; then
echo "::error::Resolved release version is empty."
exit 1
fi
echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "target_commit=$TARGET_COMMIT" >> "$GITHUB_OUTPUT"
echo "checkout_ref=$TARGET_COMMIT" >> "$GITHUB_OUTPUT"
echo "create_tag=$CREATE_TAG" >> "$GITHUB_OUTPUT"
echo "publish_to_pypi=$PUBLISH_TO_PYPI" >> "$GITHUB_OUTPUT"
echo "sync_github_release=$SYNC_GITHUB_RELEASE" >> "$GITHUB_OUTPUT"
build:
name: Build Release Artifacts
runs-on: ubuntu-latest
needs: resolve
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ needs.resolve.outputs.checkout_ref }}
submodules: recursive
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: false
- name: Check patch hygiene
run: git diff --check
- name: Validate release version metadata
run: >
uv run --with packaging python scripts/release/sync_release_version.py
--version "${{ needs.resolve.outputs.release_version }}"
--check
- name: Compile Python sources
run: uv run python -m compileall CommonGround Integrations tests scripts
- name: Run lightweight package tests
run: >
uv run --extra server --with pytest --with packaging python -m pytest
tests/test_doc_hygiene.py
tests/test_package_version.py
tests/test_release_tag_version.py
tests/test_release_version_sync.py
tests/test_project_setup_package_resources.py
tests/test_contracts_cardbox_boundary.py
-q
- name: Clean previous build artifacts
run: rm -rf build dist
- name: Build package artifacts
run: uv build --no-sources
- name: Check artifact metadata
run: uvx twine check dist/*
- name: Verify built version matches release tag
run: >
uv run --with packaging python scripts/release/check_dist_tag_version.py
--tag "${{ needs.resolve.outputs.release_tag }}"
- name: Smoke test built wheel
run: bash ./scripts/smoke_test_built_cli.sh dist/commonground_kernel-*.whl
- name: Smoke test built sdist
run: bash ./scripts/smoke_test_built_cli.sh dist/commonground_kernel-*.tar.gz
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: dist/*
if-no-files-found: error
create_tag:
name: Create Release Tag
runs-on: ubuntu-latest
needs:
- resolve
- build
if: ${{ needs.resolve.outputs.create_tag == 'true' }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Create and push release tag
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${{ needs.resolve.outputs.release_tag }}" "${{ needs.resolve.outputs.target_commit }}" -m "Release ${{ needs.resolve.outputs.release_tag }}"
git push origin "refs/tags/${{ needs.resolve.outputs.release_tag }}"
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
needs:
- resolve
- build
- create_tag
if: >-
${{
needs.resolve.outputs.publish_to_pypi == 'true'
&& needs.build.result == 'success'
&& (needs.create_tag.result == 'success' || needs.create_tag.result == 'skipped')
}}
environment:
name: pypi
url: https://pypi.org/p/commonground-kernel
steps:
- name: Require PyPI API token
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: |
if [ -z "${PYPI_API_TOKEN}" ]; then
echo "Missing environment secret PYPI_API_TOKEN for the pypi environment." >&2
exit 1
fi
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
path: dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
sync_github_release:
name: Sync GitHub Release
runs-on: ubuntu-latest
needs:
- resolve
- build
- create_tag
- publish
if: >-
${{
needs.resolve.outputs.sync_github_release == 'true'
&& needs.build.result == 'success'
&& (needs.create_tag.result == 'success' || needs.create_tag.result == 'skipped')
&& (needs.publish.result == 'success' || needs.publish.result == 'skipped')
}}
permissions:
contents: write
steps:
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
path: dist
- name: Sync GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
retry() {
local attempts="$1"
local sleep_seconds="$2"
shift 2
local attempt=1
while true; do
if "$@"; then
return 0
fi
if [ "$attempt" -ge "$attempts" ]; then
echo "Command failed after ${attempts} attempts: $*" >&2
return 1
fi
echo "Retrying (${attempt}/${attempts}) after transient failure: $*" >&2
sleep "$sleep_seconds"
attempt=$((attempt + 1))
done
}
ensure_release() {
if retry 3 5 gh release view "$RELEASE_TAG" --json url >/dev/null; then
echo "Release ${RELEASE_TAG} already exists."
return 0
fi
retry 3 5 gh release create "$RELEASE_TAG" --verify-tag --generate-notes
}
ensure_release
mapfile -t existing_assets < <(
retry 3 5 gh release view "$RELEASE_TAG" --json assets --jq '.assets[].name'
)
for asset in dist/*.tar.gz dist/*.whl; do
asset_name="$(basename "$asset")"
if printf '%s\n' "${existing_assets[@]}" | grep -Fxq "$asset_name"; then
echo "Release asset already present: $asset_name"
continue
fi
retry 3 5 gh release upload "$RELEASE_TAG" "$asset"
done