v2 Stage 5: canonical schema v2 (1.0.0 / schema 2.0.0) — docs, versio… #23
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Python uv Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| permissions: | |
| contents: write # required for GitHub Release | |
| id-token: write # required for PyPI Trusted Publishing | |
| actions: write # required for tag deletion | |
| discussions: write # attach the release-linked repo Discussion (Announcements) | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.10 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.10' | |
| - name: Install uv | |
| run: | | |
| curl -LsSf https://astral.sh/uv/install.sh | sh | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Sync dependencies | |
| run: uv sync --all-groups | |
| - name: Install dependencies | |
| run: uv pip install -e ".[neo4j]" | |
| # Keep generated docs in lockstep with the code being released: regenerate the | |
| # README `canpy --help` block and the Neo4j schema.json from source, and commit | |
| # them back to main. Releases are cut from main HEAD, so this fast-forwards; | |
| # best-effort if main moved. | |
| - name: Sync generated docs (README --help + Neo4j schema) | |
| if: startsWith(github.ref, 'refs/tags/') | |
| run: | | |
| uv run python scripts/update_readme.py | |
| uv run canpy --emit schema > schema.neo4j.json | |
| if git diff --quiet README.md schema.neo4j.json; then | |
| echo "Generated docs already current." | |
| else | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add README.md schema.neo4j.json | |
| git commit -m "docs: sync README --help and Neo4j schema for ${GITHUB_REF#refs/tags/}" | |
| git push origin HEAD:main || echo "::warning::could not push doc sync to main (diverged?)" | |
| fi | |
| - name: Run tests | |
| id: test | |
| continue-on-error: true | |
| run: uv run pytest | |
| - name: Delete tag on failure | |
| if: steps.test.conclusion == 'failure' | |
| run: | | |
| echo "Tests failed. Deleting tag ${GITHUB_REF#refs/tags/}..." | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git push --delete origin ${GITHUB_REF#refs/tags/} | |
| exit 1 | |
| - name: Build package | |
| run: uv build | |
| # Platform-independent, version-locked release assets published alongside the | |
| # wheels/sdist: the Neo4j schema contract (so a consumer can validate | |
| # producer/consumer compatibility without installing the package) and the | |
| # cargo-dist-style install script. | |
| - name: Stage release assets (Neo4j schema + installer script) | |
| run: | | |
| mkdir -p release-assets | |
| uv run canpy --emit schema > release-assets/schema.json | |
| cp packaging/install/canpy-installer.sh release-assets/canpy-installer.sh | |
| ls -lh release-assets | |
| - name: Get version from tag | |
| id: tag_name | |
| run: | | |
| echo "current_version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT | |
| shell: bash | |
| # cargo-dist-style notes: install one-liners + a download table. The categorized | |
| # "What's Changed" (merged PRs/issues grouped under emoji headings via | |
| # .github/release.yml) is appended by generate_release_notes below. Indented code | |
| # blocks avoid backticks in the heredoc. | |
| - name: Compose release notes header (install + download) | |
| env: | |
| VERSION: ${{ steps.tag_name.outputs.current_version }} | |
| run: | | |
| REPO="codellm-devkit/codeanalyzer-python" | |
| BASE="https://github.com/$REPO/releases/download/v$VERSION" | |
| cat > "$RUNNER_TEMP/RELEASE_BODY.md" <<EOF | |
| ## Install codeanalyzer-python v$VERSION | |
| Shell script (installs the canpy CLI via uv / pipx / pip): | |
| curl --proto '=https' --tlsv1.2 -LsSf https://github.com/$REPO/releases/latest/download/canpy-installer.sh | sh | |
| PyPI: | |
| pip install codeanalyzer-python==$VERSION | |
| For the optional live Neo4j push (--emit neo4j --neo4j-uri ...): | |
| pip install 'codeanalyzer-python[neo4j]==$VERSION' | |
| ## Download | |
| | File | Description | | |
| | --- | --- | | |
| | [codeanalyzer_python-$VERSION-py3-none-any.whl]($BASE/codeanalyzer_python-$VERSION-py3-none-any.whl) | Python wheel | | |
| | [codeanalyzer_python-$VERSION.tar.gz]($BASE/codeanalyzer_python-$VERSION.tar.gz) | Source distribution | | |
| | [canpy-installer.sh]($BASE/canpy-installer.sh) | Shell installer (uv / pipx / pip) | | |
| | [schema.json]($BASE/schema.json) | Neo4j schema contract | | |
| EOF | |
| echo "----- composed header -----"; cat "$RUNNER_TEMP/RELEASE_BODY.md" | |
| - name: Publish release on GitHub | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| dist/* | |
| release-assets/* | |
| body_path: ${{ runner.temp }}/RELEASE_BODY.md | |
| generate_release_notes: true # appends categorized "What's Changed" (see .github/release.yml) | |
| # Auto-open a repo-level Discussion linked to this release, seeded with | |
| # the same notes. Requires Discussions enabled and this category to exist. | |
| discussion_category_name: Announcements | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Mirror the release announcement into the ORG-level discussions, which are | |
| # backed by codellm-devkit/.github. GITHUB_TOKEN can't write cross-repo, so | |
| # this uses a PAT (ORG_DISCUSSIONS_TOKEN) with repo scope, and posts via the | |
| # createDiscussion GraphQL mutation. Reuses the already-composed RELEASE_BODY.md | |
| # so org and repo posts stay identical. | |
| - name: Announce in org-level discussions (codellm-devkit/.github) | |
| if: startsWith(github.ref, 'refs/tags/') | |
| continue-on-error: true # a failed org post must not fail an otherwise-good release | |
| env: | |
| GH_TOKEN: ${{ secrets.ORG_DISCUSSIONS_TOKEN }} | |
| VERSION: ${{ steps.tag_name.outputs.current_version }} | |
| run: | | |
| set -uo pipefail | |
| OWNER="codellm-devkit"; REPO=".github"; CATEGORY="Announcements" | |
| # The mutation needs GraphQL node IDs, not names — resolve them first. | |
| RESP=$(gh api graphql \ | |
| -f query='query($o:String!,$r:String!){repository(owner:$o,name:$r){id discussionCategories(first:25){nodes{id name}}}}' \ | |
| -f o="$OWNER" -f r="$REPO") \ | |
| || { echo "::warning::org discussion lookup failed — skipping org announcement."; exit 0; } | |
| REPO_ID=$(echo "$RESP" | jq -r '.data.repository.id') | |
| CAT_ID=$(echo "$RESP" | jq -r --arg c "$CATEGORY" '.data.repository.discussionCategories.nodes[]|select(.name==$c)|.id') | |
| if [[ -z "$REPO_ID" || "$REPO_ID" == "null" || -z "$CAT_ID" ]]; then | |
| echo "::warning::could not resolve $OWNER/$REPO discussion category '$CATEGORY' — skipping org announcement." | |
| exit 0 | |
| fi | |
| gh api graphql \ | |
| -f query='mutation($rid:ID!,$cid:ID!,$t:String!,$b:String!){createDiscussion(input:{repositoryId:$rid,categoryId:$cid,title:$t,body:$b}){discussion{url}}}' \ | |
| -f rid="$REPO_ID" -f cid="$CAT_ID" \ | |
| -f t="codeanalyzer-python v$VERSION" \ | |
| -f b="$(cat "$RUNNER_TEMP/RELEASE_BODY.md")" | |
| - name: Publish to PyPI via Trusted Publishing | |
| run: uv publish | |
| # Regenerate the Homebrew formula and push it to the shared tap. Split into its | |
| # own job (needs: release) so a tap-push failure -- e.g. a missing | |
| # HOMEBREW_TAP_TOKEN -- is isolated from the PyPI and GitHub Release steps above. | |
| # The non-Rust equivalent of what cargo-dist does for you. | |
| homebrew: | |
| needs: release | |
| if: startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| - name: Derive version from tag | |
| id: ver | |
| run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" | |
| - name: Generate Homebrew formula | |
| env: | |
| REPO: ${{ github.repository }} | |
| VERSION: ${{ steps.ver.outputs.version }} | |
| run: | | |
| chmod +x packaging/homebrew/generate_formula.sh | |
| # The release job just published the sdist as a Release asset; hash the | |
| # exact bytes users will download so the formula checksum always matches. | |
| sdist="https://github.com/${REPO}/releases/download/v${VERSION}/codeanalyzer_python-${VERSION}.tar.gz" | |
| SHA256="$(curl -fLsS "$sdist" | shasum -a 256 | cut -d' ' -f1)" | |
| REPO="$REPO" VERSION="$VERSION" SHA256="$SHA256" \ | |
| ./packaging/homebrew/generate_formula.sh > codeanalyzer-python.rb | |
| cat codeanalyzer-python.rb | |
| - name: Push formula to codellm-devkit/homebrew-tap | |
| env: | |
| TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} # PAT with write access to homebrew-tap | |
| VERSION: ${{ steps.ver.outputs.version }} | |
| run: | | |
| git clone "https://x-access-token:${TAP_TOKEN}@github.com/codellm-devkit/homebrew-tap.git" tap | |
| mkdir -p tap/Formula | |
| cp codeanalyzer-python.rb tap/Formula/codeanalyzer-python.rb | |
| cd tap | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/codeanalyzer-python.rb | |
| git commit -m "codeanalyzer-python ${VERSION}" || { echo "no formula change"; exit 0; } | |
| git push | |
| # Open a tracking issue on the python-sdk (CLDK) consumer so its | |
| # codeanalyzer-python pin and integration can be brought up to this release. | |
| # The issue body is the CHANGELOG section for this version. Split into its own | |
| # job (needs: release) so a failure here -- e.g. a missing SDK_ISSUE_TOKEN -- | |
| # is isolated from the PyPI and GitHub Release steps above. Issues are | |
| # repository-scoped (not branch-scoped), so this opens one on python-sdk. | |
| notify-python-sdk: | |
| needs: release | |
| if: startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| # Pull the lines under "## [VERSION]" up to the next "## [" heading. | |
| - name: Extract changelog section for this release | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| { | |
| echo "A new \`codeanalyzer-python\` release (\`v${VERSION}\`) is published." | |
| echo "Update the \`codeanalyzer-python\` pin in this repo and adapt the" | |
| echo "integration to the changes below (see \`PyCodeanalyzer._run_analyzer\`)." | |
| echo | |
| awk -v ver="$VERSION" ' | |
| $0 ~ ("^## \\[" ver "\\]") { f = 1; print; next } | |
| f && /^## \[/ { exit } | |
| f { print } | |
| ' CHANGELOG.md | |
| } > "$RUNNER_TEMP/sdk_issue.md" | |
| echo "----- issue body -----"; cat "$RUNNER_TEMP/sdk_issue.md" | |
| - name: Create incorporation issue in python-sdk | |
| env: | |
| # CLDK_AUTH_TOKEN is a PAT with repo + discussions scope, reused here for the | |
| # cross-repo issue write. The default GITHUB_TOKEN is scoped to this repo only | |
| # and cannot open issues cross-repo, mirroring HOMEBREW_TAP_TOKEN above. | |
| GH_TOKEN: ${{ secrets.CLDK_AUTH_TOKEN }} | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| gh issue create \ | |
| --repo codellm-devkit/python-sdk \ | |
| --title "Incorporate codeanalyzer-python v${VERSION}" \ | |
| --body-file "$RUNNER_TEMP/sdk_issue.md" |