Publishing EQ-Layer¶
EQ-Layer publishes two artifacts with the same semantic version.
- PyPI
eq-layer: the Python runtime/control layer. - npm
eq-layer: the portable Agent Skill installer and CLI bridge.
Current release: 0.2.5.
Version source of truth¶
Both packages ship the same semantic version, and each side has exactly one canonical location for it. Do not hardcode the version anywhere else.
- PyPI (
eq-layer): theversionfield of the[project]table inpyproject.toml.eq_layer/_version.pyreads it throughimportlib.metadatawhen the distribution is installed, and falls back to reading the same field from the nearestpyproject.tomlwhen running from a source checkout.eq_layer.__version__andeq-layer doctorreport that value; there is no second copy in the package. - npm (
eq-layer): theversionfield inpackage.json.npm/cli.mjsreads it at runtime throughnpm/version.mjs(used by--version,doctor, and the help text); there is no second copy in the launcher.
The release workflows verify that the GitHub release tag matches these fields before publishing, so a mismatched canonical location fails the release instead of shipping a wrong version.
PyPI automatic release flow¶
PyPI publishing is release-driven. A normal push to main never publishes a
package.
To publish a new version:
- update
pyproject.tomlandpackage.jsonto the new version, for example0.2.5; - set
.release-requestto the same version and merge that change tomainafter the normal CI passes; .github/workflows/create-release.ymlverifies the requested version, creates the GitHub Release taggedvX.Y.Zwhen it is missing, and explicitly dispatches both publishers (release.ymlfor PyPI andpublish-npm.ymlfor npm);.github/workflows/release.ymlchecks whether the exact version is already live on PyPI and no-ops if it is; otherwise it verifies that the release tag and package version match, builds the wheel/sdist, runstwine check, and publishes to PyPI through GitHub OIDC / PyPI Trusted Publishing;.github/workflows/publish-npm.ymlblocks until the exact version is live on PyPI (checking for up to 15 minutes) and fails if it never appears, then skips validation and upload when that version is already on npm. A failed PyPI release therefore also fails npm; npm cannot get ahead of PyPI.
The explicit npm dispatch is required because a GitHub Release created with
GITHUB_TOKEN does not emit a release event. A release tag alone does not
fan out to npm; create-release.yml is the component that starts both
publishers.
If the GitHub Release tag and pyproject.toml version differ, publishing fails
before any upload. This prevents a release tag from silently publishing the
wrong package version.
The PyPI Trusted Publisher must match:
- project:
eq-layer - owner:
Furox-Art - repository:
eq-layer - workflow:
release.yml - environment:
pypi
No long-lived PyPI API token is required.
Manual PyPI recovery¶
The same workflow has a manual dispatch input named version. Use it only for
recovery/retry. The supplied version must still exactly match
pyproject.toml.
Prereleases¶
GitHub fires release: published for prereleases too. Both publisher workflows
skip release events where github.event.release.prerelease is true, so a
prerelease GitHub release never reaches PyPI or npm. workflow_dispatch
remains available for explicit recovery of an already-tagged stable version;
the tag/version checks still apply to those runs.
Re-running a release¶
Both publishers are idempotent. If vX.Y.Z is already on PyPI, release.yml
stops after its preflight check. If the version is already on npm,
publish-npm.yml confirms the PyPI release is live and then skips validation
and upload. Re-triggering .release-request for a completed version is
therefore safe and does not fail with "already exists".
npm automatic release flow¶
The npm package uses the same semantic version as PyPI.
.github/workflows/create-release.yml explicitly dispatches
publish-npm.yml with the release version; a release tag on its own does not
publish npm. publish-npm.yml verifies that the requested version (release tag
or manual dispatch input) exactly matches package.json before uploading, and
waits until that version is live on PyPI so npm cannot publish before the
Python artifact. It then runs npm test and npm pack --dry-run --json. When
the version is already on the npm registry, the workflow confirms PyPI and then
no-ops instead of failing on "version already exists".
Publishing has two credential modes, and OIDC is the default:
- OIDC mode (default) — npm trusted publishing exchanges the job's identity
token and publishes with
npm publish --access public --provenance, so the artifact carries a provenance attestation. The job declaresid-token: writeand installsnpm@>=11.21.0first because older npm cannot request an attestation. - token mode (explicit only) — a maintainer manually dispatches
publish-npm.ymlwithuse_token_fallback=true. That reads theNPM_TOKENsecret and publishes withnpm publish --access public --no-provenance. Provenance is an OIDC-only attestation, so token mode cannot produce one.
The presence of NPM_TOKEN does not select token mode. The workflow chooses
OIDC unless the dispatch input explicitly opts into the fallback, and it fails
if the fallback is requested without a usable NPM_TOKEN.
First publication of a package¶
The first eq-layer@0.1.0 publication required a token because a Trusted
Publisher cannot be configured until the package already exists in the npm
registry. That historical constraint is why a granular npm access token with
Read and write (publish and stage), Bypass 2FA, and the shortest
practical expiry may still exist as NPM_TOKEN. It is no longer selected
automatically.
Trusted publishing¶
Trusted publishing is the default path. The npm package settings must keep a GitHub Actions Trusted Publisher with:
- GitHub owner/user:
Furox-Art; - repository:
eq-layer; - workflow filename:
publish-npm.yml; - environment: leave blank;
- direct
npm publishpermitted.
NPM_TOKEN is retained only for the explicit use_token_fallback=true recovery
path; it is not the normal release path.
Release validation¶
Normal main-branch CI continues to build/check the Python distributions and perform an npm package dry-run. Publishing a package does not change the scientific evidence level.