Skip to content

chore: migrate release pipeline to semantic-release (ruby-sdk parity) - #50

Merged
abbaseya merged 1 commit into
mainfrom
chore/semantic-release-migration
Jun 18, 2026
Merged

chore: migrate release pipeline to semantic-release (ruby-sdk parity)#50
abbaseya merged 1 commit into
mainfrom
chore/semantic-release-migration

Conversation

@abbaseya

Copy link
Copy Markdown
Collaborator

Summary

Migrates the python-sdk release pipeline from the manual version-bump + v*-tag-trigger + towncrier-committed-changelog model to the commit-driven semantic-release model that mirrors ruby-sdk (the ratified Convert SDK release standard). Also deletes the in-repo docs/ folder (the wiki is the canonical doc home) and sweeps every reference.

Implements qs-14 (ai-driven-product-dev_bmad-output/implementation-artifacts/2026-04-06-convert-python-sdk/qs-14-semantic-release-migration.md), a fully-resolved, human-approved quick spec. Supersedes qs-10 (towncrier) and renegotiates qs-11 (tag-trigger publish).

New release model

Merge a Conventional-Commit PR to main → on CI success, release.yml fires via workflow_run and runs three jobs:

  1. preparesemantic-release --dry-run computes the next version + notes, stamps version.py (uncommitted), uv build → wheel + sdist.
  2. publish-pypipypa/gh-action-pypi-publish uploads to PyPI via OIDC Trusted Publishing (environment: pypi, id-token: write, no tokens).
  3. release@semantic-release/github pushes the vX.Y.Z tag + creates the GitHub Release (notes = changelog), only after a successful upload (needs: [prepare, publish-pypi]).

Tag-only (no commit to main), publish-before-release, fork-PR safeguard (workflow_run.event == 'push'), and a load-bearing ['CI'] workflow-name coupling.

Changes

  • Dev-only Node tooling: package.json, release.config.mjs, .yarnrc.yml, generated yarn.lock (Berry). prepareCmd uses node -e (present in both jobs) to stamp version.py — avoids a brittle dependency on the runner's python symlink in the release job.
  • Workflows: release.yml rewritten to the 3-job workflow_run model; ci.yml drops the towncrier changelog job, adds a pr-title Conventional-Commits job, removes workflow_call, carries the coupling comment.
  • Removed committed-changelog machinery: [tool.towncrier] + dev-dep + mypy override, changes/, CHANGELOG.md, scripts/extract_release_notes.py, the verify_release.py towncrier gate. Added Changelog to [project.urls].
  • version.py0.0.0 dev placeholder (build stamps the real version).
  • New RELEASE.md (ruby-sdk parity, adapted to PyPI/semantic-release/pypa).
  • Deleted docs/ entirely + swept all references: README badges + wiki links, generalized docstrings (config.py, refresh.py, tracking/flush.py), deleted tests/test_docs_samples.py, prose-only docstring edits in two tests.

Verification (local)

Gate Result
yarn install --immutable PASS
yarn release:dry-run PASS (prints "configured to only publish from main" off-branch — expected)
git grep -n 'docs/' empty
git grep -n 'towncrier' empty
Project coverage 96.63% (floor 85%)
evaluation/ coverage 97% (floor 95%)
Test suite 816 passed
Built wheel/sdist contents no Node artifacts, no docs//changes//CHANGELOG.md

NOT verifiable locally (require a real merge to main)

  • Live OIDC PyPI publish — needs the PyPI Trusted Publisher registered (convertcom/python-sdk, workflow release.yml, environment pypi) and a real workflow_run from a CI success on main. The structure is verified; the live exchange is not.
  • workflow_run end-to-end trigger / publish-before-release skip behavior — only observable in GitHub Actions on main.
  • First-release versionsemantic-release will emit v1.0.0 on the first qualifying merge (no prior v* tag), replacing the previous hand-set 0.1.0. Flagged for review per qs-14 Decision B. If a 0.x series is wanted, pre-create the initial tag before the first merge.

One-time manual setup required before first release

  • Register the PyPI Trusted Publisher (owner convertcom, repo python-sdk, workflow release.yml, environment pypi).
  • Ensure the pypi GitHub Environment has no required reviewers / wait timers (else publish blocks). See RELEASE.md → One-Time Setup.

Related (separate repos, committed there — not in this PR)

  • ai-driven-product-dev (branch docs/python-sdk-semantic-release-quickspec): Phase 7 spec updates — qs-10 superseded, qs-11 renegotiated, Story 5.1 updated.
  • python-sdk.wiki (branch master): ReleaseProcess.md rewritten to the new model.

🤖 Generated with Claude Code

