@forumone/create-throughline 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/generator.d.ts.map +1 -1
  3. package/dist/generator.js +4 -0
  4. package/dist/generator.js.map +1 -1
  5. package/dist/post-install.d.ts.map +1 -1
  6. package/dist/post-install.js +10 -3
  7. package/dist/post-install.js.map +1 -1
  8. package/dist/templates/base/.env.example +9 -13
  9. package/dist/templates/base/README.md +55 -2
  10. package/dist/templates/base/apps/web/package.json.template +11 -2
  11. package/dist/templates/base/apps/web/scripts/check-migrations.ts +147 -0
  12. package/dist/templates/base/apps/web/scripts/lib/target.ts +95 -0
  13. package/dist/templates/base/apps/web/scripts/write-migration-snapshot.ts +119 -0
  14. package/dist/templates/base/apps/web/src/app/api/inngest/route.ts.template +8 -1
  15. package/dist/templates/base/apps/web/src/lib/database.ts +158 -0
  16. package/dist/templates/base/apps/web/src/payload.config.ts.template +73 -16
  17. package/dist/templates/base/github/scripts/check-generated.sh +44 -0
  18. package/dist/templates/base/github/workflows/ci.yml +131 -0
  19. package/dist/templates/base/package.json.template +6 -1
  20. package/dist/templates/base/scripts/check-audit.ts +117 -0
  21. package/dist/templates/base/scripts/check-single-instance.ts +113 -0
  22. package/dist/templates/base/scripts/check-tested-packages.ts +165 -0
  23. package/dist/templates/base/turbo.json +16 -1
  24. package/dist/templates/with-reference-ds/design-system/scripts/build-manifest.ts +59 -29
  25. package/dist/templates/with-reference-ds/design-system/scripts/validate.ts +26 -1
  26. package/dist/versions.json +1 -1
  27. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @forumone/create-throughline
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8b7d698: A scaffolded project ships a two-tier CI workflow:
8
+
9
+ - `fast` runs on every pull request with no database. It installs from the lockfile, then runs typecheck, lint, the workspace and migration gates, manifest validation, a generated-files check and tests, with a non-blocking audit.
10
+ - `verify` runs on pushes to main. It migrates a fresh Postgres and runs a production build.
11
+
12
+ `check-generated.sh` regenerates `payload-types.ts` and the admin import map and fails on any difference, including an uncommitted file. `apps/web` gains `generate:types` and `generate:importmap`.
13
+
14
+ - 8b7d698: A scaffolded project manages its schema through migrations from the first table on:
15
+
16
+ - **Config.** `payload.config.ts` sets `push: false` and an explicit `migrationDir`. It connects through a resolver that knows the Vercel–Neon integration's variables, keeps a migration off Neon's pooled endpoint, and accepts an optional migration-only credential.
17
+ - **Scripts.** `apps/web` gains `migrate`, `migrate:create`, `migrate:status`, `migrate:snapshot` and `check:migrations`. The check asserts that every migration has its schema snapshot, that no snapshot is orphaned, and that no drop follows a `CASCADE` that has already carried it out.
18
+ - **Write guard.** `scripts/lib/target.ts` gives scripts that write to a database a `requireTarget` guard.
19
+
20
+ - 8b7d698: A scaffolded project ships three workspace gates as root scripts:
21
+
22
+ - `check:instances`: `payload`, `@payloadcms/ui`, `react` and `react-dom` each resolve to exactly one copy
23
+ - `check:tested`: every package that ships code has at least one test
24
+ - `check:audit`: production advisories are checked against an allowlist that carries a reason for each entry and fails when an entry goes stale
25
+
26
+ ### Patch Changes
27
+
28
+ - e9077b3: The reference design system's manifest build and validation, which every scaffolded project vendors, now:
29
+
30
+ - refuse a component directory with no contract, instead of silently leaving it out of the manifest
31
+ - read the directories that hold components from a `CONTRACT_LAYERS` list
32
+ - name the manifest from the project's own `package.json`, instead of claiming to be `@forumone/throughline-reference-ds`
33
+ - treat `antiExamples.empty` and `intent.brevity` as errors
34
+
35
+ - 6800052: The scaffold compiles against the current Throughline releases, and serves its tools over MCP:
36
+
37
+ - `payload.config.ts` no longer imports `createApiKeysCollection`, which core removed.
38
+ - One `createMcpToolCollector()` is passed to every tool-bearing plugin, and `@payloadcms/plugin-mcp` is registered after them, with its key collection narrowed to admins.
39
+ - The Inngest route types the integration registry, so `serve()` can type its function list.
40
+ - `@payloadcms/plugin-form-builder` is a caret range like the rest of `@payloadcms/*`, so it no longer installs a second `@payloadcms/ui`.
41
+ - `.env.example` and the printed next steps drop the per-server MCP keys that no longer exist.
42
+
3
43
  ## 0.3.1
