create-nifra 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +559 -0
- package/dist/agent-files.d.ts +19 -11
- package/dist/agent-files.d.ts.map +1 -1
- package/dist/agent-files.js +26 -18
- package/dist/agent-files.js.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/scaffold/frameworks.d.ts +1 -1
- package/dist/scaffold/frameworks.js +1 -1
- package/package.json +3 -2
- package/src/agent-files.ts +27 -17
- package/template/nifra.assurance.ts +11 -0
- package/template/package.json +5 -5
- package/template-batteries/nifra.assurance.ts +11 -0
- package/template-batteries/package.json +8 -8
- package/template-isr/nifra.assurance.ts +11 -0
- package/template-isr/package.json +7 -7
- package/template-site/nifra.assurance.ts +11 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# create-nifra
|
|
2
|
+
|
|
3
|
+
## 5.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- df0fbb3: A project that declares `@nifrajs/cli` now launches its MCP server as `bunx @nifrajs/cli mcp`, with no version pin. `bunx` runs the CLI the project installs, so the server moves with every upgrade and the launch never goes stale. New scaffolds and `nifra init-agents` write this form. `nifra init-agents --sync-mcp` and `nifra upgrade --write` drop an existing pin in such a project, and `nifra doctor` points out a pin that is left. A project that does not declare the CLI keeps the exact-version pin, since an unpinned `bunx` there would run a cached `latest`. A launch in a workspace root's files stays pinned too, because the root need not hold the CLI's bin. `create-nifra/agent-files`: `mcpConfig(pin?)`, `mcpJson(pin?)` and `agentsMcpSection(pin?)` take an optional pin. `MCP_CONFIG` is now the unpinned config a scaffold writes.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 9498cf8: The package ships its `CHANGELOG.md`, so the installed package has the notes for every version, as the GitHub release does. Before, the tarball held only the code, `README.md`, `LICENSE` and `package.json`.
|
|
12
|
+
- f2065e2: A new app's `nifra.assurance.ts` now carries a `response-contract` baseline: every route that declares a `response` schema must enforce it, matched with `hasResponse: true`. The backend already installs `responseContract("enforce")`, so a fresh app passes. A route the contract does not cover now fails `nifra assure` and level L1 instead of sending fields its schema never declared.
|
|
13
|
+
|
|
14
|
+
## 5.1.0
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 30c2a6e: With both `nifra.config.ts` and `backend/framework.ts`, every command takes `apiPrefix`, `apiStrip`, `mounts`, `csp`, `nonce`, `serverComponents` and `use` from `backend/framework.ts` when `nifra.config.ts` leaves them unset, and validates them there. Before, `nifra dev` loaded only `nifra.config.ts` and the server entry `nifra build` generates imported only the options it set, so an `apiPrefix` set in `backend/framework.ts` alone served the backend nowhere over HTTP. NF-C035 reads the same merged value, reports a missing `apiPrefix` as a warning instead of information, and `nifra fix --code NF-C035` adds the mount to `backend/framework.ts`. A new site scaffold states `export const apiPrefix = ""` in `backend/framework.ts`, so it starts without the warning.
|
|
19
|
+
- c3551d3: `bun create nifra --db <preset> --auth better-auth` now writes the `BETTER_AUTH_URL` its template's dev server actually uses into `.env.example`: `http://localhost:4321` for the site template (`nifra dev`), `http://localhost:8787` for the ISR template (`wrangler dev`) and `http://localhost:3000` for the API and batteries templates. Before, every template got `http://localhost:3000`, so a site copied from `.env.example` had better-auth checking request origins against the wrong port.
|
|
20
|
+
|
|
21
|
+
## 5.0.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- b70b18a: The generated `./+types/<name>` module declares `Route.Loader` and `Route.Action`. Annotate a
|
|
26
|
+
route's loader, `export const loader: Route.Loader = async ({ params, api }) => ...`, and what it
|
|
27
|
+
returns is checked against `loaderOutput` at compile time. A field of the wrong type, a `defer(...)`
|
|
28
|
+
whose value does not match its `t.deferred(...)` field, or any data from a loader with no
|
|
29
|
+
`loaderOutput` is a type error. A redirect, a `Response` and returning nothing stay allowed.
|
|
30
|
+
`Route.Action` does the same against `actionOutput`, `revalidate(paths, data)` included. A field the
|
|
31
|
+
schema does not declare is not a type error, because the output guard drops it.
|
|
32
|
+
|
|
33
|
+
`@nifrajs/client` exports the types behind them: `InputOf<Module, Name>` (what a route module's
|
|
34
|
+
output schema accepts), `RouteLoaderReturn<Module>` and `RouteActionReturn<Module>`.
|
|
35
|
+
|
|
36
|
+
`nifra scaffold` and the `create-nifra` starters write the annotation. `nifra schemas` reports a
|
|
37
|
+
generated schema over an annotated loader or action as unchecked (NF-C033 warns), because the
|
|
38
|
+
annotation, not the body, is then the function's return type.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- 817492d: fix(cli): `@nifrajs/cli` no longer depends on `create-nifra`. It ships its own copy of the agent-file generators, so installing the CLI pulls in only `@nifrajs/*` packages and a scoped release-age exclusion covers the whole tree.
|
|
43
|
+
- 4c07ca7: The next steps printed after `--db` now match the scripts each preset writes: Prisma and Kysely apps are told to run `bun run db:migrate` only, since they have no `db:generate` step to run first.
|
|
44
|
+
- d192f10: fix(create-nifra): `bun run start` on a new site boots the built app instead of exiting. A `nifra build` output runs as production, where the starter's in-memory rate-limit store refuses to start unless `NIFRA_ALLOW_MEMORY_RATE_LIMIT=true`. The bun, node and deno `start` scripts now set it, as the Cloudflare one already did, because a local run is a single process. A `--docker` scaffold's `deploy` script sets it for the one container it runs. The image itself does not, so a deployed replica set still needs a shared store or an explicit opt-in.
|
|
45
|
+
- c021dc8: A `site` or `isr` scaffold created with `--db` now passes `tsc`. Its database modules import each other as `./schema.ts`, which the generated `tsconfig.json` refused with TS5097. That tsconfig now sets `allowImportingTsExtensions`, as the `api` and `batteries` templates already did.
|
|
46
|
+
- 2aacb5c: The ISR starter no longer ships `REVALIDATE_SECRET = "dev-secret"` as a `[vars]` entry in `wrangler.toml`, which `wrangler deploy` published as the production purge token. The deployed Worker now answers 404 on `/__nifra/revalidate` until `bunx wrangler secret put REVALIDATE_SECRET` sets a real one; `bun run dev` passes the local value with `--var`. An app scaffolded earlier should delete that `[vars]` entry and set the secret.
|
|
47
|
+
- d3e79c4: A layout's `meta` receives its own loader data as `data`, the value its component renders, or `null` when the layout has no loader, and the page's data as `pageData`. A layout that loads the locale sets `<html lang>` from its data, and a client navigation keeps it, since the soft-navigation head resolves each layout's `meta` with the same data the server used. A page's `meta` is unchanged and also receives `pageData`. `resolveChainMeta` from `@nifrajs/web/client` resolves a route's `meta` chain this way. Generated route types add `Route.MetaArgs`, typed with the module's own loader data.
|
|
48
|
+
|
|
49
|
+
Upgrading: a layout `meta` that read the page's data as `data` reads `pageData` now. `nifra check` reports a layout with no loader whose `meta` reads `data` (NF-C040), and the 5.0.0 upgrade notes name the change.
|
|
50
|
+
|
|
51
|
+
- 15a3a66: Root status pages (`_404.tsx`, `_<status>.tsx`) and `_error.tsx` render inside the layouts above them with their loader data, and the server puts a status page's `meta` in the head. For an unmatched URL the root middleware and layout loaders run first; otherwise the page reuses the layout data the request already loaded. When those layouts cannot render it (a loader throws, a gate or the middleware refuses) a root status page renders alone and is not hydrated, and an `_error` renders inside its layouts without their data, with the same status. A page rendered with layout data is `private, no-store`, and a client navigation that lands on one loads the URL as a document. `export const inLayouts = false` renders a page the 4.x way and runs no layout loader for it.
|
|
52
|
+
|
|
53
|
+
`createClientRouter` takes `layoutStatusRoutes`: status route ids whose page renders inside layouts, which a soft navigation loads as a document.
|
|
54
|
+
|
|
55
|
+
`nifra check` reports a root status page that imports the root layout to wrap itself, since it now renders that layout twice (NF-C034, warning). The site templates' `_404` pages no longer wrap the layout.
|
|
56
|
+
|
|
57
|
+
## 4.0.2
|
|
58
|
+
|
|
59
|
+
## 4.0.1
|
|
60
|
+
|
|
61
|
+
## 4.0.0
|
|
62
|
+
|
|
63
|
+
### Major Changes
|
|
64
|
+
|
|
65
|
+
- 5f1f3d8: feat(create-nifra)!: every template keeps its server code in `backend/`
|
|
66
|
+
|
|
67
|
+
- The `api` and `batteries` templates keep their app in `backend/` (`backend/app.ts`,
|
|
68
|
+
`backend/index.ts`), and `backend/app.ts` exports `backend` and `type Backend`, so `nifra contracts`
|
|
69
|
+
and `nifra sdk` find it without configuration.
|
|
70
|
+
- `--db` writes its data layer to `backend/db/` (the Drizzle config, scripts and `.gitignore` entries
|
|
71
|
+
point there), and `--auth` writes `backend/auth.ts`. Every module a scaffold writes is in a zone,
|
|
72
|
+
so a site's route backends can import them.
|
|
73
|
+
- The `isr` template's Workers entry and local Bun server are `backend/worker.ts` and
|
|
74
|
+
`backend/dev-server.ts`.
|
|
75
|
+
|
|
76
|
+
- 6c978a1: feat(create-nifra)!: a site scaffold picks one deploy target
|
|
77
|
+
|
|
78
|
+
- `--target bun|node|deno|cloudflare|vercel` (default `bun`) chooses where the site deploys.
|
|
79
|
+
`--deploy` is refused with that name.
|
|
80
|
+
- `--docker` adds a Dockerfile and `.dockerignore` for `bun` or `node`.
|
|
81
|
+
- The scaffold carries no hand-written server entry or build script. `nifra build` generates the
|
|
82
|
+
target's entry, and `nifra.config.ts` exports the `target` (`nifra target <t>` switches it).
|
|
83
|
+
- Only the target's own config file is written (`deno.json` for Deno, `wrangler.toml` for
|
|
84
|
+
Cloudflare), plus `start` / `deploy` scripts for it. `--ci` deploys the chosen target.
|
|
85
|
+
|
|
86
|
+
- 214d674: feat(create-nifra)!: site scaffolds use the zoned layout - `backend/app.ts`, `backend/framework.ts`,
|
|
87
|
+
and each route's loader and action in its `x.backend.ts`.
|
|
88
|
+
|
|
89
|
+
### Minor Changes
|
|
90
|
+
|
|
91
|
+
- c3057ee: feat: AGENTS.md is the one copy of an app's agent guidance
|
|
92
|
+
|
|
93
|
+
A scaffold and `nifra init-agents` write `AGENTS.md` plus a pointer to it for each agent:
|
|
94
|
+
`CLAUDE.md` and `GEMINI.md` import it, `.cursor/rules/nifra.mdc` is an always-applied Cursor rule
|
|
95
|
+
that attaches it, and `.github/copilot-instructions.md` names it. None of them carries guidance of its
|
|
96
|
+
own, so they cannot drift apart. A web app's `AGENTS.md` gains a "Project structure" section on the
|
|
97
|
+
frontend/backend zones the build enforces, and `nifra init-agents` appends it to an app with `routes/`.
|
|
98
|
+
|
|
99
|
+
- a0cfffa: `export const clientIp = "platform"` in `nifra.config.ts` makes a `cloudflare` or `vercel` build read `c.clientIp` from the header that platform's edge overwrites (`cf-connecting-ip`, `x-real-ip`), so per-caller middleware such as `rateLimit` works there. Without it an edge build still has no caller address; Bun, Node and Deno builds use the socket peer either way. `buildTarget` and `generateServerEntry` take the same `clientIp` option. Site scaffolds declare it.
|
|
100
|
+
|
|
101
|
+
### Patch Changes
|
|
102
|
+
|
|
103
|
+
- a8b33bc: The Solid and Svelte compiler plugins live on their `/plugin` subpaths, and each adapter root exports only the render adapter.
|
|
104
|
+
|
|
105
|
+
- `solidBunPlugin` is imported from `@nifrajs/web-solid/plugin`, matching `@nifrajs/web-svelte/plugin` and `@nifrajs/web-vue/plugin`.
|
|
106
|
+
- `@nifrajs/web-solid` and `@nifrajs/web-svelte` link no build-time or `node:` module, so a server or edge bundle that imports the adapter builds under edge resolve conditions (Cloudflare Workers, Vercel Edge, Deno).
|
|
107
|
+
- Solid site scaffolds import the plugin from the subpath.
|
|
108
|
+
|
|
109
|
+
Breaking: `solidBunPlugin` is no longer exported from `@nifrajs/web-solid`, nor `svelteBunPlugin` from `@nifrajs/web-svelte`. `nifra fix --code NF-C005` rewrites those imports.
|
|
110
|
+
|
|
111
|
+
- a0cfffa: Cloudflare scaffolds (a site with `--target cloudflare`, and the ISR template) declare `compatibility_date = "2025-04-01"`, the first date at which `nodejs_compat` fills `process.env` from the project's variables, so `NIFRA_ALLOW_MEMORY_RATE_LIMIT` reaches the starter's rate limit. Their local-only commands (`bun run start` for the site, `bun run dev` for ISR) set it for the one local process; a deploy still refuses to start until the variable is set.
|
|
112
|
+
- 9868241: `bun create nifra . --force` scaffolds into the current directory on Windows.
|
|
113
|
+
- dd0d5c1: With `--force`, a scaffold that fails reports its own error instead of suggesting `--force`.
|
|
114
|
+
- 6ce7975: The `AGENTS.md` a new app ships with names every route schema slot (`body`, `query`, `params`, `headers`, `cookies`) and shows a `params` schema validating and coercing a path param, where it used to say path params and headers could not be declared in the route schema.
|
|
115
|
+
- 7669f56: A project whose name has capitals, `_`, or `.` (`MyApp`, `my_app`) gets a Docker image tag, a `wrangler.toml` `name`, and Cloudflare Pages and Deno Deploy CI project names in lowercase letters, digits, and `-`, the form those platforms take. `package.json` keeps the name as given.
|
|
116
|
+
- a77b341: The `site` and `isr` templates now ignore `.env` and `.env.*` (keeping `.env.example`), as the `api` and `batteries` templates already did.
|
|
117
|
+
- ce169a2: `create-nifra` refuses a destination that already holds files unless `--force` is given, for every template. Before, the site template scaffolded into such a directory and replaced its `.gitignore`, `AGENTS.md`, `CLAUDE.md` and agent/MCP config files. An existing empty directory is now accepted without `--force`, and `bun create nifra . --force` names the project after the current directory instead of rejecting the name `.`.
|
|
118
|
+
- b83d400: The Vue site starter keeps its global stylesheet in an SFC `<style>` block, and the Preact starter renders its stylesheet unescaped, so both hydrate with no mismatch and the server-rendered page has its fonts before the client loads.
|
|
119
|
+
- 29c6c94: feat(middleware): client-IP default keys and a CSRF form field
|
|
120
|
+
|
|
121
|
+
`rateLimit()` without `key`, `header`, or `trustedProxies` keys each bucket on the caller IP the
|
|
122
|
+
server resolved: the socket peer, or the app's `server({ clientIp })` trust declaration behind a
|
|
123
|
+
proxy. It no longer throws at construction, so the scaffolded backend templates, which pass only
|
|
124
|
+
`store`, `max`, and `windowMs`, start. A request with no resolvable caller IP gets 500
|
|
125
|
+
`rate_limit_key_unavailable`. A custom `key` receives the platform as its second argument.
|
|
126
|
+
|
|
127
|
+
`ipRestriction()` without `clientIp`, `header`, or `trustedProxies` judges the same resolved caller
|
|
128
|
+
IP, and denies a request that has none. A custom `clientIp` receives the platform as its second
|
|
129
|
+
argument.
|
|
130
|
+
|
|
131
|
+
Both read an IPv4-mapped IPv6 address (`::ffff:a.b.c.d`, how Bun and Node report an IPv4 peer on
|
|
132
|
+
their default listener) as the IPv4 address it carries, so IPv4 `allow`/`deny` rules match it; a rule
|
|
133
|
+
may also be written in that form (`::ffff:10.0.0.0/104`). `rateLimit()` counts an IPv6 caller by its
|
|
134
|
+
`/64`, however the key was derived.
|
|
135
|
+
|
|
136
|
+
`csrf({ field })` also accepts the token from a form field in an
|
|
137
|
+
`application/x-www-form-urlencoded` or `multipart/form-data` body, for plain HTML forms that cannot
|
|
138
|
+
set a header. Bodies over `fieldMaxBytes` (default 64 KiB) are not read for the field, file parts
|
|
139
|
+
never count as the token, and the body stays readable by the route. Keep the Origin check on when
|
|
140
|
+
using a field.
|
|
141
|
+
|
|
142
|
+
- 4b8d8de: feat(web)!: everything a route sends to the browser passes a declared output schema
|
|
143
|
+
|
|
144
|
+
A route's backend half declares what its loader and action send to the browser:
|
|
145
|
+
`export const loaderOutput = t.object({ ... })` and `export const actionOutput = ...`. A boundary
|
|
146
|
+
loader declares `boundaryLoaders[name].output`, and a server function `serverFn({ output }, fn)`.
|
|
147
|
+
Before the page renders and before anything is serialized, the data is projected through its schema:
|
|
148
|
+
keys the schema does not declare are dropped, at every depth, even when the schema itself would accept
|
|
149
|
+
them, so the component and the browser see the same value. A declared field of the wrong shape fails
|
|
150
|
+
the request with a 500 whose message names the field's path, never its value. A loader that returns
|
|
151
|
+
data without an output schema fails the request; one that returns nothing, redirects or answers with
|
|
152
|
+
an error status needs none. A loader, action, boundary loader or server function that returns a 2xx
|
|
153
|
+
`Response` is refused, since its body would bypass the schema.
|
|
154
|
+
|
|
155
|
+
A deferred value is declared with `t.deferred(schema)`, and what it resolves to is projected and
|
|
156
|
+
validated by `schema` before it streams. A deferred value the schema does not declare as one is
|
|
157
|
+
refused.
|
|
158
|
+
|
|
159
|
+
An output schema that names a field such as `password`, `passwordHash`, `secret`, `token`, `apiKey`,
|
|
160
|
+
`privateKey` or `ssn` fails the route when it loads, unless the field is wrapped in
|
|
161
|
+
`t.declassified("why it may reach the browser", schema)`. `@nifrajs/web/zones` exports the name test
|
|
162
|
+
as `isSensitiveFieldName`.
|
|
163
|
+
|
|
164
|
+
Projection reads a nifra schema's JSON Schema, or a Standard JSON Schema such as zod's. A schema that
|
|
165
|
+
exposes neither keeps whatever its own `validate` returns.
|
|
166
|
+
|
|
167
|
+
The scaffolded site and ISR routes declare their loader and action output schemas.
|
|
168
|
+
|
|
169
|
+
- d50f73e: feat: generated route types
|
|
170
|
+
|
|
171
|
+
Every route file gets a generated `Route` namespace, imported from `./+types/<name>` by both halves
|
|
172
|
+
of the route:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
// routes/blog/[slug].backend.ts
|
|
176
|
+
import type { Route } from "./+types/[slug]"
|
|
177
|
+
export const loaderOutput = t.object({ title: t.string() })
|
|
178
|
+
export async function loader({ api, params }: Route.LoaderArgs) { ... }
|
|
179
|
+
|
|
180
|
+
// routes/blog/[slug].tsx
|
|
181
|
+
import type { Route } from "./+types/[slug]"
|
|
182
|
+
export default function Post({ data, params }: Route.ComponentProps) { ... }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`Route.Params` comes from the path (`[id]`, optional `[[lang]]`, catch-all `[...path]`).
|
|
186
|
+
`Route.LoaderData` and `Route.ActionData` are the output types of `loaderOutput` and `actionOutput`,
|
|
187
|
+
so a component is typed with exactly what reaches it. `Route.LoaderArgs` types `api` from the app's
|
|
188
|
+
`backend/app.ts`, which `.nifra/types/register.d.ts` registers once with `@nifrajs/client`'s new
|
|
189
|
+
`Register` interface; no route imports the backend for its types.
|
|
190
|
+
|
|
191
|
+
`nifra types` writes the files to `.nifra/types` (and `--check` fails when one is stale); `nifra dev`,
|
|
192
|
+
`nifra build` and `nifra check` refresh them, and `nifra dev` keeps them current as routes are added
|
|
193
|
+
or removed. A route resolves `./+types/<name>` through `"rootDirs": [".", "./.nifra/types"]` in
|
|
194
|
+
tsconfig, which the scaffolded site and ISR apps now carry; `nifra types` says so when it is missing.
|
|
195
|
+
`@nifrajs/web/route-types` exports the generator.
|
|
196
|
+
|
|
197
|
+
- 4936309: Pin generated CI workflows to the repository's current Bun release.
|
|
198
|
+
- fb7c5b3: Site and ISR scaffolds declare `@types/bun` and load its ambient types. The starter database rules in `nifra.assurance.ts` are optional, so an app without a database passes `nifra assure`; an import that matches a rule still grants its capabilities.
|
|
199
|
+
|
|
200
|
+
The ISR template's client build no longer copies `public/` into its own output directory.
|
|
201
|
+
|
|
202
|
+
- 715186c: `--auth better-auth` scaffolds ship no placeholder secret.
|
|
203
|
+
|
|
204
|
+
- `.env.example` leaves `BETTER_AUTH_SECRET` empty. Locally, better-auth falls back to its development secret; in production it refuses to start until a secret is set.
|
|
205
|
+
- The generated `backend/auth.ts` refuses a `BETTER_AUTH_SECRET` shorter than 32 characters in production, where better-auth itself only logs a warning.
|
|
206
|
+
|
|
207
|
+
- d1e2f50: feat: starters type their routes with the generated `./+types`
|
|
208
|
+
|
|
209
|
+
The site and ISR starters type the landing page as `import type { Route } from "./+types/index"`:
|
|
210
|
+
`props: Route.ComponentProps` in the page, `Route.LoaderArgs` / `Route.ActionArgs` in its backend
|
|
211
|
+
half. `data` is the `loaderOutput` schema's type, which is what reaches the browser. A scaffold
|
|
212
|
+
ships its `.nifra/types`, so the types resolve right after `bun install`, before any nifra command
|
|
213
|
+
has run. The Svelte starter types its `$props()` the same way, and the Vue starter types the props it
|
|
214
|
+
declares from `Route`. Svelte and Vue scaffolds include their `.svelte` / `.vue` files in
|
|
215
|
+
`tsconfig.json`, so svelte-check, vue-tsc and the editor type-check routes. `nifra_frontend`'s
|
|
216
|
+
loader-typing guidance now points at the route types.
|
|
217
|
+
|
|
218
|
+
- 784d772: `testClient` calls now come from `127.0.0.1`, as a socket peer's would, so middleware keyed on the caller's address, such as `rateLimit()`, runs in tests as it does behind a listener. Pass `clientIp` to test another address. `inProcessClient` calls made outside a page render still carry no address.
|
|
219
|
+
|
|
220
|
+
The batteries template declares `@nifrajs/middleware`, and its tests pass on a fresh scaffold.
|
|
221
|
+
|
|
222
|
+
- e32268d: fix(web): portable public-directory serving and required endpoint secrets
|
|
223
|
+
|
|
224
|
+
`publicDir()` now serves files on Node and Deno as well as Bun, sets `content-type` from the file
|
|
225
|
+
extension, sends `x-content-type-options: nosniff`, and never serves dot-prefixed paths other than
|
|
226
|
+
`/.well-known/`.
|
|
227
|
+
|
|
228
|
+
`revalidateEndpoint()` and `previewEndpoint()` throw at construction when `secret` is empty or
|
|
229
|
+
missing. The ISR starter answers 404 on its revalidate route until `REVALIDATE_SECRET` is set.
|
|
230
|
+
|
|
231
|
+
## 3.5.0
|
|
232
|
+
|
|
233
|
+
## 3.4.0
|
|
234
|
+
|
|
235
|
+
## 3.3.0
|
|
236
|
+
|
|
237
|
+
## 3.2.0
|
|
238
|
+
|
|
239
|
+
## 3.1.0
|
|
240
|
+
|
|
241
|
+
## 3.0.0
|
|
242
|
+
|
|
243
|
+
## 2.14.1
|
|
244
|
+
|
|
245
|
+
## 2.14.0
|
|
246
|
+
|
|
247
|
+
## 2.13.0
|
|
248
|
+
|
|
249
|
+
## 2.12.1
|
|
250
|
+
|
|
251
|
+
## 2.12.0
|
|
252
|
+
|
|
253
|
+
### Patch Changes
|
|
254
|
+
|
|
255
|
+
- 7dd3f28: The project name is validated before any file is copied. It is substituted into generated deploy
|
|
256
|
+
scripts that a shell runs (`--name NAME`), so a directory name carrying shell metacharacters, spaces,
|
|
257
|
+
or a leading `-` used to reach those scripts intact. The accepted set is npm's own for an unscoped
|
|
258
|
+
name - letters, digits, `.`, `-`, `_`, starting with a letter or digit, up to 214 characters - so
|
|
259
|
+
ordinary names like `MyApp` still scaffold. Rejecting up front also means a bad name no longer leaves
|
|
260
|
+
a half-written project directory behind.
|
|
261
|
+
|
|
262
|
+
Every action in the generated CI workflows is pinned to a commit SHA rather than a mutable tag, so a
|
|
263
|
+
scaffolded repo starts on the same footing this one uses.
|
|
264
|
+
|
|
265
|
+
## 2.11.0
|
|
266
|
+
|
|
267
|
+
## 2.10.0
|
|
268
|
+
|
|
269
|
+
## 2.9.1
|
|
270
|
+
|
|
271
|
+
## 2.9.0
|
|
272
|
+
|
|
273
|
+
## 2.8.2
|
|
274
|
+
|
|
275
|
+
## 2.8.1
|
|
276
|
+
|
|
277
|
+
## 2.8.0
|
|
278
|
+
|
|
279
|
+
## 2.7.1
|
|
280
|
+
|
|
281
|
+
## 2.7.0
|
|
282
|
+
|
|
283
|
+
### Minor Changes
|
|
284
|
+
|
|
285
|
+
- 7fd0fc7: The batteries-included backend starter is now `--template batteries` (background jobs, TTL cache, blob storage, cursor pagination on top of the `api` template), and it ships in the published package. `--template fullstack` no longer exists; asking for it explains the split: `site` is the full-stack (frontend + backend) template, `batteries` is the API starter. The README now documents all four templates, and the scaffolded `backend.ts` states its root-path convention: the CLI resolves `backend.ts` (like `routes/`, `framework.ts`, `nifra.config.ts`) from the project root - only that entry file is pinned there; merged feature modules can live anywhere.
|
|
286
|
+
|
|
287
|
+
## 2.6.1
|
|
288
|
+
|
|
289
|
+
## 2.6.0
|
|
290
|
+
|
|
291
|
+
## 2.5.0
|
|
292
|
+
|
|
293
|
+
## 2.4.0
|
|
294
|
+
|
|
295
|
+
### Patch Changes
|
|
296
|
+
|
|
297
|
+
- 06f4aaa: Build on `prepack` so the published package always ships its `dist` output - including the `create-nifra/agent-files` entry that `nifra init-agents` imports under Node.
|
|
298
|
+
|
|
299
|
+
## 2.3.0
|
|
300
|
+
|
|
301
|
+
### Minor Changes
|
|
302
|
+
|
|
303
|
+
- c8b79d7: `--db` scaffolds the data layer split by access, with a routes module that owns its own reach.
|
|
304
|
+
|
|
305
|
+
`db/read.ts` and `db/write.ts` sit in front of the connection, and `db/read-routes.ts` registers a
|
|
306
|
+
route importing only the read half. Merging it is one line.
|
|
307
|
+
|
|
308
|
+
The shape is not decoration. `nifra check` computes what a route can reach from the module that
|
|
309
|
+
registers it, following its imports; a route may not reach further than it declares, and a GET route
|
|
310
|
+
may not declare a domain write at all. A module holding both halves therefore has GET routes with no
|
|
311
|
+
legal declaration. Splitting reads from writes at the seam, and again at the routes, keeps every
|
|
312
|
+
route's declaration equal to its reach - which is what the `authenticated-write` rule needs in order to
|
|
313
|
+
mean anything.
|
|
314
|
+
|
|
315
|
+
The generated write example is commented rather than live, and says what happens when you uncomment
|
|
316
|
+
it: it fails `nifra assure` until authenticated, because the starter policy requires proof of who asked
|
|
317
|
+
before anything writes business state.
|
|
318
|
+
|
|
319
|
+
- 82b2053: Every template composes its routes from feature modules, and the effect provenance firewall ships ARMED.
|
|
320
|
+
|
|
321
|
+
`provenance.imports` now maps the database drivers and the `--db` seam, so a route that can reach a
|
|
322
|
+
database without declaring it fails `nifra check`. Combined with the `authenticated-write` rule, the
|
|
323
|
+
whole chain holds without anyone remembering anything:
|
|
324
|
+
|
|
325
|
+
- a route that writes and declares nothing fails the check;
|
|
326
|
+
- declare it, and the route fails assurance until it is authenticated;
|
|
327
|
+
- only an authenticated write ships.
|
|
328
|
+
|
|
329
|
+
Arming it required the app root to stop registering routes. Reach is computed from the module that
|
|
330
|
+
REGISTERS a route, following that module's imports, so a root that both composes and registers hands
|
|
331
|
+
every route in it the reach of everything merged there - and a GET route may not declare a domain write
|
|
332
|
+
at all, leaving those routes with no legal declaration and no fix but to move. So `src/app.ts` (api,
|
|
333
|
+
fullstack) and `backend.ts` (site, isr) merge and nothing else; the demo routes moved to `src/routes.ts`,
|
|
334
|
+
`src/notes.ts`, `counter.ts` and `page.ts` beside them. Exports are unchanged - `app`, `backend`, `queue`
|
|
335
|
+
and `wasIndexed` are all still imported from where they were.
|
|
336
|
+
|
|
337
|
+
That is the shape a feature should take anyway: a module owns its store, its adapters and the routes
|
|
338
|
+
over them, and a second feature with a database of its own gets its own file rather than a section.
|
|
339
|
+
|
|
340
|
+
- 26cec7d: A scaffolded app can run the gate it ships with.
|
|
341
|
+
|
|
342
|
+
Every template writes `nifra.assurance.ts` - an armed policy that refuses an unauthenticated write, a
|
|
343
|
+
mutation with no body schema, and a route reaching a database it never declared. None of them had a way
|
|
344
|
+
to run it: no `check` script anywhere, and the two backend templates did not even depend on
|
|
345
|
+
`@nifrajs/cli`, so `nifra check` was not on PATH without a manual install.
|
|
346
|
+
|
|
347
|
+
Every template now has `"check": "nifra check"` and the CLI in devDependencies, and the generated
|
|
348
|
+
GitHub workflow (`--ci github`) runs it before the build. A test asserts the invariant: a template that
|
|
349
|
+
ships an assurance config must ship both.
|
|
350
|
+
|
|
351
|
+
- 7f55876: Every template declares its capabilities, so a scaffolded app can reach L2 of `nifra levels`.
|
|
352
|
+
|
|
353
|
+
`nifra.assurance.ts` now carries a `capabilities` block defining `db.read` and `db.write`, and the
|
|
354
|
+
`authenticated-write` rule matches `{ access: "write", zone: "domain" }` instead of naming `db.write`.
|
|
355
|
+
Any write token added later - `payments.charge`, `orders.write` - is covered by that rule the day it is
|
|
356
|
+
declared, without editing the policy.
|
|
357
|
+
|
|
358
|
+
L2 was previously unreachable from a scaffold: the level requires a capability policy, no template
|
|
359
|
+
shipped one, and writing one from scratch was the only way up. It is now one `nifra capabilities
|
|
360
|
+
snapshot` away.
|
|
361
|
+
|
|
362
|
+
`provenance.imports` ships empty with a worked example and the one caveat that matters, which is that a
|
|
363
|
+
route's reach is computed from the module that REGISTERS it, following its imports. A module that
|
|
364
|
+
registers routes and imports a database gives every route in it database reach, and a GET route is
|
|
365
|
+
refused a domain write outright - so turning the import firewall on wants a root that is pure
|
|
366
|
+
composition, with effects owned by the modules underneath.
|
|
367
|
+
|
|
368
|
+
The `isr` template gains a `nifra.assurance.ts`; it had none, which capped it at L0.
|
|
369
|
+
|
|
370
|
+
### Patch Changes
|
|
371
|
+
|
|
372
|
+
- cee03d7: The beacon wrapper stops breaking adapters that use `#private` fields, and the `--db` sample no longer
|
|
373
|
+
collides with a template route.
|
|
374
|
+
|
|
375
|
+
A `#` field's brand check is per-instance, so a Proxy that passes itself as the receiver throws
|
|
376
|
+
`Cannot access invalid private field`. Getters broke on both views and methods broke on the unbound
|
|
377
|
+
one - an adapter using `#` worked unwrapped and broke the moment you added beacons. Both proxies now
|
|
378
|
+
read against the target, and the unbound one binds methods to it.
|
|
379
|
+
|
|
380
|
+
The generated `db/read-routes.ts` registered `GET /notes`, which the fullstack template already
|
|
381
|
+
registers. `nifra check` associates modules with routes by matching the registered path across your
|
|
382
|
+
source, so that unmerged sample lent its `db.read` reach to a template route that never touches the
|
|
383
|
+
database - failing the check on a fresh `create-nifra --template fullstack --db …`. The sample uses
|
|
384
|
+
`/db/notes` now, and says why.
|
|
385
|
+
|
|
386
|
+
- 8807004: A scaffold's feature flags declare what they contribute instead of racing to write it.
|
|
387
|
+
|
|
388
|
+
`--db`, `--auth` and `--deploy` each reached into the parsed `package.json` and spread themselves over
|
|
389
|
+
it, in an order fixed by the line their handler sat on. Last writer won, silently. A preset that
|
|
390
|
+
shadowed the scaffold's own `check` script would have removed the assurance gate from every project
|
|
391
|
+
scaffolded with it, and nothing anywhere would have reported that.
|
|
392
|
+
|
|
393
|
+
No shipped preset does that - all six were checked - which is the moment to add the rail rather than
|
|
394
|
+
after someone adds the seventh. Each flag now states its contribution, and an undeclared collision is
|
|
395
|
+
an error naming both sides. Replacing a key stays possible where it is the point: `--deploy` repoints
|
|
396
|
+
the canonical `build` and `deploy` aliases at the chosen target, and says so.
|
|
397
|
+
|
|
398
|
+
- ea0a27f: A scaffolded project's `check` script runs the assurance gate it ships with, and the dev refusals cover
|
|
399
|
+
extensionless modules.
|
|
400
|
+
|
|
401
|
+
Every template ships an assurance config, and every template's `check` script ran `nifra check` only -
|
|
402
|
+
so the policy was shipped, documented, and never executed by the command a project actually runs in CI.
|
|
403
|
+
It now runs `nifra check && nifra assure`.
|
|
404
|
+
|
|
405
|
+
`nifra dev --bun` refuses `.server` and `.fn` modules because Bun's dev bundler takes no plugins and
|
|
406
|
+
would ship them whole. The refusal missed a module with no extension at all, which is the one shape a
|
|
407
|
+
directory import produces.
|
|
408
|
+
|
|
409
|
+
- 35af9fe: A site scaffold is composed from one model instead of copied from five directories.
|
|
410
|
+
|
|
411
|
+
`create-nifra --template site --framework <react|preact|vue|solid|svelte>` produces the same app it
|
|
412
|
+
always did. What changed is where it comes from: thirteen of a site's twenty-six files are identical
|
|
413
|
+
whatever you render with, eight are emitted from a framework model, and five are genuinely the
|
|
414
|
+
framework's own.
|
|
415
|
+
|
|
416
|
+
Five hand-maintained copies had already drifted, which is the argument for this rather than a
|
|
417
|
+
consequence of it. `.vercel` was excluded from four `tsconfig.json` files and not React's, though
|
|
418
|
+
`build-vercel.ts` writes there in all five. React's Vercel entry explains the Build Output API layout
|
|
419
|
+
it emits and the three copies made later had dropped that. Vue's feature-flag defines are explained in
|
|
420
|
+
its Cloudflare entry and nowhere else. Composing restores all of it.
|
|
421
|
+
|
|
422
|
+
The `@nifrajs/*` range a scaffold installs is now one constant. It used to be a regex sweep across
|
|
423
|
+
eight `package.json` files with nothing checking the result, and the release script's own comment
|
|
424
|
+
warned that a missed bump ships templates installing the previous release. A test now fails when that
|
|
425
|
+
constant drifts from the version being published.
|
|
426
|
+
|
|
427
|
+
What is NOT generated is deliberate. `nifra.config.ts` explains why Solid wants a `solid` resolve
|
|
428
|
+
condition and what `@preact/preset-vite` is; the routes are the app a reader opens first. That prose
|
|
429
|
+
stays in files you can read and edit, because moving it into TypeScript string literals would put it
|
|
430
|
+
somewhere strictly worse.
|
|
431
|
+
|
|
432
|
+
## 2.2.0
|
|
433
|
+
|
|
434
|
+
### Patch Changes
|
|
435
|
+
|
|
436
|
+
- 5f460db: Fix `nifra init-agents`, and explain rejected route parameters.
|
|
437
|
+
|
|
438
|
+
`nifra init-agents` failed for every installed user with `Cannot find module 'create-nifra/agent-files'`.
|
|
439
|
+
The `./agent-files` subpath resolves through the `bun` condition to `src/agent-files.ts`, which the
|
|
440
|
+
published tarball did not contain - the package shipped `dist` and the templates only. It now ships
|
|
441
|
+
that source file, so the subpath resolves from a real install. Reproduced from a packed 2.1.0 tarball
|
|
442
|
+
before and after.
|
|
443
|
+
|
|
444
|
+
An invalid route parameter now says why. Route grammar is per-segment - a segment is wholly static or
|
|
445
|
+
wholly a parameter - so everything after the colon is the name, and `/v/:id.json` asks for a parameter
|
|
446
|
+
literally called `id.json`. The previous `invalid parameter ":id.json"` read as a typo rather than a
|
|
447
|
+
rule; the message now names the limitation and gives both ways out (`/v/:id/json`, or capture the whole
|
|
448
|
+
segment and split it in the handler). Reserved names, an empty name, and a name that is invalid for
|
|
449
|
+
some other reason each get their own explanation instead of sharing one.
|
|
450
|
+
|
|
451
|
+
Note for anyone who has hit this: a segment that merely _contains_ a colon without starting with one,
|
|
452
|
+
such as `/a/pre-:id`, is a literal static segment and captures nothing. That is deliberate - a colon is
|
|
453
|
+
legal inside a URL path segment (`/v1/things:batchGet`) - and is now covered by a test that documents it.
|
|
454
|
+
|
|
455
|
+
## 2.1.0
|
|
456
|
+
|
|
457
|
+
## 2.0.0
|
|
458
|
+
|
|
459
|
+
### Minor Changes
|
|
460
|
+
|
|
461
|
+
- 202e758: Schema-typed MCP tools, and the default template demonstrates the contract.
|
|
462
|
+
|
|
463
|
+
- `defineMcpTool` accepts `input`: a Standard Schema (nifra's `t`, zod, valibot, arktype, …) that
|
|
464
|
+
validates every call's arguments before the handler runs and types the handler's `args`. Invalid
|
|
465
|
+
arguments return an in-band `isError` result naming each issue, so a calling agent can correct
|
|
466
|
+
and retry. Schemas that carry a JSON Schema (nifra's `t` does) become the advertised
|
|
467
|
+
`inputSchema` automatically; an explicit `inputSchema` still overrides. The raw
|
|
468
|
+
`inputSchema`-only form keeps working unchanged.
|
|
469
|
+
- The `api` template's app now ships a `t`-validated route (body + response schemas) and its tests
|
|
470
|
+
drive the app through `testClient` - the contract-first pitch is visible in the first file a new
|
|
471
|
+
user opens, not just the docs.
|
|
472
|
+
|
|
473
|
+
### Patch Changes
|
|
474
|
+
|
|
475
|
+
- ade0c7a: Add a curated `@nifrajs/core/server` entry for the common HTTP runtime and dedicated subpaths for
|
|
476
|
+
contracts, classification, cookies, logging, routing, Standard Schema, SEO, SSE, and webhooks. The
|
|
477
|
+
package root remains backwards compatible, while new scaffolds and first-party runtime packages avoid
|
|
478
|
+
eagerly parsing opt-in causality, invariant, manifest, reflection, capability, and assurance tooling.
|
|
479
|
+
|
|
480
|
+
## 1.13.0
|
|
481
|
+
|
|
482
|
+
## 1.12.0
|
|
483
|
+
|
|
484
|
+
## 1.11.0
|
|
485
|
+
|
|
486
|
+
### Patch Changes
|
|
487
|
+
|
|
488
|
+
- 80ed7b8: Fix fresh scaffolds failing their own `nifra check`, plus two scaffolding tooling defects:
|
|
489
|
+
|
|
490
|
+
- All counter demo templates (site ×5 frameworks, isr): demo loaders now narrow on `res.ok`
|
|
491
|
+
before reading `res.data` - un-narrowed `data` is `{}` under the typed client, so the old
|
|
492
|
+
`res.data?.count` was a compile error on a fresh scaffold.
|
|
493
|
+
- Demo backends now lock output shapes with `response` schemas (`t.object(...)`), per the
|
|
494
|
+
AGENTS.md doctrine the templates themselves ship.
|
|
495
|
+
- `template-isr` now includes `@nifrajs/cli` in devDependencies so a scaffolded app can run
|
|
496
|
+
its own `nifra check` done-gate.
|
|
497
|
+
- `--link` computes `file:` paths from realpaths - a symlinked segment (macOS tmpdir
|
|
498
|
+
`/var/folders` → `/private/var/folders`) previously skewed the relative path and broke
|
|
499
|
+
every linked dependency.
|
|
500
|
+
- New regression suite `test/scaffold-check.test.ts`: static tier always asserts the
|
|
501
|
+
template sources carry both contract fixes; live tier (`SMOKE_SCAFFOLD=1`) scaffolds for
|
|
502
|
+
real, installs published packages, and runs `nifra check`.
|
|
503
|
+
|
|
504
|
+
## 1.10.0
|
|
505
|
+
|
|
506
|
+
## 1.9.1
|
|
507
|
+
|
|
508
|
+
## 1.9.0
|
|
509
|
+
|
|
510
|
+
## 1.8.0
|
|
511
|
+
|
|
512
|
+
## 1.7.0
|
|
513
|
+
|
|
514
|
+
## 1.6.0
|
|
515
|
+
|
|
516
|
+
## 1.5.0
|
|
517
|
+
|
|
518
|
+
## 1.4.0
|
|
519
|
+
|
|
520
|
+
## 1.3.1
|
|
521
|
+
|
|
522
|
+
## 1.3.0
|
|
523
|
+
|
|
524
|
+
## 1.2.2
|
|
525
|
+
|
|
526
|
+
## 1.2.1
|
|
527
|
+
|
|
528
|
+
## 1.2.0
|
|
529
|
+
|
|
530
|
+
## 1.1.0
|
|
531
|
+
|
|
532
|
+
### Minor Changes
|
|
533
|
+
|
|
534
|
+
- 9905f7f: feat(create-nifra): `--template fullstack` - a batteries-included starter
|
|
535
|
+
|
|
536
|
+
`bun create nifra my-app --template fullstack` scaffolds an app that already wires the packages a real
|
|
537
|
+
backend needs on top of core: cursor pagination (`t.pageQuery` / `t.paginated` / `paginate`), background
|
|
538
|
+
jobs (`@nifrajs/jobs`), a single-flight TTL cache (`@nifrajs/cache`), and blob storage (`@nifrajs/storage`)
|
|
539
|
+
|
|
540
|
+
- over a `notes` domain you swap for your DB. Ships with tests exercising each. Complements the existing
|
|
541
|
+
`api`, `site`, and `isr` templates.
|
|
542
|
+
|
|
543
|
+
## 1.0.0
|
|
544
|
+
|
|
545
|
+
### Patch Changes
|
|
546
|
+
|
|
547
|
+
- bb31594: Surface `@nifrajs/middleware` where agents look. The `nifra_context` conventions (and a scaffolded app's `AGENTS.md`) now carry a one-line pointer: cross-cutting concerns - rate limiting (`429`), CORS, security headers, body limits, auth, CSRF, IP restriction, caching, compression - are `app.use(...)` plugins in `@nifrajs/middleware`; call `nifra_docs("middleware")` for the full list. So an agent setting up routes finds the built-in middleware (it already shipped) without having to think to search for it.
|
|
548
|
+
|
|
549
|
+
## 1.0.0-beta.4
|
|
550
|
+
|
|
551
|
+
### Patch Changes
|
|
552
|
+
|
|
553
|
+
- 5181a35: Pin the generated MCP launch command to an exact `@nifrajs/cli` version (`bunx @nifrajs/cli@<version> mcp`) in `.mcp.json` / `.cursor/mcp.json` / `AGENTS.md`.
|
|
554
|
+
|
|
555
|
+
`bunx` keys its cache on the exact version spec. An unpinned spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every later spawn without re-checking the registry - so an editor that once launched an older `@nifrajs/cli` keeps respawning the stale binary even after a newer one is published, and the MCP server silently runs old code (e.g. without monorepo detection). Pinning the exact version makes the version part of the cache key, so each release fetches fresh. `scripts/version.ts` keeps the pin in lockstep with the published version. Re-run `nifra init-agents` to repin an existing app.
|
|
556
|
+
|
|
557
|
+
## 1.0.0-beta.3
|
|
558
|
+
|
|
559
|
+
## 0.1.0-beta.2
|
package/dist/agent-files.d.ts
CHANGED
|
@@ -36,17 +36,22 @@ export declare const MCP_SERVER_COMMAND: "bunx";
|
|
|
36
36
|
* (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
|
|
37
37
|
* package's own `package.json`, so the read can't miss at install time.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
39
|
+
* Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
|
|
40
|
+
* `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
|
|
41
|
+
* (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
|
|
42
|
+
* and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
|
|
43
|
+
* always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
|
|
44
|
+
*
|
|
45
|
+
* A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
|
|
46
|
+
* unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
|
|
47
|
+
* re-checking the registry, so the server would silently run old code. The exact version is part of the
|
|
48
|
+
* cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
|
|
49
|
+
* nifra, or unpins it once the project declares the CLI.
|
|
47
50
|
*/
|
|
48
51
|
export declare const MCP_CLI_VERSION: string;
|
|
49
52
|
export declare const MCP_SERVER_ARGS: readonly [`@nifrajs/cli@${string}`, "mcp"];
|
|
53
|
+
/** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
|
|
54
|
+
export declare const MCP_SERVER_ARGS_INSTALLED: readonly ["@nifrajs/cli", "mcp"];
|
|
50
55
|
/** The server entry registered under the `nifra` key in both Claude Code's and Cursor's MCP config. */
|
|
51
56
|
export interface McpServerConfig {
|
|
52
57
|
readonly command: string;
|
|
@@ -56,11 +61,14 @@ export interface McpServerConfig {
|
|
|
56
61
|
export interface McpConfig {
|
|
57
62
|
readonly mcpServers: Readonly<Record<string, McpServerConfig>>;
|
|
58
63
|
}
|
|
59
|
-
/** The one canonical MCP config
|
|
64
|
+
/** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
|
|
65
|
+
* `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
|
|
66
|
+
export declare function mcpConfig(pin?: string): McpConfig;
|
|
67
|
+
/** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
|
|
60
68
|
export declare const MCP_CONFIG: McpConfig;
|
|
61
69
|
/** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
|
|
62
70
|
* Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
|
|
63
|
-
export declare function mcpJson(): string;
|
|
71
|
+
export declare function mcpJson(pin?: string): string;
|
|
64
72
|
/** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
|
|
65
73
|
export declare function claudeMd(): string;
|
|
66
74
|
/** `GEMINI.md`: Gemini CLI expands `@./AGENTS.md` imports in its context files. */
|
|
@@ -74,7 +82,7 @@ export declare function copilotInstructions(): string;
|
|
|
74
82
|
* agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
|
|
75
83
|
* Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
|
|
76
84
|
*/
|
|
77
|
-
export declare function agentsMcpSection(): string;
|
|
85
|
+
export declare function agentsMcpSection(pin?: string): string;
|
|
78
86
|
/**
|
|
79
87
|
* The "## Project structure" section of a web app's `AGENTS.md`: the zones the build enforces. Shared so
|
|
80
88
|
* a scaffolded app and one `nifra init-agents` retrofits teach the same rules.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-files.d.ts","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;kFACkF;AAClF,eAAO,MAAM,kBAAkB,EAAG,MAAe,CAAA;AAEjD
|
|
1
|
+
{"version":3,"file":"agent-files.d.ts","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;kFACkF;AAClF,eAAO,MAAM,kBAAkB,EAAG,MAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,eAAe,EAAE,MAAqB,CAAA;AAenD,eAAO,MAAM,eAAe,4CAAsD,CAAA;AAElF,wGAAwG;AACxG,eAAO,MAAM,yBAAyB,kCAAmC,CAAA;AAEzE,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAA;CAC/D;AAED;0FAC0F;AAC1F,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAGjD;AAED,gGAAgG;AAChG,eAAO,MAAM,UAAU,EAAE,SAAuB,CAAA;AAEhD;qFACqF;AACrF,wBAAgB,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAKD,yEAAyE;AACzE,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,gGAAgG;AAChG,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAerD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA+B/C;AAED,iGAAiG;AACjG,eAAO,MAAM,iBAAiB,yBAAyB,CAAA;AAEvD;4GAC4G;AAC5G,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;sGACsG;AACtG,eAAO,MAAM,aAAa,cAAc,CAAA;AACxC,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AACtD,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,cAAc,cAAc,CAAA;AACzC,eAAO,MAAM,gBAAgB,4BAA4B,CAAA;AACzD,eAAO,MAAM,yBAAyB,oCAAoC,CAAA;AAC1E,eAAO,MAAM,cAAc,cAAc,CAAA;AAEzC,wFAAwF;AACxF,eAAO,MAAM,cAAc,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,EAM5F,CAAA"}
|
package/dist/agent-files.js
CHANGED
|
@@ -37,14 +37,17 @@ export const MCP_SERVER_COMMAND = "bunx";
|
|
|
37
37
|
* (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
|
|
38
38
|
* package's own `package.json`, so the read can't miss at install time.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
40
|
+
* Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
|
|
41
|
+
* `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
|
|
42
|
+
* (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
|
|
43
|
+
* and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
|
|
44
|
+
* always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
|
|
45
|
+
*
|
|
46
|
+
* A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
|
|
47
|
+
* unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
|
|
48
|
+
* re-checking the registry, so the server would silently run old code. The exact version is part of the
|
|
49
|
+
* cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
|
|
50
|
+
* nifra, or unpins it once the project declares the CLI.
|
|
48
51
|
*/
|
|
49
52
|
export const MCP_CLI_VERSION = ownVersion();
|
|
50
53
|
function ownVersion() {
|
|
@@ -57,16 +60,20 @@ function ownVersion() {
|
|
|
57
60
|
throw new Error("[nifra] agent-files: this package's package.json has no version");
|
|
58
61
|
}
|
|
59
62
|
export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"];
|
|
60
|
-
/** The
|
|
61
|
-
export const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
63
|
+
/** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
|
|
64
|
+
export const MCP_SERVER_ARGS_INSTALLED = ["@nifrajs/cli", "mcp"];
|
|
65
|
+
/** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
|
|
66
|
+
* `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
|
|
67
|
+
export function mcpConfig(pin) {
|
|
68
|
+
const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"];
|
|
69
|
+
return { mcpServers: { nifra: { command: MCP_SERVER_COMMAND, args: [...args] } } };
|
|
70
|
+
}
|
|
71
|
+
/** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
|
|
72
|
+
export const MCP_CONFIG = mcpConfig();
|
|
66
73
|
/** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
|
|
67
74
|
* Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
|
|
68
|
-
export function mcpJson() {
|
|
69
|
-
return `${JSON.stringify(
|
|
75
|
+
export function mcpJson(pin) {
|
|
76
|
+
return `${JSON.stringify(mcpConfig(pin), null, 2)}\n`;
|
|
70
77
|
}
|
|
71
78
|
const POINTER_INTRO = "This project's agent guidance - commands, the project structure, the rules, and the nifra MCP server - lives in AGENTS.md, the single source every agent reads.";
|
|
72
79
|
/** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
|
|
@@ -90,10 +97,11 @@ export function copilotInstructions() {
|
|
|
90
97
|
* agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
|
|
91
98
|
* Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
|
|
92
99
|
*/
|
|
93
|
-
export function agentsMcpSection() {
|
|
100
|
+
export function agentsMcpSection(pin) {
|
|
101
|
+
const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"];
|
|
94
102
|
return `## MCP server
|
|
95
103
|
|
|
96
|
-
This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${
|
|
104
|
+
This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${args.join(" ")}\`. It's registered for
|
|
97
105
|
Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
|
|
98
106
|
at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
|
|
99
107
|
project + the installed version, so they beat training-data recall:
|
package/dist/agent-files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-files.js","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC;kFACkF;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAe,CAAA;AAEjD
|
|
1
|
+
{"version":3,"file":"agent-files.js","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC;kFACkF;AAClF,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAe,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,UAAU,EAAE,CAAA;AAEnD,SAAS,UAAU;IACjB,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAClC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAClE,CAAA;IACD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,SAAS,IAAI,QAAQ;QACrB,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ;QAEpC,OAAO,QAAQ,CAAC,OAAO,CAAA;IACzB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;AACpF,CAAC;AACD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,gBAAgB,eAAe,EAAE,EAAE,KAAK,CAAU,CAAA;AAElF,wGAAwG;AACxG,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,cAAc,EAAE,KAAK,CAAU,CAAA;AAazE;0FAC0F;AAC1F,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,EAAE,EAAE,KAAK,CAAC,CAAA;IAC3F,OAAO,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAA;AACpF,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,UAAU,GAAc,SAAS,EAAE,CAAA;AAEhD;qFACqF;AACrF,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAA;AACvD,CAAC;AAED,MAAM,aAAa,GACjB,iKAAiK,CAAA;AAEnK,yEAAyE;AACzE,MAAM,UAAU,QAAQ;IACtB,OAAO,kBAAkB,aAAa,kBAAkB,CAAA;AAC1D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,QAAQ;IACtB,OAAO,kBAAkB,aAAa,oBAAoB,CAAA;AAC5D,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,UAAU;IACxB,OAAO,uEAAuE,aAAa,gDAAgD,CAAA;AAC7I,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB;IACjC,OAAO,GAAG,aAAa,+EAA+E,CAAA;AACxG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,gBAAgB,GAAG,EAAE,EAAE,KAAK,CAAC,CAAA;IAC3F,OAAO;;+DAEsD,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;2FAUR,CAAA;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,GAAG,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FA6BkE,CAAA;AAC/F,CAAC;AAED,iGAAiG;AACjG,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAA;AAWvD;sGACsG;AACtG,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAA;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAA;AACtD,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;AACzD,MAAM,CAAC,MAAM,yBAAyB,GAAG,iCAAiC,CAAA;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAA;AAEzC,wFAAwF;AACxF,MAAM,CAAC,MAAM,cAAc,GACzB;IACE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE;IAC3C,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE;IAC3C,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE;IAC/C,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,mBAAmB,EAAE;CAClE,CAAA"}
|
package/dist/auth.js
CHANGED
|
@@ -34,7 +34,7 @@ export const AUTH_PRESETS = {
|
|
|
34
34
|
label: "better-auth",
|
|
35
35
|
note: "email/password + sessions, mounted at /api/auth/* - add OAuth providers in backend/auth.ts.",
|
|
36
36
|
// @nifrajs/better-auth mounts it; better-auth is the implementation (peer-installed alongside).
|
|
37
|
-
deps: { "@nifrajs/better-auth": "^5.
|
|
37
|
+
deps: { "@nifrajs/better-auth": "^5.2.0", "better-auth": "^1.2.0" },
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
/** The better-auth `database` adapter expression + its import line, for the scaffolded DB's ORM. */
|
|
@@ -37,7 +37,7 @@ export interface FrameworkSpec {
|
|
|
37
37
|
* ships templates installing the PREVIOUS release. `scaffold-composition.test.ts` now fails when this
|
|
38
38
|
* drifts from what core is publishing, so the footgun is a red test rather than a silent regression.
|
|
39
39
|
*/
|
|
40
|
-
export declare const NIFRA_DEP_RANGE = "^5.
|
|
40
|
+
export declare const NIFRA_DEP_RANGE = "^5.2.0";
|
|
41
41
|
/**
|
|
42
42
|
* React is first because it is the default (`--framework` omitted scaffolds it), and because the
|
|
43
43
|
* other four are most easily read as deltas from it.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* ships templates installing the PREVIOUS release. `scaffold-composition.test.ts` now fails when this
|
|
16
16
|
* drifts from what core is publishing, so the footgun is a red test rather than a silent regression.
|
|
17
17
|
*/
|
|
18
|
-
export const NIFRA_DEP_RANGE = "^5.
|
|
18
|
+
export const NIFRA_DEP_RANGE = "^5.2.0";
|
|
19
19
|
/**
|
|
20
20
|
* React is first because it is the default (`--framework` omitted scaffolds it), and because the
|
|
21
21
|
* other four are most easily read as deltas from it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nifra",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Scaffold a new nifra app - `bun create nifra <dir>`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"template-site-solid",
|
|
41
41
|
"template-site-svelte",
|
|
42
42
|
"template-isr",
|
|
43
|
-
"template-batteries"
|
|
43
|
+
"template-batteries",
|
|
44
|
+
"CHANGELOG.md"
|
|
44
45
|
],
|
|
45
46
|
"sideEffects": false,
|
|
46
47
|
"scripts": {
|
package/src/agent-files.ts
CHANGED
|
@@ -40,14 +40,17 @@ export const MCP_SERVER_COMMAND = "bunx" as const
|
|
|
40
40
|
* (`../package.json` → the same root, since `dist/` sits beside the manifest). npm always ships a
|
|
41
41
|
* package's own `package.json`, so the read can't miss at install time.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
43
|
+
* Two launch forms. A project that declares `@nifrajs/cli` gets the unpinned
|
|
44
|
+
* `bunx @nifrajs/cli mcp` ({@link MCP_SERVER_ARGS_INSTALLED}): `bunx` runs the copy the project installs
|
|
45
|
+
* (its `node_modules`, or an ancestor's) whenever there is one, so an upgrade moves the server with it
|
|
46
|
+
* and nothing needs re-pinning. A pinned spec never does that, not even at the installed version: it
|
|
47
|
+
* always runs a registry copy. Every scaffold declares the CLI, so every scaffold is unpinned.
|
|
48
|
+
*
|
|
49
|
+
* A project that does not declare it keeps the pin ({@link MCP_SERVER_ARGS}): with no local install an
|
|
50
|
+
* unpinned spec resolves `latest` once and `bunx` reuses that cached copy on every later spawn without
|
|
51
|
+
* re-checking the registry, so the server would silently run old code. The exact version is part of the
|
|
52
|
+
* cache key, so each release fetches fresh; `nifra init-agents --sync-mcp` moves it to the installed
|
|
53
|
+
* nifra, or unpins it once the project declares the CLI.
|
|
51
54
|
*/
|
|
52
55
|
export const MCP_CLI_VERSION: string = ownVersion()
|
|
53
56
|
|
|
@@ -66,6 +69,9 @@ function ownVersion(): string {
|
|
|
66
69
|
}
|
|
67
70
|
export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"] as const
|
|
68
71
|
|
|
72
|
+
/** The launch args for a project that declares `@nifrajs/cli`: unpinned, so `bunx` runs its install. */
|
|
73
|
+
export const MCP_SERVER_ARGS_INSTALLED = ["@nifrajs/cli", "mcp"] as const
|
|
74
|
+
|
|
69
75
|
/** The server entry registered under the `nifra` key in both Claude Code's and Cursor's MCP config. */
|
|
70
76
|
export interface McpServerConfig {
|
|
71
77
|
readonly command: string
|
|
@@ -77,17 +83,20 @@ export interface McpConfig {
|
|
|
77
83
|
readonly mcpServers: Readonly<Record<string, McpServerConfig>>
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
/** The one canonical MCP config
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
86
|
+
/** The one canonical MCP config both registries serialize - the anti-drift seam. `pin` is the
|
|
87
|
+
* `@nifrajs/cli` version to pin; without one the launch follows the project's install. */
|
|
88
|
+
export function mcpConfig(pin?: string): McpConfig {
|
|
89
|
+
const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"]
|
|
90
|
+
return { mcpServers: { nifra: { command: MCP_SERVER_COMMAND, args: [...args] } } }
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
/** The config a scaffold writes: every scaffold declares the CLI, so its launch is unpinned. */
|
|
94
|
+
export const MCP_CONFIG: McpConfig = mcpConfig()
|
|
95
|
+
|
|
87
96
|
/** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
|
|
88
97
|
* Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
|
|
89
|
-
export function mcpJson(): string {
|
|
90
|
-
return `${JSON.stringify(
|
|
98
|
+
export function mcpJson(pin?: string): string {
|
|
99
|
+
return `${JSON.stringify(mcpConfig(pin), null, 2)}\n`
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
const POINTER_INTRO =
|
|
@@ -118,10 +127,11 @@ export function copilotInstructions(): string {
|
|
|
118
127
|
* agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
|
|
119
128
|
* Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
|
|
120
129
|
*/
|
|
121
|
-
export function agentsMcpSection(): string {
|
|
130
|
+
export function agentsMcpSection(pin?: string): string {
|
|
131
|
+
const args = pin === undefined ? MCP_SERVER_ARGS_INSTALLED : [`@nifrajs/cli@${pin}`, "mcp"]
|
|
122
132
|
return `## MCP server
|
|
123
133
|
|
|
124
|
-
This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${
|
|
134
|
+
This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${args.join(" ")}\`. It's registered for
|
|
125
135
|
Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
|
|
126
136
|
at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
|
|
127
137
|
project + the installed version, so they beat training-data recall:
|
|
@@ -80,5 +80,16 @@ export default defineAssuranceConfig({
|
|
|
80
80
|
// an authenticated one rather than weakening the whole class.
|
|
81
81
|
{ name: "read", match: { methods: ["GET"] }, require: [] },
|
|
82
82
|
],
|
|
83
|
+
// Checked on every route it matches, whichever rule above owns the route: a route that declares a
|
|
84
|
+
// `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
|
|
85
|
+
// before its routes, so a route the contract does not cover fails here instead of sending fields
|
|
86
|
+
// its schema never declared.
|
|
87
|
+
baseline: [
|
|
88
|
+
{
|
|
89
|
+
name: "response-contract",
|
|
90
|
+
match: { hasResponse: true },
|
|
91
|
+
require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
83
94
|
},
|
|
84
95
|
})
|
package/template/package.json
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@nifrajs/client": "^5.
|
|
15
|
-
"@nifrajs/core": "^5.
|
|
16
|
-
"@nifrajs/middleware": "^5.
|
|
17
|
-
"@nifrajs/schema": "^5.
|
|
14
|
+
"@nifrajs/client": "^5.2.0",
|
|
15
|
+
"@nifrajs/core": "^5.2.0",
|
|
16
|
+
"@nifrajs/middleware": "^5.2.0",
|
|
17
|
+
"@nifrajs/schema": "^5.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@nifrajs/cli": "^5.
|
|
20
|
+
"@nifrajs/cli": "^5.2.0",
|
|
21
21
|
"@types/bun": "latest",
|
|
22
22
|
"typescript": "^6.0.3"
|
|
23
23
|
}
|
|
@@ -86,5 +86,16 @@ export default defineAssuranceConfig({
|
|
|
86
86
|
// an authenticated one rather than weakening the whole class.
|
|
87
87
|
{ name: "read", match: { methods: ["GET"] }, require: [] },
|
|
88
88
|
],
|
|
89
|
+
// Checked on every route it matches, whichever rule above owns the route: a route that declares a
|
|
90
|
+
// `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
|
|
91
|
+
// before its routes, so a route the contract does not cover fails here instead of sending fields
|
|
92
|
+
// its schema never declared.
|
|
93
|
+
baseline: [
|
|
94
|
+
{
|
|
95
|
+
name: "response-contract",
|
|
96
|
+
match: { hasResponse: true },
|
|
97
|
+
require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
89
100
|
},
|
|
90
101
|
})
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@nifrajs/cache": "^5.
|
|
15
|
-
"@nifrajs/client": "^5.
|
|
16
|
-
"@nifrajs/core": "^5.
|
|
17
|
-
"@nifrajs/jobs": "^5.
|
|
18
|
-
"@nifrajs/middleware": "^5.
|
|
19
|
-
"@nifrajs/schema": "^5.
|
|
20
|
-
"@nifrajs/storage": "^5.
|
|
14
|
+
"@nifrajs/cache": "^5.2.0",
|
|
15
|
+
"@nifrajs/client": "^5.2.0",
|
|
16
|
+
"@nifrajs/core": "^5.2.0",
|
|
17
|
+
"@nifrajs/jobs": "^5.2.0",
|
|
18
|
+
"@nifrajs/middleware": "^5.2.0",
|
|
19
|
+
"@nifrajs/schema": "^5.2.0",
|
|
20
|
+
"@nifrajs/storage": "^5.2.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@nifrajs/cli": "^5.
|
|
23
|
+
"@nifrajs/cli": "^5.2.0",
|
|
24
24
|
"@types/bun": "latest",
|
|
25
25
|
"typescript": "^6.0.3"
|
|
26
26
|
}
|
|
@@ -81,5 +81,16 @@ export default defineAssuranceConfig({
|
|
|
81
81
|
// an authenticated one rather than weakening the whole class.
|
|
82
82
|
{ name: "read", match: { methods: ["GET"] }, require: [] },
|
|
83
83
|
],
|
|
84
|
+
// Checked on every route it matches, whichever rule above owns the route: a route that declares a
|
|
85
|
+
// `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
|
|
86
|
+
// before its routes, so a route the contract does not cover fails here instead of sending fields
|
|
87
|
+
// its schema never declared.
|
|
88
|
+
baseline: [
|
|
89
|
+
{
|
|
90
|
+
name: "response-contract",
|
|
91
|
+
match: { hasResponse: true },
|
|
92
|
+
require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
84
95
|
},
|
|
85
96
|
})
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
"check": "nifra check && nifra assure"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@nifrajs/client": "^5.
|
|
14
|
-
"@nifrajs/core": "^5.
|
|
15
|
-
"@nifrajs/middleware": "^5.
|
|
16
|
-
"@nifrajs/schema": "^5.
|
|
17
|
-
"@nifrajs/web": "^5.
|
|
18
|
-
"@nifrajs/web-react": "^5.
|
|
13
|
+
"@nifrajs/client": "^5.2.0",
|
|
14
|
+
"@nifrajs/core": "^5.2.0",
|
|
15
|
+
"@nifrajs/middleware": "^5.2.0",
|
|
16
|
+
"@nifrajs/schema": "^5.2.0",
|
|
17
|
+
"@nifrajs/web": "^5.2.0",
|
|
18
|
+
"@nifrajs/web-react": "^5.2.0",
|
|
19
19
|
"react": "^19.0.0",
|
|
20
20
|
"react-dom": "^19.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@nifrajs/cli": "^5.
|
|
23
|
+
"@nifrajs/cli": "^5.2.0",
|
|
24
24
|
"@types/bun": "^1.4.2",
|
|
25
25
|
"@types/react": "^19.0.0",
|
|
26
26
|
"@types/react-dom": "^19.0.0",
|
|
@@ -83,5 +83,16 @@ export default defineAssuranceConfig({
|
|
|
83
83
|
// an authenticated one rather than weakening the whole class.
|
|
84
84
|
{ name: "read", match: { methods: ["GET"] }, require: [] },
|
|
85
85
|
],
|
|
86
|
+
// Checked on every route it matches, whichever rule above owns the route: a route that declares a
|
|
87
|
+
// `response` schema must enforce it at runtime. `backend/app.ts` installs `responseContract("enforce")`
|
|
88
|
+
// before its routes, so a route the contract does not cover fails here instead of sending fields
|
|
89
|
+
// its schema never declared.
|
|
90
|
+
baseline: [
|
|
91
|
+
{
|
|
92
|
+
name: "response-contract",
|
|
93
|
+
match: { hasResponse: true },
|
|
94
|
+
require: [NIFRA_ASSURANCE.RESPONSE_CONTRACT],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
86
97
|
},
|
|
87
98
|
})
|