Staging failed on boot with 42P01, relation "payload.users" does not exist. The schema was empty because no migration existed, and the adapter cannot create tables itself: db-postgres/connect.js gates push on NODE_ENV !== 'production', so it is inert in a deployed container regardless of config. The generated migration is schema-qualified to "payload" throughout but does not create that schema — schemaName says where tables go, it does not create anything. It only worked against the throwaway database used to generate it because the schema was created there by hand, so every real environment would have failed on the first statement. CREATE SCHEMA IF NOT EXISTS is hand-added at the top of up(), which makes it exactly the kind of edit a regeneration discards silently; a test asserts it is present and ordered before the first CREATE TABLE. payload-types.ts is now committed rather than ignored. Ignoring it meant CI typechecked against looser types than a developer with a generated copy, which is how a Record<string, unknown> cast passed CI and then failed locally the moment the file appeared. The post page uses the generated Post and Media types instead, and narrows heroImage rather than asserting it, since the field is an id at shallow depth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YmbBhr8s7GusjDE12hrZM
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
/**
|
|
* The generated migration is schema-qualified to "payload" throughout but does
|
|
* not create that schema — `schemaName` says where tables go, it does not
|
|
* create anything. On staging and production, which have never run it, the
|
|
* whole migration fails with `schema "payload" does not exist`.
|
|
*
|
|
* The CREATE SCHEMA is therefore hand-added, which makes it exactly the kind
|
|
* of edit a regeneration silently discards. This is the guard.
|
|
*/
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const DIR = path.join(__dirname, '..', '..', 'migrations');
|
|
|
|
function migrationFiles() {
|
|
return fs
|
|
.readdirSync(DIR)
|
|
.filter((f) => f.endsWith('.ts') && f !== 'index.ts');
|
|
}
|
|
|
|
describe('payload migrations', () => {
|
|
it('ships at least one migration, so a container has tables to find', () => {
|
|
expect(migrationFiles().length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('creates the payload schema before creating anything in it', () => {
|
|
const initial = migrationFiles().find((f) => f.includes('initial'))!;
|
|
const sql = fs.readFileSync(path.join(DIR, initial), 'utf8');
|
|
|
|
expect(sql).toMatch(/CREATE SCHEMA IF NOT EXISTS "payload"/);
|
|
|
|
// Ordering matters: the schema must be created before the first object
|
|
// that lives in it, or the migration fails on its first statement.
|
|
expect(sql.indexOf('CREATE SCHEMA IF NOT EXISTS "payload"'))
|
|
.toBeLessThan(sql.indexOf('CREATE TABLE "payload"'));
|
|
});
|
|
|
|
it('creates the tables the app queries on boot', () => {
|
|
const initial = migrationFiles().find((f) => f.includes('initial'))!;
|
|
const sql = fs.readFileSync(path.join(DIR, initial), 'utf8');
|
|
for (const table of ['users', 'posts', '_posts_v', 'media', 'payload_migrations']) {
|
|
expect(sql).toContain(`CREATE TABLE "payload"."${table}"`);
|
|
}
|
|
});
|
|
|
|
it('is wired into the adapter, so it runs on server init', () => {
|
|
const config = fs.readFileSync(
|
|
path.join(__dirname, '..', '..', 'payload.config.ts'), 'utf8',
|
|
);
|
|
expect(config).toMatch(/prodMigrations:\s*migrations/);
|
|
});
|
|
});
|