create-nifra 3.5.0 → 4.0.2
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 +3 -3
- package/dist/agent-files.d.ts +41 -26
- package/dist/agent-files.d.ts.map +1 -1
- package/dist/agent-files.js +80 -47
- package/dist/agent-files.js.map +1 -1
- package/dist/agents.d.ts +3 -3
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +65 -69
- package/dist/agents.js.map +1 -1
- package/dist/auth.d.ts +5 -4
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +27 -14
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts +23 -25
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +141 -130
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +49 -48
- package/dist/db.js.map +1 -1
- package/dist/scaffold/frameworks.d.ts +13 -38
- package/dist/scaffold/frameworks.d.ts.map +1 -1
- package/dist/scaffold/frameworks.js +9 -32
- package/dist/scaffold/frameworks.js.map +1 -1
- package/dist/scaffold/route-types.d.ts +9 -0
- package/dist/scaffold/route-types.d.ts.map +1 -0
- package/dist/scaffold/route-types.js +61 -0
- package/dist/scaffold/route-types.js.map +1 -0
- package/dist/scaffold/site-files.d.ts +14 -8
- package/dist/scaffold/site-files.d.ts.map +1 -1
- package/dist/scaffold/site-files.js +29 -60
- package/dist/scaffold/site-files.js.map +1 -1
- package/dist/scaffold/site.d.ts +8 -5
- package/dist/scaffold/site.d.ts.map +1 -1
- package/dist/scaffold/site.js +47 -34
- package/dist/scaffold/site.js.map +1 -1
- package/dist/scaffold/targets.d.ts +39 -0
- package/dist/scaffold/targets.d.ts.map +1 -0
- package/dist/scaffold/targets.js +116 -0
- package/dist/scaffold/targets.js.map +1 -0
- package/package.json +4 -1
- package/src/agent-files.ts +89 -47
- package/template/README.md +12 -6
- package/template/{src → backend}/app.test.ts +2 -2
- package/template/{src → backend}/app.ts +8 -3
- package/template/{src → backend}/index.ts +2 -2
- package/template/{src → backend}/routes.ts +1 -1
- package/template/nifra.assurance.ts +16 -14
- package/template/package.json +7 -7
- package/template/tsconfig.json +1 -1
- package/template-batteries/README.md +10 -6
- package/template-batteries/{src → backend}/app.test.ts +23 -23
- package/template-batteries/{src → backend}/app.ts +8 -3
- package/template-batteries/{src → backend}/index.ts +2 -2
- package/template-batteries/{src → backend}/notes.ts +1 -1
- package/template-batteries/nifra.assurance.ts +16 -14
- package/template-batteries/package.json +10 -9
- package/template-batteries/tsconfig.json +1 -1
- package/template-isr/README.md +3 -0
- package/template-isr/{backend.ts → backend/app.ts} +2 -0
- package/template-isr/{server.ts → backend/dev-server.ts} +4 -4
- package/template-isr/{worker.ts → backend/worker.ts} +4 -2
- package/template-isr/build.ts +3 -1
- package/template-isr/gitignore +7 -1
- package/template-isr/nifra.assurance.ts +15 -13
- package/template-isr/package.json +10 -9
- package/template-isr/routes/index.backend.ts +14 -0
- package/template-isr/routes/index.tsx +5 -17
- package/template-isr/tsconfig.json +5 -4
- package/template-isr/wrangler.toml +6 -1
- package/template-site/README.md +24 -23
- package/template-site/{backend.ts → backend/app.ts} +6 -4
- package/template-site/gitignore +5 -10
- package/template-site/nifra.assurance.ts +15 -13
- package/template-site/nifra.config.ts +4 -4
- package/template-site/routes/index.backend.ts +18 -0
- package/template-site/routes/index.tsx +2 -15
- package/template-site-preact/README.md +21 -20
- package/template-site-preact/nifra.config.ts +4 -4
- package/template-site-preact/routes/_layout.tsx +3 -1
- package/template-site-preact/routes/index.tsx +2 -15
- package/template-site-solid/README.md +21 -19
- package/template-site-solid/nifra.config.ts +5 -4
- package/template-site-solid/routes/index.tsx +2 -15
- package/template-site-svelte/README.md +21 -19
- package/template-site-svelte/nifra.config.ts +3 -2
- package/template-site-svelte/routes/index.svelte +3 -16
- package/template-site-vue/README.md +21 -20
- package/template-site-vue/nifra.config.ts +4 -3
- package/template-site-vue/routes/_layout.vue +21 -25
- package/template-site-vue/routes/index.vue +12 -18
- package/dist/scaffold/site-build.d.ts +0 -60
- package/dist/scaffold/site-build.d.ts.map +0 -1
- package/dist/scaffold/site-build.js +0 -198
- package/dist/scaffold/site-build.js.map +0 -1
- package/template-site/.dockerignore +0 -14
- package/template-site/Dockerfile +0 -25
- package/template-site/_worker.ts +0 -17
- package/template-site/deno.json +0 -7
- package/template-site/server-bun.ts +0 -35
- package/template-site/server-deno.ts +0 -38
- package/template-site/server-node.ts +0 -40
- package/template-site/server-vercel.ts +0 -17
- package/template-site/wrangler.toml +0 -4
- /package/template-isr/{page.ts → backend/page.ts} +0 -0
- /package/template-site/{counter.ts → backend/counter.ts} +0 -0
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`** - a typed nifra server (`
|
|
14
|
+
- **`api`** - a typed nifra server (`backend/app.ts` + `backend/index.ts`) with an example test and
|
|
15
15
|
`dev`/`start`/`test`/`typecheck` scripts.
|
|
16
16
|
- **`site`** - the full-stack template: a nifra + React SSR site (file-routed frontend + typed
|
|
17
|
-
`backend.ts`)
|
|
18
|
-
frontend with `--framework react|preact|vue|solid|svelte`.
|
|
17
|
+
`backend/app.ts`) for one deploy target, `--target bun|node|deno|cloudflare|vercel` (default `bun`).
|
|
18
|
+
Pick the frontend with `--framework react|preact|vue|solid|svelte`.
|
|
19
19
|
- **`isr`** - a nifra + React app with **Incremental Static Regeneration** on Cloudflare Workers + KV
|
|
20
20
|
(pages cached + served stale-while-revalidate; on-demand purge endpoint).
|
|
21
21
|
- **`batteries`** - the `api` starter plus the batteries a real API needs: background jobs
|
package/dist/agent-files.d.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The canonical generators for the agent
|
|
3
|
-
*
|
|
2
|
+
* The canonical generators for the agent files an app ships. `AGENTS.md` is the single source of agent
|
|
3
|
+
* guidance; every other agent's file is a thin pointer to it, so no two copies can drift:
|
|
4
4
|
*
|
|
5
|
-
* .
|
|
6
|
-
* .
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* AGENTS.md - the guidance (read natively by Codex, Cursor, Copilot and others)
|
|
6
|
+
* CLAUDE.md - Claude Code: `@AGENTS.md` import
|
|
7
|
+
* GEMINI.md - Gemini CLI: `@./AGENTS.md` import
|
|
8
|
+
* .cursor/rules/nifra.mdc - Cursor: an always-applied rule pointing at AGENTS.md
|
|
9
|
+
* .github/copilot-instructions.md - GitHub Copilot: points at AGENTS.md
|
|
10
|
+
* .mcp.json, .cursor/mcp.json - the nifra MCP server, registered for Claude Code and Cursor
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents` (retrofit an existing app)
|
|
13
|
+
* import these, so the files can never drift apart. This module is dependency-free (pure string + object
|
|
14
|
+
* generation) on purpose - `create-nifra` ships with no runtime deps, and `@nifrajs/cli` imports it as a
|
|
15
|
+
* workspace dependency.
|
|
14
16
|
*
|
|
15
17
|
* Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
|
|
16
|
-
* `@nifrajs/cli` package
|
|
17
|
-
*
|
|
18
|
-
* `@nifrajs/cli
|
|
19
|
-
*
|
|
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 - robust across every
|
|
22
|
-
* template and for any existing app.
|
|
18
|
+
* `@nifrajs/cli` package, and the bare npm package literally named `nifra` (this monorepo's
|
|
19
|
+
* `@nifrajs/web` shim) exposes NO `nifra` bin, so `bunx nifra mcp` would fetch the wrong package in an
|
|
20
|
+
* app that does not carry `@nifrajs/cli`. Naming the bin-owning package resolves the locally-installed
|
|
21
|
+
* bin when present and otherwise fetches the package that actually provides the `nifra` command.
|
|
23
22
|
*/
|
|
24
23
|
/** The MCP launch command, shared by `.mcp.json` and `.cursor/mcp.json`. See the module header for why
|
|
25
24
|
* the package is named explicitly rather than relying on the bare `nifra` bin. */
|
|
@@ -43,7 +42,8 @@ export declare const MCP_SERVER_COMMAND: "bunx";
|
|
|
43
42
|
* keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
|
|
44
43
|
* old code). Pinning the exact version makes the version part of the cache key, so each release fetches
|
|
45
44
|
* 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`
|
|
45
|
+
* at its scaffold-time version until `nifra init-agents --sync-mcp` re-pins it to the nifra the project
|
|
46
|
+
* installs - 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"];
|
|
@@ -61,20 +61,27 @@ 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
|
-
/**
|
|
65
|
-
* `CLAUDE.md` - Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
|
|
66
|
-
* a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
|
|
67
|
-
* to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
|
|
68
|
-
* own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
|
|
69
|
-
* two files. Keep this short; the depth is in `AGENTS.md`.
|
|
70
|
-
*/
|
|
64
|
+
/** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
|
|
71
65
|
export declare function claudeMd(): string;
|
|
66
|
+
/** `GEMINI.md`: Gemini CLI expands `@./AGENTS.md` imports in its context files. */
|
|
67
|
+
export declare function geminiMd(): string;
|
|
68
|
+
/** `.cursor/rules/nifra.mdc`: an always-applied Cursor rule; `@AGENTS.md` attaches the file. */
|
|
69
|
+
export declare function cursorRule(): string;
|
|
70
|
+
/** `.github/copilot-instructions.md`: Copilot has no import syntax, so this names the file. */
|
|
71
|
+
export declare function copilotInstructions(): string;
|
|
72
72
|
/**
|
|
73
73
|
* The "## MCP server" section appended to a scaffolded (or retrofitted) `AGENTS.md`, so non-Claude
|
|
74
74
|
* agents (Cursor, and anything that reads `AGENTS.md`) also learn the MCP exists and what to prefer.
|
|
75
75
|
* Mirrors the CLAUDE.md preamble's guidance without the Claude-specific `@import`.
|
|
76
76
|
*/
|
|
77
77
|
export declare function agentsMcpSection(): string;
|
|
78
|
+
/**
|
|
79
|
+
* The "## Project structure" section of a web app's `AGENTS.md`: the zones the build enforces. Shared so
|
|
80
|
+
* a scaffolded app and one `nifra init-agents` retrofits teach the same rules.
|
|
81
|
+
*/
|
|
82
|
+
export declare function agentsStructureSection(): string;
|
|
83
|
+
/** The heading `nifra init-agents` looks for before appending {@link agentsStructureSection}. */
|
|
84
|
+
export declare const STRUCTURE_HEADING = "## Project structure";
|
|
78
85
|
/** Identifies a generated agent-discovery file: where it goes (relative to the project root) and how to
|
|
79
86
|
* produce its content. `merge` is for files that augment an existing one (AGENTS.md) rather than own it. */
|
|
80
87
|
export interface AgentFileSpec {
|
|
@@ -84,9 +91,17 @@ export interface AgentFileSpec {
|
|
|
84
91
|
readonly label: string;
|
|
85
92
|
}
|
|
86
93
|
/** The standalone files this module fully owns (whole-file generators). AGENTS.md is handled separately
|
|
87
|
-
* because create-nifra builds it from `agents.ts` and the retrofit command appends
|
|
94
|
+
* because create-nifra builds it from `agents.ts` and the retrofit command appends sections to it. */
|
|
88
95
|
export declare const MCP_JSON_PATH = ".mcp.json";
|
|
89
96
|
export declare const CURSOR_MCP_JSON_PATH = ".cursor/mcp.json";
|
|
90
97
|
export declare const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
98
|
+
export declare const GEMINI_MD_PATH = "GEMINI.md";
|
|
99
|
+
export declare const CURSOR_RULE_PATH = ".cursor/rules/nifra.mdc";
|
|
100
|
+
export declare const COPILOT_INSTRUCTIONS_PATH = ".github/copilot-instructions.md";
|
|
91
101
|
export declare const AGENTS_MD_PATH = "AGENTS.md";
|
|
102
|
+
/** Every agent's pointer to `AGENTS.md`, in the order they are written and reported. */
|
|
103
|
+
export declare const AGENT_POINTERS: readonly {
|
|
104
|
+
readonly path: string;
|
|
105
|
+
readonly content: () => string;
|
|
106
|
+
}[];
|
|
92
107
|
//# sourceMappingURL=agent-files.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-files.d.ts","sourceRoot":"","sources":["../src/agent-files.ts"],"names":[],"mappings":"AAAA
|
|
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;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,EAAE,MAIrB,CAAA;AACT,eAAO,MAAM,eAAe,4CAAsD,CAAA;AAElF,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,2FAA2F;AAC3F,eAAO,MAAM,UAAU,EAAE,SAIxB,CAAA;AAED;qFACqF;AACrF,wBAAgB,OAAO,IAAI,MAAM,CAEhC;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,IAAI,MAAM,CAczC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA6B/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
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The canonical generators for the agent
|
|
3
|
-
*
|
|
2
|
+
* The canonical generators for the agent files an app ships. `AGENTS.md` is the single source of agent
|
|
3
|
+
* guidance; every other agent's file is a thin pointer to it, so no two copies can drift:
|
|
4
4
|
*
|
|
5
|
-
* .
|
|
6
|
-
* .
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* AGENTS.md - the guidance (read natively by Codex, Cursor, Copilot and others)
|
|
6
|
+
* CLAUDE.md - Claude Code: `@AGENTS.md` import
|
|
7
|
+
* GEMINI.md - Gemini CLI: `@./AGENTS.md` import
|
|
8
|
+
* .cursor/rules/nifra.mdc - Cursor: an always-applied rule pointing at AGENTS.md
|
|
9
|
+
* .github/copilot-instructions.md - GitHub Copilot: points at AGENTS.md
|
|
10
|
+
* .mcp.json, .cursor/mcp.json - the nifra MCP server, registered for Claude Code and Cursor
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents` (retrofit an existing app)
|
|
13
|
+
* import these, so the files can never drift apart. This module is dependency-free (pure string + object
|
|
14
|
+
* generation) on purpose - `create-nifra` ships with no runtime deps, and `@nifrajs/cli` imports it as a
|
|
15
|
+
* workspace dependency.
|
|
14
16
|
*
|
|
15
17
|
* Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
|
|
16
|
-
* `@nifrajs/cli` package
|
|
17
|
-
*
|
|
18
|
-
* `@nifrajs/cli
|
|
19
|
-
*
|
|
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 - robust across every
|
|
22
|
-
* template and for any existing app.
|
|
18
|
+
* `@nifrajs/cli` package, and the bare npm package literally named `nifra` (this monorepo's
|
|
19
|
+
* `@nifrajs/web` shim) exposes NO `nifra` bin, so `bunx nifra mcp` would fetch the wrong package in an
|
|
20
|
+
* app that does not carry `@nifrajs/cli`. Naming the bin-owning package resolves the locally-installed
|
|
21
|
+
* bin when present and otherwise fetches the package that actually provides the `nifra` command.
|
|
23
22
|
*/
|
|
24
23
|
import { readFileSync } from "node:fs";
|
|
25
24
|
/** The MCP launch command, shared by `.mcp.json` and `.cursor/mcp.json`. See the module header for why
|
|
@@ -44,7 +43,8 @@ export const MCP_SERVER_COMMAND = "bunx";
|
|
|
44
43
|
* keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
|
|
45
44
|
* old code). Pinning the exact version makes the version part of the cache key, so each release fetches
|
|
46
45
|
* 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`
|
|
46
|
+
* at its scaffold-time version until `nifra init-agents --sync-mcp` re-pins it to the nifra the project
|
|
47
|
+
* installs - 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"];
|
|
@@ -59,35 +59,22 @@ export const MCP_CONFIG = {
|
|
|
59
59
|
export function mcpJson() {
|
|
60
60
|
return `${JSON.stringify(MCP_CONFIG, null, 2)}\n`;
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
|
|
65
|
-
* to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
|
|
66
|
-
* own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
|
|
67
|
-
* two files. Keep this short; the depth is in `AGENTS.md`.
|
|
68
|
-
*/
|
|
62
|
+
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.";
|
|
63
|
+
/** `CLAUDE.md`: Claude Code expands the `@AGENTS.md` import in place. */
|
|
69
64
|
export function claudeMd() {
|
|
70
|
-
return `# CLAUDE.md
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 - fix it, don't ship around it.
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
@AGENTS.md
|
|
90
|
-
`;
|
|
65
|
+
return `# CLAUDE.md\n\n${POINTER_INTRO}\n\n@AGENTS.md\n`;
|
|
66
|
+
}
|
|
67
|
+
/** `GEMINI.md`: Gemini CLI expands `@./AGENTS.md` imports in its context files. */
|
|
68
|
+
export function geminiMd() {
|
|
69
|
+
return `# GEMINI.md\n\n${POINTER_INTRO}\n\n@./AGENTS.md\n`;
|
|
70
|
+
}
|
|
71
|
+
/** `.cursor/rules/nifra.mdc`: an always-applied Cursor rule; `@AGENTS.md` attaches the file. */
|
|
72
|
+
export function cursorRule() {
|
|
73
|
+
return `---\ndescription: nifra project guidance\nalwaysApply: true\n---\n\n${POINTER_INTRO} Read it before changing code:\n\n@AGENTS.md\n`;
|
|
74
|
+
}
|
|
75
|
+
/** `.github/copilot-instructions.md`: Copilot has no import syntax, so this names the file. */
|
|
76
|
+
export function copilotInstructions() {
|
|
77
|
+
return `${POINTER_INTRO} Read AGENTS.md at the repository root before changing code, and follow it.\n`;
|
|
91
78
|
}
|
|
92
79
|
/**
|
|
93
80
|
* The "## MCP server" section appended to a scaffolded (or retrofitted) `AGENTS.md`, so non-Claude
|
|
@@ -109,10 +96,56 @@ project + the installed version, so they beat training-data recall:
|
|
|
109
96
|
- \`nifra_check\` - the done-gate (typecheck + drift lints). Run it before calling work complete; a
|
|
110
97
|
failing check means it isn't done. (\`nifra check --json\` in a terminal does the same.)`;
|
|
111
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The "## Project structure" section of a web app's `AGENTS.md`: the zones the build enforces. Shared so
|
|
101
|
+
* a scaffolded app and one `nifra init-agents` retrofits teach the same rules.
|
|
102
|
+
*/
|
|
103
|
+
export function agentsStructureSection() {
|
|
104
|
+
return `${STRUCTURE_HEADING}
|
|
105
|
+
|
|
106
|
+
Every file a build loads belongs to one side, and the build refuses an import that crosses the wrong
|
|
107
|
+
way - it fails naming the import chain rather than shipping server code to the browser.
|
|
108
|
+
|
|
109
|
+
| Where | Holds | Reaches the browser |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| \`routes/x.tsx\` (\`.svelte\`, \`.vue\`, \`.mdx\`) | a page: the component, \`meta\` | yes |
|
|
112
|
+
| \`routes/x.backend.ts\` | that page's \`loader\`, \`action\`, \`loaderOutput\`, \`actionOutput\`, \`middleware\` | never |
|
|
113
|
+
| \`frontend/\` | components, hooks, browser-only code | yes |
|
|
114
|
+
| \`backend/\` | \`app.ts\` (the API), \`framework.ts\`, the database, auth, secrets | never |
|
|
115
|
+
| \`shared/\` | schemas, types and pure helpers both sides import | yes |
|
|
116
|
+
| \`public/\` | static files served as they are | yes |
|
|
117
|
+
|
|
118
|
+
A file outside those folders joins a side with a \`.frontend.ts\`, \`.backend.ts\` or \`.shared.ts\` suffix.
|
|
119
|
+
|
|
120
|
+
- **A route is two files.** \`routes/blog/[slug].tsx\` renders; \`routes/blog/[slug].backend.ts\` loads.
|
|
121
|
+
A server-only export (\`loader\`, \`action\`, ...) in the \`.tsx\` file is a build error.
|
|
122
|
+
- **Frontend code never imports backend code** - not \`backend/\`, not a \`.backend.ts\` file, not a
|
|
123
|
+
database driver or \`node:\` built-in. Data reaches a page only as its loader's return value.
|
|
124
|
+
- **Every loader and action declares what it sends**: \`export const loaderOutput = t.object({ ... })\`
|
|
125
|
+
(and \`actionOutput\`). Only what the schema declares reaches the browser, and a loader without one
|
|
126
|
+
is refused.
|
|
127
|
+
- **Type a route with its generated \`./+types/<name>\`**: \`import type { Route } from "./+types/[slug]"\`
|
|
128
|
+
gives \`Route.LoaderArgs\` (with the typed \`api\`) in the backend half and \`Route.ComponentProps\` in
|
|
129
|
+
the page. \`nifra dev\`, \`nifra build\`, \`nifra check\` and \`nifra types\` write them.
|
|
130
|
+
- **Secrets stay in \`backend/\`.** A value only the server may see is read there (\`process.env\`), never
|
|
131
|
+
in a frontend file; a build that finds what looks like a credential in browser output fails.`;
|
|
132
|
+
}
|
|
133
|
+
/** The heading `nifra init-agents` looks for before appending {@link agentsStructureSection}. */
|
|
134
|
+
export const STRUCTURE_HEADING = "## Project structure";
|
|
112
135
|
/** The standalone files this module fully owns (whole-file generators). AGENTS.md is handled separately
|
|
113
|
-
* because create-nifra builds it from `agents.ts` and the retrofit command appends
|
|
136
|
+
* because create-nifra builds it from `agents.ts` and the retrofit command appends sections to it. */
|
|
114
137
|
export const MCP_JSON_PATH = ".mcp.json";
|
|
115
138
|
export const CURSOR_MCP_JSON_PATH = ".cursor/mcp.json";
|
|
116
139
|
export const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
140
|
+
export const GEMINI_MD_PATH = "GEMINI.md";
|
|
141
|
+
export const CURSOR_RULE_PATH = ".cursor/rules/nifra.mdc";
|
|
142
|
+
export const COPILOT_INSTRUCTIONS_PATH = ".github/copilot-instructions.md";
|
|
117
143
|
export const AGENTS_MD_PATH = "AGENTS.md";
|
|
144
|
+
/** Every agent's pointer to `AGENTS.md`, in the order they are written and reported. */
|
|
145
|
+
export const AGENT_POINTERS = [
|
|
146
|
+
{ path: CLAUDE_MD_PATH, content: claudeMd },
|
|
147
|
+
{ path: GEMINI_MD_PATH, content: geminiMd },
|
|
148
|
+
{ path: CURSOR_RULE_PATH, content: cursorRule },
|
|
149
|
+
{ path: COPILOT_INSTRUCTIONS_PATH, content: copilotInstructions },
|
|
150
|
+
];
|
|
118
151
|
//# sourceMappingURL=agent-files.js.map
|
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
|
|
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;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAG7E,CAAC,OAAO,CAAA;AACT,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,gBAAgB,eAAe,EAAE,EAAE,KAAK,CAAU,CAAA;AAalF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,UAAU,GAAc;IACnC,UAAU,EAAE;QACV,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE;KACnE;CACF,CAAA;AAED;qFACqF;AACrF,MAAM,UAAU,OAAO;IACrB,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAA;AACnD,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;IAC9B,OAAO;;+DAEsD,kBAAkB,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;2FAUnB,CAAA;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,GAAG,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;+FA2BkE,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/agents.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
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
|
-
* the first prompt: validate at the boundary, the never-throwing client, `
|
|
5
|
-
* entry, and (for full-stack apps) the
|
|
6
|
-
*
|
|
4
|
+
* the first prompt: validate at the boundary, the never-throwing client, `backend.fetch` as the universal
|
|
5
|
+
* entry, and (for full-stack apps) the frontend/backend zones the build enforces. Tailored per template so
|
|
6
|
+
* the paths + commands are real.
|
|
7
7
|
*/
|
|
8
8
|
import type { AuthChoice } from "./auth.js";
|
|
9
9
|
import type { Framework, TemplateName } from "./cli.js";
|
package/dist/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,EAAc,KAAK,QAAQ,EAAiB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,EAAc,KAAK,QAAQ,EAAiB,MAAM,SAAS,CAAA;AA8QlE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAA;IACtB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,+DAA+D;AAC/D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAuBtD"}
|
package/dist/agents.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
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
|
-
* the first prompt: validate at the boundary, the never-throwing client, `
|
|
5
|
-
* entry, and (for full-stack apps) the
|
|
6
|
-
*
|
|
4
|
+
* the first prompt: validate at the boundary, the never-throwing client, `backend.fetch` as the universal
|
|
5
|
+
* entry, and (for full-stack apps) the frontend/backend zones the build enforces. Tailored per template so
|
|
6
|
+
* the paths + commands are real.
|
|
7
7
|
*/
|
|
8
|
-
import { agentsMcpSection } from "./agent-files.js";
|
|
8
|
+
import { agentsMcpSection, agentsStructureSection } from "./agent-files.js";
|
|
9
9
|
import { DB_PRESETS } from "./db.js";
|
|
10
10
|
const FRAMEWORK_LABEL = {
|
|
11
11
|
react: "React",
|
|
@@ -21,7 +21,7 @@ const API_RULES = `## The backend: \`server()\`
|
|
|
21
21
|
import { server } from "@nifrajs/core/server"
|
|
22
22
|
import { t } from "@nifrajs/schema"
|
|
23
23
|
|
|
24
|
-
export const
|
|
24
|
+
export const backend = server()
|
|
25
25
|
.get("/users/:id", { response: t.object({ id: t.string() }) }, (c) => ({ id: c.params.id }))
|
|
26
26
|
.post(
|
|
27
27
|
"/users",
|
|
@@ -33,19 +33,21 @@ export const app = server()
|
|
|
33
33
|
},
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
export type
|
|
36
|
+
export type Backend = typeof backend
|
|
37
37
|
\`\`\`
|
|
38
38
|
|
|
39
39
|
Rules an agent must follow:
|
|
40
40
|
|
|
41
|
-
- **Validate every input at the boundary.** The route schema slots are \`{ body, query
|
|
42
|
-
\`response\`, below) - use \`t\` from \`@nifrajs/schema\` (installed) or any Standard Schema
|
|
43
|
-
Read the typed, already-validated \`c.body\` / \`c.query\`
|
|
44
|
-
|
|
45
|
-
schema exists to remove.
|
|
46
|
-
- **Path params are
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
- **Validate every input at the boundary.** The route schema slots are \`{ body, query, params, headers,
|
|
42
|
+
cookies }\` (plus \`response\`, below) - use \`t\` from \`@nifrajs/schema\` (installed) or any Standard Schema
|
|
43
|
+
(zod, valibot). Read the typed, already-validated \`c.body\` / \`c.query\` / \`c.params\` / \`c.headers\` /
|
|
44
|
+
\`c.cookies\` (invalid input was rejected with a 422 before the handler ran). **Never** hand-parse
|
|
45
|
+
\`await c.req.json()\` and poke at properties - that's the bug class the schema exists to remove.
|
|
46
|
+
- **Path params are strings until a \`params\` schema says otherwise.** \`:id\` is inferred from the path
|
|
47
|
+
literal as a \`string\` on \`c.params.id\`. Declare \`{ params: t.object({ id: t.string({ format: "uuid" }) }) }\`
|
|
48
|
+
to reject a malformed id with a 422 before the handler runs, or \`t.query({ id: t.integer() })\` to coerce a
|
|
49
|
+
numeric one, instead of a format check inside the handler. The schema covers every path param. Header
|
|
50
|
+
names in a \`headers\` schema are lower-case.
|
|
49
51
|
- **Lock the output shape with \`response\` (no drift).** Add \`{ response: t.object({...}) }\` to a route:
|
|
50
52
|
the handler's return is type-checked against it, and the typed client sees exactly that shape. One
|
|
51
53
|
contract, both sides - the frontend physically can't drift from the backend's output.
|
|
@@ -60,8 +62,9 @@ Rules an agent must follow:
|
|
|
60
62
|
- **Raw \`Response\` is for redirects / streams / files** (\`Response.redirect(url, 302)\`, a file body, an
|
|
61
63
|
SSE stream). The typed client infers \`res.data: never\` on such a route and \`nifra check\` emits a
|
|
62
64
|
non-blocking \`response-route\` warning - both are EXPECTED there, not defects.
|
|
63
|
-
- **\`
|
|
64
|
-
directly (\`await
|
|
65
|
+
- **\`backend.fetch(request)\` is the whole app** - \`(Request) => Response | Promise<Response>\`. Tests
|
|
66
|
+
drive it directly (\`await backend.fetch(new Request("http://x/users/1"))\`); no server needs to be
|
|
67
|
+
running.
|
|
65
68
|
- **Don't reach for a heavy ORM/HTTP layer.** Routing, validation, cookies, and the typed client are
|
|
66
69
|
built in. Parameterize DB queries; use \`timingSafeEqual\` (or WebCrypto) for secret comparison.
|
|
67
70
|
- **Cross-cutting concerns - use \`@nifrajs/middleware\`, don't hand-roll.** Rate limiting (\`429\` +
|
|
@@ -73,30 +76,30 @@ Rules an agent must follow:
|
|
|
73
76
|
|
|
74
77
|
\`\`\`ts
|
|
75
78
|
import { client } from "@nifrajs/client"
|
|
76
|
-
import type {
|
|
79
|
+
import type { Backend } from "./backend/app" // type-only import - server code never ships
|
|
77
80
|
|
|
78
|
-
const api = client<
|
|
81
|
+
const api = client<Backend>("https://api.example.com")
|
|
79
82
|
const res = await api.users({ id: "42" }).get()
|
|
80
83
|
if (res.ok) res.data // typed from the route's return type - zero codegen
|
|
81
84
|
else res.error // errors are returned, never thrown
|
|
82
85
|
\`\`\`
|
|
83
86
|
|
|
84
87
|
**Never hand-roll \`fetch\` + ad-hoc response types for this app's own API** - that's exactly how a screen
|
|
85
|
-
drifts from the backend. \`client<
|
|
88
|
+
drifts from the backend. \`client<Backend>\` derives both the request inputs and \`res.data\` from the
|
|
86
89
|
route types, so \`tsc\` (and \`nifra build\`) catch any mismatch the moment a route changes. When building a
|
|
87
90
|
page that calls the API, reach for the client first.
|
|
88
91
|
`;
|
|
89
|
-
/** File
|
|
92
|
+
/** File routing, loaders and the zones - only for full-stack (site/isr) templates. */
|
|
90
93
|
function webRules(framework) {
|
|
91
94
|
const label = FRAMEWORK_LABEL[framework];
|
|
92
95
|
return `## The frontend: file routing + loaders (${label})
|
|
93
96
|
|
|
94
97
|
- Routes live in \`routes/\`. \`index.tsx\` → \`/\`, \`[id].tsx\` → \`/:id\`, \`_layout.tsx\` wraps a subtree,
|
|
95
98
|
\`_404.tsx\` / \`_error.tsx\` are the fallbacks.
|
|
96
|
-
- A
|
|
97
|
-
|
|
99
|
+
- A page's \`.tsx\` file exports \`default\` (the component) and \`meta\` (head tags). Its \`.backend.ts\` file
|
|
100
|
+
exports \`loader\` (data) and \`action\` (mutations), each with its output schema.
|
|
98
101
|
- **The \`server()\` backend is IN-PROCESS, not a public HTTP surface.** Loaders/actions call it through
|
|
99
|
-
\`
|
|
102
|
+
\`api\` (the typed client - no network) during SSR; there is no \`GET /your-route\` endpoint on the page
|
|
100
103
|
server to curl. Build features through loaders/actions + the typed client, not direct HTTP calls.
|
|
101
104
|
- Swap UI frameworks by changing one import (\`@nifrajs/web-${framework}\` → another adapter); your route,
|
|
102
105
|
loader, and action code do not change.
|
|
@@ -105,20 +108,14 @@ function webRules(framework) {
|
|
|
105
108
|
and navigate without a full reload. Throwing in an action or returning in a loader is a silent bug
|
|
106
109
|
that produces a false "operation failed" error on the client.
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
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) - and the loader is *not* stripped. So a top-level
|
|
112
|
-
\`import db from "./db"\` (or anything touching \`process.env\`, secrets, \`node:\` APIs) ships server code to
|
|
113
|
-
the client and crashes hydration. Reach server resources through \`ctx.api\` / \`ctx.env\` (injected via
|
|
114
|
-
\`createWebApp\`) inside the loader instead - never as a top-level import in a \`routes/\` file.
|
|
111
|
+
${agentsStructureSection()}
|
|
115
112
|
`;
|
|
116
113
|
}
|
|
117
114
|
/** Per-ORM body of the DB rules: the query idiom, schema location, and migrate story all differ. */
|
|
118
115
|
function dbRulesBody(orm) {
|
|
119
116
|
if (orm === "prisma") {
|
|
120
|
-
return `A Prisma data layer is wired: \`prisma/schema.prisma\` (a starter \`Note\` model) +
|
|
121
|
-
singleton \`PrismaClient\`).
|
|
117
|
+
return `A Prisma data layer is wired: \`prisma/schema.prisma\` (a starter \`Note\` model) +
|
|
118
|
+
\`backend/db/index.ts\` (a singleton \`PrismaClient\`).
|
|
122
119
|
|
|
123
120
|
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
124
121
|
\`\`\`ts
|
|
@@ -137,9 +134,9 @@ singleton \`PrismaClient\`).
|
|
|
137
134
|
applies the migration and regenerates the client). \`bun run db:studio\` opens a DB browser.`;
|
|
138
135
|
}
|
|
139
136
|
if (orm === "kysely") {
|
|
140
|
-
return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape
|
|
141
|
-
sync with your migrations) + \`db/index.ts\` (the typed client). You own the
|
|
142
|
-
run by \`db/migrate.ts\`).
|
|
137
|
+
return `A Kysely typed query builder is wired in \`backend/db/\`: \`backend/db/schema.ts\` (the DB-shape
|
|
138
|
+
interface - keep it in sync with your migrations) + \`backend/db/index.ts\` (the typed client). You own the
|
|
139
|
+
migrations (\`backend/db/migrations/\`, run by \`backend/db/migrate.ts\`).
|
|
143
140
|
|
|
144
141
|
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
145
142
|
\`\`\`ts
|
|
@@ -154,11 +151,11 @@ run by \`db/migrate.ts\`).
|
|
|
154
151
|
.post("/notes", { body: t.object({ title: t.string({ minLength: 1 }) }) }, async (c) =>
|
|
155
152
|
c.db.insertInto("notes").values({ title: c.body.title }).returningAll().executeTakeFirstOrThrow())
|
|
156
153
|
\`\`\`
|
|
157
|
-
- **Migrations:** add a file to \`db/migrations/\` (copy the \`0001_create_notes.ts\` shape),
|
|
158
|
-
\`db/schema.ts\` interface to match, then \`bun run db:migrate\`.`;
|
|
154
|
+
- **Migrations:** add a file to \`backend/db/migrations/\` (copy the \`0001_create_notes.ts\` shape),
|
|
155
|
+
update the \`backend/db/schema.ts\` interface to match, then \`bun run db:migrate\`.`;
|
|
159
156
|
}
|
|
160
|
-
return `A Drizzle data layer is wired in \`db/\`: \`db/schema.ts\` (a starter \`notes\`
|
|
161
|
-
typed client).
|
|
157
|
+
return `A Drizzle data layer is wired in \`backend/db/\`: \`backend/db/schema.ts\` (a starter \`notes\`
|
|
158
|
+
table) + \`backend/db/index.ts\` (the typed client).
|
|
162
159
|
|
|
163
160
|
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
164
161
|
\`\`\`ts
|
|
@@ -173,8 +170,8 @@ typed client).
|
|
|
173
170
|
(await c.db.insert(notes).values({ title: c.body.title }).returning())[0],
|
|
174
171
|
)
|
|
175
172
|
\`\`\`
|
|
176
|
-
- **Migrations:** edit \`db/schema.ts\`, then \`bun run db:generate\` (writes SQL to
|
|
177
|
-
\`bun run db:migrate\` (applies it). \`bun run db:studio\` opens a DB browser.`;
|
|
173
|
+
- **Migrations:** edit \`backend/db/schema.ts\`, then \`bun run db:generate\` (writes SQL to
|
|
174
|
+
\`backend/db/migrations\`) + \`bun run db:migrate\` (applies it). \`bun run db:studio\` opens a DB browser.`;
|
|
178
175
|
}
|
|
179
176
|
/** DB rules - only when scaffolded with `--db`. Teaches the wired data layer + the `c.db` seam. */
|
|
180
177
|
function dbRules(db) {
|
|
@@ -184,15 +181,15 @@ function dbRules(db) {
|
|
|
184
181
|
${dbRulesBody(p.orm)} ${p.note}
|
|
185
182
|
|
|
186
183
|
- **Query is fully typed** - types flow from the schema/model; \`c.db\` is typed end to end.
|
|
187
|
-
-
|
|
188
|
-
|
|
184
|
+
- **\`backend/db/\` is backend code.** Reach it via \`c.db\` on the backend, or through \`api\` from a
|
|
185
|
+
route's \`.backend.ts\` loader. A frontend file that imports it fails the build.`;
|
|
189
186
|
}
|
|
190
187
|
/** Auth rules - only when scaffolded with `--auth`. Teaches the better-auth mount + the typed guards. */
|
|
191
188
|
function authRules(_auth) {
|
|
192
189
|
return `## Authentication (better-auth)
|
|
193
190
|
|
|
194
|
-
better-auth is configured in \`auth.ts\` (email/password + sessions, backed by your scaffolded
|
|
195
|
-
its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifrajs/better-auth\`.
|
|
191
|
+
better-auth is configured in \`backend/auth.ts\` (email/password + sessions, backed by your scaffolded
|
|
192
|
+
database via its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifrajs/better-auth\`.
|
|
196
193
|
|
|
197
194
|
- **Mount it once** on the backend - it serves every auth endpoint under \`/api/auth/*\`:
|
|
198
195
|
\`\`\`ts
|
|
@@ -201,9 +198,10 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
|
|
|
201
198
|
import { auth } from "./auth"
|
|
202
199
|
export const app = server().use(betterAuth(auth))
|
|
203
200
|
\`\`\`
|
|
204
|
-
- **Generate the auth tables** once, and after changing the config:
|
|
205
|
-
|
|
206
|
-
\`
|
|
201
|
+
- **Generate the auth tables** once, and after changing the config:
|
|
202
|
+
\`bunx @better-auth/cli@latest generate --config backend/auth.ts\` writes their schema (Prisma: into
|
|
203
|
+
\`prisma/schema.prisma\`; Drizzle: a \`schema.ts\` at the root unless \`--output\` names a file under
|
|
204
|
+
\`backend/db/\`); then \`bun run db:migrate\`.
|
|
207
205
|
- **Read / require a session** with the typed guards (pass the raw \`Request\` - \`c.req\` in a handler, or a
|
|
208
206
|
loader's \`request\`):
|
|
209
207
|
\`\`\`ts
|
|
@@ -214,7 +212,8 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
|
|
|
214
212
|
return { id: user.id }
|
|
215
213
|
})
|
|
216
214
|
\`\`\`
|
|
217
|
-
- \`auth\` is **
|
|
215
|
+
- \`backend/auth.ts\` is **backend code** - a frontend file cannot import it - and \`BETTER_AUTH_SECRET\`
|
|
216
|
+
stays in \`.env\`.`;
|
|
218
217
|
}
|
|
219
218
|
const SECURITY_RULES = `## Production defaults (don't ship the happy path only)
|
|
220
219
|
|
|
@@ -233,28 +232,25 @@ const SECURITY_RULES = `## Production defaults (don't ship the happy path only)
|
|
|
233
232
|
const COMMANDS = {
|
|
234
233
|
api: `- \`bun install\` - install dependencies
|
|
235
234
|
- \`bun run dev\` - run the API (watch mode)
|
|
236
|
-
- \`bun test\` - drive \`
|
|
235
|
+
- \`bun test\` - drive \`backend.fetch\` directly; no server required
|
|
237
236
|
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)`,
|
|
238
237
|
site: `- \`bun install\` - install dependencies
|
|
239
|
-
- \`nifra dev\` - true-HMR dev server
|
|
240
|
-
- \`nifra build\` -
|
|
241
|
-
|
|
242
|
-
- \`
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
the bare \`bun run build\` script targets **Cloudflare Pages for DEPLOY** (emits \`dist/\`, a different layout)
|
|
246
|
-
- it does NOT pair with \`nifra start\`/\`bun run start\` (which serve \`dist-bun/\`). Don't mix the two.`,
|
|
238
|
+
- \`bun run dev\` (\`nifra dev\`) - true-HMR dev server
|
|
239
|
+
- \`bun run build\` (\`nifra build\`) - the deploy directory for the app's \`target\` in \`nifra.config.ts\`; it
|
|
240
|
+
generates the server entry, so the app has no per-runtime entry or build script
|
|
241
|
+
- \`bun run start\` - run the built app locally (on targets with a local runner)
|
|
242
|
+
- \`nifra target <t>\` - switch where the app deploys: bun | node | deno | cloudflare | vercel
|
|
243
|
+
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)`,
|
|
247
244
|
isr: `- \`bun install\` - install dependencies
|
|
248
|
-
- \`
|
|
249
|
-
|
|
250
|
-
- \`
|
|
251
|
-
- \`
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
**Cloudflare Pages for DEPLOY** (\`dist/\`) and does NOT pair with \`nifra start\` - don't mix the two.`,
|
|
245
|
+
- \`bun run build\` - \`build.ts\`: the client bundle into \`public/assets\`, the worker
|
|
246
|
+
(\`backend/worker.ts\`) into \`dist-server/worker.js\`
|
|
247
|
+
- \`bun run dev\` - \`wrangler dev\` on real workerd with a local KV (run \`bun run build\` first)
|
|
248
|
+
- \`bun run dev:bun\` - the same app on a local Bun server (\`backend/dev-server.ts\`, in-memory cache)
|
|
249
|
+
- \`bun run build && bunx wrangler deploy\` - deploy (create the KV namespace first; see README)
|
|
250
|
+
- \`nifra check\` - typecheck + typed-client lint (run before you call work done)`,
|
|
255
251
|
batteries: `- \`bun install\` - install dependencies
|
|
256
|
-
- \`bun run dev\` - run the API (watch mode); \`
|
|
257
|
-
- \`bun test\` - drives \`
|
|
252
|
+
- \`bun run dev\` - run the API (watch mode); \`backend/index.ts\` also starts the job worker (\`queue.start()\`)
|
|
253
|
+
- \`bun test\` - drives \`backend.fetch\` directly (pagination, jobs via \`queue.drain()\`, cache, storage); no server required
|
|
258
254
|
- \`bun run typecheck\` - run before you call work done
|
|
259
255
|
|
|
260
256
|
Wired: cursor pagination (\`t.pageQuery\`/\`t.paginated\`/\`paginate\`), background jobs (\`@nifrajs/jobs\`),
|
|
@@ -267,8 +263,8 @@ const DONE = `## Definition of done - run \`nifra check\`
|
|
|
267
263
|
Before considering any change complete, run **\`nifra check\`**. It (1) typechecks - the frontend↔backend
|
|
268
264
|
contract is compiler-enforced, so the typed client deriving \`res.data\` from your routes turns any
|
|
269
265
|
mismatch into an error; (2) flags any hand-rolled \`fetch()\` to this app's own API (which bypasses that
|
|
270
|
-
check); and (3)
|
|
271
|
-
|
|
266
|
+
check); and (3) enforces the frontend/backend zones - a frontend file reaching backend code, or a loader
|
|
267
|
+
or action without an output schema, fails it. Use \`nifra check --json\` for machine-readable
|
|
272
268
|
diagnostics. A failing \`nifra check\` means the work isn't done - fix it, don't ship around it.`;
|
|
273
269
|
/** Build the full `AGENTS.md` for a freshly scaffolded app. */
|
|
274
270
|
export function agentsMd(opts) {
|