create-nifra 2.1.0 → 2.3.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/README.md +4 -4
- package/dist/agent-files.d.ts +14 -14
- package/dist/agent-files.js +29 -29
- package/dist/agents.d.ts +4 -4
- package/dist/agents.js +63 -63
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +8 -8
- package/dist/cli.d.ts +6 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +56 -35
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts +6 -6
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +138 -41
- package/dist/db.js.map +1 -1
- package/dist/scaffold/features.d.ts +41 -0
- package/dist/scaffold/features.d.ts.map +1 -0
- package/dist/scaffold/features.js +48 -0
- package/dist/scaffold/features.js.map +1 -0
- package/dist/scaffold/frameworks.d.ts +73 -0
- package/dist/scaffold/frameworks.d.ts.map +1 -0
- package/dist/scaffold/frameworks.js +100 -0
- package/dist/scaffold/frameworks.js.map +1 -0
- package/dist/scaffold/site-build.d.ts +60 -0
- package/dist/scaffold/site-build.d.ts.map +1 -0
- package/dist/scaffold/site-build.js +198 -0
- package/dist/scaffold/site-build.js.map +1 -0
- package/dist/scaffold/site-files.d.ts +16 -0
- package/dist/scaffold/site-files.d.ts.map +1 -0
- package/dist/scaffold/site-files.js +112 -0
- package/dist/scaffold/site-files.js.map +1 -0
- package/dist/scaffold/site.d.ts +33 -0
- package/dist/scaffold/site.d.ts.map +1 -0
- package/dist/scaffold/site.js +99 -0
- package/dist/scaffold/site.js.map +1 -0
- package/package.json +3 -2
- package/src/agent-files.ts +149 -0
- package/template/README.md +3 -3
- package/template/nifra.assurance.ts +80 -0
- package/template/package.json +5 -3
- package/template/src/app.ts +10 -11
- package/template/src/routes.ts +13 -0
- package/template-isr/README.md +1 -1
- package/template-isr/backend.ts +11 -15
- package/template-isr/nifra.assurance.ts +81 -0
- package/template-isr/package.json +8 -7
- package/template-isr/page.ts +12 -0
- package/template-isr/routes/_404.tsx +1 -1
- package/template-isr/routes/_layout.tsx +1 -1
- package/template-isr/routes/index.tsx +2 -2
- package/template-isr/server.ts +1 -1
- package/template-isr/worker.ts +2 -2
- package/template-isr/wrangler.toml +2 -2
- package/template-site/Dockerfile +1 -1
- package/template-site/README.md +6 -6
- package/template-site/backend.ts +11 -13
- package/template-site/counter.ts +16 -0
- package/template-site/nifra.assurance.ts +83 -0
- package/template-site/nifra.config.ts +1 -1
- package/template-site/routes/index.tsx +1 -1
- package/template-site/server-node.ts +1 -1
- package/template-site/server-vercel.ts +1 -1
- package/template-site-preact/README.md +7 -7
- package/template-site-preact/nifra.config.ts +1 -1
- package/template-site-preact/routes/index.tsx +1 -1
- package/template-site-solid/README.md +7 -7
- package/template-site-solid/nifra.config.ts +2 -2
- package/template-site-solid/routes/index.tsx +1 -1
- package/template-site-svelte/README.md +7 -7
- package/template-site-svelte/nifra.config.ts +2 -2
- package/template-site-svelte/routes/_layout.svelte +1 -1
- package/template-site-svelte/routes/index.svelte +1 -1
- package/template-site-vue/README.md +7 -7
- package/template-site-vue/nifra.config.ts +2 -2
- package/template-site-vue/routes/_layout.vue +2 -2
- package/template-site-vue/routes/index.vue +3 -3
- package/template-site/build-bun.ts +0 -27
- package/template-site/build-deno.ts +0 -25
- package/template-site/build-node.ts +0 -26
- package/template-site/build-vercel.ts +0 -45
- package/template-site/build.ts +0 -29
- package/template-site/framework.ts +0 -6
- package/template-site/package.json +0 -38
- package/template-site/tsconfig.json +0 -37
- package/template-site-preact/.dockerignore +0 -14
- package/template-site-preact/Dockerfile +0 -20
- package/template-site-preact/_worker.ts +0 -17
- package/template-site-preact/backend.ts +0 -15
- package/template-site-preact/build-bun.ts +0 -27
- package/template-site-preact/build-deno.ts +0 -25
- package/template-site-preact/build-node.ts +0 -26
- package/template-site-preact/build-vercel.ts +0 -45
- package/template-site-preact/build.ts +0 -29
- package/template-site-preact/deno.json +0 -7
- package/template-site-preact/framework.ts +0 -6
- package/template-site-preact/gitignore +0 -16
- package/template-site-preact/package.json +0 -35
- package/template-site-preact/server-bun.ts +0 -35
- package/template-site-preact/server-deno.ts +0 -38
- package/template-site-preact/server-node.ts +0 -40
- package/template-site-preact/server-vercel.ts +0 -17
- package/template-site-preact/tsconfig.json +0 -38
- package/template-site-preact/wrangler.toml +0 -4
- package/template-site-solid/.dockerignore +0 -14
- package/template-site-solid/Dockerfile +0 -20
- package/template-site-solid/_worker.ts +0 -17
- package/template-site-solid/backend.ts +0 -15
- package/template-site-solid/build-bun.ts +0 -33
- package/template-site-solid/build-deno.ts +0 -32
- package/template-site-solid/build-node.ts +0 -33
- package/template-site-solid/build-vercel.ts +0 -47
- package/template-site-solid/build.ts +0 -36
- package/template-site-solid/deno.json +0 -7
- package/template-site-solid/framework.ts +0 -6
- package/template-site-solid/gitignore +0 -16
- package/template-site-solid/package.json +0 -35
- package/template-site-solid/server-bun.ts +0 -35
- package/template-site-solid/server-deno.ts +0 -38
- package/template-site-solid/server-node.ts +0 -40
- package/template-site-solid/server-vercel.ts +0 -17
- package/template-site-solid/tsconfig.json +0 -38
- package/template-site-solid/wrangler.toml +0 -4
- package/template-site-svelte/.dockerignore +0 -14
- package/template-site-svelte/Dockerfile +0 -20
- package/template-site-svelte/_worker.ts +0 -17
- package/template-site-svelte/backend.ts +0 -15
- package/template-site-svelte/build-bun.ts +0 -32
- package/template-site-svelte/build-deno.ts +0 -31
- package/template-site-svelte/build-node.ts +0 -32
- package/template-site-svelte/build-vercel.ts +0 -46
- package/template-site-svelte/build.ts +0 -35
- package/template-site-svelte/deno.json +0 -7
- package/template-site-svelte/framework.ts +0 -6
- package/template-site-svelte/gitignore +0 -16
- package/template-site-svelte/package.json +0 -35
- package/template-site-svelte/server-bun.ts +0 -35
- package/template-site-svelte/server-deno.ts +0 -38
- package/template-site-svelte/server-node.ts +0 -40
- package/template-site-svelte/server-vercel.ts +0 -17
- package/template-site-svelte/tsconfig.json +0 -37
- package/template-site-svelte/wrangler.toml +0 -4
- package/template-site-vue/.dockerignore +0 -14
- package/template-site-vue/Dockerfile +0 -20
- package/template-site-vue/_worker.ts +0 -17
- package/template-site-vue/backend.ts +0 -15
- package/template-site-vue/build-bun.ts +0 -37
- package/template-site-vue/build-deno.ts +0 -36
- package/template-site-vue/build-node.ts +0 -37
- package/template-site-vue/build-vercel.ts +0 -54
- package/template-site-vue/build.ts +0 -40
- package/template-site-vue/deno.json +0 -7
- package/template-site-vue/framework.ts +0 -6
- package/template-site-vue/gitignore +0 -16
- package/template-site-vue/package.json +0 -36
- package/template-site-vue/server-bun.ts +0 -35
- package/template-site-vue/server-deno.ts +0 -38
- package/template-site-vue/server-node.ts +0 -40
- package/template-site-vue/server-vercel.ts +0 -17
- package/template-site-vue/tsconfig.json +0 -37
- package/template-site-vue/wrangler.toml +0 -4
package/README.md
CHANGED
|
@@ -11,11 +11,11 @@ bun create nifra my-app
|
|
|
11
11
|
|
|
12
12
|
`--template <name>` (default `api`):
|
|
13
13
|
|
|
14
|
-
- **`api`**
|
|
14
|
+
- **`api`** - a typed nifra server (`src/app.ts` + `src/index.ts`) with an example test and
|
|
15
15
|
`dev`/`start`/`test`/`typecheck` scripts.
|
|
16
|
-
- **`site`**
|
|
16
|
+
- **`site`** - a nifra + React SSR site, one source deployable to Cloudflare Pages, Node, Deno, or
|
|
17
17
|
Vercel Edge.
|
|
18
|
-
- **`isr`**
|
|
18
|
+
- **`isr`** - a nifra + React app with **Incremental Static Regeneration** on Cloudflare Workers + KV
|
|
19
19
|
(pages cached + served stale-while-revalidate; on-demand purge endpoint).
|
|
20
20
|
|
|
21
21
|
```sh
|
|
@@ -34,7 +34,7 @@ MIT.
|
|
|
34
34
|
|
|
35
35
|
## For AI agents
|
|
36
36
|
|
|
37
|
-
Start with [`LLM.md`](./LLM.md)
|
|
37
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
38
38
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
39
39
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
40
40
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/dist/agent-files.d.ts
CHANGED
|
@@ -2,33 +2,33 @@
|
|
|
2
2
|
* The canonical generators for the agent-discovery files an app ships so a coding agent auto-discovers
|
|
3
3
|
* the project's nifra MCP server and prefers it over writing nifra from memory:
|
|
4
4
|
*
|
|
5
|
-
* .mcp.json
|
|
6
|
-
* .cursor/mcp.json
|
|
7
|
-
* CLAUDE.md
|
|
8
|
-
* AGENTS.md "MCP"
|
|
5
|
+
* .mcp.json - Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
|
|
6
|
+
* .cursor/mcp.json - Cursor's MCP registry (same server config)
|
|
7
|
+
* CLAUDE.md - Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
|
|
8
|
+
* AGENTS.md "MCP" - a section for non-Claude agents pointing at the same server
|
|
9
9
|
*
|
|
10
10
|
* Single source of truth: both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents`
|
|
11
11
|
* (retrofit an existing app) import these, so the four files can never drift apart. This module is
|
|
12
|
-
* dependency-free (pure string + object generation) on purpose
|
|
12
|
+
* dependency-free (pure string + object generation) on purpose - `create-nifra` ships with no runtime
|
|
13
13
|
* deps, and `@nifrajs/cli` imports it as a workspace dependency.
|
|
14
14
|
*
|
|
15
15
|
* Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
|
|
16
16
|
* `@nifrajs/cli` package. A scaffolded `site` app carries `@nifrajs/cli` as a devDependency, so either
|
|
17
|
-
* spelling would resolve `node_modules/.bin/nifra` locally
|
|
17
|
+
* spelling would resolve `node_modules/.bin/nifra` locally - BUT the `api`/`isr` templates do NOT carry
|
|
18
18
|
* `@nifrajs/cli`, and the bare npm package literally named `nifra` (this monorepo's `@nifrajs/web` shim)
|
|
19
19
|
* exposes NO `nifra` bin. So `bunx nifra mcp` would fetch the wrong package and fail there. Naming the
|
|
20
20
|
* bin-owning package, `bunx @nifrajs/cli mcp`, resolves the locally-installed bin when present and
|
|
21
|
-
* otherwise fetches the package that actually provides the `nifra` command
|
|
21
|
+
* otherwise fetches the package that actually provides the `nifra` command - robust across every
|
|
22
22
|
* template and for any existing app.
|
|
23
23
|
*/
|
|
24
24
|
/** The MCP launch command, shared by `.mcp.json` and `.cursor/mcp.json`. See the module header for why
|
|
25
25
|
* the package is named explicitly rather than relying on the bare `nifra` bin. */
|
|
26
26
|
export declare const MCP_SERVER_COMMAND: "bunx";
|
|
27
27
|
/**
|
|
28
|
-
* The `@nifrajs/cli` version the launch command pins to
|
|
28
|
+
* The `@nifrajs/cli` version the launch command pins to - DERIVED at load time from this package's own
|
|
29
29
|
* `version`, never hardcoded. `fixed` changeset versioning ([["@nifrajs/*", "create-nifra", "nifra"]] in
|
|
30
30
|
* `.changeset/config.json`) bumps `create-nifra` and `@nifrajs/cli` in lockstep, so `create-nifra@x`
|
|
31
|
-
* always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update
|
|
31
|
+
* always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update -
|
|
32
32
|
* there is no stale literal to drift (the 1.0.0 cut shipped a beta pin exactly this way). `check:publish`
|
|
33
33
|
* still asserts it equals `@nifrajs/cli`'s version as a belt-and-suspenders guard on the `fixed` link.
|
|
34
34
|
*
|
|
@@ -39,11 +39,11 @@ export declare const MCP_SERVER_COMMAND: "bunx";
|
|
|
39
39
|
*
|
|
40
40
|
* Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
|
|
41
41
|
* An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
|
|
42
|
-
* later spawn WITHOUT re-checking the registry
|
|
42
|
+
* later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
|
|
43
43
|
* keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
|
|
44
44
|
* old code). Pinning the exact version makes the version part of the cache key, so each release fetches
|
|
45
45
|
* fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
|
|
46
|
-
* at its scaffold-time version until `nifra init-agents` is re-run
|
|
46
|
+
* at its scaffold-time version until `nifra init-agents` is re-run - an acceptable, deterministic trade.
|
|
47
47
|
*/
|
|
48
48
|
export declare const MCP_CLI_VERSION: string;
|
|
49
49
|
export declare const MCP_SERVER_ARGS: readonly [`@nifrajs/cli@${string}`, "mcp"];
|
|
@@ -56,16 +56,16 @@ export interface McpServerConfig {
|
|
|
56
56
|
export interface McpConfig {
|
|
57
57
|
readonly mcpServers: Readonly<Record<string, McpServerConfig>>;
|
|
58
58
|
}
|
|
59
|
-
/** The one canonical MCP config object both registries serialize
|
|
59
|
+
/** The one canonical MCP config object both registries serialize - the anti-drift seam. */
|
|
60
60
|
export declare const MCP_CONFIG: McpConfig;
|
|
61
61
|
/** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
|
|
62
62
|
* Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
|
|
63
63
|
export declare function mcpJson(): string;
|
|
64
64
|
/**
|
|
65
|
-
* `CLAUDE.md`
|
|
65
|
+
* `CLAUDE.md` - Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
|
|
66
66
|
* a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
|
|
67
67
|
* to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
|
|
68
|
-
* own line
|
|
68
|
+
* own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
|
|
69
69
|
* two files. Keep this short; the depth is in `AGENTS.md`.
|
|
70
70
|
*/
|
|
71
71
|
export declare function claudeMd(): string;
|
package/dist/agent-files.js
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
* The canonical generators for the agent-discovery files an app ships so a coding agent auto-discovers
|
|
3
3
|
* the project's nifra MCP server and prefers it over writing nifra from memory:
|
|
4
4
|
*
|
|
5
|
-
* .mcp.json
|
|
6
|
-
* .cursor/mcp.json
|
|
7
|
-
* CLAUDE.md
|
|
8
|
-
* AGENTS.md "MCP"
|
|
5
|
+
* .mcp.json - Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
|
|
6
|
+
* .cursor/mcp.json - Cursor's MCP registry (same server config)
|
|
7
|
+
* CLAUDE.md - Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
|
|
8
|
+
* AGENTS.md "MCP" - a section for non-Claude agents pointing at the same server
|
|
9
9
|
*
|
|
10
10
|
* Single source of truth: both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents`
|
|
11
11
|
* (retrofit an existing app) import these, so the four files can never drift apart. This module is
|
|
12
|
-
* dependency-free (pure string + object generation) on purpose
|
|
12
|
+
* dependency-free (pure string + object generation) on purpose - `create-nifra` ships with no runtime
|
|
13
13
|
* deps, and `@nifrajs/cli` imports it as a workspace dependency.
|
|
14
14
|
*
|
|
15
15
|
* Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
|
|
16
16
|
* `@nifrajs/cli` package. A scaffolded `site` app carries `@nifrajs/cli` as a devDependency, so either
|
|
17
|
-
* spelling would resolve `node_modules/.bin/nifra` locally
|
|
17
|
+
* spelling would resolve `node_modules/.bin/nifra` locally - BUT the `api`/`isr` templates do NOT carry
|
|
18
18
|
* `@nifrajs/cli`, and the bare npm package literally named `nifra` (this monorepo's `@nifrajs/web` shim)
|
|
19
19
|
* exposes NO `nifra` bin. So `bunx nifra mcp` would fetch the wrong package and fail there. Naming the
|
|
20
20
|
* bin-owning package, `bunx @nifrajs/cli mcp`, resolves the locally-installed bin when present and
|
|
21
|
-
* otherwise fetches the package that actually provides the `nifra` command
|
|
21
|
+
* otherwise fetches the package that actually provides the `nifra` command - robust across every
|
|
22
22
|
* template and for any existing app.
|
|
23
23
|
*/
|
|
24
24
|
import { readFileSync } from "node:fs";
|
|
@@ -26,10 +26,10 @@ import { readFileSync } from "node:fs";
|
|
|
26
26
|
* the package is named explicitly rather than relying on the bare `nifra` bin. */
|
|
27
27
|
export const MCP_SERVER_COMMAND = "bunx";
|
|
28
28
|
/**
|
|
29
|
-
* The `@nifrajs/cli` version the launch command pins to
|
|
29
|
+
* The `@nifrajs/cli` version the launch command pins to - DERIVED at load time from this package's own
|
|
30
30
|
* `version`, never hardcoded. `fixed` changeset versioning ([["@nifrajs/*", "create-nifra", "nifra"]] in
|
|
31
31
|
* `.changeset/config.json`) bumps `create-nifra` and `@nifrajs/cli` in lockstep, so `create-nifra@x`
|
|
32
|
-
* always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update
|
|
32
|
+
* always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update -
|
|
33
33
|
* there is no stale literal to drift (the 1.0.0 cut shipped a beta pin exactly this way). `check:publish`
|
|
34
34
|
* still asserts it equals `@nifrajs/cli`'s version as a belt-and-suspenders guard on the `fixed` link.
|
|
35
35
|
*
|
|
@@ -40,15 +40,15 @@ export const MCP_SERVER_COMMAND = "bunx";
|
|
|
40
40
|
*
|
|
41
41
|
* Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
|
|
42
42
|
* An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
|
|
43
|
-
* later spawn WITHOUT re-checking the registry
|
|
43
|
+
* later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
|
|
44
44
|
* keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
|
|
45
45
|
* old code). Pinning the exact version makes the version part of the cache key, so each release fetches
|
|
46
46
|
* fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
|
|
47
|
-
* at its scaffold-time version until `nifra init-agents` is re-run
|
|
47
|
+
* at its scaffold-time version until `nifra init-agents` is re-run - an acceptable, deterministic trade.
|
|
48
48
|
*/
|
|
49
49
|
export const MCP_CLI_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
50
50
|
export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"];
|
|
51
|
-
/** The one canonical MCP config object both registries serialize
|
|
51
|
+
/** The one canonical MCP config object both registries serialize - the anti-drift seam. */
|
|
52
52
|
export const MCP_CONFIG = {
|
|
53
53
|
mcpServers: {
|
|
54
54
|
nifra: { command: MCP_SERVER_COMMAND, args: [...MCP_SERVER_ARGS] },
|
|
@@ -60,31 +60,31 @@ export function mcpJson() {
|
|
|
60
60
|
return `${JSON.stringify(MCP_CONFIG, null, 2)}\n`;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* `CLAUDE.md`
|
|
63
|
+
* `CLAUDE.md` - Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
|
|
64
64
|
* a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
|
|
65
65
|
* to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
|
|
66
|
-
* own line
|
|
66
|
+
* own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
|
|
67
67
|
* two files. Keep this short; the depth is in `AGENTS.md`.
|
|
68
68
|
*/
|
|
69
69
|
export function claudeMd() {
|
|
70
70
|
return `# CLAUDE.md
|
|
71
71
|
|
|
72
72
|
This project ships a **nifra MCP server**, registered for Claude Code in \`.mcp.json\` (it launches with
|
|
73
|
-
\`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`). **Prefer the MCP tools** over writing nifra from memory
|
|
73
|
+
\`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`). **Prefer the MCP tools** over writing nifra from memory - they are
|
|
74
74
|
typechecked against *this* project and this installed version, so they beat training-data recall:
|
|
75
75
|
|
|
76
|
-
- \`nifra_docs\` / \`nifra_example\`
|
|
76
|
+
- \`nifra_docs\` / \`nifra_example\` - exact signatures + verified, compiling snippets. Reach for these
|
|
77
77
|
before hand-writing a route, a loader, or a client call; a remembered API is often stale or wrong.
|
|
78
|
-
- \`nifra_context\`
|
|
79
|
-
- \`nifra_types\`
|
|
80
|
-
Call it for a precise type; **never read \`node_modules/@nifrajs/**/*.d.ts\`**
|
|
78
|
+
- \`nifra_context\` - this project's route index + conventions, so changes fit the existing surface.
|
|
79
|
+
- \`nifra_types\` - the EXACT TypeScript of any \`@nifrajs/*\` symbol (e.g. \`nifra_types({ name: "RateLimitStore" })\`).
|
|
80
|
+
Call it for a precise type; **never read \`node_modules/@nifrajs/**/*.d.ts\`** - the tool is the authoritative, complete source.
|
|
81
81
|
- **\`nifra_check\` is the done-gate.** Before declaring any change complete, run \`nifra_check\` (or
|
|
82
82
|
\`nifra check --json\` in a terminal). It typechecks the frontend↔backend contract and flags drift
|
|
83
83
|
(hand-rolled \`fetch()\` to this app's own API, server-only imports in \`routes/\`). A failing check
|
|
84
|
-
means the work isn't done
|
|
84
|
+
means the work isn't done - fix it, don't ship around it.
|
|
85
85
|
|
|
86
|
-
The full nifra cookbook for this app
|
|
87
|
-
the gotchas
|
|
86
|
+
The full nifra cookbook for this app - backend rules, the typed never-throwing client, file routing, and
|
|
87
|
+
the gotchas - lives in \`AGENTS.md\`, imported here so it stays the single source of truth:
|
|
88
88
|
|
|
89
89
|
@AGENTS.md
|
|
90
90
|
`;
|
|
@@ -97,16 +97,16 @@ the gotchas — lives in \`AGENTS.md\`, imported here so it stays the single sou
|
|
|
97
97
|
export function agentsMcpSection() {
|
|
98
98
|
return `## MCP server
|
|
99
99
|
|
|
100
|
-
This project ships a **nifra MCP server**
|
|
100
|
+
This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`. It's registered for
|
|
101
101
|
Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
|
|
102
|
-
at that command. **Prefer its tools** over writing nifra from memory
|
|
102
|
+
at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
|
|
103
103
|
project + the installed version, so they beat training-data recall:
|
|
104
104
|
|
|
105
|
-
- \`nifra_docs\` / \`nifra_example\`
|
|
106
|
-
- \`nifra_context\` / \`nifra_routes\`
|
|
107
|
-
- \`nifra_types\`
|
|
108
|
-
precise type; **never read \`@nifrajs\` \`.d.ts\` files**
|
|
109
|
-
- \`nifra_check\`
|
|
105
|
+
- \`nifra_docs\` / \`nifra_example\` - exact signatures + verified, compiling snippets.
|
|
106
|
+
- \`nifra_context\` / \`nifra_routes\` - this project's route index + per-route schemas.
|
|
107
|
+
- \`nifra_types\` - the EXACT TypeScript of any \`@nifrajs/*\` symbol (interface/type/function). Call it for a
|
|
108
|
+
precise type; **never read \`@nifrajs\` \`.d.ts\` files** - this is the authoritative, complete source.
|
|
109
|
+
- \`nifra_check\` - the done-gate (typecheck + drift lints). Run it before calling work complete; a
|
|
110
110
|
failing check means it isn't done. (\`nifra check --json\` in a terminal does the same.)`;
|
|
111
111
|
}
|
|
112
112
|
/** The standalone files this module fully owns (whole-file generators). AGENTS.md is handled separately
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate the `AGENTS.md` a scaffolded app ships with
|
|
2
|
+
* Generate the `AGENTS.md` a scaffolded app ships with - the conventions file read by coding agents
|
|
3
3
|
* (Claude Code, Cursor, …). It encodes nifra's non-obvious rules so an agent writes correct code from
|
|
4
4
|
* the first prompt: validate at the boundary, the never-throwing client, `app.fetch` as the universal
|
|
5
|
-
* entry, and (for full-stack apps) the one gotcha that bites everyone
|
|
5
|
+
* entry, and (for full-stack apps) the one gotcha that bites everyone - never top-level-import
|
|
6
6
|
* server-only code into a route module. Tailored per template so the paths + commands are real.
|
|
7
7
|
*/
|
|
8
8
|
import type { AuthChoice } from "./auth.js";
|
|
@@ -12,9 +12,9 @@ export interface AgentsMdOptions {
|
|
|
12
12
|
readonly template: TemplateName;
|
|
13
13
|
readonly framework: Framework;
|
|
14
14
|
readonly name: string;
|
|
15
|
-
/** Drizzle preset, when scaffolded with `--db`
|
|
15
|
+
/** Drizzle preset, when scaffolded with `--db` - adds a Database section. */
|
|
16
16
|
readonly db?: DbChoice;
|
|
17
|
-
/** Auth preset, when scaffolded with `--auth`
|
|
17
|
+
/** Auth preset, when scaffolded with `--auth` - adds an Authentication section. */
|
|
18
18
|
readonly auth?: AuthChoice;
|
|
19
19
|
}
|
|
20
20
|
/** Build the full `AGENTS.md` for a freshly scaffolded app. */
|
package/dist/agents.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate the `AGENTS.md` a scaffolded app ships with
|
|
2
|
+
* Generate the `AGENTS.md` a scaffolded app ships with - the conventions file read by coding agents
|
|
3
3
|
* (Claude Code, Cursor, …). It encodes nifra's non-obvious rules so an agent writes correct code from
|
|
4
4
|
* the first prompt: validate at the boundary, the never-throwing client, `app.fetch` as the universal
|
|
5
|
-
* entry, and (for full-stack apps) the one gotcha that bites everyone
|
|
5
|
+
* entry, and (for full-stack apps) the one gotcha that bites everyone - never top-level-import
|
|
6
6
|
* server-only code into a route module. Tailored per template so the paths + commands are real.
|
|
7
7
|
*/
|
|
8
8
|
import { agentsMcpSection } from "./agent-files.js";
|
|
@@ -14,7 +14,7 @@ const FRAMEWORK_LABEL = {
|
|
|
14
14
|
preact: "Preact",
|
|
15
15
|
svelte: "Svelte",
|
|
16
16
|
};
|
|
17
|
-
/** Shared backend rules
|
|
17
|
+
/** Shared backend rules - every template is, at its core, a nifra `server()`. */
|
|
18
18
|
const API_RULES = `## The backend: \`server()\`
|
|
19
19
|
|
|
20
20
|
\`\`\`ts
|
|
@@ -39,54 +39,54 @@ export type App = typeof app
|
|
|
39
39
|
Rules an agent must follow:
|
|
40
40
|
|
|
41
41
|
- **Validate every input at the boundary.** The route schema slots are \`{ body, query }\` (plus
|
|
42
|
-
\`response\`, below)
|
|
42
|
+
\`response\`, below) - use \`t\` from \`@nifrajs/schema\` (installed) or any Standard Schema (zod, valibot).
|
|
43
43
|
Read the typed, already-validated \`c.body\` / \`c.query\` (invalid input was rejected with a 422 before the
|
|
44
|
-
handler ran). **Never** hand-parse \`await c.req.json()\` and poke at properties
|
|
44
|
+
handler ran). **Never** hand-parse \`await c.req.json()\` and poke at properties - that's the bug class the
|
|
45
45
|
schema exists to remove.
|
|
46
46
|
- **Path params are NOT a schema slot.** \`:id\` etc. are inferred from the path literal as \`string\` and
|
|
47
47
|
read via \`c.params.id\`; there is no \`params\` (or \`headers\`) key in the route schema. Validate a param's
|
|
48
|
-
shape inside the handler (a length/format check), not via \`{ params: ... }\`
|
|
48
|
+
shape inside the handler (a length/format check), not via \`{ params: ... }\` - that's a type error.
|
|
49
49
|
- **Lock the output shape with \`response\` (no drift).** Add \`{ response: t.object({...}) }\` to a route:
|
|
50
50
|
the handler's return is type-checked against it, and the typed client sees exactly that shape. One
|
|
51
|
-
contract, both sides
|
|
51
|
+
contract, both sides - the frontend physically can't drift from the backend's output.
|
|
52
52
|
- **Data-or-error routes: set the status with \`c.set.status\`, then read the error off \`res.error\` (NOT \`res.data\`).**
|
|
53
53
|
The typed client maps \`res.ok\` to the HTTP status. A route that does \`c.set.status = 404; return { ok: false,
|
|
54
54
|
error: "not_found" }\` arrives at the client as \`res.ok === false\`, \`res.data === null\`, and the body on
|
|
55
55
|
\`res.error\` (\`res.error.error === "not_found"\`). So branch on \`res.ok\` FIRST, then read \`res.data\` on success
|
|
56
|
-
or \`res.error\` on failure
|
|
56
|
+
or \`res.error\` on failure - never assert a 4xx onto \`res.data\`. Declaring the error arm in \`response\`
|
|
57
57
|
(\`t.union([Item, t.object({ ok: t.literal(false), error: t.string() })])\`) documents the shape and keeps the
|
|
58
58
|
success \`res.data\` precise, but a 4xx still surfaces via \`res.error\`. Do NOT return a raw \`Response\` just to
|
|
59
59
|
set an error status.
|
|
60
60
|
- **Raw \`Response\` is for redirects / streams / files** (\`Response.redirect(url, 302)\`, a file body, an
|
|
61
61
|
SSE stream). The typed client infers \`res.data: never\` on such a route and \`nifra check\` emits a
|
|
62
|
-
non-blocking \`response-route\` warning
|
|
63
|
-
- **\`app.fetch(request)\` is the whole app**
|
|
62
|
+
non-blocking \`response-route\` warning - both are EXPECTED there, not defects.
|
|
63
|
+
- **\`app.fetch(request)\` is the whole app** - \`(Request) => Response | Promise<Response>\`. Tests drive it
|
|
64
64
|
directly (\`await app.fetch(new Request("http://x/users/1"))\`); no server needs to be running.
|
|
65
65
|
- **Don't reach for a heavy ORM/HTTP layer.** Routing, validation, cookies, and the typed client are
|
|
66
66
|
built in. Parameterize DB queries; use \`timingSafeEqual\` (or WebCrypto) for secret comparison.
|
|
67
|
-
- **Cross-cutting concerns
|
|
67
|
+
- **Cross-cutting concerns - use \`@nifrajs/middleware\`, don't hand-roll.** Rate limiting (\`429\` +
|
|
68
68
|
\`Retry-After\`), CORS, security headers, body limits, auth (\`bearer\`/\`apiKey\`/\`basicAuth\`/\`jwt\`), CSRF,
|
|
69
|
-
IP restriction, response caching, compression
|
|
69
|
+
IP restriction, response caching, compression - all \`app.use(...)\` plugins in \`@nifrajs/middleware\`.
|
|
70
70
|
Call \`nifra_docs("middleware")\` for the full list + usage before building one of these by hand.
|
|
71
71
|
|
|
72
|
-
## The client is typed + never throws
|
|
72
|
+
## The client is typed + never throws - ALWAYS use it for internal API calls
|
|
73
73
|
|
|
74
74
|
\`\`\`ts
|
|
75
75
|
import { client } from "@nifrajs/client"
|
|
76
|
-
import type { app } from "./app" // type-only import
|
|
76
|
+
import type { app } from "./app" // type-only import - server code never ships to the client
|
|
77
77
|
|
|
78
78
|
const api = client<typeof app>("https://api.example.com")
|
|
79
79
|
const res = await api.users({ id: "42" }).get()
|
|
80
|
-
if (res.ok) res.data // typed from the route's return type
|
|
80
|
+
if (res.ok) res.data // typed from the route's return type - zero codegen
|
|
81
81
|
else res.error // errors are returned, never thrown
|
|
82
82
|
\`\`\`
|
|
83
83
|
|
|
84
|
-
**Never hand-roll \`fetch\` + ad-hoc response types for this app's own API**
|
|
84
|
+
**Never hand-roll \`fetch\` + ad-hoc response types for this app's own API** - that's exactly how a screen
|
|
85
85
|
drifts from the backend. \`client<typeof app>\` derives both the request inputs and \`res.data\` from the
|
|
86
86
|
route types, so \`tsc\` (and \`nifra build\`) catch any mismatch the moment a route changes. When building a
|
|
87
87
|
page that calls the API, reach for the client first.
|
|
88
88
|
`;
|
|
89
|
-
/** File-routing + the server-only gotcha
|
|
89
|
+
/** File-routing + the server-only gotcha - only for full-stack (site/isr) templates. */
|
|
90
90
|
function webRules(framework) {
|
|
91
91
|
const label = FRAMEWORK_LABEL[framework];
|
|
92
92
|
return `## The frontend: file routing + loaders (${label})
|
|
@@ -96,7 +96,7 @@ function webRules(framework) {
|
|
|
96
96
|
- A route file may export: \`default\` (the page component), \`loader\` (server-only data), \`action\`
|
|
97
97
|
(server-only mutation), and \`meta\` (head tags). The \`loader\` returns data typed straight into the page.
|
|
98
98
|
- **The \`server()\` backend is IN-PROCESS, not a public HTTP surface.** Loaders/actions call it through
|
|
99
|
-
\`ctx.api\` (the typed client
|
|
99
|
+
\`ctx.api\` (the typed client - no network) during SSR; there is no \`GET /your-route\` endpoint on the page
|
|
100
100
|
server to curl. Build features through loaders/actions + the typed client, not direct HTTP calls.
|
|
101
101
|
- Swap UI frameworks by changing one import (\`@nifrajs/web-${framework}\` → another adapter); your route,
|
|
102
102
|
loader, and action code do not change.
|
|
@@ -108,10 +108,10 @@ function webRules(framework) {
|
|
|
108
108
|
## ⚠️ The one rule that bites everyone: never import server-only code at a route's top level
|
|
109
109
|
|
|
110
110
|
A route module's \`loader\`/\`action\` run **only on the server**, but the module is **also bundled for the
|
|
111
|
-
browser** (for the component)
|
|
111
|
+
browser** (for the component) - and the loader is *not* stripped. So a top-level
|
|
112
112
|
\`import db from "./db"\` (or anything touching \`process.env\`, secrets, \`node:\` APIs) ships server code to
|
|
113
113
|
the client and crashes hydration. Reach server resources through \`ctx.api\` / \`ctx.env\` (injected via
|
|
114
|
-
\`createWebApp\`) inside the loader instead
|
|
114
|
+
\`createWebApp\`) inside the loader instead - never as a top-level import in a \`routes/\` file.
|
|
115
115
|
`;
|
|
116
116
|
}
|
|
117
117
|
/** Per-ORM body of the DB rules: the query idiom, schema location, and migrate story all differ. */
|
|
@@ -133,11 +133,11 @@ singleton \`PrismaClient\`).
|
|
|
133
133
|
c.db.note.create({ data: { title: c.body.title } }),
|
|
134
134
|
)
|
|
135
135
|
\`\`\`
|
|
136
|
-
- **Migrations:** edit \`prisma/schema.prisma\`, then \`bun run db:migrate\` (\`prisma migrate dev\`
|
|
136
|
+
- **Migrations:** edit \`prisma/schema.prisma\`, then \`bun run db:migrate\` (\`prisma migrate dev\` - creates +
|
|
137
137
|
applies the migration and regenerates the client). \`bun run db:studio\` opens a DB browser.`;
|
|
138
138
|
}
|
|
139
139
|
if (orm === "kysely") {
|
|
140
|
-
return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape interface
|
|
140
|
+
return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape interface - keep it in
|
|
141
141
|
sync with your migrations) + \`db/index.ts\` (the typed client). You own the migrations (\`db/migrations/\`,
|
|
142
142
|
run by \`db/migrate.ts\`).
|
|
143
143
|
|
|
@@ -176,25 +176,25 @@ typed client).
|
|
|
176
176
|
- **Migrations:** edit \`db/schema.ts\`, then \`bun run db:generate\` (writes SQL to \`db/migrations\`) +
|
|
177
177
|
\`bun run db:migrate\` (applies it). \`bun run db:studio\` opens a DB browser.`;
|
|
178
178
|
}
|
|
179
|
-
/** DB rules
|
|
179
|
+
/** DB rules - only when scaffolded with `--db`. Teaches the wired data layer + the `c.db` seam. */
|
|
180
180
|
function dbRules(db) {
|
|
181
181
|
const p = DB_PRESETS[db];
|
|
182
182
|
return `## Database (${p.label})
|
|
183
183
|
|
|
184
184
|
${dbRulesBody(p.orm)} ${p.note}
|
|
185
185
|
|
|
186
|
-
- **Query is fully typed**
|
|
187
|
-
- **Never top-level-import \`db\` into a \`routes/\` page file**
|
|
186
|
+
- **Query is fully typed** - types flow from the schema/model; \`c.db\` is typed end to end.
|
|
187
|
+
- **Never top-level-import \`db\` into a \`routes/\` page file** - it's server-only; reach it via \`c.db\` on
|
|
188
188
|
the backend (or \`ctx.api\` from a loader). A top-level import ships it to the browser and breaks the build.`;
|
|
189
189
|
}
|
|
190
|
-
/** Auth rules
|
|
190
|
+
/** Auth rules - only when scaffolded with `--auth`. Teaches the better-auth mount + the typed guards. */
|
|
191
191
|
function authRules(_auth) {
|
|
192
192
|
return `## Authentication (better-auth)
|
|
193
193
|
|
|
194
194
|
better-auth is configured in \`auth.ts\` (email/password + sessions, backed by your scaffolded database via
|
|
195
195
|
its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifrajs/better-auth\`.
|
|
196
196
|
|
|
197
|
-
- **Mount it once** on the backend
|
|
197
|
+
- **Mount it once** on the backend - it serves every auth endpoint under \`/api/auth/*\`:
|
|
198
198
|
\`\`\`ts
|
|
199
199
|
import { server } from "@nifrajs/core/server"
|
|
200
200
|
import { betterAuth } from "@nifrajs/better-auth"
|
|
@@ -204,7 +204,7 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
|
|
|
204
204
|
- **Generate the auth tables** once, and after changing the config: \`bunx @better-auth/cli@latest generate\`
|
|
205
205
|
writes them into your schema (\`db/schema.ts\` for Drizzle, \`prisma/schema.prisma\` for Prisma); then
|
|
206
206
|
\`bun run db:migrate\`.
|
|
207
|
-
- **Read / require a session** with the typed guards (pass the raw \`Request\`
|
|
207
|
+
- **Read / require a session** with the typed guards (pass the raw \`Request\` - \`c.req\` in a handler, or a
|
|
208
208
|
loader's \`request\`):
|
|
209
209
|
\`\`\`ts
|
|
210
210
|
import { getSession, requireSession } from "@nifrajs/better-auth"
|
|
@@ -214,74 +214,74 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
|
|
|
214
214
|
return { id: user.id }
|
|
215
215
|
})
|
|
216
216
|
\`\`\`
|
|
217
|
-
- \`auth\` is **server-only**
|
|
217
|
+
- \`auth\` is **server-only** - never import it into a \`routes/\` page, and keep \`BETTER_AUTH_SECRET\` in \`.env\`.`;
|
|
218
218
|
}
|
|
219
219
|
const SECURITY_RULES = `## Production defaults (don't ship the happy path only)
|
|
220
220
|
|
|
221
|
-
- **Dependencies: install current, never pin from memory.** Your training data lags npm by months
|
|
221
|
+
- **Dependencies: install current, never pin from memory.** Your training data lags npm by months - do
|
|
222
222
|
not hand-write a version range or reach for a remembered (often deprecated or vulnerable) API. Add a
|
|
223
|
-
dependency with \`bun add <pkg>\` so it resolves the **latest stable**, then use *that* version's API
|
|
223
|
+
dependency with \`bun add <pkg>\` so it resolves the **latest stable**, then use *that* version's API -
|
|
224
224
|
if unsure of a signature, read \`node_modules/<pkg>\`'s types or run \`bun pm view <pkg> version\`, don't
|
|
225
225
|
guess. Never **downgrade** versions already in \`package.json\` (especially the pinned \`@nifrajs/*\` ones).
|
|
226
226
|
- Validate at every trust boundary (above). Reject unknown fields and out-of-range values.
|
|
227
227
|
- Constant-time comparison for secrets/tokens/HMACs (\`timingSafeEqual\` or WebCrypto), never \`===\`.
|
|
228
|
-
- Verify ownership/permission on any resource a handler reads or mutates
|
|
228
|
+
- Verify ownership/permission on any resource a handler reads or mutates - auth'd ≠ authorized.
|
|
229
229
|
- Idempotency on retryable side effects (webhooks, payments): short-circuit on a repeated key.
|
|
230
230
|
- Money in integer minor units; timestamps as UTC; parse client dates at the boundary.
|
|
231
231
|
- No secrets/PII in logs or error responses.
|
|
232
232
|
`;
|
|
233
233
|
const COMMANDS = {
|
|
234
|
-
api: `- \`bun install\`
|
|
235
|
-
- \`bun run dev\`
|
|
236
|
-
- \`bun test\`
|
|
237
|
-
- \`nifra check\`
|
|
238
|
-
site: `- \`bun install\`
|
|
239
|
-
- \`nifra dev\`
|
|
240
|
-
- \`nifra build\`
|
|
241
|
-
- \`nifra start\`
|
|
242
|
-
- \`nifra check\`
|
|
234
|
+
api: `- \`bun install\` - install dependencies
|
|
235
|
+
- \`bun run dev\` - run the API (watch mode)
|
|
236
|
+
- \`bun test\` - drive \`app.fetch\` directly; no server required
|
|
237
|
+
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)`,
|
|
238
|
+
site: `- \`bun install\` - install dependencies
|
|
239
|
+
- \`nifra dev\` - true-HMR dev server
|
|
240
|
+
- \`nifra build\` - complete Bun deploy (server + content-hashed client assets)
|
|
241
|
+
- \`nifra start\` - run the generated Bun server - pairs with \`nifra build\`
|
|
242
|
+
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)
|
|
243
243
|
|
|
244
244
|
For a local production run use \`nifra build && nifra start\` (or \`bun run build:bun && bun run start\`). NOTE:
|
|
245
245
|
the bare \`bun run build\` script targets **Cloudflare Pages for DEPLOY** (emits \`dist/\`, a different layout)
|
|
246
|
-
|
|
247
|
-
isr: `- \`bun install\`
|
|
248
|
-
- \`nifra dev\`
|
|
249
|
-
- \`nifra build\`
|
|
250
|
-
- \`nifra start\`
|
|
251
|
-
- \`nifra check\`
|
|
246
|
+
- it does NOT pair with \`nifra start\`/\`bun run start\` (which serve \`dist-bun/\`). Don't mix the two.`,
|
|
247
|
+
isr: `- \`bun install\` - install dependencies
|
|
248
|
+
- \`nifra dev\` - true-HMR dev server
|
|
249
|
+
- \`nifra build\` - complete Bun deploy (server + content-hashed client assets)
|
|
250
|
+
- \`nifra start\` - run the generated Bun server (SSR + ISR cache) - pairs with \`nifra build\`
|
|
251
|
+
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)
|
|
252
252
|
|
|
253
253
|
For a local production run use \`nifra build && nifra start\`. NOTE: the bare \`bun run build\` script targets
|
|
254
|
-
**Cloudflare Pages for DEPLOY** (\`dist/\`) and does NOT pair with \`nifra start\`
|
|
255
|
-
fullstack: `- \`bun install\`
|
|
256
|
-
- \`bun run dev\`
|
|
257
|
-
- \`bun test\`
|
|
258
|
-
- \`bun run typecheck\`
|
|
254
|
+
**Cloudflare Pages for DEPLOY** (\`dist/\`) and does NOT pair with \`nifra start\` - don't mix the two.`,
|
|
255
|
+
fullstack: `- \`bun install\` - install dependencies
|
|
256
|
+
- \`bun run dev\` - run the API (watch mode); \`src/index.ts\` also starts the job worker (\`queue.start()\`)
|
|
257
|
+
- \`bun test\` - drives \`app.fetch\` directly (pagination, jobs via \`queue.drain()\`, cache, storage); no server required
|
|
258
|
+
- \`bun run typecheck\` - run before you call work done
|
|
259
259
|
|
|
260
260
|
Wired: cursor pagination (\`t.pageQuery\`/\`t.paginated\`/\`paginate\`), background jobs (\`@nifrajs/jobs\`),
|
|
261
261
|
a TTL cache (\`@nifrajs/cache\`), blob storage (\`@nifrajs/storage\`). Swap the in-memory \`notes\` store for
|
|
262
262
|
your DB, and the memory cache/job/storage adapters for shared ones (Redis / CF KV / R2) when multi-process.`,
|
|
263
263
|
};
|
|
264
|
-
/** The agent's stop condition
|
|
265
|
-
const DONE = `## Definition of done
|
|
264
|
+
/** The agent's stop condition - what makes the anti-drift guarantees actually fire. */
|
|
265
|
+
const DONE = `## Definition of done - run \`nifra check\`
|
|
266
266
|
|
|
267
|
-
Before considering any change complete, run **\`nifra check\`**. It (1) typechecks
|
|
267
|
+
Before considering any change complete, run **\`nifra check\`**. It (1) typechecks - the frontend↔backend
|
|
268
268
|
contract is compiler-enforced, so the typed client deriving \`res.data\` from your routes turns any
|
|
269
269
|
mismatch into an error; (2) flags any hand-rolled \`fetch()\` to this app's own API (which bypasses that
|
|
270
270
|
check); and (3) flags a server-only import (a DB driver, \`node:\`/\`bun:\`, \`./db\`) at the top level of a
|
|
271
|
-
\`routes/\` page
|
|
272
|
-
diagnostics. A failing \`nifra check\` means the work isn't done
|
|
271
|
+
\`routes/\` page - that ships server code to the browser. Use \`nifra check --json\` for machine-readable
|
|
272
|
+
diagnostics. A failing \`nifra check\` means the work isn't done - fix it, don't ship around it.`;
|
|
273
273
|
/** Build the full `AGENTS.md` for a freshly scaffolded app. */
|
|
274
274
|
export function agentsMd(opts) {
|
|
275
275
|
const isWeb = opts.template === "site" || opts.template === "isr";
|
|
276
276
|
const what = isWeb
|
|
277
|
-
? `a full-stack nifra app
|
|
278
|
-
: `a nifra API
|
|
277
|
+
? `a full-stack nifra app - a typed \`server()\` backend plus file-routed, server-rendered ${FRAMEWORK_LABEL[opts.framework]} pages`
|
|
278
|
+
: `a nifra API - a typed, contract-first \`server()\` backend`;
|
|
279
279
|
// Each entry is a self-contained markdown block; join with a blank line between blocks. The web-rules
|
|
280
|
-
// block is empty for API templates
|
|
280
|
+
// block is empty for API templates - dropped before joining so there's no doubled blank line.
|
|
281
281
|
const blocks = [
|
|
282
|
-
`# AGENTS.md
|
|
282
|
+
`# AGENTS.md - ${opts.name}\n\nGuidance for AI coding agents working in this repo. This is ${what}. nifra is Bun-native, Web-standard, and framework-agnostic; the same app runs on Bun, Node, Deno, and the edge.`,
|
|
283
283
|
`## Commands\n\n${COMMANDS[opts.template]}`,
|
|
284
|
-
// Surface the MCP early
|
|
284
|
+
// Surface the MCP early - an agent that learns the typechecked tools exist (and that `nifra check`
|
|
285
285
|
// is the done-gate) up front will reach for them instead of writing nifra from stale memory.
|
|
286
286
|
agentsMcpSection(),
|
|
287
287
|
API_RULES.trim(),
|
|
@@ -290,7 +290,7 @@ export function agentsMd(opts) {
|
|
|
290
290
|
isWeb ? webRules(opts.framework).trim() : "",
|
|
291
291
|
SECURITY_RULES.trim(),
|
|
292
292
|
DONE,
|
|
293
|
-
"## Full reference\n\nThe complete, machine-readable API + docs live in **`llms-full.txt`** (served at the nifra site root). When you need an exact signature or a feature you haven't used, read that rather than guessing
|
|
293
|
+
"## Full reference\n\nThe complete, machine-readable API + docs live in **`llms-full.txt`** (served at the nifra site root). When you need an exact signature or a feature you haven't used, read that rather than guessing - it inlines every doc page and the export index.",
|
|
294
294
|
];
|
|
295
295
|
return `${blocks.filter(Boolean).join("\n\n")}\n`;
|
|
296
296
|
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `--auth better-auth`
|
|
2
|
+
* `--auth better-auth` - wire authentication into a scaffolded app, the same "bundle the right tool,
|
|
3
3
|
* correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
|
|
4
4
|
* into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
|
|
5
5
|
* logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
|
|
8
8
|
*
|
|
9
9
|
* `--auth` therefore requires `--db` (the CLI enforces it). better-auth's clean adapters cover Drizzle
|
|
10
|
-
* and Prisma; Kysely is rejected (it has no drop-in adapter
|
|
10
|
+
* and Prisma; Kysely is rejected (it has no drop-in adapter - wire better-auth's own dialect manually).
|
|
11
11
|
* One preset today: `better-auth`.
|
|
12
12
|
*/
|
|
13
13
|
import { type DbChoice } from "./db.js";
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `--auth better-auth`
|
|
2
|
+
* `--auth better-auth` - wire authentication into a scaffolded app, the same "bundle the right tool,
|
|
3
3
|
* correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
|
|
4
4
|
* into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
|
|
5
5
|
* logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
|
|
8
8
|
*
|
|
9
9
|
* `--auth` therefore requires `--db` (the CLI enforces it). better-auth's clean adapters cover Drizzle
|
|
10
|
-
* and Prisma; Kysely is rejected (it has no drop-in adapter
|
|
10
|
+
* and Prisma; Kysely is rejected (it has no drop-in adapter - wire better-auth's own dialect manually).
|
|
11
11
|
* One preset today: `better-auth`.
|
|
12
12
|
*/
|
|
13
13
|
import { appendFile, writeFile } from "node:fs/promises";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { DB_PRESETS } from "./db.js";
|
|
16
16
|
export const AUTH_CHOICES = ["better-auth"];
|
|
17
|
-
/** ORMs better-auth wires via a drop-in adapter. Kysely is excluded
|
|
17
|
+
/** ORMs better-auth wires via a drop-in adapter. Kysely is excluded - see {@link assertAuthableDb}. */
|
|
18
18
|
const AUTHABLE_ORMS = new Set(["drizzle", "prisma"]);
|
|
19
19
|
/**
|
|
20
20
|
* Guard `--auth` against a DB whose ORM has no clean better-auth adapter. Throws an actionable error
|
|
@@ -31,9 +31,9 @@ export function assertAuthableDb(db) {
|
|
|
31
31
|
export const AUTH_PRESETS = {
|
|
32
32
|
"better-auth": {
|
|
33
33
|
label: "better-auth",
|
|
34
|
-
note: "email/password + sessions, mounted at /api/auth/*
|
|
34
|
+
note: "email/password + sessions, mounted at /api/auth/* - add OAuth providers in auth.ts.",
|
|
35
35
|
// @nifrajs/better-auth mounts it; better-auth is the implementation (peer-installed alongside).
|
|
36
|
-
deps: { "@nifrajs/better-auth": "^2.
|
|
36
|
+
deps: { "@nifrajs/better-auth": "^2.3.0", "better-auth": "^1.2.0" },
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
/** The better-auth `database` adapter expression + its import line, for the scaffolded DB's ORM. */
|
|
@@ -54,7 +54,7 @@ function adapterFor(db) {
|
|
|
54
54
|
database: `drizzleAdapter(db, { provider: "${provider}" })`,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
/** The generated `auth.ts`
|
|
57
|
+
/** The generated `auth.ts` - a better-auth instance backed by the scaffolded DB client. */
|
|
58
58
|
function authModule(db) {
|
|
59
59
|
const { importLine, database } = adapterFor(db);
|
|
60
60
|
return `import { betterAuth as createBetterAuth } from "better-auth"
|
|
@@ -67,7 +67,7 @@ import { db } from "./db"
|
|
|
67
67
|
export const auth = createBetterAuth({
|
|
68
68
|
database: ${database},
|
|
69
69
|
emailAndPassword: { enabled: true },
|
|
70
|
-
// Set in .env
|
|
70
|
+
// Set in .env - generate a strong value: openssl rand -base64 32
|
|
71
71
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
72
72
|
baseURL: process.env.BETTER_AUTH_URL,
|
|
73
73
|
})
|
|
@@ -76,7 +76,7 @@ export type Auth = typeof auth
|
|
|
76
76
|
`;
|
|
77
77
|
}
|
|
78
78
|
const ENV = `
|
|
79
|
-
# better-auth
|
|
79
|
+
# better-auth - REQUIRED. Generate a 32+ char secret: openssl rand -base64 32
|
|
80
80
|
BETTER_AUTH_SECRET="change-me-before-production"
|
|
81
81
|
BETTER_AUTH_URL="http://localhost:3000"
|
|
82
82
|
`;
|