flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
# Compose derives its project name from the directory it runs in, so each git
|
|
|
|
|
# worktree would otherwise get its own stack -- and the second one to start dies
|
|
|
|
|
# on the port binds ("Bind for :::15434 failed: port is already allocated").
|
|
|
|
|
# Pinning it means every worktree shares the one local stack and the one local
|
|
|
|
|
# database, which is what the primary clone's directory name already produced.
|
|
|
|
|
# Override it if you genuinely want a second, isolated stack.
|
|
|
|
|
export COMPOSE_PROJECT_NAME ?= civitai
|
|
|
|
|
|
2024-10-30 18:07:05 -04:00
|
|
|
# Start the containers in the background
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: start
|
2024-10-30 18:07:05 -04:00
|
|
|
start:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
docker compose up -d
|
2024-10-30 18:07:05 -04:00
|
|
|
|
|
|
|
|
# Stop all containers
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: stop
|
2024-10-30 18:07:05 -04:00
|
|
|
stop:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
docker compose stop
|
2024-10-30 18:07:05 -04:00
|
|
|
|
2024-11-01 11:23:22 -04:00
|
|
|
# Remove containers
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: down
|
|
|
|
|
down:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
docker compose down
|
2024-10-31 11:22:19 -04:00
|
|
|
|
2024-11-01 11:23:22 -04:00
|
|
|
# Restart containers
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: restart
|
2024-11-01 11:23:22 -04:00
|
|
|
restart: stop start
|
2024-10-31 11:22:19 -04:00
|
|
|
|
2024-11-01 11:23:22 -04:00
|
|
|
# Rebuild the containers
|
2024-10-31 11:41:05 -04:00
|
|
|
.PHONY: rebuild
|
|
|
|
|
rebuild:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
docker compose down \
|
|
|
|
|
&& docker compose up --build -d
|
2024-10-31 11:41:05 -04:00
|
|
|
|
2024-10-30 18:07:05 -04:00
|
|
|
# Stop and remove all containers, networks, images, and volumes
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: burn
|
2024-10-30 18:07:05 -04:00
|
|
|
burn:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
docker compose down \
|
|
|
|
|
&& docker compose down --volumes
|
2024-10-30 18:07:05 -04:00
|
|
|
|
2025-05-23 15:13:16 -04:00
|
|
|
ROWS ?= 1000
|
2025-06-07 17:36:07 -04:00
|
|
|
TRUNC_QUEUE ?= true
|
2024-10-31 11:22:19 -04:00
|
|
|
# Initialize the database and seed it with data
|
|
|
|
|
.PHONY: bootstrap-db
|
|
|
|
|
bootstrap-db:
|
2025-06-07 17:36:07 -04:00
|
|
|
npx cross-env NODE_ENV=development tsx ./scripts/local-dev/gen_seed.ts --rows=$(ROWS) --trunc=$(TRUNC_QUEUE)
|
2024-11-01 11:23:22 -04:00
|
|
|
|
|
|
|
|
# Run new migrations
|
|
|
|
|
.PHONY: run-migrations
|
|
|
|
|
run-migrations:
|
2024-12-17 14:22:24 -05:00
|
|
|
npx cross-env NODE_ENV=development tsx ./scripts/local-dev/run_migrations.ts
|
2024-10-31 11:22:19 -04:00
|
|
|
|
|
|
|
|
# Trigger metrics and search data jobs
|
|
|
|
|
.PHONY: bootstrap-metrics
|
|
|
|
|
bootstrap-metrics:
|
2024-12-17 14:22:24 -05:00
|
|
|
npx cross-env NODE_ENV=development tsx ./scripts/local-dev/bootstrap-metrics-search.ts
|
2024-11-01 11:23:22 -04:00
|
|
|
|
|
|
|
|
.PHONY: copy-env
|
|
|
|
|
copy-env:
|
|
|
|
|
cp -u ./.env-example ./.env.development
|
|
|
|
|
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
# `npm i` cannot work here: package.json's `preinstall` runs `npx only-allow pnpm`,
|
|
|
|
|
# which exits 1 under npm. `make init` was dead on that line.
|
ci: validate the default Windows contributor setup, non-blocking (#4162)
Every job in this repo runs on ubuntu-latest, so nothing validates Windows —
while Windows support is real and maintained: defender-exclusions.ps1, win32
branches in test-unit-run.mjs / bench.mjs / console.mjs / worktree.mjs, and
it.skipIf(win32) guards in scripts/__tests__. A supported platform verified by
nothing is how a contributor's first hour goes to a break nobody else can
reproduce.
The specific exposure this targets: `pnpm install` runs `preinstall`
(npx only-allow pnpm) and `postinstall` (pnpm run db:generate). The Makefile
carried `# TODO fix postinstall on git bash` against that path — and the target
it sat on was DEAD for everyone, because `npm i` exits 1 under only-allow. So the
hook path has gone a long time unexercised on Windows. Un-breaking the target
(4107) means the next Windows contributor is the first to walk it in a while.
This walks it first.
Both shells on purpose. pwsh is the Windows default; bash is Git Bash, and the
TODO was Git-Bash-specific. A pwsh-only job would report green over the single
case we have written evidence about.
Non-blocking (continue-on-error) to start. A gate that lands red on day one and
stops unrelated PRs trains everyone to click through, which is worse than no
gate. Flip it once it has been green long enough to mean something.
Does NOT start the compose stack: those are Linux containers and Docker on
Windows runners is slow and flaky — noise, not signal. Everything up to
`pnpm dev` is covered; the services half is Linux-identical and already exercised
by the ubuntu jobs.
Costs nothing: windows-latest is a standard GitHub-hosted runner, free for public
repositories.
The install step carries a positive control, because `pnpm install` exiting 0 is
not evidence the postinstall hook ran — a hook that silently no-ops also exits 0,
and the missing client then surfaces much later as a confusing runtime error. The
job asserts packages/civitai-db-schema/prisma/schema.prisma exists afterwards.
That file is a genuine control precisely because it is GITIGNORED: it cannot
arrive from the checkout, so its presence proves the hook executed on this runner.
The guard was tested against its own failure modes before being trusted, rather
than assumed to work:
artifact absent -> rc=1, "postinstall did not run"
artifact 3 lines -> rc=1, "slim schema looks truncated"
artifact 200 lines -> rc=0
Each failure fires for its OWN reason with a distinct title, so a red run names
which thing broke. The 50-line floor sits against a source schema of 8,087 lines —
two orders of magnitude of headroom, so ordinary churn cannot trip it while a stub
still fails.
Also restores the git-bash TODO to the Makefile rather than leaving it deleted.
Nothing has verified it either way; dropping the note would have retired the only
written record of a known hazard on a path that had stopped being walked. It now
points at this workflow as the thing that will answer it.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 21:18:04 -05:00
|
|
|
#
|
|
|
|
|
# This line previously carried `# TODO fix postinstall on git bash`. That note is
|
|
|
|
|
# preserved rather than dropped, because nothing has verified it either way: the
|
|
|
|
|
# target it sat on was DEAD for everyone (see above), so the `postinstall` hook it
|
|
|
|
|
# warned about -- `pnpm run db:generate`, i.e. generate-slim-schema.js then
|
|
|
|
|
# `prisma generate` -- has not been exercised here in a long time. Un-breaking the
|
|
|
|
|
# target does not fix whatever that was; it just means the next Windows contributor
|
|
|
|
|
# is the first to walk the path in a while.
|
|
|
|
|
#
|
|
|
|
|
# `.github/workflows/windows-dev-env.yml` now runs `pnpm install` on windows-latest
|
|
|
|
|
# under BOTH pwsh and Git Bash to find out. It is non-blocking. When it has been
|
|
|
|
|
# green for a while, delete this note; if it goes red under bash, this is the lead.
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
.PHONY: install
|
|
|
|
|
install:
|
|
|
|
|
pnpm install
|
|
|
|
|
|
|
|
|
|
# Kept so `make npm-install` still does the right thing for anyone with it in
|
|
|
|
|
# their fingers. It installs with pnpm, because npm is refused.
|
2024-11-01 11:35:30 -04:00
|
|
|
.PHONY: npm-install
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
npm-install: install
|
2024-11-01 11:35:30 -04:00
|
|
|
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
# Must go through `db:generate`, not a bare `prisma generate`: the generate step
|
|
|
|
|
# reads packages/civitai-db-schema/prisma/schema.prisma, which is gitignored and
|
|
|
|
|
# produced by scripts/generate-slim-schema.js. On a fresh clone that file does
|
|
|
|
|
# not exist yet, so `prisma generate` alone has nothing to read. Going through
|
|
|
|
|
# pnpm also puts node_modules/.bin on PATH, which a bare `prisma` needs.
|
2025-03-31 14:41:32 -04:00
|
|
|
.PHONY: gen-prisma
|
|
|
|
|
gen-prisma:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
pnpm run db:generate
|
2025-03-31 14:41:32 -04:00
|
|
|
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
# Through `pnpm exec` so cross-env and next resolve from node_modules/.bin
|
|
|
|
|
# without the caller having to add it to PATH by hand.
|
2025-03-31 14:41:32 -04:00
|
|
|
.PHONY: dev
|
|
|
|
|
dev:
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
pnpm exec cross-env NODE_OPTIONS=--disable-warning=ExperimentalWarning next dev
|
2025-03-31 14:41:32 -04:00
|
|
|
|
2024-11-05 13:25:51 -05:00
|
|
|
.PHONY: run
|
2025-03-31 14:41:32 -04:00
|
|
|
run: gen-prisma dev
|
2024-10-31 11:22:19 -04:00
|
|
|
|
2025-05-23 15:13:16 -04:00
|
|
|
.PHONY: reseed
|
|
|
|
|
reseed: bootstrap-db bootstrap-metrics
|
|
|
|
|
|
2024-11-05 13:38:14 -05:00
|
|
|
.PHONY: init
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
init: copy-env install start run-migrations reseed run
|
2024-11-05 13:38:14 -05:00
|
|
|
|
2025-02-07 11:06:01 -05:00
|
|
|
.PHONY: rerun
|
2025-05-23 15:13:16 -04:00
|
|
|
rerun: start reseed dev
|
2025-02-07 11:06:01 -05:00
|
|
|
|
2024-11-05 13:38:14 -05:00
|
|
|
.PHONY: init-devcontainer
|
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start
The flake shipped nodejs_22 while package.json declares engines.node
">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on
node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does
not support, and nothing said so.
Toolchain:
- node and pnpm are now DERIVED from .nvmrc and package.json's packageManager
rather than named twice. .nvmrc is treated as the authority because it is what
every workflow's actions/setup-node reads and what the Dockerfile tracks.
- flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is
exactly 24.19.0, which is what made agreeing with .nvmrc possible at all.
- pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new
rev the unversioned attribute resolves to 11.21.0 -- a major bump that
rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11
to every dev shell silently.
- postgresql_16 -> postgresql_17, matching the primary `db` container. The
postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers;
that is now stated in the file instead of left to be guessed.
- npm_config_manage_package_manager_versions=false. Measured: without it, pnpm
downloads and re-execs the exact version from the packageManager field, so the
flake's pnpm pin was being defeated at runtime (`pnpm --version` returns
10.28.1 with the var unset, 10.34.5 with it set).
Guards (`nix flake check`, 4 checks):
- toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc,
and its pnpm must share a major with packageManager. Deliberately does NOT
re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts
already owns that, and a predicate open-coded twice starts disagreeing.
- prisma-pin: re-derives the resolved @prisma/client AND its engine commit from
pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were
correct but unguarded: package.json declares `^6.3.0`, a caret range, so a
routine lockfile refresh moves the client while the flake's engines stay put,
and the failure surfaces at runtime in every dev shell.
- pin-guards-selftest: breaks each pin on purpose and requires the guard that
owns it to fire while the others stay silent.
- dev-scripts: builds the shell entrypoints, which is what runs their shellcheck.
(`nix flake check` builds checks.* but only EVALUATES packages.*, measured.)
Entrypoints:
- `nix run .#dev` - docker preflight, submodule, .env.development, compose up,
wait for postgres, pnpm install, then `next dev`. Every step idempotent and
non-destructive; migrations and seeding stay opt-in.
- `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The
daemon re-execs itself with process.execPath, so whichever node starts the CLI
is the node it runs on until it is restarted.
- `nix run .#doctor` - the same pin checks against the working tree.
Compose project is pinned to `civitai` so every worktree shares the one local
stack instead of each spawning a duplicate that fails on the port binds.
* fix(flake): give `nix run` the same env as the dev shell, not just the shell
Found by running the bootstrap on a genuinely clean worktree rather than
reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both
values it carried were absent from `nix run .#dev`:
- `pnpm install`'s postinstall runs `prisma generate`. Without
PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for
platform `linux-nixos` and the bootstrap died on
`404 ... /linux-nixos/libquery_engine.so.node.sha256`.
- pnpm re-execed itself as 10.28.1 from the packageManager field even though
PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had
not pinned.
The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and
an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server`
gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it
sees the lockfile move, which would have hit the identical 404.
* docs: describe the toolchain the repo actually has, not the one it used to
Every claim below was checked against the code before rewriting, and the
measurements are quoted where they are load-bearing.
README.md
- "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority.
- `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's
`preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent
(measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths
the README offered went through it.
- MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions
sent people to the wrong port to mint the keys the next step needs.
- `git submodule update --recursive` -> `--init`; without `--init` it is a no-op
on a fresh clone, which is precisely when it is being run.
- Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and
regenerated from `schema.full.prisma` on every `db:generate`, so edits to it
were silently discarded.
- Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence.
- engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0
against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An
earlier draft of this very README claimed it refuses. It does not, and that is
the reason the drift survived so long.
Makefile
- `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias.
- `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim
schema that does not exist yet on a fresh clone; now `pnpm run db:generate`,
which generates it first.
- `dev` ran bare `cross-env`/`next`, requiring the caller to put
node_modules/.bin on PATH by hand; now via `pnpm exec`.
- `docker-compose` (EOL v1) -> `docker compose`.
- COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a
worktree died with `Bind for :::15434 failed: port is already allocated`
because compose named the project after the directory.
.envrc.example (new, tracked) + .gitignore
- `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the
flake at all -- the only reference was a line in CLAUDE.md filed under
worktree hygiene. Placeholders only; the real .envrc stays ignored.
.claude/skills/dev-server/SKILL.md
- The skill said nothing about node. The daemon is spawned with
`process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every
`next dev` it supervises, so the first shell to run a CLI verb decides the
node for everything, indefinitely. Measured on this box: daemon on 26.7.0,
with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check.
- `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm.
src/__tests__/node-version-consistency.test.ts
- Comment-only. It said flake.nix "is on a different major" and could not be
aligned because the pinned nixpkgs had no Node 24 this new. Both halves are
now false, and a comment a maintainer might act on is worth correcting.
Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header
line in scripts/generate-slim-schema.js telling readers to run `npm run
db:generate`; CLAUDE.md's local-dev section (no node version, no services) and
its stale "flake's 22.22.2" figure.
NOT changed, because it could not be exercised here: the devcontainer pins
typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the
tag to use -- there is no `1-24`, the template major moved on, so `3-24`.
* docs(flake): the four postgres containers are not all one version
prisma-pit and db are postgres 17; notification-db and logical-db are 15. The
comment justifying postgresql_17 read as though they were uniform, which would
have made the next person's version decision from the wrong premise.
* docs: keep the non-Nix path the default, demote the flake to optional
The flake is used by one maintainer. Everyone else uses Docker + nvm, and that
has to stay the path a contributor lands on. The previous revision inverted
that: README's Installation section led with "With Nix (recommended...)" and
titled the standard path "Without Nix" — framing the majority workflow as the
fallback. CLAUDE.md opened "From nothing to a running app, one command:" with
`nix run .#dev`, and the dev-server skill led its fix with "Start it through the
flake and this cannot happen".
None of that made Nix *required* — verified: `.github/` is untouched by this
branch, no workflow references Nix (the apparent hits are substrings of
`eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to
any CI gate. It was purely an ordering-and-emphasis problem, which is the kind
that costs a new contributor twenty minutes before they find the section that
applies to them.
Changes, all editorial:
- README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the
Nix section opens with a blockquote saying it is not the supported default,
that nothing requires it, and why it exists at all (NixOS has no published
`linux-nixos` Prisma engine, so a flake is the practical way to work there).
The signals/buzz instructions lead with `docker compose up -d` and mention
`nix run .#dev -- --full` parenthetically.
- CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the
default path, with the flake shown after it as NixOS-only and explicitly
flagged as something not to assume a contributor has. The dev-server step no
longer instructs going through `nix run .#dev-server`; it states the
requirement (a shell whose node matches `.nvmrc`) and notes the flake does that
for you on NixOS.
- dev-server SKILL.md: the fix is now stated setup-agnostically — start the
daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which
`nvm use` gives you — with the flake wrapper presented as the optional NixOS
convenience, and an explicit note that nothing in the document depends on Nix.
No behaviour, tooling or gate changes: the Makefile, flake, guards and their
tests are untouched by this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 19:48:54 -05:00
|
|
|
init-devcontainer: copy-env install run-migrations reseed
|
2024-11-05 13:38:14 -05:00
|
|
|
|
2024-10-31 11:22:19 -04:00
|
|
|
.PHONY: default
|
2024-10-30 18:07:05 -04:00
|
|
|
default: start
|