From 33025fa909027277fb350942e656e09c0f72da4a Mon Sep 17 00:00:00 2001 From: boronine-bot Date: Wed, 21 Oct 2026 19:50:17 +0000 Subject: [PATCH 1/4] Add an OIDC TestPyPI publishing rehearsal workflow --- .github/workflows/testpypi.yml | 167 +++++++++++++++++++++++++++++++++ README.md | 2 + TESTPYPI.md | 88 +++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 .github/workflows/testpypi.yml create mode 100644 TESTPYPI.md diff --git a/.github/workflows/testpypi.yml b/.github/workflows/testpypi.yml new file mode 100644 index 0000000..e6fbe9a --- /dev/null +++ b/.github/workflows/testpypi.yml @@ -0,0 +1,167 @@ +name: Rehearse publishing to TestPyPI + +on: + workflow_dispatch: + inputs: + version: + description: 'Unused test version (for example 5.0.5.dev2026102101); never uploaded to production PyPI' + required: true + type: string + +permissions: + contents: read + +concurrency: + group: testpypi-publishing + cancel-in-progress: false + +env: + TEST_VERSION: ${{ inputs.version }} + +jobs: + build: + if: github.repository == 'hsluv/hsluv-python' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + strategy: + matrix: + python-version: ['3.11', '3.15'] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Install build tools + run: python -m pip install build packaging twine + + - name: Apply a test-only version to the build copy + run: | + python - <<'PY' + import os + from pathlib import Path + import re + from packaging.version import Version + + text = os.environ['TEST_VERSION'] + version = Version(text) + if str(version) != text or not version.is_prerelease or version.local is not None: + raise SystemExit('Use a canonical PEP 440 development or pre-release version, without a local suffix.') + module = Path('hsluv.py') + source, count = re.subn( + r'''(?m)^__version__ = ['"][^'"]+['"]$''', + f'__version__ = {text!r}', + module.read_text(), + ) + if count != 1: + raise SystemExit('Expected exactly one literal __version__ assignment.') + module.write_text(source) + print(f'TestPyPI-only build version: {text}; no commit or tag is created.') + PY + + - name: Build and check distributions + run: | + python -m build + python -m twine check --strict dist/* + + - name: Test installed source distribution and wheel + run: | + python -m pip install dist/*.tar.gz + cd "$(mktemp -d)" + python -m unittest discover -s "$GITHUB_WORKSPACE/tests" -v + python -m pip install --force-reinstall "$GITHUB_WORKSPACE"/dist/*.whl + python -m unittest discover -s "$GITHUB_WORKSPACE/tests" -v + python - <<'PY' + import os + from importlib.metadata import version + import hsluv + assert hsluv.__version__ == version('hsluv') == os.environ['TEST_VERSION'] + PY + + - name: Save tested distributions + if: matrix.python-version == '3.11' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: testpypi-distributions + path: dist/* + if-no-files-found: error + + publish: + needs: build + runs-on: ubuntu-24.04 + environment: + name: testpypi + url: https://test.pypi.org/project/hsluv/ + permissions: + id-token: write + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: testpypi-distributions + path: dist/ + + - name: Publish only to TestPyPI using OIDC + uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + verify-published: + needs: publish + runs-on: ubuntu-24.04 + steps: + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.11' + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: testpypi-distributions + path: dist/ + + - name: Download the exact wheel from TestPyPI and verify its hash + run: | + python - <<'PY' + import hashlib + import os + from pathlib import Path + import subprocess + import sys + import time + + # The index may take a short time to expose the newly uploaded version. + for attempt in range(6): + result = subprocess.run([ + sys.executable, '-m', 'pip', '--isolated', 'download', + '--index-url', 'https://test.pypi.org/simple/', + '--no-deps', '--only-binary=:all:', '--no-cache-dir', + '--dest', 'published', f'hsluv=={os.environ["TEST_VERSION"]}', + ]) + if result.returncode == 0: + break + if attempt == 5: + raise SystemExit('The uploaded wheel could not be downloaded from TestPyPI.') + time.sleep(10) + + built, = Path('dist').glob('*.whl') + downloaded, = Path('published').glob('*.whl') + if built.name != downloaded.name or hashlib.sha256(built.read_bytes()).digest() != hashlib.sha256(downloaded.read_bytes()).digest(): + raise SystemExit('TestPyPI wheel differs from the tested build artifact.') + print(f'Verified TestPyPI wheel: {downloaded.name}') + PY + + - name: Install the published wheel and run its tests outside the source tree + run: | + python -m pip install --no-deps published/*.whl + mkdir source + tar xf dist/*.tar.gz -C source + cd "$(mktemp -d)" + python -m unittest discover -s "$GITHUB_WORKSPACE"/source/hsluv-*/tests -v + python - <<'PY' + import os + from importlib.metadata import version + import hsluv + assert hsluv.__version__ == version('hsluv') == os.environ['TEST_VERSION'] + print(f'Installed and tested {hsluv.__version__} from TestPyPI.') + PY diff --git a/README.md b/README.md index 93667f4..b27bfca 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ For HPLuv (the pastel variant), use `hpluv_to_hex`, `hpluv_to_rgb`, `hex_to_hplu Run `python -m unittest discover -v`. +To rehearse publishing without affecting production PyPI, see [TESTPYPI.md](https://github.com/hsluv/hsluv-python/blob/main/TESTPYPI.md). + ## Authors * Robert McGinley ([mcginleyr1](https://github.com/mcginleyr1)) diff --git a/TESTPYPI.md b/TESTPYPI.md new file mode 100644 index 0000000..bd88313 --- /dev/null +++ b/TESTPYPI.md @@ -0,0 +1,88 @@ +# Rehearsing publishing with TestPyPI + +This is an alternative to the production pre-release workflow proposed in +[PR #81](https://github.com/hsluv/hsluv-python/pull/81). It publishes **only to +TestPyPI**, using OIDC Trusted Publishing, and then downloads and tests the +uploaded wheel. It does not publish to production PyPI or require a release tag. + +## One-time setup for Alexei + +1. Merge `.github/workflows/testpypi.yml` into `main`. +2. Sign in to your **individual TestPyPI account** at . + Accounts, project permissions, and Trusted Publishers are separate from + production PyPI. Follow the individual-account guidance in + [issue #84](https://github.com/hsluv/hsluv-python/issues/84): do not share + credentials; use 2FA and store recovery codes securely. +3. In GitHub **Settings → Environments**, create **`testpypi`**. Restrict + deployments to the **branch `main`**. For this initial rehearsal, adding + `boronine` as a required reviewer is recommended where available. This is a + GitHub identity, independent of your TestPyPI username. +4. If you already own `hsluv` on TestPyPI, add a GitHub Trusted Publisher at + . Otherwise, + if the name is available, register a **pending publisher** at + with project name `hsluv`. + A pending publisher creates the project on first upload, not at registration. + If someone else owns the name, resolve ownership first; production ownership + does not grant access on TestPyPI. + + | Field | Value | + | --- | --- | + | Owner (GitHub repository owner) | `hsluv` | + | Repository name | `hsluv-python` | + | Workflow filename | `testpypi.yml` | + | Environment | `testpypi` | + + `Owner: hsluv` means the **GitHub organization**, not a shared TestPyPI login. + No password or API token is stored in GitHub. Do not register this test + workflow as a Trusted Publisher on **production** PyPI. + +See the official [TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/) +and [Trusted Publishing instructions](https://docs.pypi.org/trusted-publishers/using-a-publisher/). + +## Run a rehearsal + +1. Open **Actions → Rehearse publishing to TestPyPI → Run workflow** and select + **`main`**. +2. Enter an **unused**, canonical PEP 440 development or pre-release version, + for example `5.0.5.dev2026102101`. This is a testing example, not a decision + about the next real release. Increase the test suffix for subsequent runs. + Stable versions and local suffixes such as `+test` are deliberately rejected. +3. The workflow changes the literal `hsluv.__version__` in the runner's build + copy only. It does **not** commit a version bump, modify the repository on + GitHub, or create a tag. The test version also appears in the built package + metadata, so `hsluv.__version__` and the installed version remain consistent. +4. Review the build results on Python 3.11 and 3.15. Approve the `testpypi` + deployment if you configured required reviewers. Both builds must pass + before the isolated OIDC upload job runs. +5. Confirm that the verification job downloads the exact wheel from TestPyPI, + compares its SHA-256 hash to the tested build artifact, installs it, and runs + the tests outside the source tree. It retries briefly for index propagation. +6. Inspect the version, files, and rendered description at + . + +To try the uploaded wheel yourself, use a fresh virtual environment and replace +the example version with the one you published: + +```sh +python -m pip --isolated install --index-url https://test.pypi.org/simple/ \ + --only-binary=:all: --no-deps "hsluv==5.0.5.dev2026102101" +``` + +HSLuv has no runtime dependencies, so there is no need to mix production PyPI +and TestPyPI with `--extra-index-url`. The workflow installs its build tools +from production PyPI, but uploads distributions and fetches the published wheel +only from TestPyPI. + +## Limits and retries + +- TestPyPI may periodically prune projects or accounts. Recheck ownership and + publisher configuration if a previously working setup stops working. +- Uploaded filenames cannot be overwritten. For another build, choose a new + test version; do not enable `skip-existing` to mask a failed rehearsal. + If only verification failed after a successful upload, retry the verification + job rather than re-uploading the same files. +- This workflow is intentionally manual. It does not upload on every push or PR. +- TestPyPI success does not verify production PyPI ownership or its OIDC + publisher configuration. A production release needs separate authorization + and configuration. This PR does not select a real release version or enable + production publishing. From 17cace909701b6339b4c1b67537392eeb41d5c6a Mon Sep 17 00:00:00 2001 From: boronine-bot Date: Wed, 21 Oct 2026 19:54:11 +0000 Subject: [PATCH 2/4] Reduce TestPyPI rehearsal setup to 30 lines --- .github/workflows/testpypi.yml | 176 ++++----------------------------- README.md | 2 - TESTPYPI.md | 91 +---------------- 3 files changed, 21 insertions(+), 248 deletions(-) diff --git a/.github/workflows/testpypi.yml b/.github/workflows/testpypi.yml index e6fbe9a..d770338 100644 --- a/.github/workflows/testpypi.yml +++ b/.github/workflows/testpypi.yml @@ -1,167 +1,27 @@ -name: Rehearse publishing to TestPyPI - -on: - workflow_dispatch: - inputs: - version: - description: 'Unused test version (for example 5.0.5.dev2026102101); never uploaded to production PyPI' - required: true - type: string - -permissions: - contents: read - -concurrency: - group: testpypi-publishing - cancel-in-progress: false - -env: - TEST_VERSION: ${{ inputs.version }} - +name: Publish to TestPyPI +on: workflow_dispatch +permissions: {contents: read} jobs: build: if: github.repository == 'hsluv/hsluv-python' && github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 - strategy: - matrix: - python-version: ['3.11', '3.15'] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Install build tools - run: python -m pip install build packaging twine - - - name: Apply a test-only version to the build copy - run: | - python - <<'PY' - import os - from pathlib import Path - import re - from packaging.version import Version - - text = os.environ['TEST_VERSION'] - version = Version(text) - if str(version) != text or not version.is_prerelease or version.local is not None: - raise SystemExit('Use a canonical PEP 440 development or pre-release version, without a local suffix.') - module = Path('hsluv.py') - source, count = re.subn( - r'''(?m)^__version__ = ['"][^'"]+['"]$''', - f'__version__ = {text!r}', - module.read_text(), - ) - if count != 1: - raise SystemExit('Expected exactly one literal __version__ assignment.') - module.write_text(source) - print(f'TestPyPI-only build version: {text}; no commit or tag is created.') - PY - - - name: Build and check distributions - run: | - python -m build - python -m twine check --strict dist/* - - - name: Test installed source distribution and wheel - run: | - python -m pip install dist/*.tar.gz - cd "$(mktemp -d)" - python -m unittest discover -s "$GITHUB_WORKSPACE/tests" -v - python -m pip install --force-reinstall "$GITHUB_WORKSPACE"/dist/*.whl - python -m unittest discover -s "$GITHUB_WORKSPACE/tests" -v - python - <<'PY' - import os - from importlib.metadata import version - import hsluv - assert hsluv.__version__ == version('hsluv') == os.environ['TEST_VERSION'] - PY - - - name: Save tested distributions - if: matrix.python-version == '3.11' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: testpypi-distributions - path: dist/* - if-no-files-found: error - + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: {persist-credentials: false} + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 + with: {python-version: '3.11'} + - run: python -m pip install build + - run: python -m unittest discover -v + - run: python -m build + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: {name: distributions, path: dist/} publish: needs: build runs-on: ubuntu-24.04 - environment: - name: testpypi - url: https://test.pypi.org/project/hsluv/ - permissions: - id-token: write + environment: testpypi + permissions: {id-token: write} steps: - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - name: testpypi-distributions - path: dist/ - - - name: Publish only to TestPyPI using OIDC - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - - verify-published: - needs: publish - runs-on: ubuntu-24.04 - steps: - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.11' - - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - name: testpypi-distributions - path: dist/ - - - name: Download the exact wheel from TestPyPI and verify its hash - run: | - python - <<'PY' - import hashlib - import os - from pathlib import Path - import subprocess - import sys - import time - - # The index may take a short time to expose the newly uploaded version. - for attempt in range(6): - result = subprocess.run([ - sys.executable, '-m', 'pip', '--isolated', 'download', - '--index-url', 'https://test.pypi.org/simple/', - '--no-deps', '--only-binary=:all:', '--no-cache-dir', - '--dest', 'published', f'hsluv=={os.environ["TEST_VERSION"]}', - ]) - if result.returncode == 0: - break - if attempt == 5: - raise SystemExit('The uploaded wheel could not be downloaded from TestPyPI.') - time.sleep(10) - - built, = Path('dist').glob('*.whl') - downloaded, = Path('published').glob('*.whl') - if built.name != downloaded.name or hashlib.sha256(built.read_bytes()).digest() != hashlib.sha256(downloaded.read_bytes()).digest(): - raise SystemExit('TestPyPI wheel differs from the tested build artifact.') - print(f'Verified TestPyPI wheel: {downloaded.name}') - PY - - - name: Install the published wheel and run its tests outside the source tree - run: | - python -m pip install --no-deps published/*.whl - mkdir source - tar xf dist/*.tar.gz -C source - cd "$(mktemp -d)" - python -m unittest discover -s "$GITHUB_WORKSPACE"/source/hsluv-*/tests -v - python - <<'PY' - import os - from importlib.metadata import version - import hsluv - assert hsluv.__version__ == version('hsluv') == os.environ['TEST_VERSION'] - print(f'Installed and tested {hsluv.__version__} from TestPyPI.') - PY + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: {name: distributions, path: dist/} + - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 + with: {repository-url: 'https://test.pypi.org/legacy/'} diff --git a/README.md b/README.md index b27bfca..93667f4 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,6 @@ For HPLuv (the pastel variant), use `hpluv_to_hex`, `hpluv_to_rgb`, `hex_to_hplu Run `python -m unittest discover -v`. -To rehearse publishing without affecting production PyPI, see [TESTPYPI.md](https://github.com/hsluv/hsluv-python/blob/main/TESTPYPI.md). - ## Authors * Robert McGinley ([mcginleyr1](https://github.com/mcginleyr1)) diff --git a/TESTPYPI.md b/TESTPYPI.md index bd88313..522f258 100644 --- a/TESTPYPI.md +++ b/TESTPYPI.md @@ -1,88 +1,3 @@ -# Rehearsing publishing with TestPyPI - -This is an alternative to the production pre-release workflow proposed in -[PR #81](https://github.com/hsluv/hsluv-python/pull/81). It publishes **only to -TestPyPI**, using OIDC Trusted Publishing, and then downloads and tests the -uploaded wheel. It does not publish to production PyPI or require a release tag. - -## One-time setup for Alexei - -1. Merge `.github/workflows/testpypi.yml` into `main`. -2. Sign in to your **individual TestPyPI account** at . - Accounts, project permissions, and Trusted Publishers are separate from - production PyPI. Follow the individual-account guidance in - [issue #84](https://github.com/hsluv/hsluv-python/issues/84): do not share - credentials; use 2FA and store recovery codes securely. -3. In GitHub **Settings → Environments**, create **`testpypi`**. Restrict - deployments to the **branch `main`**. For this initial rehearsal, adding - `boronine` as a required reviewer is recommended where available. This is a - GitHub identity, independent of your TestPyPI username. -4. If you already own `hsluv` on TestPyPI, add a GitHub Trusted Publisher at - . Otherwise, - if the name is available, register a **pending publisher** at - with project name `hsluv`. - A pending publisher creates the project on first upload, not at registration. - If someone else owns the name, resolve ownership first; production ownership - does not grant access on TestPyPI. - - | Field | Value | - | --- | --- | - | Owner (GitHub repository owner) | `hsluv` | - | Repository name | `hsluv-python` | - | Workflow filename | `testpypi.yml` | - | Environment | `testpypi` | - - `Owner: hsluv` means the **GitHub organization**, not a shared TestPyPI login. - No password or API token is stored in GitHub. Do not register this test - workflow as a Trusted Publisher on **production** PyPI. - -See the official [TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/) -and [Trusted Publishing instructions](https://docs.pypi.org/trusted-publishers/using-a-publisher/). - -## Run a rehearsal - -1. Open **Actions → Rehearse publishing to TestPyPI → Run workflow** and select - **`main`**. -2. Enter an **unused**, canonical PEP 440 development or pre-release version, - for example `5.0.5.dev2026102101`. This is a testing example, not a decision - about the next real release. Increase the test suffix for subsequent runs. - Stable versions and local suffixes such as `+test` are deliberately rejected. -3. The workflow changes the literal `hsluv.__version__` in the runner's build - copy only. It does **not** commit a version bump, modify the repository on - GitHub, or create a tag. The test version also appears in the built package - metadata, so `hsluv.__version__` and the installed version remain consistent. -4. Review the build results on Python 3.11 and 3.15. Approve the `testpypi` - deployment if you configured required reviewers. Both builds must pass - before the isolated OIDC upload job runs. -5. Confirm that the verification job downloads the exact wheel from TestPyPI, - compares its SHA-256 hash to the tested build artifact, installs it, and runs - the tests outside the source tree. It retries briefly for index propagation. -6. Inspect the version, files, and rendered description at - . - -To try the uploaded wheel yourself, use a fresh virtual environment and replace -the example version with the one you published: - -```sh -python -m pip --isolated install --index-url https://test.pypi.org/simple/ \ - --only-binary=:all: --no-deps "hsluv==5.0.5.dev2026102101" -``` - -HSLuv has no runtime dependencies, so there is no need to mix production PyPI -and TestPyPI with `--extra-index-url`. The workflow installs its build tools -from production PyPI, but uploads distributions and fetches the published wheel -only from TestPyPI. - -## Limits and retries - -- TestPyPI may periodically prune projects or accounts. Recheck ownership and - publisher configuration if a previously working setup stops working. -- Uploaded filenames cannot be overwritten. For another build, choose a new - test version; do not enable `skip-existing` to mask a failed rehearsal. - If only verification failed after a successful upload, retry the verification - job rather than re-uploading the same files. -- This workflow is intentionally manual. It does not upload on every push or PR. -- TestPyPI success does not verify production PyPI ownership or its OIDC - publisher configuration. A production release needs separate authorization - and configuration. This PR does not select a real release version or enable - production publishing. +# TestPyPI setup for Alexei +Use your individual TestPyPI account (separate from production; see [#84](https://github.com/hsluv/hsluv-python/issues/84)). Create GitHub environment `testpypi`, allow branch `main`, and optionally require approval. On **test.pypi.org**, configure a GitHub Trusted Publisher: owner `hsluv`, repository `hsluv-python`, workflow `testpypi.yml`, environment `testpypi`. Use the existing `hsluv` project's Publishing settings if you own it; otherwise register a pending publisher for that name if available. No API token is needed. +Merge this workflow, then run **Actions → Publish to TestPyPI → Run workflow** from `main`. It uploads the committed `hsluv.__version__`; if that version already exists on TestPyPI, change it before another upload. Inspect and install the exact version in a clean environment with `python -m pip --isolated install --index-url https://test.pypi.org/simple/ --only-binary=:all: --no-deps "hsluv==VERSION"`. This workflow never uploads to production PyPI. See [PyPA's TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/). From 4645e2cee7c582ea7101e9cd56a2fdb2ed34e11c Mon Sep 17 00:00:00 2001 From: boronine-bot Date: Wed, 21 Oct 2026 20:00:14 +0000 Subject: [PATCH 3/4] Minimize TestPyPI publishing proof of concept --- .github/workflows/testpypi.yml | 18 +++++------------- TESTPYPI.md | 3 --- 2 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 TESTPYPI.md diff --git a/.github/workflows/testpypi.yml b/.github/workflows/testpypi.yml index d770338..9cedd41 100644 --- a/.github/workflows/testpypi.yml +++ b/.github/workflows/testpypi.yml @@ -1,27 +1,19 @@ name: Publish to TestPyPI +# Configure a TestPyPI Trusted Publisher for hsluv/hsluv-python, testpypi.yml, +# environment testpypi. Run from main with a version not already on TestPyPI. on: workflow_dispatch -permissions: {contents: read} +permissions: {contents: read, id-token: write} jobs: - build: + publish: if: github.repository == 'hsluv/hsluv-python' && github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 + environment: testpypi steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: {persist-credentials: false} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 with: {python-version: '3.11'} - run: python -m pip install build - - run: python -m unittest discover -v - run: python -m build - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: {name: distributions, path: dist/} - publish: - needs: build - runs-on: ubuntu-24.04 - environment: testpypi - permissions: {id-token: write} - steps: - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 - with: {name: distributions, path: dist/} - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 with: {repository-url: 'https://test.pypi.org/legacy/'} diff --git a/TESTPYPI.md b/TESTPYPI.md deleted file mode 100644 index 522f258..0000000 --- a/TESTPYPI.md +++ /dev/null @@ -1,3 +0,0 @@ -# TestPyPI setup for Alexei -Use your individual TestPyPI account (separate from production; see [#84](https://github.com/hsluv/hsluv-python/issues/84)). Create GitHub environment `testpypi`, allow branch `main`, and optionally require approval. On **test.pypi.org**, configure a GitHub Trusted Publisher: owner `hsluv`, repository `hsluv-python`, workflow `testpypi.yml`, environment `testpypi`. Use the existing `hsluv` project's Publishing settings if you own it; otherwise register a pending publisher for that name if available. No API token is needed. -Merge this workflow, then run **Actions → Publish to TestPyPI → Run workflow** from `main`. It uploads the committed `hsluv.__version__`; if that version already exists on TestPyPI, change it before another upload. Inspect and install the exact version in a clean environment with `python -m pip --isolated install --index-url https://test.pypi.org/simple/ --only-binary=:all: --no-deps "hsluv==VERSION"`. This workflow never uploads to production PyPI. See [PyPA's TestPyPI guide](https://packaging.python.org/en/latest/guides/using-testpypi/). From dd4c0545f7d8012afff17fecfe8727353f278310 Mon Sep 17 00:00:00 2001 From: boronine-bot Date: Wed, 21 Oct 2026 20:02:20 +0000 Subject: [PATCH 4/4] Use readable YAML formatting for publishing workflow --- .github/workflows/testpypi.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/testpypi.yml b/.github/workflows/testpypi.yml index 9cedd41..767afff 100644 --- a/.github/workflows/testpypi.yml +++ b/.github/workflows/testpypi.yml @@ -1,8 +1,14 @@ name: Publish to TestPyPI + # Configure a TestPyPI Trusted Publisher for hsluv/hsluv-python, testpypi.yml, # environment testpypi. Run from main with a version not already on TestPyPI. -on: workflow_dispatch -permissions: {contents: read, id-token: write} +on: + workflow_dispatch: + +permissions: + contents: read + id-token: write + jobs: publish: if: github.repository == 'hsluv/hsluv-python' && github.ref == 'refs/heads/main' @@ -10,10 +16,16 @@ jobs: environment: testpypi steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: {persist-credentials: false} + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 - with: {python-version: '3.11'} + with: + python-version: '3.11' + - run: python -m pip install build - run: python -m build + - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 - with: {repository-url: 'https://test.pypi.org/legacy/'} + with: + repository-url: https://test.pypi.org/legacy/