Skip to content

Auto-include fixtures from fixtures/auto, fix history lookup, add tests and docs - #262

Open
xvaara wants to merge 9 commits into
graphile:watch-fixturesfrom
xvaara:auto-fixture
Open

xvaara wants to merge 9 commits into
graphile:watch-fixturesfrom
xvaara:auto-fixture

Conversation

@xvaara

@xvaara xvaara commented Sep 19, 2026 •

Copy link
Copy Markdown

Builds on #269 (this PR targets its watch-fixtures branch). Following the Discord discussion, it limits automatic inclusion to migrations/fixtures/auto/, makes creating that folder the opt-in instead of a setting, fixes a bug in the committed history lookup, and adds tests and docs. The earlier design of this PR, which wrote generated blocks into the current migration, is dropped.

Each commit stands on its own:

  1. Fix lookup of committed fixture history. The history regex captures (^|\n) before the path, so const [, relativePath] = match read that group. Every committed fixture was keyed by "" or "\n", none ever matched, and every fixture was included again in every migration. This fix is independent of the rest and can be cherry-picked.
  2. Only include fixtures from fixtures/auto automatically. Other fixtures (indexes, constraints, anything that shouldn't re-run on change) are only included manually. --!include inside fixtures/auto is an error, so no fixture runs more than once through nesting and no DAG is needed. Each fixture's hash comes straight from its file.
  3. Keep headers first and skip fixtures the migration already includes.
    • The automatic fixtures go after the headers and --! no-transaction, so commit keeps --! Message: and a no-transaction migration stays outside a transaction. Only that section is stripped on uncommit, so headers survive it too.
    • New fixtures that are empty or only comments are ignored.
  4. Test and document automatic fixtures. 17 tests: 12 without a database, plus 5 lifecycle tests (run, commit, uncommit and recommit in current.sql and current/ modes; the commit message surviving commit and uncommit; uncommit while a fixture edit is pending; a failed commit restoring what you wrote). Before the setting was removed, I ran them against Add opt-in setting to automatically include fixtures in migrations on change #269 with it switched on, and 14 of them failed. The README gains a section covering the folder rule, ordering by path, changes that need a DROP (such as a new function parameter), adoption by moving files into the folder (each runs once), locks, and the pg-minify dollar-quoting limitation.
  5. Turn on automatic fixtures by creating fixtures/auto. The autoIncludeFixtures setting is removed; the folder's presence is the opt-in. Without the folder, reading the current migration skips the committed fixture history entirely.
  6. Reject --! no-transaction in automatic fixtures. The automatic section always runs inside the migration's transaction, so a fixture in fixtures/auto that declares it is now an error instead of being skipped with an easy-to-miss warning. Keep such fixtures in another folder and include them manually.
  7. Drop the uncommit-only readCurrentMigration option. An earlier commit let uncommit ignore automatic fixtures when checking that the current migration is blank. That needed an extra option on readCurrentMigration for one rare case, so it is dropped: as in Add opt-in setting to automatically include fixtures in migrations on change #269, uncommit refuses while a fixture has pending changes, and you commit or revert the edit first. Nothing is lost either way, and uncommit.ts is unchanged from Add opt-in setting to automatically include fixtures in migrations on change #269.
  8. Forbid including automatic fixtures with --!include. Following review, compileIncludes takes a forbidAutoIncludes flag, passed down to nested includes, which readCurrentMigration sets. Neither the current migration nor a fixture it includes can include a file from fixtures/auto/, whatever the spelling, so automatic fixtures only ever run in the automatic section. That removes the already-included detection, replacing the TODO. A fixture change that depends on the rest of the migration goes in the next migration, as the README now says. The compile and run commands are unchanged.

For discussion:

  • Projects that already have a fixtures/auto/ folder for something else would switch this on when they upgrade. Is a release note enough, or would a more distinctive folder name be better? At least any --!include of files in such a folder now fails with a clear error.

Validation: TypeScript build (every commit), ESLint, Prettier and the Jest suite. 118 of 119 tests pass; migrate.test.ts › "handles errors during migration gracefully" fails locally on Node 22, and fails the same way on main.

🤖 Generated with Claude Code

@xvaara
xvaara marked this pull request as ready for review September 19, 2026 01:45
@xvaara
xvaara changed the base branch from main to watch-fixtures October 16, 2026 07:46
xvaara and others added 4 commits October 16, 2026 10:49
The history regex captures `(^|\n)` before the path, so destructuring
`[, relativePath]` read that group: every committed fixture was keyed
by "" or "\n", none ever matched, and every fixture was included in
every migration. Use the path group, and normalise it so `./x.sql` and
`x.sql` are the same fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixtures elsewhere (indexes, constraints, anything else that should not
be re-run on change) are only included manually. Fixtures in
fixtures/auto must not use --!include, so none is run more than once
through nesting and each one's hash comes straight from its file. Also
record which fixtures are empty or only comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Put the automatic fixtures after the headers and `--! no-transaction`,
  so `commit` keeps the message and no-transaction migrations stay
  outside a transaction; strip only that section on uncommit, so
  headers such as the message survive it too.
- Read already-included fixtures from the compiled migration, so other
  spellings (`./auto/x.sql`) and includes through another fixture count.
- Ignore new fixtures that are empty or only comments.
- Let uncommit check the current migration without automatic fixtures;
  they are derived again afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xvaara xvaara changed the title Add opt-in automatic fixture blocks Auto-include fixtures from fixtures/auto, fix history lookup, add tests and docs Oct 16, 2026
xvaara and others added 3 commits October 16, 2026 10:57
Remove the autoIncludeFixtures setting: the folder's presence is the
opt-in. Without it, reading the current migration skips the committed
fixture history entirely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The automatic section always runs inside the migration's transaction,
so a fixture in fixtures/auto that declares --! no-transaction is now an
error instead of being skipped with an easy-to-miss warning. Keep such
fixtures in another folder and include them manually.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uncommit again checks the compiled current migration, as in graphile#269, so it
refuses while a fixture has pending changes; commit or revert the edit
first. Nothing is lost either way, and readCurrentMigration keeps its
original signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/current.ts
Comment on lines +94 to +100
const autoIncludes = await compileIncludes(
parsedSettings,
changedFixtures
.map((relativePath) => `--!include ${relativePath}`)
.join("\n") +
"\n\n--!endAuto\n\n" +
compiled;
.join("\n"),
new Set(paths),
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should forbid any --!include auto/* imports; could be done via extending the paths set, but I'd instead pass a boolean to a new forbidAutoIncludes = false param.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From claude opus:

Done in ddf3d1c: compileIncludes now has a forbidAutoIncludes = false param, passed down to nested includes. readCurrentMigration sets it, so neither the current migration nor any fixture it includes can --!include auto/*, whatever the spelling (the check uses the resolved path). Auto fixtures themselves already can't contain any --!include (see getLocalFixtures), so they only ever run in the automatic section. The "already included" detection is gone, and the README now says that a fixture change depending on the rest of the migration goes in the next migration. The compile and run commands keep the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From claude opus:

Update in 4fb10f6, following the Discord discussion: this is now a middle ground.

  • The current migration may --!include auto/… directly again, to run a fixture after something it depends on, such as a policy after the create table in the same migration. That fixture is then not added at the start.
  • No fixture can include an auto fixture: compileIncludes now always passes forbidAutoIncludes = true to nested includes, so only the top-level file can include one. Auto fixtures themselves still can't contain any --!include.

The reason: with auto fixtures only ever running first, every new table with its policies in auto/ needs two migrations, or the policy written inline first. watch also fails as soon as the policy file is saved, because the automatic section runs before the create table. Running the section at the end instead would only flip the problem: dropping a column that a view uses needs the updated view first.

xvaara and others added 2 commits October 16, 2026 15:54
Fixtures in fixtures/auto now only ever run in the automatic section:
compileIncludes takes a forbidAutoIncludes flag, which
readCurrentMigration sets, so neither the current migration nor a
fixture it includes can --!include one. That removes the
already-included detection. AUTO_FIXTURES_FOLDER moves to migration.ts
to avoid a circular import. The compile and run commands are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The current migration may again --!include a fixture from fixtures/auto,
for example after creating the table a policy applies to; that fixture
is then not added at the start. Only the top-level file may do so:
compileIncludes now always sets forbidAutoIncludes for nested includes,
so no fixture can include an automatic fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants