A package manager for gno workspaces.
go install moul.io/gnopm@latest
gnomod.tomlA gno package path ends in its version, so the obvious layout gives each version its own directory. Bumping then means copying the directory and editing the copy.
git cannot pair a copy. The review diff of a version bump becomes a set of
added files with no content diff at all, which is backwards: a bump is by definition the
compatibility change that most needs reviewing, and it is the one change you cannot see.
git log --follow, blame and bisect all stop at the copy
too. One real port of 25 realms landed as +11,103 / −0 across 112 files, with
every behaviour change invisible.
gnopm removes the copy. The toolchain already reads the version from the module line, so the directory does not have to repeat it.
p/alice/md/gnomod.toml module = "gno.land/p/alice/md/v1"
p/alice/md/md.gno edited in place
gnomod.toml. Then you edit the files, and git
diffs them.status to ask, sync to fix. Those are the two you
type.gnopm doc documents a version that has no directory anywhere, which
is the one somebody is asking about when they meet an old import. Put the two signatures of a
bump next to each other and that IS the compatibility diff. No gno toolchain needed: gno
source is Go syntax, so the standard library parses it.publish says what is live, parked or absent on the chain your paths
point at, and sends the rest in dependency order. It holds no key and signs nothing: gnokey
does, and it prompts you.verify catches that while it is still
cheap.Every image above is generated from real command output by a script in the repository, so none of them can quietly stop matching what the tool prints.
If gnopm can work something out, it does. A flag is an admission that it could not.
Only sync and
bump write.
A change that bumps a version carries the pin that keeps the old one resolvable.
Making generated prose load-bearing would break every lock over a wording change.
A gno import path contains its version, so there is no constraint to solve. gno skipped the class of problem that produces SAT solvers. Keep not solving it.
It reads chains and generates commands. It never signs and never holds a key.
gnopm init # nothing else creates a gno workspace
gnopm search avl # the chain is the registry
gnopm get gno.land/p/nt/tinyavl/v0
gnopm tree
Three things are missing on purpose, and each removes a class of bug the other ecosystems still fight. No resolver: an import path contains its version, so two versions are two paths and there is no constraint to solve. No registry: the chain is the registry, so nothing can be operated, ranked, spammed or taken offline, and a search cannot disagree with what an import resolves to. No lockfile drift: a published path can never be redefined or withdrawn, so a content hash pins something that genuinely cannot change.
Type npm install's verbs at gnopm and it will tell you this itself rather
than saying "unknown command".
gnopm init # make this directory a workspace
gnopm status # what resolves, and whether anything is out of date
gnopm sync # make the state good
gnopm bump md # promote a package, in place; then edit the files
gnopm unbump md # take a number back, while nothing has published it
gnopm ls # every module and where its source is
gnopm doc md # the documentation, for any version, directory or not
gnopm tree # the dependency tree, in the terminal
gnopm search avl # find a published package on the chain
gnopm outdated # which imported versions have a newer one
gnopm why md/v0 # who still imports this version
gnopm verify # prove every pinned version still reproduces (for CI)
gnopm tidy # make the whole workspace right, chain included
gnopm publish # what is missing on chain, in dependency order
A number only means something once the version is published: until then it names no bytes, resolves for no importer, and derives no address. So the rule is bump when the last version shipped, and edit in place when it did not.
Getting it wrong is the default behaviour of a stack of pull requests. The first bumps
v0 to v1 and lands. The second is written against it, sees
v1 taken, and goes to v2. Both merge before either is published, and
the package reaches a chain as v2 with v1 existing nowhere. Often the
lock cannot even show it: bump will not pin a version living on no commit the
default branch has, so the number is skipped rather than recorded.
bump stays offline and literal, because a tool that phones home before doing
what it was told is a tool you cannot use in CI. Asking it to decide is opt-in:
gnopm bump md -if-published # bumps only if v0 is live or parked on the chain
gnopm unbump md # the inverse: lower to the lowest number nothing has taken
gnopm tidy # find every number spent on nothing
unbump reads its target off the chain rather than out of the lock, which is what
makes it safe rather than merely guarded: the target is by construction a number the chain has
never seen, so it cannot withdraw a published version and cannot redefine one either.
Migrating a repository that still has versioned directories:
gnopm deversion -n # the plan
gnopm deversion # do it, as git renames
Everything is in packages so other programs do not have to shell out:
pkg/gnomodlock is the
gnomod.lock format with no dependency on the CLI, and
pkg/gnopm is the
operations. The repository root is the command and a high-level integration test, nothing
else.