Uh oh!
There was an error while loading. Please reload this page.
feat(packaging): publish the CLI to npm and PyPI - #2
Merged
Conversation
Homebrew, Scoop, deb and rpm already ship; npm and PyPI are where most agents and developers look first, and goreleaser publishes to neither. Both are built here from the binaries goreleaser has already produced, so every channel ships the same bytes for a tag. Names are `modelslab-cli` on both registries. `modelslab` is taken on each by the respective SDK; both packages still register the `modelslab` command. npm: an entry package plus one package per platform - `modelslab-cli` contains only a launcher shim and declares the six platform packages as optionalDependencies. npm installs the one matching os/cpu. - The obvious alternative, a postinstall that downloads the binary, was rejected: it needs network at install time and produces a silently broken install under `npm ci --ignore-scripts`, which plenty of CI and agent sandboxes set. - The shim resolves the binary three ways. A plain require.resolve covers a normal global install and nothing else — it fails through any symlink, which means `npm install <path>`, `npm link`, and every pnpm install. Found by installing the built package rather than by reading the code. PyPI: one wheel per platform - Each wheel carries the binary and a console script that execv's it. Wheels are written directly rather than through a build backend; nothing is compiled, so a backend would only add a dependency and hide the platform tag. - Two bugs that static checks do not catch, both found by installing and running: - pip decides executability with `stat.S_ISREG(mode) and mode & 0o111`, so the zip entry needs the file-TYPE bits. A bare 0o755 fails S_ISREG, the binary unpacks 0644, and the first run dies with EPERM. `twine check` passes it. - Tags are semver, wheel filenames are PEP 440. `v1.2.3-rc1` naively yields `modelslab_cli-1.2.3-rc1-py3-none-*.whl`, which pip reads as version 1.2.3 with build tag `rc1` — build tags must start with a digit, so the file is invalid. Tags are normalised to `1.2.3rc1`. Release and CI - release.yml builds and publishes both on tag, guarded on the token being configured so a missing secret skips that registry instead of failing the release. `secrets` is not an available context in a step-level `if`, so the tokens are mapped to env and the guard reads that. - Binaries are collected from goreleaser's artifacts.json rather than by parsing dist/ directory names: those carry microarchitecture suffixes (_v1, _v8.0) that differ per target and move between goreleaser versions. - ci.yml builds both packages on every PR and then installs and RUNS them. Both failure modes above pass every static check, so the only test that means anything is executing the result. Verified locally against a real `goreleaser build --snapshot`: 6 binaries collected, 7 npm packages and 6 wheels built, all wheels pass twine check, and both an npm install and a wheel install produce a working `modelslab --version`. Publishing needs NPM_TOKEN and PYPI_TOKEN in repository secrets; until they are set the new steps no-op.
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Homebrew, Scoop, deb and rpm already ship. npm and PyPI are where most agents and developers look first, and goreleaser publishes to neither — which is why the audit still scores the CLI as partial.
Both are built from the binaries goreleaser has already produced, never rebuilt, so every channel ships identical bytes for a tag.
Names
modelslab-clion both registries —modelslabis taken on each by the respective SDK. Both packages still register themodelslabcommand.npm: entry package + one package per platform
modelslab-clicontains only a launcher shim (2.6 kB) and declares six platform packages asoptionalDependencies. npm installs the one matchingos/cpuand skips the rest.The obvious alternative — one package with a postinstall that downloads the binary — was rejected on purpose. It needs network at install time and produces a silently broken install under
npm ci --ignore-scripts, which plenty of CI and agent sandboxes set. Six small packages buy an install that cannot half-work.Three bugs, all found by installing rather than by reading
1. The shim only worked for a plain global install.
require.resolvefails through any symlink, because resolution then starts from the link target, which has nonode_modules. That'snpm install <path>,npm link, and every pnpm install. It now tries three strategies: plain resolve, resolve anchored at the real directory and cwd, then a walk up looking for the sibling package.2. pip did not preserve the executable bit. pip decides with
stat.S_ISREG(mode) and mode & 0o111, so the zip entry's mode must carry the file-type bits, not just permissions. A bare0o755failsS_ISREG, the binary unpacks0644, and the first run dies withEPERM:twine checkpassed all six wheels anyway. Only installing and running caught it.3. Prerelease tags produced invalid wheel filenames. Tags are semver, wheel filenames are PEP 440.
v1.2.3-rc1naively yieldsmodelslab_cli-1.2.3-rc1-py3-none-*.whl, which pip reads as version1.2.3with build tagrc1— and build tags must start with a digit, so the file is simply invalid. Normalised to1.2.3rc1, with anything unexpressible rejected outright.Release wiring
artifacts.json, not by parsingdist/directory names. A real snapshot build confirmed why: the dirs aremodelslab_darwin_arm64_v8.0,modelslab_linux_amd64_v1— microarchitecture suffixes that differ per target and move between goreleaser versions. My first attempt parsed the names and would have mangled them.secretsis not an available context in a step-levelif, so tokens map toenvand the guard reads that.CI
ci.ymlgains apackagingjob that builds both and then installs and runs them. All three bugs above pass every static check; executing the result is the only test that means anything.Verified locally
Against a real
goreleaser build --snapshot:Go tests unaffected.
Needs you
NPM_TOKENandPYPI_TOKENin repository secrets. Until they're set the new steps no-op and the release behaves exactly as it does today. Both names are currently unregistered, so whoever holds the accounts should claim them before the next tag.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.