4
44
 
5
45
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAgC,MAAM,EAAE,EAAE,EAAE,IAAI,EAAa,MAAM,kBAAkB,CAAA;AAK5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAK3C,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC7F;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,iBAAe,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmClF;AAiBD,iBAAS,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI3C;AAUD,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AA6BD,eAAO,MAAM,SAAS;;;;;;;CAAsE,CAAA"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAgC,MAAM,EAAE,EAAE,EAAE,IAAI,EAAa,MAAM,kBAAkB,CAAA;AAK5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAK3C,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC7F;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,iBAAe,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmClF;AAiBD,iBAAS,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI3C;AAcD,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AA6BD,eAAO,MAAM,SAAS;;;;;;;CAAsE,CAAA"}
package/dist/generator.js CHANGED
@@ -117,9 +117,13 @@ function isBinary(filename) {
117
117
  /**
118
118
  * npm strips files literally named `.gitignore` from a published tarball, so
119
119
  * we author them as `gitignore` in templates and restore the dot on output.
120
+ * `.github` goes the same way: a dot-directory in a tarball is at the mercy of
121
+ * whichever ignore rules the publishing npm applies, and the scaffold's CI is
122
+ * not something to lose silently.
120
123
  */
121
124
  const DOTFILE_RENAMES = {
122
125
  gitignore: '.gitignore',
126
+ github: '.github',
123
127
  };
124
128
  function applyDotfileRename(name) {
125
129
  return DOTFILE_RENAMES[name] ?? name;
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"post-install.d.ts","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkFrD"}
1
+ {"version":3,"file":"post-install.d.ts","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwFrD"}
@@ -28,11 +28,18 @@ export function printNextSteps(answers) {
28
28
  lines.push(` ${pc.dim('openssl rand -base64 48 # PAYLOAD_SECRET')}`);
29
29
  lines.push(` ${pc.dim('openssl rand -base64 48 # APPROVAL_TOKEN_SECRET')}`);
30
30
  lines.push(` ${pc.dim('openssl rand -base64 48 # FORMS_IP_HASH_SECRET')}`);
31
+ // `push: false`: the schema exists only once a migration has created it.
32
+ lines.push(formatStep(step++, 'Create and apply the first migration:'));
33
+ lines.push(` ${pc.dim('pnpm --dir apps/web migrate:create initial')}`);
34
+ lines.push(` ${pc.dim('pnpm --dir apps/web migrate')}`);
35
+ // Committed and checked by CI, so the first push is green only with them.
36
+ lines.push(formatStep(step++, 'Generate the Payload types and import map, and commit them:'));
37
+ lines.push(` ${pc.dim('pnpm --dir apps/web generate:types')}`);
38
+ lines.push(` ${pc.dim('pnpm --dir apps/web generate:importmap')}`);
31
39
  lines.push(formatStep(step++, `${pc.bold('pnpm dev')} to run locally`));
32
40
  lines.push(formatStep(step++, 'Open http://localhost:3000/admin and create your first admin user'));
33
- lines.push(formatStep(step++, 'In the admin, go to API Keys and create one key per MCP server'));
34
- lines.push(formatStep(step++, 'Paste those keys into .env.local'));
35
- lines.push(formatStep(step, 'Wire Claude (or your MCP client) to your MCP endpoints — see the generated README.md'));
41
+ lines.push(formatStep(step++, 'In the admin, create one key under MCP → Payload MCP API Keys'));
42
+ lines.push(formatStep(step, 'Give that key to Claude (or your MCP client) for /api/mcp — see the generated README.md'));
36
43
  // eslint-disable-next-line no-console
37
44
  console.log();
38
45
  // eslint-disable-next-line no-console
@@ -1 +1 @@
1
- {"version":3,"file":"post-install.js","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"post-install.js","sourceRoot":"","sources":["../src/post-install.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,9 @@
1
1
  # --- Required ---
2
2
  DATABASE_URI=postgres://user:pass@host/dbname
3
+ # Optional: a schema-owner credential used only by `pnpm migrate`, so the app
4
+ # itself can run as a role that reads and writes rows and no more. See
5
+ # apps/web/src/lib/database.ts.
6
+ # MIGRATION_DATABASE_URL=
3
7
  PAYLOAD_SECRET=generate-with-openssl-rand-base64-48
4
8
  NEXT_PUBLIC_SERVER_URL=http://localhost:3000
5
9
 
@@ -23,19 +27,11 @@ APPROVAL_TOKEN_SECRET=
23
27
  # Generate: openssl rand -base64 48
24
28
  FORMS_IP_HASH_SECRET=
25
29
 
26
- # --- MCP server keys ---
27
- # Each key is generated in the Payload admin (API Keys collection) after
28
- # first deploy and pasted back here for local dev.
29
- COMPONENT_SERVER_API_KEY=
30
- PUBLISHING_SERVER_API_KEY=
31
- APPROVALS_SERVER_API_KEY=
32
- AUDIT_SERVER_API_KEY=
33
- FORMS_SERVER_API_KEY=
34
- INTEGRATIONS_SERVER_API_KEY=
35
-
36
- # System API key used by scheduled workflows that talk back to the
37
- # Publishing MCP server (executes scheduled publishes).
38
- PUBLISHING_SYSTEM_API_KEY=
30
+ # --- MCP ---
31
+ # No keys here. Every Throughline tool is served on one /api/mcp by
32
+ # @payloadcms/plugin-mcp; create a key in the admin under MCP -> Payload MCP
33
+ # API Keys and give it to your MCP client, not to this file. Scheduled
34
+ # publishes run in process and need no key either.
39
35
 
40
36
  # --- Optional: storage ---
41
37
  # Vercel Blob token if deploying to Vercel; remove this and the storage
@@ -32,10 +32,20 @@ openssl rand -base64 48
32
32
  # - RESEND_API_KEY — from Resend dashboard
33
33
  # - INNGEST_EVENT_KEY/SIGNING — from Inngest (optional in local dev)
34
34
 
35
- # 5. Start the dev server
35
+ # 5. Create and apply the first migration. The config sets `push: false`, so
36
+ # migrations are the only way the schema changes, from the first table on.
37
+ pnpm --dir apps/web migrate:create initial
38
+ pnpm --dir apps/web migrate
39
+
40
+ # 6. Generate the Payload types and admin import map, and commit both along
41
+ # with the migration. CI fails until they match the config.
42
+ pnpm --dir apps/web generate:types
43
+ pnpm --dir apps/web generate:importmap
44
+
45
+ # 7. Start the dev server
36
46
  pnpm dev
37
47
 
38
- # 6. Visit http://localhost:3000/admin and create your first user.
48
+ # 8. Visit http://localhost:3000/admin and create your first user.
39
49
  ```
40
50
 
41
51
  ## After admin signup
@@ -66,6 +76,49 @@ pnpm dev
66
76
  - `pnpm lint` — ESLint
67
77
  - `pnpm test` — run all package tests
68
78
 
79
+ ## Migrations
80
+
81
+ The schema changes only through `apps/web/src/migrations` — `push` is off, in
82
+ development too. From `apps/web`:
83
+
84
+ - `pnpm migrate:create <name>` — generate a migration from the config. It diffs against the `.json` snapshot beside the newest migration, not against the database, and needs no database.
85
+ - `pnpm migrate` — apply pending migrations. Connects through `MIGRATION_DATABASE_URL` when it is set.
86
+ - `pnpm migrate:status` — what has been applied.
87
+ - `pnpm migrate:snapshot` — after **hand-writing** a migration, write its snapshot, or the generator falls a step behind and re-proposes changes that already shipped.
88
+ - `pnpm check:migrations` — every migration has a snapshot, no snapshot is orphaned, and no drop follows a `CASCADE` that already carried it out. Also runs from the root.
89
+
90
+ Scripts that write to a database can call `requireTarget` from `apps/web/scripts/lib/target.ts`, which prints the database, host and variable it is about to write through, and demands `--confirm` before overwriting anything.
91
+
92
+ ## Generated files
93
+
94
+ `apps/web/src/payload-types.ts` and `apps/web/src/app/(payload)/admin/importMap.js`
95
+ are generated from `payload.config.ts` and **committed**. Regenerate both after
96
+ any change to the config, a collection or a plugin version, and commit the
97
+ result — CI regenerates them and fails on a difference:
98
+
99
+ ```bash
100
+ pnpm --dir apps/web generate:types
101
+ pnpm --dir apps/web generate:importmap
102
+ ```
103
+
104
+ A stale import map is a 500 on any admin screen that renders a component the
105
+ map has never heard of.
106
+
107
+ ## CI
108
+
109
+ `.github/workflows/ci.yml` runs two tiers. `fast` runs on every pull request
110
+ and needs no database: install from the lockfile, typecheck, lint, the gates
111
+ below, manifest validation, the generated-files check and tests. `verify` runs
112
+ on pushes to main: it migrates a fresh Postgres and runs a production build.
113
+
114
+ ## Workspace gates
115
+
116
+ Each is explained at the top of its script in `scripts/`:
117
+
118
+ - `pnpm check:instances` — `payload`, `@payloadcms/ui`, `react` and `react-dom` each resolve to exactly one copy. Two byte-identical copies are two React contexts, and a runtime failure nothing else can see.
119
+ - `pnpm check:tested` — every workspace package that ships code has at least one test. Not a coverage number. `apps/web` starts on its exception list; delete the entry with your first test.
120
+ - `pnpm check:audit` — high and critical production advisories against an allowlist that carries a reason for each entry, and that fails when an entry stops applying.
121
+
69
122
  ## Project layout
70
123
 
71
124
  ```
@@ -9,7 +9,14 @@
9
9
  "start": "next start",
10
10
  "clean": "rm -rf .next .turbo",
11
11
  "typecheck": "tsc --noEmit",
12
- "payload": "payload"
12
+ "payload": "payload",
13
+ "generate:types": "payload generate:types",
14
+ "generate:importmap": "payload generate:importmap",
15
+ "migrate": "PAYLOAD_MIGRATING=1 payload migrate",
16
+ "migrate:create": "payload migrate:create",
17
+ "migrate:status": "payload migrate:status",
18
+ "migrate:snapshot": "tsx scripts/write-migration-snapshot.ts",
19
+ "check:migrations": "tsx scripts/check-migrations.ts"
13
20
  },
14
21
  "dependencies": {
15
22
  "@forumone/throughline-approvals": "^{{version_approvals}}",
@@ -25,7 +32,8 @@
25
32
  "{{designSystemName}}": "workspace:*",
26
33
  {{/if}}"@payloadcms/db-postgres": "^3.83.0",
27
34
  "@payloadcms/next": "^3.83.0",
28
- "@payloadcms/plugin-form-builder": "3.83.0",
35
+ "@payloadcms/plugin-form-builder": "^3.83.0",
36
+ "@payloadcms/plugin-mcp": "^3.83.0",
29
37
  "@payloadcms/richtext-lexical": "^3.83.0",
30
38
  "graphql": "^16.8.1",
31
39
  "inngest": "^4.2.0",
@@ -38,6 +46,7 @@
38
46
  "@types/node": "^20.17.0",
39
47
  "@types/react": "^19.2.0",
40
48
  "@types/react-dom": "^19.2.0",
49
+ "tsx": "^4.21.0",
41
50
  "typescript": "^5.6.0"
42
51
  }
43
52
  }
@@ -0,0 +1,147 @@
1
+ import { readdir, readFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ /*
6
+ Three checks on `src/migrations`, each about a failure nobody sees until the
7
+ moment it has to work.
8
+
9
+ pnpm check:migrations
10
+
11
+ With `push: false` a migration is the only way this schema ever changes, so the
12
+ migration chain has to stay usable.
13
+
14
+ ## 1. A migration with no schema snapshot
15
+
16
+ `migrate:create` does not diff the config against the database. It diffs it
17
+ against the `.json` snapshot beside the **most recent** migration, and writes one
18
+ only when it generated the SQL itself. So a migration written by hand leaves the
19
+ generator a step behind, and every run after it re-proposes a change that
20
+ already shipped — often as drizzle's interactive rename prompt, which cannot be
21
+ answered non-interactively, so the command exits having written nothing.
22
+
23
+ Every migration is checked, not only the newest: a gap in the middle is inert
24
+ today and was a blocked generator on the day it merged, which is the day this
25
+ needs to fail. Write one with `pnpm migrate:snapshot`.
26
+
27
+ ## 2. An orphaned snapshot
28
+
29
+ A `.json` with no `.ts` beside it. Delete a migration and leave its snapshot,
30
+ and that snapshot sorts last and becomes what the generator diffs against — a
31
+ schema no database will ever be in.
32
+
33
+ ## 3. A drop that a CASCADE has already carried out
34
+
35
+ Payload's generator can emit, in this order:
36
+
37
+ DROP TABLE "posts" CASCADE;
38
+ ALTER TABLE "search_rels" DROP CONSTRAINT "search_rels_posts_fk";
39
+
40
+ The CASCADE already removed that constraint, so the second statement errors,
41
+ and because a migration runs in a transaction it takes the whole migration — or
42
+ the whole rollback — with it. What is flagged is a bare `DROP CONSTRAINT` or
43
+ `DROP INDEX` that follows a cascading `DROP` in the same function. A bare drop
44
+ with no CASCADE above it is dropping something that is still there, and
45
+ demanding `IF EXISTS` of it would be the kind of blanket rule people learn to
46
+ suppress.
47
+ */
48
+
49
+ const here = path.dirname(fileURLToPath(import.meta.url))
50
+ const MIGRATIONS = path.resolve(here, '../src/migrations')
51
+
52
+ const BARE_DROP = /\bDROP\s+(CONSTRAINT|INDEX)\s+"/gi
53
+
54
+ // A `DROP` that cascaded, kept inside one statement by `[^;]*`. Not the word
55
+ // on its own, which also matches the `ON DELETE cascade` of every foreign key.
56
+ const CASCADE = /\bDROP\b[^;]*\bCASCADE\b/i
57
+
58
+ const stem = (name: string) => name.replace(/\.(json|ts)$/, '')
59
+
60
+ const entries = await readdir(MIGRATIONS).catch((error: NodeJS.ErrnoException) => {
61
+ if (error.code === 'ENOENT') return [] as string[]
62
+ throw error
63
+ })
64
+
65
+ const files = entries.filter((name) => name.endsWith('.ts') && name !== 'index.ts').sort()
66
+
67
+ if (files.length === 0) {
68
+ console.log('No migrations yet. Create the first with:\n\n' + ' pnpm migrate:create initial\n')
69
+ process.exit(0)
70
+ }
71
+
72
+ // Basenames on both sides, because that is what `createMigration` sorts: it
73
+ // reads the directory, keeps the `.json`, sorts, and takes the last.
74
+ const snapshots = new Set(entries.filter((name) => name.endsWith('.json')).map(stem))
75
+
76
+ const missing = files.filter((file) => !snapshots.has(stem(file)))
77
+
78
+ if (missing.length > 0) {
79
+ console.error(
80
+ `${missing.length} migration(s) with no schema snapshot:\n\n` +
81
+ missing.map((file) => ` ${file}\n ${stem(file)}.json <- missing`).join('\n\n') +
82
+ '\n\nmigrate:create diffs the config against the snapshot beside the most\n' +
83
+ 'recent migration. Without one it diffs against a schema that is a step\n' +
84
+ 'behind, and re-proposes a change that has already shipped. Write it with:\n\n' +
85
+ ' pnpm migrate:snapshot\n\n' +
86
+ 'Run it only once the migration is complete: the snapshot claims the\n' +
87
+ 'database matches the config.\n',
88
+ )
89
+ process.exit(1)
90
+ }
91
+
92
+ const migrationNames = new Set(files.map(stem))
93
+ const orphans = [...snapshots].filter((name) => !migrationNames.has(name)).sort()
94
+
95
+ if (orphans.length > 0) {
96
+ console.error(
97
+ `${orphans.length} schema snapshot(s) with no migration beside them:\n\n` +
98
+ orphans.map((name) => ` ${name}.json`).join('\n') +
99
+ '\n\nA snapshot sorting last is what migrate:create diffs against, so one\n' +
100
+ 'left behind by a deleted migration describes a schema no database will\n' +
101
+ 'reach. Delete it, or restore the migration it belongs to.\n',
102
+ )
103
+ process.exit(1)
104
+ }
105
+
106
+ const problems: Array<{ file: string; statement: string }> = []
107
+
108
+ for (const file of files) {
109
+ const source = await readFile(path.join(MIGRATIONS, file), 'utf8')
110
+
111
+ for (const name of ['up', 'down'] as const) {
112
+ const start = source.indexOf(`export async function ${name}`)
113
+ if (start === -1) continue
114
+ const other = source.indexOf('export async function', start + 1)
115
+ const body = source.slice(start, other === -1 ? undefined : other)
116
+
117
+ for (const match of body.matchAll(BARE_DROP)) {
118
+ const at = match.index ?? 0
119
+ if (!CASCADE.test(body.slice(0, at))) continue
120
+
121
+ const absolute = start + at
122
+ const lineStart = source.lastIndexOf('\n', absolute) + 1
123
+ const lineEnd = source.indexOf('\n', absolute)
124
+ problems.push({
125
+ file,
126
+ statement: source.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim(),
127
+ })
128
+ }
129
+ }
130
+ }
131
+
132
+ if (problems.length > 0) {
133
+ console.error(
134
+ `${problems.length} drop(s) after a CASCADE without IF EXISTS:\n\n` +
135
+ problems.map((p) => ` ${p.file}\n ${p.statement}`).join('\n\n') +
136
+ '\n\nA preceding DROP ... CASCADE has already removed these, so the\n' +
137
+ 'migration will abort. Add IF EXISTS:\n\n' +
138
+ ' DROP CONSTRAINT IF EXISTS "..."\n' +
139
+ ' DROP INDEX IF EXISTS "..."\n',
140
+ )
141
+ process.exit(1)
142
+ }
143
+
144
+ console.log(
145
+ `Checked ${files.length} migration(s): every drop is guarded, every migration has\n` +
146
+ `its schema snapshot, and the generator will diff against ${stem(files.at(-1)!)}.`,
147
+ )
@@ -0,0 +1,95 @@
1
+ import { resolveDatabaseConnection } from '../../src/lib/database'
2
+
3
+ /*
4
+ Which database a script is about to write to, and an explicit confirmation of
5
+ it. For seed, import and repair scripts:
6
+
7
+ const where = requireTarget(process.argv, overwriting, 'Replaces every page')
8
+
9
+ A script reads its connection string from the environment like everything else,
10
+ so a shell that happens to have production's connection string loaded points it
11
+ at production and nothing asks. This prints the target before anything is
12
+ written, and demands `--confirm <database>@<host>` when the run would overwrite.
13
+
14
+ It is not authentication — anyone who can run the script can type the token. It
15
+ is the difference between a mistake and a decision, and the print alone is worth
16
+ it: a run aimed at the wrong host is visible before it writes rather than after.
17
+
18
+ Sized to the risk: creating what is missing is recoverable by deleting it;
19
+ replacing what an editor has worked on is not.
20
+ */
21
+
22
+ export interface Target {
23
+ host: string
24
+ database: string
25
+ /** The environment variable the connection string came from, or `(none)`. */
26
+ source: string
27
+ }
28
+
29
+ /**
30
+ * The database the config will actually connect to — through the same resolver
31
+ * `payload.config.ts` uses, not by reading `DATABASE_URI` directly. A guard that
32
+ * reports a different database than the code connects to is not a weaker guard;
33
+ * it is a false one.
34
+ */
35
+ export function target(): Target {
36
+ let connection = ''
37
+ let source = '(none)'
38
+ try {
39
+ const resolved = resolveDatabaseConnection()
40
+ connection = resolved.value
41
+ source = resolved.name
42
+ } catch {
43
+ // Nothing set at all: a target of "none", which every destructive run refuses.
44
+ }
45
+
46
+ try {
47
+ const parsed = new URL(connection)
48
+ return { host: parsed.hostname, database: parsed.pathname.replace(/^\//, ''), source }
49
+ } catch {
50
+ return { host: '(unparseable)', database: '', source }
51
+ }
52
+ }
53
+
54
+ /*
55
+ The token that has to be typed back. Not the database name on its own: Neon
56
+ names every branch's database `neondb`, so `--confirm neondb` would match
57
+ production and a preview alike. The host distinguishes them, so the host is in
58
+ the token — long enough that it cannot be typed from memory while thinking about
59
+ a different environment.
60
+ */
61
+ export const confirmToken = (where: Target): string => `${where.database}@${where.host}`
62
+
63
+ /**
64
+ * Print the target, and exit unless a destructive run has named it back.
65
+ *
66
+ * @param argv The script's arguments, for `--confirm <database>@<host>`.
67
+ * @param destructive Whether this run would overwrite existing documents.
68
+ * @param consequence One line naming what would be overwritten, shown in the refusal.
69
+ */
70
+ export function requireTarget(argv: string[], destructive: boolean, consequence: string): Target {
71
+ const where = target()
72
+ console.log(
73
+ `\n target ${where.database || '(none)'} on ${where.host}\n from ${where.source}`,
74
+ )
75
+
76
+ if (!destructive) return where
77
+
78
+ const index = argv.indexOf('--confirm')
79
+ const confirmed = index === -1 ? undefined : argv[index + 1]
80
+ const expected = confirmToken(where)
81
+
82
+ if (where.database === '' || confirmed !== expected) {
83
+ console.error(
84
+ `\n Refusing to write.\n\n` +
85
+ ` ${consequence}\n` +
86
+ ` in the database "${where.database}" on ${where.host},\n` +
87
+ ` read from ${where.source}.\n\n` +
88
+ ` Re-run with --confirm ${where.database ? expected : '<database>@<host>'}\n` +
89
+ ` if that is what you meant.\n`,
90
+ )
91
+ process.exit(2)
92
+ }
93
+
94
+ return where
95
+ }
@@ -0,0 +1,119 @@
1
+ import { readdir, readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { parseArgs } from 'node:util'
5
+ import { getPayload } from 'payload'
6
+ import config from '@payload-config'
7
+
8
+ /*
9
+ Writes the `.json` schema snapshot beside a hand-written migration.
10
+
11
+ pnpm migrate:snapshot # the newest migration
12
+ pnpm migrate:snapshot 20260812_154500_add_jobs
13
+ pnpm migrate:snapshot --force # replace an existing snapshot
14
+
15
+ `migrate:create` diffs the config against the snapshot beside the **most
16
+ recent** migration, and writes a fresh one only when it generates the SQL
17
+ itself. A migration written by hand therefore leaves the generator one step
18
+ behind, and every run after it re-proposes a change that already shipped.
19
+
20
+ This closes the gap the way `createMigration` does, minus the diff: it asks the
21
+ adapter for the schema the config describes and serialises it. The adapter
22
+ builds that schema from the config alone, so this needs **no database**.
23
+
24
+ ## When it would be a lie
25
+
26
+ The snapshot says: *the database, once this migration has run, matches the
27
+ config.* Run it only after the hand-written migration is complete. A snapshot
28
+ claiming a config change the migration only half implements makes the next
29
+ generated migration quietly omit the missing half.
30
+
31
+ Overwriting needs `--force`, because the common mistake is running this instead
32
+ of writing the migration: the snapshot is rewritten to match a config that moved
33
+ on, and the change becomes invisible to the generator — the schema drifts with
34
+ no migration anywhere that would apply it.
35
+
36
+ The config is loaded, so the environment it validates must be present. Values
37
+ that only need to parse — secrets, keys — can be placeholders on this run.
38
+ */
39
+
40
+ const here = path.dirname(fileURLToPath(import.meta.url))
41
+ const MIGRATIONS = path.resolve(here, '../src/migrations')
42
+
43
+ // `pnpm run x -- --flag` forwards the `--` itself, which `parseArgs` would read
44
+ // as "positionals from here".
45
+ const argv = process.argv.slice(2).filter((arg) => arg !== '--')
46
+
47
+ const { positionals, values } = parseArgs({
48
+ args: argv,
49
+ allowPositionals: true,
50
+ options: { force: { type: 'boolean', default: false } },
51
+ })
52
+
53
+ const migrations = (await readdir(MIGRATIONS).catch(() => [] as string[]))
54
+ .filter((name) => name.endsWith('.ts') && name !== 'index.ts')
55
+ .map((name) => name.slice(0, -'.ts'.length))
56
+ .sort()
57
+
58
+ if (migrations.length === 0) {
59
+ console.error(`No migrations in ${MIGRATIONS}.`)
60
+ process.exit(1)
61
+ }
62
+
63
+ const target = positionals[0]?.replace(/\.(json|ts)$/, '') ?? migrations.at(-1)!
64
+
65
+ if (!migrations.includes(target)) {
66
+ console.error(
67
+ `No migration named ${target}.\n\nMigrations in src/migrations:\n` +
68
+ migrations.map((name) => ` ${name}`).join('\n') +
69
+ '\n',
70
+ )
71
+ process.exit(1)
72
+ }
73
+
74
+ const destination = path.join(MIGRATIONS, `${target}.json`)
75
+ const existing = await readFile(destination, 'utf8').then(
76
+ () => true,
77
+ () => false,
78
+ )
79
+
80
+ if (existing && !values.force) {
81
+ console.error(
82
+ `${target}.json already exists, and describes the schema as of that migration.\n` +
83
+ 'Rewriting it to match a config that has since changed hides the change from\n' +
84
+ '`migrate:create`. If replacing it is genuinely what you want, pass --force.\n',
85
+ )
86
+ process.exit(1)
87
+ }
88
+
89
+ if (target !== migrations.at(-1)) {
90
+ console.warn(
91
+ `Warning: ${target} is not the newest migration. Only the newest snapshot is\n` +
92
+ 'read by migrate:create, so this describes a state nothing will diff against.\n',
93
+ )
94
+ }
95
+
96
+ // No database, and nothing that runs on startup (seeds, syncs, cron
97
+ // registration) from a script whose whole job is to serialise a schema.
98
+ const payload = await getPayload({ config, disableDBConnect: true, disableOnInit: true })
99
+
100
+ const db = payload.db as typeof payload.db & {
101
+ requireDrizzleKit: () => { generateDrizzleJson: (schema: unknown) => Promise<unknown> }
102
+ schema: unknown
103
+ }
104
+
105
+ const { generateDrizzleJson } = db.requireDrizzleKit()
106
+ const snapshot = await generateDrizzleJson(db.schema)
107
+
108
+ // Byte for byte what `createMigration` writes, with no trailing newline, so a
109
+ // hand-written migration's snapshot is indistinguishable from a generated one.
110
+ await writeFile(destination, JSON.stringify(snapshot, null, 2))
111
+
112
+ console.log(
113
+ `Wrote src/migrations/${target}.json\n\n` +
114
+ 'Commit it beside the migration. Confirm the generator is unblocked with:\n\n' +
115
+ ' pnpm migrate:create check --skip-empty\n\n' +
116
+ 'which exits without writing anything when there is no diff.\n',
117
+ )
118
+
119
+ process.exit(0)