Changelog
All notable changes to scadman are recorded here. Format loosely follows Keep a Changelog; versions follow SemVer (pre-1.0: expect breaking changes between minor versions).
[Unreleased]
0.1.0-alpha.4 — 2026-11-10
Fourth alpha. A documentation site, and a run of fixes to how the manifest and the
lockfile are read, most of them cases where scadman did the wrong thing without saying
so. Some can stop a project that used to pass: a scadman.toml with unknown keys, a git
dependency naming two refs, an abbreviated rev, and sync straight after remove.
Each now fails with a message that says what to change.
Added
- A documentation site at https://cameronbrooks11.github.io/scadman/, built from
docs/. The first-project walkthrough and the command table moved there from the README, which keeps install and a four-command summary; editor and GUI integration has its own page. - A manifest and lockfile reference (
docs/manifest.md): thescadman.tomlkeys, whatscadman.lockrecords, which manifest editssyncdetects, and where scadman keeps its files. CONTRIBUTING.md: setup, the check and test commands, pull-request conventions, and the release steps.
Fixed
- An unknown table or key in
scadman.tomlis now an error. A misspelled[dependencies]header used to read as a project with no dependencies:list,lockanddoctorall reported success. The same went for a misspelled key under[project]. The error names the table or key and its line. A dependency’s ownscadman.tomlstill tolerates unknown keys outside its dependency tables, so a library written for a newer scadman stays readable. Breaking for a manifest that carried extra keys: remove them. - A git dependency naming more than one of
rev,tagandbranchis now refused.lockused to accept it and silently userevovertag, andtagoverbranch, so addingtag = "v2"beside an oldrevline locked the old commit without a word.addalready rejected the combination; a hand-edited manifest, and a dependency’s own, are now held to the same rule. - A tag or branch whose name looks like a commit hash is held at its locked commit.
In a project with a path dependency, a tag such as
20210101(seven or more hex digits) was taken for a commit, so everysyncre-fetched it: the lock moved on its own when the tag moved upstream, andsyncfailed without the network. Whether a reference is a commit now comes from the manifest key it was declared under, not from its spelling. - Every command now refuses a lockfile of an unknown version. Only
sync,runandenvchecked it.graph,listanddoctorread a newer lockfile without comment, andupdate <name>used it as its base and wrote it back in the old format.lockandupdateno longer overwrite one either; the error says to upgrade scadman or delete the file. The version is read on its own, so a newer lockfile that does not parse is still reported by its version. - A removed dependency no longer stays available. After
scadman remove, the library stayed in the lockfile:syncandrunwent on exposing it, the project’s own import of it kept rendering, anddoctorcalled the lockfile up to date.sync,runandenvnow stop with the same “out of date … runscadman lock” error an added-but-unlocked dependency gets, anddoctorreports it. Libraries that are in the lockfile because another one depends on them are unaffected. - A lockfile
hashthat is not a content hash is refused. The hash is joined onto the store root, sohash = "/some/dir"orhash = "../../dir"madesynclink the package to that directory and put it onOPENSCADPATH. A hash must now be 64 lowercase hex digits, checked wherever the lockfile is read and again where the environment is built, before the existing environment is touched. - A failed copy into the store cleans up after itself. When an insert failed
partway (a full disk, for one), its
.staging-*directory stayed in the store for good: the name is unique to the run, so nothing later reused or removed it. - A git dependency’s
rootis checked at lock time. An absolute or..root on a git dependency locked cleanly, was written intoscadman.lock, and failed only atsync; only path dependencies andadd --rootwere checked earlier. A root that did not exist in the library (a typo) also locked, andsyncthen said the package was “not in the store — run a sync”.locknow refuses both, for any dependency from any manifest, and says which root is wrong. A bad root is also no longer reported as an invalid dependency name. - An abbreviated
revis refused with a message that says so. git fetches a commit only by its full name, sorev = "061fef7"could never lock: it failed with git’scouldn't find remote ref.add --revaccepted it anyway, andsyncprefix-matched it against the lockfile as if it were supported.addandlocknow ask for the full 40-digit hash, or a tag or branch, and arevis compared with the lockfile whole. - The include-scan knows what an
on_pathlibrary provides. When a dependency imported a path such as<util/x.scad>that anon_pathlibrary ships at its root, OpenSCAD resolved it andsyncstill warned that the dependency “importsutil/but it is not in your dependencies”, naming a library that does not exist. Such an import is now followed instead of reported, so scadman’s warnings match what OpenSCAD can and cannot open. - Both crates declare the minimum Rust version.
rust-version = "1.88"was set for the workspace but neither crate inherited it. An older toolchain was already turned away, but only because a dependency (sha2) requires 1.88 too; scadman’s own requirement was not stated where cargo reads it. cargo now namesscadman-cliandscadman-corein that message. scadman add <url>now says the URL is in the name position. Passing only a git URL used to fail with the package-name character error, which read as if the URL were malformed. It now prints`<url>` looks like a git URL, not a package name — usage: `scadman add <NAME> <GIT>`with a corrected command using the repository’s name, e.g.scadman add BOSL https://github.com/revarbat/BOSL.
Changed
- Release binaries are built from the committed
Cargo.lock. The release build now passes--locked, so a lockfile that disagrees withCargo.tomlfails the release rather than being rewritten inside the job. CI checks the same thing on every pull request, so a stale lock is caught before it reaches a tag.
0.1.0-alpha.3 — 2026-09-02
Third alpha. A doctor fix, a raised minimum Rust version, and the first release
built by a pipeline that ships checksums and publishes a full release rather than a
pre-release.
Fixed
scadman doctorno longer reports an unparseable manifest as missing. It loaded the manifest in a way that collapsed a parse error into the same result as an absent file, so a typo inscadman.tomlprintedscadman.toml not found — run `scadman init`— advice that then fails withscadman.toml already exists. Doctor now separates the cases and prints the TOML parse error, with the offending line, which is what it exists to do.
Changed
- The minimum supported Rust version is now 1.88 (was 1.85).
scadman-core’s resolver uses let-chains, which stabilised in 1.88. This affects building from source only — the prebuilt binary is unaffected. CI now checks against the version declared inCargo.tomlrather than a separately pinned one, so the two cannot drift. - Polished CLI output.
lockandsyncreportLocked 3 packagesrather thanLocked 3 package(s);doctordescribes an absent store as(not created yet)rather than(empty); and the error for a version dependency now gives the reason and the way out — registries aren’t supported yet, use a git or path source. - Release archives ship with a
checksums.txt(SHA-256,sha256sum -cformat) so a downloaded binary can be verified before it goes onPATH. The README’s install step does exactly that. - Releases are no longer marked pre-release. GitHub excludes pre-releases
from
/releases/latest, so that endpoint returned 404 and scripted installs had nothing to resolve against. The version string still carries the maturity signal — this is an alpha.
0.1.0-alpha.2 — 2026-07-30
Second alpha. Rounds out the CLI (graph, doctor, update), adds local path
dependencies and editor integration, makes the include-scan precise, and smooths first-run
onboarding. Validated against 12 real projects (a dogfooding pass).
Added
scadman graph— print the resolved dependency graph as a tree, or--jsonfor tooling.scadman doctor— a setup report: OpenSCAD, store, manifest, lockfile freshness, and the built environment, each line nudging the next step.scadman update [name…]— advance dependencies to newer commits and report what moved. With no arguments it re-resolves everything (likelock, but with a delta report); with names it advances only those and holds the rest at their locked revisions. Exactrevpins never move;doctornotes when dependencies track a branch/tag.- Path dependencies — depend on a local sibling library with
Name = { path = "../lib" }(orscadman add Name --path ../lib;--root/--on-pathfor a src-layout sibling).sync/run/envre-read it so code edits show up immediately, while git dependencies beside it stay pinned and are served from the store (offline-capable). Only the root manifest may declare one, and a lockfile referencing one is local, not reproducible. - Minimum OpenSCAD version — declare
openscad = "2021.01"under[project](a>=prefix is accepted) anddoctor/runwarn when the installed OpenSCAD is older. Advisory only; OpenSCAD compatibility is really feature-based. - Editor integration —
scadman env --write-vscodewritesopenscad.search_paths(for openscad-LSP), relative to${workspaceFolder}so.vscode/settings.jsonis committable; andscadman run -- model.scadopens the OpenSCAD GUI with dependencies resolved.
Changed
scadman add <url>with no ref now tracks the remote’s default branch (likegit clone) — pass--tag/--branch/--revto pin otherwise.- The include-scan is reachability-based — it follows the project’s imports and warns only about undeclared libraries the files you actually use reach, instead of every file a dependency ships (far fewer false positives on real libraries).
- Content hashes are documented as reproducible across Linux and macOS (path-separator- and file-mode-independent).
0.1.0-alpha — 2026-07-28
First public alpha. A working, project-oriented OpenSCAD dependency manager, grounded in a 228-repo ecosystem survey and validated against real libraries.
Added
- Project model —
scadman.tomlmanifest +scadman.locklockfile pinning each dependency to an exact commit and content hash. - CLI —
init,add,remove,list,lock,sync,run,env. - Content-addressed store — immutable, deduplicated package content with atomic, symlink-skipping inserts.
- Collect-and-unify resolver — one resolved version per package identity, structured
conflict diagnostics, no version-range solving (none is needed yet — see
docs/resolver-direction.md). - Per-project environment — exposes each package under its own name via
OPENSCADPATH, so a library’s self-rootedinclude <Name/…>resolves to the pinned content. - Library roots —
root(expose a subdir as the library root) andon_path(place it onOPENSCADPATH) support src-layout libraries like dotSCAD. - Include-scan — warns when installed code imports a library you did not declare.
- Editor integration —
scadman env/env --jsonfor openscad-LSP and preview extensions to resolve against pinned versions.
Known limitations
GitHub-hosted git sources only; one version per identity; per-OS lockfile hashes; no hosted registry or native OpenSCAD-GUI integration yet. See the README’s Status & scope.