Skip to main content

Operational scripts

Operational scripts are one-off backend CLIs under apps/backend/src/scripts/. They backfill data, reconcile provider state, or run targeted migrations that do not belong in Sequelize schema migrations or seeds. Schema changes and baseline seed data still go through Migrations & Seeds (make migrate-up, make seed-up). Operational scripts are for work that is environment-specific, replayable, or too risky to fold into a migration file.

How to run

Local development

With the stack up, run from the host against the backend container:
Flags are real argv — no ARGS= wrapper, no -- separator after an npm script name.

Production (Railway)

Operational scripts compile with the normal backend build (tsc → apps/backend/dist/scripts/). On the built image, run compiled JS only:
Use this as a Railway one-off or start-command override on the same Docker image as webapp and consumer. See Railway for how the production image is built and deployed — this page does not prescribe Railway UI steps beyond that context.
Always run --help and a --dryRun pass before mutating production data.

How to create a script

1. Add a thin CLI file

Create apps/backend/src/scripts/<name>.ts and wire it through operationalScriptEntrypoint:

2. Put domain logic elsewhere

Keep the script file thin. Business logic belongs in apps/backend/src/utilities/ or the relevant tool module (e.g. apps/backend/src/tools/paymentProcessor/stripe/).

3. Build output

The backend tsc build emits apps/backend/dist/scripts/<name>.js. No extra package.json script entry is required.

Shared flags (via the wrapper)

Journals default to operational-journals/<scriptName>/ under the process cwd. Only non-dry-run forward runs persist them.

Best practices

  • Run --help first — every script must implement it through the wrapper.
  • Dry-run locally (--dryRun) before any production forward run.
  • For mutating forwards: set supportsRevert: true, append journal entries during the run, and implement revert with a domain journal helper.
  • Design for idempotency when replay or partial failure is likely.
Catalog migration example: migrateLegacyProductsToV1.ts with catalogMigrationJournal.ts.

Avoid

  • Per-script entries in apps/backend/package.json — use the run contract above instead.
  • Documenting or relying on ts-node in production.
  • make targets that pass script flags through ARGS='…'.
  • Calling Stripe or other vendor SDKs outside tool implementation trees.
  • Confusing these with make migrate-up / make seed-up — those are Sequelize schema/seed runners, not operational one-offs.

What’s next?

  • Migrations & Seeds — schema migrations and baseline seeds.
  • Railway — production image, deploy flow, and one-off commands on the built image.
  • Billing architecture — catalog IDs and product/price shape (context for catalog migration scripts).