How to write and use a package
A package bundles two things a schema can depend on — generators and assets — behind one namespaced name, so you can reuse and share them. A package is pure data: installing one never runs code on your machine. This recipe covers writing a phony.json, loading a local one, and pulling one in from git.
The phony.json document
{
"name": "@acme/finance",
"version": "1.0.0",
"generators": [
{ "name": "@acme/finance:gross",
"params": { "net": { "type": "number", "default": 100 },
"rate": { "type": "number", "default": 0.2 } },
"let": { "tax": { "computed": "round(net * rate, 2)" } },
"body": "{{ net + tax }}" }
],
"assets": {
"lists": { "@acme/finance:currencies": { "*": ["USD","EUR","TRY"], "tr_TR": ["TRY","USD","EUR"] } },
"models": { "@acme/finance:ticker": { "*": "models/ticker.ngram" } }
},
"dependencies": { }
}| key | meaning |
|---|---|
name / version | the package's identity (name is namespaced, @scope/pkg) |
generators | an array of composition definitions — the same { name, params, let, body } shape you'd register by hand |
assets.lists / assets.models | locale contributions: asset → { locale → data | path } (see Serve locale-specific data) |
dependencies | other packages to pull in, by git source |
Each generator is compiled and registered under its name on load, so a schema reaches it with use exactly like a built-in. A package that ships only generators needs no assets; a data-only locale pack ships only assets.
Use a local package directory
The fastest loop: point --package at a directory containing a phony.json. Save the package above as finance/phony.json, then:
phony generate --use @acme/finance:gross --package ./finance --seed 42 -n 1phony prints a load line to stderr and the values to stdout:
✓ Loaded @acme/finance v1.0.0 — 1 generator(s), 0 asset(s)[ "120" ]--package is repeatable; later directories extend or override earlier ones (useful for layering locale contributions). To pass params to a generator, run a definition file instead of --use:
{ "use": "@acme/finance:gross", "params": { "net": 250, "rate": 0.18 } }Install a git dependency
For a shared package, declare it under dependencies and let Phony fetch it. The value is a git source — an object { "git": …, "version": … } (a semver range) or { "git": …, "ref": … } (a pinned branch/commit). A project phony.json:
{
"name": "@me/app",
"version": "0.0.0",
"dependencies": {
"@acme/finance": { "git": "https://github.com/acme/finance", "version": "1.0.0" }
}
}Then install the closure:
phony installInstalled 1 package(s):
@acme/finance v1.0.0 (316fb9aca01e)install resolves each dependency to an exact commit, fetches it into the store, and writes a phony.lock pinning the commit and a content checksum:
{
"version": 1,
"packages": [
{
"name": "@acme/finance",
"git": "https://github.com/acme/finance",
"version": "v1.0.0",
"commit": "316fb9aca01e2cb9b1b3abca86353a8e9d8cebe3",
"checksum": "sha256:fb8158718d592ab8501d8507c3ca1f3ff2301272cf99204d3e4d0c87de32c1aa"
}
]
}Commit phony.lock — a later phony install (or any generate) reuses the locked commit verbatim and verifies the checksum, so every machine gets byte-identical packages.
Once installed, generate auto-loads the project's dependency closure — no --package needed:
phony generate --use @acme/finance:gross --seed 1 -n 1 -f jsonl✓ Loaded @acme/finance v1.0.0 — 1 generator(s), 0 asset(s)
✓ Loaded @me/app v0.0.0 — 0 generator(s), 0 asset(s)"120"Managing dependencies
| command | does |
|---|---|
phony add <host>/<path>@<version-or-ref> | fetch, verify the declared name, add to phony.json, re-lock |
phony install | materialise the locked closure into the store; write the lock if missing |
phony install --update | re-resolve versions, ignoring the existing lock |
phony install --frozen | never fetch; require an up-to-date lock and a warm cache (CI) |
phony update [name] | re-resolve everything, or just name |
phony remove <name> | drop a dependency and re-lock |
phony list [--json] | print the resolved closure (name, version, short commit) |
The add shorthand builds an https:// git URL from <host>/<path> and picks the trailing @<spec> as a version range when it looks semver-shaped, else a ref. For example phony add github.com/acme/finance@1.0.0 records the dependency above. You can also write the shorthand directly as a dependency value: "@acme/finance": "github.com/acme/finance@1.0.0".
The content-addressed store
Fetched packages live in a global store — $PHONY_HOME/store if set, else ~/.phony/store — shared across all your projects and keyed by commit:
~/.phony/store/
git/<host>/<scope>/<pkg>/<commit>/ # each fetched package, by exact commit
blobs/<sha256> # remote asset blobs, by content hashBecause entries are addressed by commit and content hash, two projects pinning the same commit share one copy, and a materialised entry is never rewritten.
Pitfalls
versionandrefare mutually exclusive in a dependency — a semver range or a pinned branch/commit, not both.addalways produces anhttps://URL. To depend on a local path or a non-HTTPS remote during development, write thedependenciesobject form by hand with an explicitgitvalue.- A stale lock wins.
phony installreuses an existingphony.lockas-is; after editing dependencies by hand, runphony install --update(orphony update) to re-resolve. - The root project's own generators load last and shadow a dependency's — so a schema can override a packaged generator by defining one with the same name.
See also
- Serve locale-specific data
- Concepts: Packages