From 93009fa898a46241f86a389fb0d0b5ba4b79070d Mon Sep 17 00:00:00 2001 From: Arnav Goel Date: Sat, 25 Apr 2026 18:50:23 -0400 Subject: [PATCH 1/2] docs(readme): make hero banner full width Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d365c0..833e593 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -

- ks-xlsx-parser -

+ks-xlsx-parser

Star on GitHub From f68c923971aacf6961d810260dbe4ae382cb9069 Mon Sep 17 00:00:00 2001 From: Arnav Goel Date: Mon, 11 May 2026 16:21:15 -0400 Subject: [PATCH 2/2] docs: add release-process checklist for v0.2.0+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the one-time setup (GitHub pypi environment + PyPI Trusted Publisher binding + branch protection) and the per-release checklist the maintainer should run through before tagging. The pypi environment was already created via API; the PyPI-side Trusted Publisher binding still has to be added manually on pypi.org (it's not in git — see step 2 in the doc). Once that's done, tagging vX.Y.Z auto-fires the release workflow end-to-end (build → GitHub Release → PyPI publish). Also documents common failure modes (yank, hotfix, redo-on-CI-red) and the rationale for Trusted Publishing over a long-lived API token. --- docs/RELEASE_PROCESS.md | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/RELEASE_PROCESS.md diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md new file mode 100644 index 0000000..e2b9717 --- /dev/null +++ b/docs/RELEASE_PROCESS.md @@ -0,0 +1,117 @@ +# Release process + +This document is the **operational** companion to [`.github/workflows/release.yml`](../.github/workflows/release.yml). The workflow is tag-triggered (`v*.*.*`); pushing such a tag builds wheel + sdist, attaches a `testBench-vX.Y.Z.zip`, creates a GitHub Release, and publishes to PyPI. **All three actions are partially or fully irreversible** — PyPI in particular does not allow re-publishing a version. Run through this checklist before tagging. + +## One-time setup + +These have to be done once per repository and persist across releases. + +### 1. GitHub Environment: `pypi` + +The release workflow's `pypi` job declares `environment: pypi`. That environment must exist on the repo, otherwise the OIDC token exchange with PyPI's Trusted Publisher endpoint fails. + +```bash +# Create the empty environment (idempotent) +gh api -X PUT repos/knowledgestack/ks-xlsx-parser/environments/pypi +``` + +Optional but recommended after creation: open https://github.com/knowledgestack/ks-xlsx-parser/settings/environments/pypi and add a **required reviewer** so a tag push needs explicit approval before the PyPI publish step runs. This is a safety net — once a tag is pushed the release workflow auto-fires; a reviewer gate gives you one last "are you sure?" before the irreversible PyPI publish. + +### 2. PyPI Trusted Publisher binding + +This **cannot** be done via API or a PR — it requires logging into pypi.org as a maintainer of `ks-xlsx-parser`. + +1. Go to https://pypi.org/manage/project/ks-xlsx-parser/settings/publishing/ +2. Click **Add a new publisher** → **GitHub** +3. Fill in: + - **PyPI Project Name:** `ks-xlsx-parser` + - **Owner:** `knowledgestack` + - **Repository:** `ks-xlsx-parser` + - **Workflow filename:** `release.yml` + - **Environment name:** `pypi` +4. Save. + +Verify with: + +```bash +# Should list any publishers tied to the project (requires you to be logged in) +open "https://pypi.org/manage/project/ks-xlsx-parser/settings/publishing/" +``` + +If you're spinning up a new project that doesn't exist on PyPI yet, the publisher has to be configured as a **pending publisher** under your account first. Same form, accessible at https://pypi.org/manage/account/publishing/. + +### 3. Branch protection on `main` + +CI on a PR validates {ubuntu, macOS} × Python {3.10, 3.11, 3.12} before merge. Add a branch protection rule on `main` requiring those status checks to pass before a PR is mergeable: + +```bash +gh api -X PUT repos/knowledgestack/ks-xlsx-parser/branches/main/protection \ + -F required_status_checks[strict]=true \ + -F 'required_status_checks[contexts][]=tests (ubuntu-latest / py3.10)' \ + -F 'required_status_checks[contexts][]=tests (ubuntu-latest / py3.11)' \ + -F 'required_status_checks[contexts][]=tests (ubuntu-latest / py3.12)' \ + -F 'required_status_checks[contexts][]=tests (macos-latest / py3.10)' \ + -F 'required_status_checks[contexts][]=tests (macos-latest / py3.11)' \ + -F 'required_status_checks[contexts][]=tests (macos-latest / py3.12)' \ + -F 'required_status_checks[contexts][]=testBench round-trip (ubuntu / py3.12)' \ + -F enforce_admins=false \ + -F required_pull_request_reviews[required_approving_review_count]=1 \ + -F restrictions= 2>/dev/null +``` + +Or set in the UI: https://github.com/knowledgestack/ks-xlsx-parser/settings/branches + +## Per-release checklist + +For every new version `X.Y.Z`: + +1. **Decide the version number.** Follow [SemVer](https://semver.org/). Breaking API change → major bump. New feature, no breakage → minor. Bugfix only → patch. +2. **Bump version in two places** (kept in sync to avoid drift): + - `pyproject.toml` — `version = "X.Y.Z"` + - `src/ks_xlsx_parser/__init__.py` — `__version__ = "X.Y.Z"` +3. **Write the CHANGELOG entry** under a new `## [X.Y.Z] — YYYY-MM-DD` heading in [`CHANGELOG.md`](../CHANGELOG.md). Use the section labels documented at the top of that file (Added / Changed / Fixed / Performance / Docs / Internal / ⚠️ BREAKING). +4. **(Optional but recommended) Write hand-curated release notes** at `docs/launch/RELEASE_NOTES_vX.Y.Z.md`. If present, the release workflow picks it up automatically as the GitHub Release body; otherwise GitHub auto-generates from commits. +5. **Run `make test` locally** and verify all tests pass. +6. **Open a PR**, wait for CI green on all matrix cells, get a review, merge to `main`. +7. **Tag from `main`:** + ```bash + git checkout main && git pull + git tag -a vX.Y.Z -m "vX.Y.Z — " + git push origin vX.Y.Z + ``` +8. **Watch the workflow.** https://github.com/knowledgestack/ks-xlsx-parser/actions — the `Release` workflow should run `build` → `github-release` → `pypi`. If the `pypi` job is gated on a reviewer, approve it in the Actions UI. +9. **Verify post-release:** + - PyPI: https://pypi.org/project/ks-xlsx-parser/X.Y.Z/ resolves and `pip install ks-xlsx-parser==X.Y.Z` works in a fresh venv. + - GitHub Release: https://github.com/knowledgestack/ks-xlsx-parser/releases/tag/vX.Y.Z shows the release notes + wheel + sdist + `testBench-vX.Y.Z.zip`. + - The `[Unreleased]` heading at the top of `CHANGELOG.md` is reset to "Nothing yet" for the next cycle (manual; do this in a follow-up PR). + +## Common failure modes + +**`pypi` job fails with "trusted publisher" error.** The PyPI Trusted Publisher binding is missing or pointing at the wrong workflow / environment. Fix at pypi.org (step 2 above). The GitHub Release will already exist — once the binding is correct, re-run the failed job via `gh run rerun --failed`. + +**`pypi` job fails with "version already exists".** Someone published this version manually or a previous CI run partially succeeded. PyPI does not allow republishing. Bump to the next patch (`X.Y.Z+1`), update CHANGELOG / `RELEASE_NOTES`, delete the local tag, re-tag, force-delete the remote tag if it exists (`git push --delete origin vX.Y.Z`), and re-push. + +**CI fails on a matrix cell after a tag is pushed.** The tag-trigger workflow doesn't depend on CI — it runs in parallel. If CI is red, delete the tag (`git push --delete origin vX.Y.Z`), fix the issue on a PR, then re-tag from the merged fix. The GitHub Release artifact from the doomed run can be deleted via `gh release delete vX.Y.Z`. + +**Wheel doesn't include something it should.** Check `MANIFEST.in` / `pyproject.toml`'s `[tool.setuptools.packages.find]`. The `py.typed` marker, `*.pyi` stubs, and any data files (`.json`, `.xlsx` fixtures) only ship if explicitly declared. + +## Hotfix / yank + +If a published release has a critical bug: + +```bash +# Yank from PyPI (hides from `pip install` but doesn't delete — required for cache-poisoning safety) +# UI: https://pypi.org/manage/project/ks-xlsx-parser/release/X.Y.Z/ + +# Tag a hotfix +git checkout main +# … apply fix, bump to X.Y.Z+1, update CHANGELOG … +git tag -a vX.Y.Z+1 -m "vX.Y.Z+1 — hotfix for " +git push origin vX.Y.Z+1 +``` + +Yanked versions remain installable if pinned explicitly; `pip install ks-xlsx-parser` without a version constraint skips them. This is the safe default for accidental release of broken code. + +## Why we use Trusted Publishing instead of an API token + +PyPI API tokens stored as repo secrets are forge-able by anyone with write access to the workflow file. Trusted Publishing uses GitHub OIDC: only a real run of the configured workflow on the configured environment can mint a publish token, and the token is short-lived. No long-lived secret lives in the repo. Trade-off: one-time PyPI-side setup (step 2) and the environment binding (step 1) are both required.