Scripts¶
Catalog of helper scripts that live outside task targets. Anything you'd run by hand to bootstrap, release, or troubleshoot.
Root scripts (scripts/)¶
| Script | What it does | When to use it |
|---|---|---|
dev-host-ip.sh |
Prints the LAN IPv4 that phones, simulators, and browsers use to reach this dev machine, skipping loopback, VPN, and container/bridge interfaces. Empty output means none was found. | Called by task dev and the frontend dev tasks to pin the S3 presign host and EXPO_PUBLIC_API_URL. Run by hand to debug a device that cannot reach the backend. |
py-tool.sh |
Runs a Python dev tool (sqlfluff, mkdocs) from the repo-local .venv-tools/, falling back to PATH, and fails with an install hint when it finds neither. |
Called by task db:sql:lint, db:sql:format, docs, and docs:build so a missing linter fails the check instead of skipping quietly. |
wait-for-healthy.sh |
Polls docker inspect until the named containers report healthy, with a timeout. |
Called by task docker:wait (and via task dev / db:reset) so migrations don't start against a Postgres that is still running initdb. |
pull-secrets.sh |
Pulls backend secrets from GCP Secret Manager and either exports them to the current shell, or execs the backend with them injected. No secrets are written to disk. |
Daily local dev once you're past first-time setup. |
release.sh |
Validates the current branch, computes the next semver tag, writes backend/VERSION and frontend/VERSION, pushes the tag, and creates a GitHub Release. The tag push is what triggers Cloud Build. |
Cutting a production release. |
run_e2e.sh |
Boots the full docker-compose stack, waits for backend (/health) and frontend to be reachable, runs Playwright tests in e2e/, and (optionally) tears the stack down. |
Running E2E suite against a clean local environment. |
setup-capture.sh |
One-shot, idempotent bring-up of the link-capture / enrichment pipeline: checks host prerequisites, installs Go deps, starts the local stack (Postgres, Redis, MinIO, Photon), starts the self-hosted LLM server (Ollama) and pulls the resolver model, and seeds the capture env keys into backend/.env.local. |
First time you want to capture a link locally and watch it resolve a place + discovered links. |
pull-secrets.sh¶
# Most common: export into current shell, then run task dev
eval $(./scripts/pull-secrets.sh)
task dev
# Or pull and run backend directly in one shot
./scripts/pull-secrets.sh --run
# Pull infra secrets (DB password, etc.) from GCP too — by default these
# use local docker-compose defaults
./scripts/pull-secrets.sh --run --full
Prerequisites: gcloud auth login + gcloud config set project development-485000.
task db:seed:catalog uses the same gcloud-login-gated pattern to pull the published location seed set from the GCS bucket and load it into local Postgres, skipping the 14 GB global Overture file (you still get countries, cities, admin areas, curated prestige lists, and Wikidata notable places, just not the bulk Overture POI dump). Override the published version with SEED_VERSION=<tag> (defaults to the latest) and the per-city promotion with CITY_HIGHLIGHTS=<n>. Prerequisite: local compose up (task dev).
release.sh¶
Lives under .github/scripts/. Two ways to cut a release:
- GitHub Actions (preferred) — run the Release workflow (Actions → Release → Run workflow); pick a
bump(patch/minor/major) or pass an explicitversion. It runs the script non-interactively onmain. - Locally:
.github/scripts/release.sh # interactive: prompts for the version
RELEASE_BUMP=minor .github/scripts/release.sh # non-interactive bump (patch|minor|major)
RELEASE_VERSION=v1.2.3 .github/scripts/release.sh # explicit version
Bumps backend/VERSION + frontend/VERSION (both, when either service changed), commits, tags vX.Y.Z, pushes, and creates the GitHub Release. Must run on main with a clean working tree; the tag push triggers Cloud Build.
run_e2e.sh¶
./scripts/run_e2e.sh
# Builds and starts compose stack, waits for /health, runs Playwright,
# prompts at the end whether to tear down.
Works with both docker and podman (auto-detects). Report at e2e/playwright-report/index.html.
Backend scripts (backend/scripts/)¶
| Script | What it does | When to use it |
|---|---|---|
split_schema.py |
Splits a pg_dump -s dump (stdin) into the per-domain db/schema/*.sql snapshot sqlc generates against. Honors SCHEMA_OUT_DIR. |
Invoked by task db:schema (and the drift check); not run standalone. |
schema-drift-check.sh |
Applies db/migrations to a throwaway DB, dumps its schema, and diffs against db/schema/. Fails on drift; skips when Postgres is down (hard-fails under CI=true). |
Invoked by task db:schema:verify, CI, and the pre-commit hook. |
seed/build-sim-seed.py |
Builds the small, self-contained simulation/<version>/ seed bundle (trimmed collections + downscaled geo covers + compressed prestige photos) from a full seed cache and the geo-cover set, then it is uploaded to GCS. Paths and tuning are flags (--help). |
When the seed data refreshes or the sim scope changes; run by an operator, not wired into a task. |
seed/gen-sim-hotspots.py |
Regenerates cmd/test/global-hotspots/hotspots.json (major cities per country) that the sim seeds activity at and the cover scope derives from. |
Alongside build-sim-seed.py when the seed refreshes. |
Frontend scripts (frontend/scripts/)¶
| Script | What it does | When to use it |
|---|---|---|
compare_i18n.js |
Walks frontend/i18n/locales/en-US.json and compares its key set against zh-TW.json, ja-JP.json, zh-CN.json. Reports keys missing from any target and keys present in a target but absent from English (orphans). |
Before every release, catches dropped translations. |
build-luts.js |
Converts every .cube color LUT in frontend/assets/luts/ to a 512x512 Hald PNG (trilinear-upsampling the 33-cube source to 64-cube), writing {name}.png next to each source. The PNGs feed the Skia LUT shader used by the camera/photo filters. |
After adding or editing a .cube filter. |
gen-flags.mjs |
Downloads the circle-flag SVG for every COUNTRY_CODES entry in frontend/utils/countries.ts from HatScripts/circle-flags (following alias stubs), strips whitespace, and writes the inline map frontend/assets/flags/flagData.ts that CircleFlag renders from. Bundling the flags means no per-flag network fetch at render. |
After changing the supported country set. |
gen-share-locales.js |
Generates the OS share-card's localized strings for both platforms from one source (39-locale coverage), emitting the iOS extension's Localizable.strings and the Android target's strings.xml (keys share_saved_title, share_go_to_plan). |
After editing the share-card translations. |
bump-version.js |
Increments the iOS build number + Android versionCode in frontend/native-build.json (monotonic, required for every store upload), and for patch/minor/major also bumps the marketing version in package.json. Driven by task native:version:*. |
Before cutting a native binary. |
build-with-secrets.sh |
Pulls EXPO_PUBLIC_SENTRY_DSN and SENTRY_AUTH_TOKEN from GCP Secret Manager (no values written to disk), then runs the matching release-build command. ios/android exec expo run:* (Release build onto a device); beta-ios/beta-android build, sign (match / shared keystore), and upload to TestFlight / Play Internal via fastlane; release-ios/release-android promote to the stores; web runs expo export --platform web followed by sentry-cli sourcemaps upload. Default project development-485000 (override with GCP_PROJECT=…). |
Cutting a native release build, OR producing a web bundle with source maps uploaded to Sentry. Local dev (expo start) doesn't need it, since Sentry no-ops when DSN is empty. |
native-doctor.sh |
Preflight for native release builds: checks the installed toolchain against the pinned matrix (.nvmrc / .ruby-version / .xcode-version), gcloud auth, and that the signing secrets are reachable in GCP Secret Manager (no values printed). |
task native:doctor, on a new machine before a release. |
cd frontend
node scripts/compare_i18n.js
The script resolves localesDir relative to its own location via import.meta.url, so it works on any checkout.
build-with-secrets.sh¶
cd frontend
# iOS release build
./scripts/build-with-secrets.sh ios
# Android release build
./scripts/build-with-secrets.sh android
# Cut a beta via fastlane (build + sign + upload)
./scripts/build-with-secrets.sh beta-ios # -> TestFlight internal
./scripts/build-with-secrets.sh beta-android # -> Play Internal testing
# Regenerate native projects (ios/ and android/) before a release build if app.config.js changed
./scripts/build-with-secrets.sh prebuild
# Web export + Sentry source-map upload (release name: com.tomoda.app@<package.json version>)
./scripts/build-with-secrets.sh web
Prerequisites: gcloud CLI installed and authenticated (gcloud auth login), with secretAccessor permission on tomoda-sentry-dsn and tomoda-sentry-auth-token in project development-485000. The script aborts with a clear error if either gcloud isn't on PATH, gcloud isn't authenticated, or a secret can't be read.
Secret values are exported into the script process only — never written to disk — and inherited by the child expo / sentry-cli process via exec (native) or in the same shell (web). The shell history captures only the script invocation, not the secret values.
For web specifically: the wrapper runs expo export --platform web into dist/, then calls sentry-cli releases new/sourcemaps upload/releases finalize with --strip-prefix set to the frontend directory so the uploaded artifact paths match what's served from the CDN. The release name is com.tomoda.app@<version from package.json>, override with SENTRY_RELEASE=<custom> if you need a different identifier.
When to reach for the locale-reconcile skill instead¶
For more than a "what's missing?" check — automatic key extraction, hardcoded-string sweeps, orphan removal — see the locale-reconcile skill. It's the supported tool for substantive i18n maintenance; compare_i18n.js is just the diff reporter.