Add demo accounts and starter content to production
@slashclone/seed-through-the-real-path
Fill a production app with demo accounts and starter content safely, through its own API or CLI instead of raw database inserts. Validation and audit logs run as for any user. Idempotent scripts with a dry run, short-lived tokens in a private file, a cleanup step that revokes them, and a guard that shows which database you are about to write to. Never prints tokens.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1---
- 2name: seed-through-the-real-path
- 3description: "Seed a production app with house accounts and starter content through the app's own public write path (its CLI or API) instead of inserting database rows, so validation, scanning, signing, and audit logs run. Idempotent scripts with a dry run, short-lived least-scope tokens in a private file that is never printed, a cleanup step that revokes them, and a guard that confirms the target database. Use when someone needs demo accounts, launch content, official or featured content, or sample data in production, or asks how to seed a production database safely."
- 4---
- 6# Seed through the real path
- 8Goal: production content that looks exactly like content a real user made, because it went through the same doors. Nothing skips validation, scanning, signing, or the audit log.
- 10## Why not insert rows
- 12A row written straight into the database skips every check the app makes on a write. It can hold content the scanner would reject, miss a signature or a hash, skip counters and search indexes, and leave no audit record. It also hides bugs in the write path that your first real user will then find. Seed with the same CLI or API calls a user would make. Write rows directly only for the account records themselves when no public sign-up path exists, and keep that part as small as you can.
- 14## The shape
- 16Split seeding into two kinds of script.
- 181. **Account setup** (an operator script that talks to the app's own server functions or admin API):
- 19 - Creates each house account if it is missing, looked up by handle, so a rerun changes nothing.
- 20 - Mints one short-lived token per account with the smallest scope that can publish. Name every token the same (for example `seeding`) so cleanup can find them.
- 21 - Writes the tokens to a file outside the repo, created with mode 0600. Never prints a token. Prints one line per account: the handle and "ready".
- 22 - Optional flags: `--only <handle>` to touch one account, `--seeding` to lift a plan or rate limit while seeding.
- 232. **Content seeding** (a script that only calls the public CLI or API, with the tokens from the file):
- 24 - Reads a plan file in the repo (accounts, content, which items to feature).
- 25 - For each item, checks whether that exact version already exists and skips it if so. Published versions are usually immutable, so a rerun must never try to overwrite one; a change is a new version.
- 26 - Uses the app's normal admin actions for featuring or curation, acting as a named staff member, so those steps are audited too.
- 28## Required flags
- 30- `--dry-run` on every script. It resolves the target, reads the plan, and prints what it would create, skip, or change. It writes nothing. Always run it first and read the counts.
- 31- `--done` on the account script. It revokes every token with the seeding name, puts each account back on its normal plan and limits, and prints one line per account. Run it the moment seeding ends, even if seeding failed halfway. Then delete the token file.
- 33## Rules learned the hard way
- 351. **Mint tokens against the environment you will publish to.** If production config lives in its own env file (for example `.env.production`), mint from that file. A token minted with the dev env goes into the dev database, and production rejects it with a 401. Revoke the stray token in dev too.
- 362. **Never read a token file in a way whose errors can print it.** In Node, `require()` on a file without a `.json` extension parses it as JavaScript, and the syntax error quotes the file, token included, into your log. Use `JSON.parse(readFileSync(path, "utf8"))` inside a try, and on failure print a generic message with no contents. `scripts/seed-guard.mjs` exports `readTokenFile()` that does this; import it rather than writing your own. If a token ever lands in a log, revoke it at once and say so.
- 373. **Confirm which database a script points at before it writes.** Do not `source` an env file in the shell. A value with `&` in it ends the assignment and backgrounds it, the variable stays unset, and many scripts then fall back to a default dev URL without a word. Let the runtime layer the files instead, in order, later files winning. In Node, pass `--env-file` once per file, base file first and production file last. Shared values stay in the base file and production values override them.
- 39 Make every seeding script print the target host (never the full URL) as its first line, and check it before you continue. To check by hand:
- 41 ```bash
- 42 node scripts/seed-guard.mjs target --env-file .env --env-file .env.production --var DATABASE_URL --expect <part of the production host>
- 43 ```
- 45 It exits non-zero if the host does not match. A count in the dry run (for example how many items exist) is a second check: dev and production rarely have the same number.
- 47## Token file checks
- 49Before the content script runs, check the file:
- 51```bash
- 52node scripts/seed-guard.mjs tokens --file /path/outside/repo/seed-tokens.json
- 53```
- 55It fails if the file is inside the repo, readable by anyone but you, or not a JSON object of account to token. It prints account names only.
- 57## Audit trail
- 59- The app's own audit log should show every step, because every step went through a real action. Do not add a quiet bypass.
- 60- Keep a short session note: which accounts, which content and versions, the dry-run counts, the real counts, and when tokens were revoked. Counts only, never tokens, prompt text, or file contents from users.
- 61- If something slipped (a wrong target, a leaked token), write down what happened, what you revoked, and the rule that prevents it next time.
- 63## Run order
- 651. Dry run of the account script against production. Check the host and counts.
- 662. Account script for real, with `--seeding` if you need limits lifted. Tokens go to the private file.
- 673. `seed-guard.mjs tokens` on the file.
- 684. Dry run of the content script. Check what it will create and skip.
- 695. Content script for real. Check a few items in the live app as a signed-out visitor.
- 706. `--done`. Delete the token file. Note the session.
- 72## Do not
- 74- Do not commit the token file, paste a token into chat, or pass a token on the command line where shell history keeps it. Read it from the file.
- 75- Do not give seeding tokens broad scopes "just in case". Publish scope only.
- 76- Do not leave plan or limit lifts in place after seeding.
- 77- Do not seed from a branch that has not been reviewed: the content goes public.
