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.