FishTea
Documentation

Versioning and updates

An application generated from FishTea is its own project, with its own git history, its own edits and its own life. The framework moves underneath it. This is how those two facts are kept from fighting.

Three versions, kept apart#

They are separate because they fail separately, and conflating them is what makes upgrades frightening.

version where what it answers
framework fishtea --version which build of the framework is running
spec format spec.jsonfishtea the shape of the spec document
business revision spec.jsonbusiness.version which revision of your business this is

The framework is semver. An application pins a range in its package.json:

"fishtea": { "template": "ecommerce", "version": "^1.1.0", "home": "/path/to/fishtea" }

A minor release satisfies ^1.1.0, so most upgrades need no action from an application at all. That is the entire point of a pin — not ceremony, but permission to do nothing.

The spec format moves only when the vocabulary changes shape, which is far rarer than the code changing. Migrations key off the format, never off the framework version, so one migration serves many releases and most releases ship none.

Checking where you stand#

fishtea version
fishtea      1.2.0
spec format 1.2
bun         1.4.0
home        /home/you/.fishtea/versions/1.2.0

application /home/you/projects/myshop
  archetype  ecommerce
  pins       ^1.1.0 ✓ satisfied
  spec       format 1.1 → 1.2 available (fishtea update) · business v2026.09.22-3
  generated  by 1.1.0, updated 2026-09-22

fishtea doctor reports the same compatibility line alongside the database, classifier and binding, and every command that runs an application warns once if the pin is not satisfied — because the symptom otherwise shows up much deeper, as something confusing.

Two ways to be incompatible, with opposite fixes:

Updating an application#

fishtea update --dry-run    # show the plan, change nothing
fishtea update              # carry it out

Three things get reconciled, and you are told about each before anything is written.

The pin#

Moved forward to accept the running framework and its future minors.

The spec#

If spec.json is at an older format, the migration chain runs against it. An additive format change — a new optional section, a new allowed form for an existing field — rewrites nothing and says so rather than manufacturing activity:

spec       format 1.1 → 1.2
             1.1→1.2  adds the optional `plugins` section and the `impl.plugin` call form — both additive
             additive: the marker moves, nothing in your spec is rewritten

A migrated spec is validated before it is written. A spec this build could not load is never saved.

The files#

calls/, seed/, rules/ and scripts/ were copied from an archetype when the application was created. .fishtea/manifest.json records, for every one of those files, two hashes: what the archetype said, and what was actually left in your directory.

Two hashes rather than one, because they are not always the same — the scaffolder transforms files as it copies them, filtering seed rows and rewriting fields from your interview answers. Recording only one would make every untouched archetype file look like an upstream change on the very next update.

With both, an update is a three-way merge and never a guess:

the archetype changed it you changed it what happens
no no nothing
yes no updated in place
no yes left alone — it is yours now, and stays yours through every future release
yes yes conflict: your file stands, theirs lands beside it as <file>.new
you deleted it left deleted; files are never resurrected

A conflict clears when your file and the archetype's agree again — however they got there. Accept the .new, revert your edit, or happen to make the same change; any of them ends it. Ignoring a .new does not: the conflict is reported again next time, because silently adopting it would lose your edit.

files      2 to update, 1 new, 1 conflicting, 3 yours, 21 unchanged

   ↑ calls/cart_add.sql
   + calls/products_newest.sql
   ! calls/products_related.sql — you changed it and so did the archetype; the new one lands as .new

Commit .fishtea/manifest.json with the application. It is the only record of where a file came from, so a teammate on another machine reconciles the same way you would.

--spec-only and --files-only each do just their half.

Applications made before any of this existed#

fishtea update --adopt --template ecommerce

Adoption claims only files that currently match the archetype exactly. Anything that differs is left unrecorded, which reads as "yours" forever — so adoption can never cause a later update to overwrite an edit you made before tracking began.

Installing and switching framework versions#

./install.sh --from /path/to/fishtea                 # build from a checkout — the only route today

Once releases are published, the installer also serves from the site and takes a version:

curl -fsSL https://fishtea.io/install | bash         # the newest release
curl -fsSL https://fishtea.io/install | bash -s 1.1.0

The layout is deliberate:

~/.fishtea/
  versions/1.1.0/        a whole framework: bin/fishtea, templates/, plugins/, packages/ui/
  versions/1.2.0/
  current -> versions/1.2.0
  bin/fishtea -> ../current/bin/fishtea      ← the only thing on PATH
fishtea versions          # everything installed; * is running
fishtea use 1.1.0         # switch — one symlink
fishtea upgrade           # install the newest beside the current one and switch
fishtea upgrade --from /path/to/fishtea
fishtea uninstall 1.0.0   # the current version is protected

Switching is repointing one symlink, which matters most for going back. A framework you cannot downgrade is a framework you are afraid to upgrade, so nothing is deleted on upgrade and the version in use cannot be removed out from under itself.

Upgrading the framework never touches an application. An application moves when it runs fishtea update, which is a separate step you can read first. That separation is what lets you carry the framework forward across several projects without auditing all of them at once.

In the console#

fishtea tui → screen 6, Versions. What is running, what else is installed, and where this application stands — its pin, whether the running build satisfies it, its spec format, which build generated it. It can plan and apply an update, or switch framework version, without leaving the application directory. The plan is shown first and applied only on a second keypress.

Releasing the framework#

bun run release 1.2.0         # the only writer of every version site
bun run release --check       # assert they all agree (the test suite runs this)
bun run build-release --install --tarball

release rewrites the literal in packages/spec/src/release.ts and every workspace package.json in one move. build-release compiles the binary and stages the runtime files beside it — --compile embeds code, not data directories, so templates/, plugins/ and packages/ui ship alongside it.

Publishing is deliberately not automated:

bun run release 1.2.0 && bun test packages && git commit -am "Release 1.2.0" && git tag v1.2.0