This is a maintainer doc. Most contributors do not need it.
The primary repository is github.com/nilstate/scafld. The Go module path is
github.com/nilstate/scafld/v2 because the product release line is already v2.
Release tags are vX.Y.Z. The source tree keeps wrapper package metadata at
the development placeholder version; release jobs stamp npm and PyPI metadata
from the tag.
.github/workflows/release.yml fires on tags matching v*.*.*.
Stages:
- validate -- run
make checkand build a release snapshot. - github-release -- build native Go binaries,
checksums.txt, andmanifest.json, then create the GitHub release. - publish-pypi -- build and publish the PyPI launcher wrapper.
- publish-npm -- publish the npm launcher wrapper.
- package-managers -- render Homebrew, Scoop, WinGet, and OCI manifests from the published release checksums and attach them to the release.
- publish-homebrew and publish-scoop -- publish owned registry manifests when repository credentials are configured.
- publish-oci -- publish the CI runner image to GHCR.
The order is intentional: npm and PyPI wrappers download and verify GitHub release assets, so the GitHub release must exist before either wrapper package is published.
Use the same product version everywhere in published artifacts:
- GitHub tag:
vX.Y.Z - npm:
scafld@X.Y.Z - PyPI:
scafld==X.Y.Z - Go module install:
go install github.com/nilstate/scafld/v2/cmd/scafld@vX.Y.Z
Before a release, run:
make check
scripts/build-release-artifacts.sh X.Y.Z
scripts/smoke-release-installers.sh X.Y.ZThe release script publishes raw executable assets for package-manager
compatibility. Those binaries are built with static CGO-disabled targets,
trimmed paths, no embedded VCS metadata, stripped symbols/DWARF data, an empty
Go build id, and disabled inlining to keep the downloadable artifacts small
without adding an executable packer. scripts/check-release-size.sh runs at
the end of artifact generation and fails if any raw binary exceeds the generous
release budget.
scripts/smoke-release-installers.sh exercises the npm and PyPI launchers
against the local dist/ directory through SCAFLD_INSTALL_BASE_URL=file://....
It verifies download, checksum matching, executable permissions, launcher
handoff, and --version.
Do not commit generated wrapper version changes. .github/workflows/release.yml
runs scripts/set-release-version.sh "${GITHUB_REF_NAME#v}" inside the release
job before building npm and PyPI packages.
The workflow currently reuses the existing repository secrets:
PYPI_API_TOKENNPM_TOKEN
The wrappers are thin launchers. They never reimplement scafld behavior; they
download the matching native binary and verify it against checksums.txt.
git add -A
git commit -m "release: vX.Y.Z"
git tag vX.Y.Z
git push origin main vX.Y.ZWatch the Actions tab. Verify:
- GitHub release contains all six native binaries plus
checksums.txtandmanifest.json. npm install -g scafld@X.Y.Zworks.pipx install scafld==X.Y.Zworks.go install github.com/nilstate/scafld/v2/cmd/scafld@vX.Y.Zworks.docker manifest inspect ghcr.io/nilstate/scafld:vX.Y.Zworks without authenticated package access.
After the GitHub release is live, publish or submit the manifests rendered from the release checksums:
- Homebrew: update
nilstate/homebrew-tapwithFormula/scafld.rb. - Scoop: update
nilstate/scoop-bucketwithbucket/scafld.json. - WinGet: submit
0state.scafldmanifests tomicrosoft/winget-pkgs. - GHCR: confirm the published image is publicly inspectable. If anonymous
docker manifest inspect ghcr.io/nilstate/scafld:vX.Y.Zreturnsdenied, make thenilstate/scafldcontainer package public in GitHub Packages and rerun the metadata audit.
For WinGet, do not copy from a local .stage/ directory. Stage the upstream
submission from the uploaded release artifact so the manifest hashes are checked
against the published checksums.txt:
scripts/prepare-winget-submission.sh X.Y.Z /path/to/winget-pkgsThat command downloads package-managers-rendered.tar.gz and checksums.txt
from the GitHub release, verifies every InstallerSha256, and copies the
versioned manifests into the WinGet checkout.