Skip to content

Scripts

Scripts

Every repeated operation in this project has a script, and the script is the authority on how it is done — a hand-typed variant of any of these is how dev environments drift apart. This page says what each one is for and how to run it. The scripts themselves carry the detail in their header comments.

Paths are from the repo root unless stated.

Getting production data onto this machine

Real facility data is the only input that finds real problems, so dev works from production dumps rather than fixtures. The dumps hold researchers' names and addresses — db/dumps/ is gitignored and they never travel by git.

script what it does
scripts/dump-prod-facilities.sh Pulls a fresh dump of every production facility from do1
db/forensics/load-dumps.sh Loads each dump into its own read-only forensics_<facility> schema
scripts/dump-prod-facilities.sh                 # all facilities
scripts/dump-prod-facilities.sh zri udemo       # just these two
bash db/forensics/load-dumps.sh                 # all dumps
bash db/forensics/load-dumps.sh zri             # reload one facility's schema

dump-prod-facilities.sh always re-pulls db/env/ from the live deployment first, so a rotated production credential cannot strand you. It then dumps with do1's own mysqldump over ssh (matching its server version), --single-transaction, so production is never locked.

It self-prunes to one known-good dump per facility, and the gate is worth understanding: an older dump is deleted only if the new dump's highest stock.id is greater than or equal to the old one's. AUTO_INCREMENT only moves forward, so a backwards maximum means a truncated dump or a production system in trouble — then the old dump is kept, the run says so loudly, and it exits nonzero. A nonzero exit therefore does not mean nothing worked; read the per-facility lines.

Your ssh key must be in the agent (ssh-add ~/.ssh/id_ed25519) or the run dies at Permission denied (publickey).

Building the dev facilities

scripts/rebuild-dev-facility.sh zri udemo gui_test    # each from its newest dump
scripts/rebuild-dev-facility.sh zri --dump <file.sql>  # one, from a dump you name

Drop and recreate the database, load the dump, baseline InitialSchema (the dump is that schema), run the whole migration chain, then seed facility_settings from the facility's production settings file in db/env via migrate-config.

This is the same flow as a real customer cutover, which is why running it often is the point: every rebuild re-runs the real migration against a real facility's real configuration, so a mapping that breaks on real data breaks here instead of at a customer. See Build a dev facility for what a dev facility is and why it keeps the production database name.

Surveying real data

These answer "what is actually in the customers' databases", which is how a design assumption gets checked before it is built on. All read-only.

db/forensics/run-forensics.py — the data-cleanliness survey. Reads the forensics_* schemas and writes a multi-tab workbook to db/forensics/out/zfm-forensics-<date>.xlsx: per-facility usage totals, stock attribution gaps, user-table readiness against the required/unique name rules, tank sortOrder integrity, stocks by PI, and per-attribute field usage (populated, distinct, max length, last used) within and across facilities.

cd db/forensics
python3 run-forensics.py        # needs openpyxl; see requirements.txt

Load the schemas first. It reaches MySQL through docker exec, so no host MySQL client is needed, and the output holds real data, so out/ is gitignored.

db/forensics/substock-forensics.sql — reports how each substock diverges from its base stock, ahead of the substock rework. Classifies each divergence as HARD (differing internal parents, or no base stock at all — blocks migration, needs the customer to resolve), SOFT (differing fertilization date — a rule choice), or INFO (differing PI or researcher — legitimate variation).

docker exec -i zfm-mysql mysql -uroot -prootpass forensics_uofl < db/forensics/substock-forensics.sql

db/forensics/config-inventory.py — writes out/zfm-config-inventory-<date>.xlsx, a review sheet of every configuration surface in the system: the deployment env's Joi schema, the FacilitySettings document and its edit tiers, what publish() sends the client, and the client's own constants. Rows are colour-coded for "settled", "needs a decision" and "actively wrong". Hand-compiled from the source files named in its header, so it is a snapshot that needs re-checking against them, not a generated artefact.

Facility provisioning and configuration

script what it does
scripts/provision-customer.sh Creates a facility's database and its DML-only runtime login
zf-server: npm run create-super Creates the platform super user in a facility that has none
zf-server: npm run promote-super Promotes a migrating facility's existing owner account to super, and normalises its name and initials. Cutover only — delete with the last facility to migrate
zf-server: npm run migrate-config Converts an old-style .env into a FacilitySettings document

The first two belong to a deployment task and are documented in sequence there — see deployment docs → New customer and Migrate customer. Don't run them from this page's fragments.

migrate-config is the executable, tested authority on where every old environment key went:

cd zf-server
npm run migrate-config -- <old-env-path> [--out <new-env-path>] [--dry-run] [--force]

Start with --dry-run. CONFIG-MIGRATION-CHECKLIST.md in the repo root records the verdict for each key; the script and its spec are what actually decide.

Build-time generation

These run from npm hooks, so you rarely invoke them. Knowing they exist matters when output looks stale.

script run by what it does
zf-server/scripts/generate-build-info.mjs prebuild, pretest, pretypecheck, prestart Writes the server's gitignored src/version/build-info.ts from git describe
zf-react-client/scripts/generate-build-info.mjs the client's equivalent hooks The same for the client, from its own build
zf-react-client/scripts/typegen.mjs pretypecheck Regenerates expo-router's typed-routes declaration without starting Metro

typegen.mjs exists because experiments.typedRoutes makes Href a union of the routes that existed when Metro last ran: add a route file and tsc rejects every link to it until something regenerates that union. It calls the same Expo function the CLI does, through the CLI's own module resolution — but it is Expo internals, so if it throws after an SDK upgrade, delete the call and restart Metro instead. See Versioning and Release for what the build-info scripts produce.

Working the codebase

scripts/comments-reviewed.sh — which files' comments have been read and which are still waiting. Reviewing a codebase's comments is slow work spread over weeks, so the only thing that has to survive is the answer to "where was I".

scripts/comments-reviewed.sh                          # the client's source
scripts/comments-reviewed.sh zf-server/src            # the server's
scripts/comments-reviewed.sh zf-server/src '*.entity.ts'
scripts/comments-reviewed.sh -r                       # what HAS been reviewed

It asks git rather than a list anyone maintains: a file counts as reviewed once it appears in a commit whose subject begins docs(comments). That cannot drift or go stale — but it does mean folding a comment pass into a feature commit loses the record, which is a reason to keep those passes on their own commits.