Project Overview
Relevant source files
The following files were used as context for generating this wiki page:
The numan-plugins repository serves as a CI/CD build and sign pipeline feeder for the official numan registry. Its primary purpose is to bridge the gap between source-only Nushell plugins and the registry's requirement for signed, hash-pinned binary artifacts. The project automates the cross-compilation of plugins from immutable upstream commits, packages them into platform-specific archives, and publishes them as GitHub release assets.
Sources: README.md:1-12, REVIEW.md:21-34
This system enforces a strict trust boundary where this repository focuses solely on building and hosting binaries. It does not hold signing keys or private material; signing is handled downstream by the numan-registry. Every released asset is designed to be immutable, ensuring that a specific version of a plugin always corresponds to the same verified bytes.
Sources: README.md:17-26, REVIEW.md:21-25
System Architecture
The architecture is centered around a declarative manifest.json that defines the "active" set of plugins to be built. The system uses GitHub Actions to execute a multi-stage pipeline: validation, compilation across a target matrix, deterministic packaging, and a stateful "draft-then-finalize" release transaction.
Core Components
| Component | Description |
|---|---|
| Manifest | A JSON file defining plugin metadata, upstream source pins, and the build matrix. |
| Validation Scripts | Tools like validate_manifest.py and ensure_release_absent.py that enforce safety invariants. |
| Packaging Engine | package_plugin.py which creates deterministic .tar.gz or .zip archives. |
| Spec Generator | gen_spec.py which produces the metadata required by the numan-registry for intake. |
| Release Manager | release_transaction.py which handles atomic GitHub release creation and asset uploading. |
Sources: README.md:29-41, scripts/package_plugin.py:1-18, scripts/gen_spec.py:1-25
High-Level Build Flow
The following diagram illustrates the lifecycle of a plugin from manifest definition to registry readiness.
The flow ensures that every step is verified before proceeding to the next, preventing half-published or corrupt releases.
Sources: README.md:46-59, scripts/release_transaction.py:1-15
Build Matrix and Targets
The project supports a variety of architecture and operating system combinations to ensure broad compatibility for Nushell users.
| Target Triple | OS Runner | Packaging Format |
|---|---|---|
x86_64-unknown-linux-gnu | ubuntu-latest | .tar.gz |
aarch64-unknown-linux-gnu | ubuntu-latest (cross) | .tar.gz |
x86_64-apple-darwin | macos-15-intel | .tar.gz |
aarch64-apple-darwin | macos-15 | .tar.gz |
x86_64-pc-windows-msvc | windows-latest | .zip |
Sources: manifest.json:6-14, manifest.json:200-206, scripts/test_workflow_safety.py:126-140
Safety Invariants and Trust Boundary
Safety is a primary architectural concern. The repository implements several invariants to prevent supply chain contamination and unauthorized releases.
- Immutable Pins: Every plugin must pin a specific
source_commitand a human-facingtag. The workflow verifies that the tag resolves to the exact commit SHA before building. - No Signing Keys: Signing material is never stored in this repository. Signing occurs only in the
numan-registryafter asset intake. - Draft-Then-Finalize: Releases are created as run-owned drafts. Assets are verified against their built hashes before the release is moved to a public state.
- Manual Only: Publication workflows (
build.yml) can only be triggered viaworkflow_dispatch. Pull requests and pushes are restricted to validation-only paths.
Sources: REVIEW.md:21-34, scripts/test_workflow_safety.py:46-55, scripts/release_transaction.py:133-162
Release Transaction Sequence
The release_transaction.py script manages the state of a release to ensure atomicity.
This sequence prevents the creation of "ghost" releases and ensures the tag remains owned by the specific workflow run.
Sources: scripts/release_transaction.py:44-118, scripts/test_workflow_safety.py:84-91
Packaging and Determinism
The package_plugin.py script ensures that rebuilding a plugin from the same source produces byte-identical archives. This is critical for maintaining stable sha256 hashes in the registry.
- Fixed Timestamps: All file modification times (
mtime) are set to a fixed epoch (1980-01-01 UTC). - Standardized Permissions: Unix modes are forced to
0o755for executables. - Normalized Gzip: Gzip headers are written with
mtime=0to ensure stability.
Sources: scripts/package_plugin.py:27-31, scripts/package_plugin.py:42-55, scripts/test_package_plugin.py:33-40
FIXED_MTIME = 315532800 # 1980-01-01 UTC
UNIX_MODE = 0o755 # executable
def build_tar_gz(binary: Path, arcname: str, out: Path) -> None:
raw = io.BytesIO()
with tarfile.open(fileobj=raw, mode="w") as tar:
info = tarfile.TarInfo(name=arcname)
data = binary.read_bytes()
info.size = len(data)
info.mtime = FIXED_MTIME
info.mode = UNIX_MODE
# ... additional normalization ...
Sources: scripts/package_plugin.py:27-41
Registry Integration
The final output of a successful build is a registry specification file generated by gen_spec.py. This spec includes:
- Provenance: Upstream repository URL, immutable revision (
rev), and cargo name. - Compatibility: Supported
nu_versionand versions the plugin wasverified_with. - Artifact URLs: Direct links to the GitHub release assets for each supported target triple.
Sources: scripts/gen_spec.py:105-130, README.md:65-69
Conclusion
The numan-plugins project provides a robust, safety-oriented pipeline for delivering binary Nushell plugins. By enforcing strict immutability, deterministic packaging, and a multi-stage release process, it ensures that users of the numan tool can install plugins with high confidence in their provenance and integrity.