The fluffy-chainsaw CLI

Two loops cover almost everything:

# local dev
fluffy-chainsaw local -out .env      # start deps (Postgres, emulators, buckets) + write .env
node --env-file=.env src/index.ts
fluffy-chainsaw local-prune          # tear the containers down

# deploy
fluffy-chainsaw deploy -project <id> -region <region>   # build -> scan -> provision [-> migrate] [-> firebase]

Everything else is the deploy loop broken into inspectable steps.

Commands

Command What it does When you run it
fluffy-chainsaw local Scans code + yaml, starts containerized substitutes (Postgres, Pub/Sub emulator, Firebase Auth/RTDB emulators, filesystem buckets) plus every runner in dev mode: the repo is mounted at /repo and the runner runs node --watch, so code edits restart it in-place — the image rebuild only matters for deploy. Runner images must set WORKDIR under /repo mirroring the repo layout and an exec-form CMD ["node", "<entry>"]. Writes every descriptor to .env. --deps-only starts deps without runners. Every local dev session.
fluffy-chainsaw local-prune Removes the local containers/volumes for this project. Cleanup.
fluffy-chainsaw deploy One shot: buildscan -target pulumi-go (refreshes the program) → provisionmigrate (when a database declares migrations) → firebase (only when the app uses auth/Hosting). --stack <name> picks the deployment stack: per-stack values resolve to that stack's branch, and with a per-stack deploy.project the target project follows automatically. A stack marked deploy.pinned skips build and deploys the digests in releases/<stack>.images.json. The normal deploy.
fluffy-chainsaw release Promote the source stack's built images into the target stack's registry by digest (--to prod, --from defaults to the default stack), verify the copy, and write releases/<stack>.images.json — commit that file; it is what a pinned stack deploys. Prod never rebuilds: a rebuild would ship an artifact nobody tested. Cutting a release to a pinned stack.
fluffy-chainsaw scan (alias emit) Scan markers + merge fluffy-chainsaw.yaml → the desired state / provisioning program. -target pulumi-go generates a standalone Pulumi Go program; -target json emits tfvars for a Terraform root you own. To inspect what will be provisioned, or when driving the steps manually.
fluffy-chainsaw eject Write the generated Pulumi program into a directory you keep. When you want to own/inspect/version the program.
fluffy-chainsaw build Run each runner's make target; capture the image ref (the target's last stdout line). No-op if there are no runners. Before provisioning, or via deploy.
fluffy-chainsaw provision pulumi up on the generated program (-stack to pick the stack), owning every prerequisite deterministically: generates the program if missing, runs go mod tidy, picks the state backend (PULUMI_BACKEND_URLgs://fluffy-chainsaw-state-<project>, created on first use › file://~/.fluffy-chainsaw/state), defaults the passphrase to the documented dummy, creates the stack, seeds gcp:project/gcp:region/fluffy-chainsaw:deployerPrincipal (explicitly-set config always wins), and retries the apply while freshly-enabled APIs propagate. Apply infrastructure, or via deploy.
fluffy-chainsaw migrate Run each migrated database's migrations.run command as that database's migrator identity, then grant DML to its runners. --local targets the fluffy-chainsaw local postgres. See migrations.md. After adding a migration, or via deploy/local.
fluffy-chainsaw wire Read stack outputs (-from pulumi or -from terraform), slice descriptors to one runner's uses, write KEY=VALUE (--runner <name> -out .env). Reproduce a deployed runner's env for local runs, CI, or debugging. A deployed runner already has this env on its service.
fluffy-chainsaw adopt (alias import) Import resources you own that already exist in the project into state, skipping existing: true externals. First deploy into a project where your resources already live — run it between scan and provision so they're reconciled, not collided with.
fluffy-chainsaw firebase Generate (or merge into) firebase.json the Hosting rewrites that put the frontend and the /auth/** + /api/** runner on one origin. Merges — it won't clobber your other rewrites. Writes the app's own hosting.site when declared (default: the project's default site). Apps using auth/Hosting; run via deploy.
fluffy-chainsaw workspace deploy Deploy every child in the workspace manifest (fluffy-chainsaw.workspace.yaml: a defaults: block plus children: [{dir: <app dir>, select: <name>}, ...]) in order, each exactly like cd <child> && fluffy-chainsaw deploy. Before any deploy, a fail-closed pre-pass rejects two children owning the same physical resource (E_WORKSPACE_DOUBLE_OWNERSHIP). -only child1,child2 deploys a subset (the ownership check still covers the whole manifest). One repo, several independently-deployable apps.

Common flags

Run fluffy-chainsaw <command> -h for the full flag list of any command.

The manual deploy pipeline

When you want to see every step:

fluffy-chainsaw scan -target pulumi-go       # what must exist
fluffy-chainsaw build                        # images for every runner
fluffy-chainsaw adopt                        # only if resources already exist in the project
fluffy-chainsaw provision                    # pulumi up
fluffy-chainsaw wire --runner orders-api -out .env   # reproduce a runner's env if needed