Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

.nornir/pypi-publish.md

Publishing facett + facett-demo to PyPI — the owner's steps

Two PyPI projects, released together at one version (facett-build-thing PYPI_VERSION, 0.2.1 today), published by ignalina (ignalina.dk):

PyPI project import what built from
facett facett (+ facett.streamlit with [streamlit]) the components: wasm bundles core/docs/model3d/sim + loader + manifest; wheel only pkg-facet-wrapped/ → pkg-facet-wrapped/dist/
facett-demo facett_demo, command facett-demo the Streamlit app; needs facett[streamlit]; live korp data with [korp] py/ → py/dist/

Both are built and checked on the build box, never on a CI host:

cargo xtask wrap-wasm                 # the components → wasm (pinned cargo image)
cargo ops publish-pip                 # both wheels + the demo's sdist, pinned pip image, network off
cargo ops verify-demo-install         # clean container: pip install both, facett-demo starts, healthy
cargo ops publish-pypi --dry-run      # what PyPI will check + "is this version already up?"; sends nothing

Every file passes the NAME GATE first (pypi::name_gate: no personal name, mail domain or home path in any member, decompressed — dry run or not, and publish-pip refuses to call such a wheel built). Upload order is always facett first (the demo pins it). PyPI never accepts the same file name twice — a mistake is fixed with a NEW version, not a re-upload.

Road A — API token (cargo ops publish-pypi)

  1. Account: create (or use) the ignalina PyPI account on https://pypi.org/account/register/ and, separately, on https://test.pypi.org/account/register/ (two different sites, two accounts). Turn on 2FA (required to make tokens). Optionally create a PyPI organization ignalina and add the projects to it after the first upload.
  2. Token: Account settings → API tokens → Add API token, scope Entire account (a project-scoped token cannot exist before the project does). Do this on both sites.
  3. The verb takes the token from $TEST_PYPI_API_TOKEN / $PYPI_API_TOKEN, else from ~/.pypirc ([testpypi] / [pypi], username = __token__, password = pypi-…) — twine's order; it never prints it. (2026-10-03: the account-wide token that sat in the t14s ~/.pypirc was exposed — the owner revokes it and creates project-scoped tokens for facett and facett-demo; do NOT use that ~/.pypirc. 0.2.1 is built and gate-checked on dwarf and NOT uploaded until the new tokens exist.) Rehearse on TestPyPI:
export TEST_PYPI_API_TOKEN='pypi-…'          # or ~/.pypirc [testpypi]
cargo ops publish-pypi --repository testpypi --dry-run
cargo ops publish-pypi --repository testpypi

then, in a scratch venv: pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ facett-demo && facett-demo.

  1. Publish for real:
export PYPI_API_TOKEN='pypi-…'
cargo ops publish-pypi --repository pypi --dry-run
cargo ops publish-pypi --repository pypi
  1. Replace both account-wide tokens with tokens scoped to the projects facett and facett-demo, and delete the account-wide ones.

Road B — GitHub trusted publishing via the ignalina mirror (no token stored)

facett is mirrored like knut/korp/skade: ~/mirror-ignalina/mirror-to-github.sh (Codeberg → private github.com/Ignalina/<repo>, SSH, branches + tags + LFS, force-pushed). The workflow builds nothing; it publishes release assets.

  1. GitHub: create the EMPTY private repo https://github.com/new?owner=Ignalina&name=facett&visibility=private (no README).
  2. Codeberg: commit publish/github/.github/workflows/pypi.yml to the repo ROOT as .github/workflows/pypi.yml on main and push (the mirror overwrites GitHub-only commits, so the file must come from Codeberg; Actions are off on Codeberg, so it is inert there).
  3. Mirror: bash ~/mirror-ignalina/mirror-to-github.sh facett.
  4. GitHub Ignalina/facett → Settings → Environments: pypi (required reviewer), testpypi.
  5. PyPI (logged in as ignalina) → Your account → Publishing → Add a new pending publisher → GitHub, twice: project facett, owner Ignalina, repository facett, workflow pypi.yml, environment pypi; then the same for project facett-demo. On test.pypi.org the same two with environment testpypi.
  6. Release: the four build/check commands above, git tag v0.2.1 && git push origin v0.2.1, mirror again, then on GitHub draft a release on tag v0.2.1 with the three files + the two SHA256SUMS (renamed SHA256SUMS.facett, SHA256SUMS.facett-demo) as a PRE-release → Actions → pypi → Run workflow (v0.2.1, testpypi) → install from TestPyPI as in A.3 → untick pre-release / Set as latest → published to pypi.org after the reviewer approves. Details: publish/github/README.md.

Owner TODO (not decided here)