name: publish description: Publish crates to crates.io with pre-flight checks user-invocable: true
Publish
Publish our 4 crates to crates.io with pre-flight validation. The workspace
has two canonical crates (manifold-csg-sys, manifold-csg) and two thin
facade re-exports (manifold3d-sys, manifold3d) under the same version
numbers. Facades always ship in lockstep with their canonical counterparts
via = version pins.
Arguments
- No arguments: publish all 4 crates in dependency order
--dry-run: run all checks but don't actually publish
Pre-flight checks
Run all checks before publishing. Stop on first failure.
1. Clean working tree
git status
Must be on main with no uncommitted changes. Refuse to publish from a feature branch.
2. Build and test
cargo test --features nalgebra
cargo clippy --all-targets --all-features -- -D warnings
3. Version bump
Version bumps happen at publish time, not in feature PRs. Read CLAUDE.md for the versioning scheme, then:
- Check if versions have already been bumped by comparing the workspace + sys crate versions against what's actually published on crates.io:
If the in-repo version is already ahead of the published version, it's pre-bumped — DO NOT bump again. Subsequent feature PRs ride into the pre-bumped version. (Pitfall: assuming the workspace version is the published version. They diverge whenever a PR includes its own bump, which CLAUDE.md says PRs "usually should" do.)curl -sS https://crates.io/api/v1/crates/manifold-csg | python3 -c "import json,sys; print(json.load(sys.stdin)['crate']['max_version'])" curl -sS https://crates.io/api/v1/crates/manifold-csg-sys | python3 -c "import json,sys; print(json.load(sys.stdin)['crate']['max_version'])" - If not already bumped:
manifold-csg-sys: bump the patch component (e.g.,3.4.102→3.4.103) whenever the upstream contents differ from the last publish — pinned commit changed, carry-patches added/removed, or FFI declarations changed. If the upstream major.minor changed (new manifold3d release), update major.minor accordingly.manifold-csg(workspace version): bump patch for additive changes, minor for breaking changes (pre-1.0, minor bumps can break).manifold3d-sysversion must match the newmanifold-csg-sysversion exactly (= pin).manifold3dinherits the workspace version, so bumps automatically. Itsmanifold-csgdependency=pin must be updated to the new workspace version.- Update
manifold-csg'smanifold-csg-sysdependency version to match. Updatemanifold3d-sys'smanifold-csg-sys=pin andmanifold3d'smanifold-csg=pin.
- Commit the version bumps on a branch, open a PR, and get it merged before proceeding. Branch protection requires this — you cannot push version bumps directly to main.
4. Carry-patch audit
For each patch in crates/manifold-csg-sys/patches/:
- Check if the upstream PR has been merged (use
gh api) - If merged AND included in our pinned commit, warn that the patch can be removed
- If the patch fails to apply during build, stop — the pin and patches are inconsistent
5. Release notes
We don't keep a CHANGELOG file (it gets stale; git log + GitHub Releases cover the same ground). Tag the release after publishing — GitHub auto-generates release notes from PRs since the last tag:
git tag -a v<version> -m "Release <version>"
git push origin v<version>
6. Dry run
Run a dry run for each crate in publish order:
cargo publish --dry-run -p manifold-csg-sys
cargo publish --dry-run -p manifold3d-sys
cargo publish --dry-run -p manifold-csg
cargo publish --dry-run -p manifold3d
Facade dry runs will fail if their canonical counterparts aren't already on crates.io at the expected version — this is expected for first publish and version bumps. The real dry-run validation for facades happens after their canonical is published.
For each crate, check:
- Crate size — warn if over 500KB (we don't vendor C++ source, so it should be small)
- File list —
cargo package --list -p <crate>— verify no secrets, build artifacts, or unnecessary files are included - License files —
LICENSE-APACHEandLICENSE-MITmust be present
7. docs.rs build simulation
DOCS_RS=1 cargo doc --no-deps --features nalgebra \
-p manifold-csg-sys -p manifold-csg -p manifold3d-sys -p manifold3d
Must build without errors. docs.rs runs with --network=none, so build.rs must detect DOCS_RS and skip the C clone/build.
8. API stability check
Before first publish or any version where the public API changed:
- List all
pubitems inmanifold-csg— are any provisional or likely to change? - Flag return types that lock us in (e.g.,
Vec<f64>vs a newtype) - Remind: once published, the API is semver-locked. For pre-1.0 crates (
0.x.y), minor bumps (0.1.0→0.2.0) can contain breaking changes, but patch bumps (0.1.0→0.1.1) must be backwards-compatible. Post-1.0, breaking changes require a major bump.
9. Documentation
cargo doc --features nalgebra --no-deps
Must build without warnings. Spot-check that crate-level docs render correctly.
Publish
Only after all checks pass. Always get explicit user confirmation before running cargo publish.
Order matters
Dependencies must reach crates.io before their dependents:
manifold-csg-sys(no internal deps)manifold3d-sys(facade ofmanifold-csg-sys)manifold-csg(depends onmanifold-csg-sys)manifold3d(facade ofmanifold-csg)
Between each publish, crates.io may take a moment to index the new version. If the next publish fails with "no matching package", wait and retry.
cargo publish -p manifold-csg-sys
cargo publish -p manifold3d-sys
cargo publish -p manifold-csg
cargo publish -p manifold3d
Post-publish
- Tag the release:
git tag -a v<version> -m "Release <version>"(using themanifold-csgversion number — facades share the workspace version) - Push the tag:
git push origin v<version> - Verify on crates.io that all 4 crates appear at the new version
- Verify docs.rs builds succeed for all 4
Rules
- NEVER publish without explicit user confirmation — dry run is the default mindset
- Do NOT publish from a dirty working tree or a non-main branch
- Do NOT publish if tests or clippy fail
- If any publish succeeds but a later one fails, report this clearly — published versions can only be yanked, not deleted. Common failure: facade fails because its canonical isn't indexed yet; retry after a delay.
- Sys crate patch bumps must be semver-compatible (additions only)
- Facade crates must always share the exact version of their canonical counterpart (
=pin in Cargo.toml enforces this)