Replaces the manual version-bump + tag-trigger + towncrier-committed-changelog
release model with the commit-driven, zero-touch semantic-release model that
mirrors ruby-sdk: workflow_run-triggered on a successful CI run on main, version
computed from Conventional Commits, stamped into version.py at build time
(uncommitted), published to PyPI via OIDC Trusted Publishing (pypa), then only
the vX.Y.Z tag + a GitHub Release (notes = changelog) created after a successful
upload. No commit is ever pushed to main.

Implements qs-14 phases 1-6:
- Dev-only Node release tooling: package.json, release.config.mjs, .yarnrc.yml,
  generated yarn.lock (Berry). prepareCmd uses `node -e` (present in both jobs)
  to stamp version.py, avoiding a runner python-symlink dependency.
- release.yml rewritten to 3 jobs (prepare -> publish-pypi -> release) with the
  workflow_run trigger on ['CI'], twin if-guard, fork-PR safeguard, OIDC publish.
- ci.yml: removed the towncrier changelog job, added a PR-title Conventional
  Commits job, removed workflow_call, carried the load-bearing CI-name comment.
- Removed committed-changelog machinery: [tool.towncrier], towncrier dev-dep +
  mypy override, changes/, CHANGELOG.md, scripts/extract_release_notes.py, the
  verify_release.py towncrier gate. Added Changelog URL to [project.urls].
- version.py -> 0.0.0 dev placeholder (build stamps the real version).
- Added root RELEASE.md (ruby-sdk parity, adapted to PyPI/semantic-release/pypa).
- Deleted in-repo docs/ and swept every reference (README badges + wiki links,
  generalized docstrings, deleted test_docs_samples.py).

Verification: yarn install --immutable OK; yarn release:dry-run OK; coverage
96.63% project / 97% evaluation (floors 85%/95%); git grep docs/ and towncrier
both empty; built wheel/sdist exclude Node artifacts + docs/changes/CHANGELOG.

Spec: ai-driven-product-dev qs-14-semantic-release-migration.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@abbaseya abbaseya self-assigned this Jun 18, 2026
@abbaseya
abbaseya requested a review from usmanabbas7 June 18, 2026 19:50

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request transitions the project's release process from a manual, towncrier-based workflow to a fully automated pipeline using semantic-release and OIDC Trusted Publishing. It removes local documentation files (moving them to the project wiki), deletes towncrier configuration and news fragments, and introduces Node-based release tooling configuration (package.json, release.config.mjs, .yarnrc.yml). Additionally, __version__ is set to a "0.0.0" dev placeholder, which is stamped dynamically at build time. Feedback on the pull request highlights two key improvements in release.config.mjs: a security risk where expanding ${nextRelease.notes} directly inside a double-quoted shell string could lead to syntax errors or command injection, and a robustness issue where the regular expression used to stamp the version is too strict and could fail if formatting tools modify the quote style or spacing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread release.config.mjs
Comment on lines +97 to +98
verifyReleaseCmd:
'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi; if [ -n "$RELEASE_NOTES_FILE" ]; then printf \'%s\' "${nextRelease.notes}" > "$RELEASE_NOTES_FILE"; fi',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Expanding ${nextRelease.notes} directly inside a double-quoted shell string ("${nextRelease.notes}") is highly risky. If any commit message contains double quotes, backticks, or shell metacharacters (e.g., $()), it will cause shell syntax errors, breaking the release pipeline, or potentially lead to command injection. Since @semantic-release/github automatically handles release notes and publishing, writing them to a file is unnecessary and can be safely removed to eliminate this risk.

Suggested change
verifyReleaseCmd:
'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi; if [ -n "$RELEASE_NOTES_FILE" ]; then printf \'%s\' "${nextRelease.notes}" > "$RELEASE_NOTES_FILE"; fi',
verifyReleaseCmd:
'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi',

Comment thread release.config.mjs
Comment on lines +105 to +106
prepareCmd:
'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = "[^"]*"/,`__version__ = "${nextRelease.version}"`))\'',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regular expression /__version__ = "[^"]*"/ strictly expects double quotes and exact spacing around the assignment operator. If a developer or an automated formatter (like Ruff) changes the quote style to single quotes (e.g., __version__ = '0.0.0') or adjusts the spacing, the replacement will silently fail, resulting in the placeholder version 0.0.0 being built and published. Using a more flexible regex that supports both single and double quotes makes the version-stamping process much more robust.

Suggested change
prepareCmd:
'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = "[^"]*"/,`__version__ = "${nextRelease.version}"`))\'',
prepareCmd:
'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = ["\'][^"\']*["\']/,\'__version__ = "\' + nextRelease.version + \'"\'))\'',

@abbaseya
abbaseya merged commit d004e68 into main Jun 18, 2026
23 checks passed
@abbaseya
abbaseya deleted the chore/semantic-release-migration branch June 18, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant