Skip to content

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 ​

json
{
  "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": { }
}
keymeaning
name / versionthe package's identity (name is namespaced, @scope/pkg)
generatorsan array of composition definitions — the same { name, params, let, body } shape you'd register by hand
assets.lists / assets.modelslocale contributions: asset → { locale → data | path } (see Serve locale-specific data)
dependenciesother 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:

bash
phony generate --use @acme/finance:gross --package ./finance --seed 42 -n 1

phony prints a load line to stderr and the values to stdout:

text
✓ Loaded @acme/finance v1.0.0 — 1 generator(s), 0 asset(s)
json
[ "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:

json
{ "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:

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:

bash
phony install
text
Installed 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:

json
{
  "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:

bash
phony generate --use @acme/finance:gross --seed 1 -n 1 -f jsonl
text
✓ Loaded @acme/finance v1.0.0 — 1 generator(s), 0 asset(s)
✓ Loaded @me/app v0.0.0 — 0 generator(s), 0 asset(s)
json
"120"

Managing dependencies ​

commanddoes
phony add <host>/<path>@<version-or-ref>fetch, verify the declared name, add to phony.json, re-lock
phony installmaterialise the locked closure into the store; write the lock if missing
phony install --updatere-resolve versions, ignoring the existing lock
phony install --frozennever 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:

text
~/.phony/store/
  git/<host>/<scope>/<pkg>/<commit>/   # each fetched package, by exact commit
  blobs/<sha256>                       # remote asset blobs, by content hash

Because 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 ​

  • version and ref are mutually exclusive in a dependency — a semver range or a pinned branch/commit, not both.
  • add always produces an https:// URL. To depend on a local path or a non-HTTPS remote during development, write the dependencies object form by hand with an explicit git value.
  • A stale lock wins. phony install reuses an existing phony.lock as-is; after editing dependencies by hand, run phony install --update (or phony 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 ​

Phony Cloud — Documentation & Specification