create-nifra 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/agents.d.ts +22 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +259 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth.d.ts +30 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +90 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +101 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +450 -0
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts +39 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +467 -0
- package/dist/db.js.map +1 -0
- package/package.json +50 -0
- package/template/README.md +24 -0
- package/template/gitignore +6 -0
- package/template/package.json +20 -0
- package/template/src/app.test.ts +8 -0
- package/template/src/app.ts +8 -0
- package/template/src/index.ts +5 -0
- package/template/tsconfig.json +16 -0
- package/template-isr/README.md +46 -0
- package/template-isr/backend.ts +12 -0
- package/template-isr/build.ts +23 -0
- package/template-isr/gitignore +7 -0
- package/template-isr/package.json +24 -0
- package/template-isr/routes/_404.tsx +10 -0
- package/template-isr/routes/_layout.tsx +18 -0
- package/template-isr/routes/index.tsx +34 -0
- package/template-isr/server.ts +56 -0
- package/template-isr/tsconfig.json +28 -0
- package/template-isr/worker.ts +52 -0
- package/template-isr/wrangler.toml +20 -0
- package/template-site/.dockerignore +14 -0
- package/template-site/Dockerfile +20 -0
- package/template-site/README.md +44 -0
- package/template-site/_worker.ts +17 -0
- package/template-site/backend.ts +12 -0
- package/template-site/build-bun.ts +27 -0
- package/template-site/build-deno.ts +25 -0
- package/template-site/build-node.ts +26 -0
- package/template-site/build-vercel.ts +45 -0
- package/template-site/build.ts +29 -0
- package/template-site/deno.json +7 -0
- package/template-site/framework.ts +6 -0
- package/template-site/gitignore +16 -0
- package/template-site/nifra.config.ts +9 -0
- package/template-site/package.json +37 -0
- package/template-site/routes/_404.tsx +20 -0
- package/template-site/routes/_layout.tsx +40 -0
- package/template-site/routes/index.tsx +53 -0
- package/template-site/server-bun.ts +35 -0
- package/template-site/server-deno.ts +38 -0
- package/template-site/server-node.ts +40 -0
- package/template-site/server-vercel.ts +17 -0
- package/template-site/tsconfig.json +37 -0
- package/template-site/wrangler.toml +4 -0
- package/template-site-preact/.dockerignore +14 -0
- package/template-site-preact/Dockerfile +20 -0
- package/template-site-preact/README.md +43 -0
- package/template-site-preact/_worker.ts +17 -0
- package/template-site-preact/backend.ts +12 -0
- package/template-site-preact/build-bun.ts +27 -0
- package/template-site-preact/build-deno.ts +25 -0
- package/template-site-preact/build-node.ts +26 -0
- package/template-site-preact/build-vercel.ts +45 -0
- package/template-site-preact/build.ts +29 -0
- package/template-site-preact/deno.json +7 -0
- package/template-site-preact/framework.ts +6 -0
- package/template-site-preact/gitignore +16 -0
- package/template-site-preact/nifra.config.ts +9 -0
- package/template-site-preact/package.json +34 -0
- package/template-site-preact/routes/_404.tsx +20 -0
- package/template-site-preact/routes/_layout.tsx +40 -0
- package/template-site-preact/routes/index.tsx +53 -0
- package/template-site-preact/server-bun.ts +35 -0
- package/template-site-preact/server-deno.ts +38 -0
- package/template-site-preact/server-node.ts +40 -0
- package/template-site-preact/server-vercel.ts +17 -0
- package/template-site-preact/tsconfig.json +38 -0
- package/template-site-preact/wrangler.toml +4 -0
- package/template-site-solid/.dockerignore +14 -0
- package/template-site-solid/Dockerfile +20 -0
- package/template-site-solid/README.md +43 -0
- package/template-site-solid/_worker.ts +17 -0
- package/template-site-solid/backend.ts +12 -0
- package/template-site-solid/build-bun.ts +33 -0
- package/template-site-solid/build-deno.ts +32 -0
- package/template-site-solid/build-node.ts +33 -0
- package/template-site-solid/build-vercel.ts +47 -0
- package/template-site-solid/build.ts +36 -0
- package/template-site-solid/deno.json +7 -0
- package/template-site-solid/framework.ts +6 -0
- package/template-site-solid/gitignore +16 -0
- package/template-site-solid/nifra.config.ts +14 -0
- package/template-site-solid/package.json +34 -0
- package/template-site-solid/routes/_404.tsx +20 -0
- package/template-site-solid/routes/_layout.tsx +39 -0
- package/template-site-solid/routes/index.tsx +53 -0
- package/template-site-solid/server-bun.ts +35 -0
- package/template-site-solid/server-deno.ts +38 -0
- package/template-site-solid/server-node.ts +40 -0
- package/template-site-solid/server-vercel.ts +17 -0
- package/template-site-solid/tsconfig.json +38 -0
- package/template-site-solid/wrangler.toml +4 -0
- package/template-site-svelte/.dockerignore +14 -0
- package/template-site-svelte/Dockerfile +20 -0
- package/template-site-svelte/README.md +45 -0
- package/template-site-svelte/_worker.ts +17 -0
- package/template-site-svelte/backend.ts +12 -0
- package/template-site-svelte/build-bun.ts +32 -0
- package/template-site-svelte/build-deno.ts +31 -0
- package/template-site-svelte/build-node.ts +32 -0
- package/template-site-svelte/build-vercel.ts +46 -0
- package/template-site-svelte/build.ts +35 -0
- package/template-site-svelte/deno.json +7 -0
- package/template-site-svelte/framework.ts +6 -0
- package/template-site-svelte/gitignore +16 -0
- package/template-site-svelte/nifra.config.ts +13 -0
- package/template-site-svelte/package.json +34 -0
- package/template-site-svelte/routes/_404.svelte +16 -0
- package/template-site-svelte/routes/_layout.svelte +35 -0
- package/template-site-svelte/routes/index.svelte +47 -0
- package/template-site-svelte/server-bun.ts +35 -0
- package/template-site-svelte/server-deno.ts +38 -0
- package/template-site-svelte/server-node.ts +40 -0
- package/template-site-svelte/server-vercel.ts +17 -0
- package/template-site-svelte/tsconfig.json +37 -0
- package/template-site-svelte/wrangler.toml +4 -0
- package/template-site-vue/.dockerignore +14 -0
- package/template-site-vue/Dockerfile +20 -0
- package/template-site-vue/README.md +45 -0
- package/template-site-vue/_worker.ts +17 -0
- package/template-site-vue/backend.ts +12 -0
- package/template-site-vue/build-bun.ts +37 -0
- package/template-site-vue/build-deno.ts +36 -0
- package/template-site-vue/build-node.ts +37 -0
- package/template-site-vue/build-vercel.ts +54 -0
- package/template-site-vue/build.ts +40 -0
- package/template-site-vue/deno.json +7 -0
- package/template-site-vue/framework.ts +6 -0
- package/template-site-vue/gitignore +16 -0
- package/template-site-vue/nifra.config.ts +19 -0
- package/template-site-vue/package.json +35 -0
- package/template-site-vue/routes/_404.vue +18 -0
- package/template-site-vue/routes/_layout.vue +40 -0
- package/template-site-vue/routes/index.vue +56 -0
- package/template-site-vue/server-bun.ts +35 -0
- package/template-site-vue/server-deno.ts +38 -0
- package/template-site-vue/server-node.ts +40 -0
- package/template-site-vue/server-vercel.ts +17 -0
- package/template-site-vue/tsconfig.json +37 -0
- package/template-site-vue/wrangler.toml +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nifra Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# create-nifra
|
|
2
|
+
|
|
3
|
+
Scaffold a new [nifra](../../README.md) app.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bun create nifra my-app
|
|
7
|
+
# or: npm create nifra my-app
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Templates
|
|
11
|
+
|
|
12
|
+
`--template <name>` (default `api`):
|
|
13
|
+
|
|
14
|
+
- **`api`** — a typed nifra server (`src/app.ts` + `src/index.ts`) with an example test and
|
|
15
|
+
`dev`/`start`/`test`/`typecheck` scripts.
|
|
16
|
+
- **`site`** — a nifra + React SSR site, one source deployable to Cloudflare Pages, Node, Deno, or
|
|
17
|
+
Vercel Edge.
|
|
18
|
+
- **`isr`** — a nifra + React app with **Incremental Static Regeneration** on Cloudflare Workers + KV
|
|
19
|
+
(pages cached + served stale-while-revalidate; on-demand purge endpoint).
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bun create nifra my-app --template isr
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd my-app
|
|
29
|
+
bun install
|
|
30
|
+
bun run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
MIT.
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the `AGENTS.md` a scaffolded app ships with — the conventions file read by coding agents
|
|
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, `app.fetch` as the universal
|
|
5
|
+
* entry, and (for full-stack apps) the one gotcha that bites everyone — never top-level-import
|
|
6
|
+
* server-only code into a route module. Tailored per template so the paths + commands are real.
|
|
7
|
+
*/
|
|
8
|
+
import type { AuthChoice } from "./auth.js";
|
|
9
|
+
import type { Framework, TemplateName } from "./cli.js";
|
|
10
|
+
import { type DbChoice } from "./db.js";
|
|
11
|
+
export interface AgentsMdOptions {
|
|
12
|
+
readonly template: TemplateName;
|
|
13
|
+
readonly framework: Framework;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
/** Drizzle preset, when scaffolded with `--db` — adds a Database section. */
|
|
16
|
+
readonly db?: DbChoice;
|
|
17
|
+
/** Auth preset, when scaffolded with `--auth` — adds an Authentication section. */
|
|
18
|
+
readonly auth?: AuthChoice;
|
|
19
|
+
}
|
|
20
|
+
/** Build the full `AGENTS.md` for a freshly scaffolded app. */
|
|
21
|
+
export declare function agentsMd(opts: AgentsMdOptions): string;
|
|
22
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,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;AAgPlE,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,CAoBtD"}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the `AGENTS.md` a scaffolded app ships with — the conventions file read by coding agents
|
|
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, `app.fetch` as the universal
|
|
5
|
+
* entry, and (for full-stack apps) the one gotcha that bites everyone — never top-level-import
|
|
6
|
+
* server-only code into a route module. Tailored per template so the paths + commands are real.
|
|
7
|
+
*/
|
|
8
|
+
import { DB_PRESETS } from "./db.js";
|
|
9
|
+
const FRAMEWORK_LABEL = {
|
|
10
|
+
react: "React",
|
|
11
|
+
solid: "Solid",
|
|
12
|
+
vue: "Vue",
|
|
13
|
+
preact: "Preact",
|
|
14
|
+
svelte: "Svelte",
|
|
15
|
+
};
|
|
16
|
+
/** Shared backend rules — every template is, at its core, a nifra `server()`. */
|
|
17
|
+
const API_RULES = `## The backend: \`server()\`
|
|
18
|
+
|
|
19
|
+
\`\`\`ts
|
|
20
|
+
import { server } from "@nifrajs/core"
|
|
21
|
+
import { t } from "@nifrajs/schema"
|
|
22
|
+
|
|
23
|
+
export const app = server()
|
|
24
|
+
.get("/users/:id", { response: t.object({ id: t.string() }) }, (c) => ({ id: c.params.id }))
|
|
25
|
+
.post(
|
|
26
|
+
"/users",
|
|
27
|
+
// body validates the input; response LOCKS the output shape (the contract).
|
|
28
|
+
{ body: t.object({ name: t.string() }), response: t.object({ id: t.string(), name: t.string() }) },
|
|
29
|
+
(c) => {
|
|
30
|
+
// c.body is VALIDATED + TYPED. Invalid input was already rejected with a 400 before this ran.
|
|
31
|
+
return { id: crypto.randomUUID(), name: c.body.name }
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export type App = typeof app
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
Rules an agent must follow:
|
|
39
|
+
|
|
40
|
+
- **Validate every input at the boundary.** Pass a schema (\`{ body, query, params, headers }\`) on any
|
|
41
|
+
route that reads untrusted input — use \`t\` from \`@nifrajs/schema\` or any Standard Schema (zod, valibot).
|
|
42
|
+
Then read \`c.body\` / \`c.query\` / \`c.params\`, which are typed and already validated. **Never** hand-parse
|
|
43
|
+
\`await c.req.json()\` and poke at properties — that's the bug class the schema exists to remove.
|
|
44
|
+
- **Lock the output shape with \`response\` (no drift).** Add \`{ response: t.object({...}) }\` to a route:
|
|
45
|
+
the handler's return is type-checked against it, and the typed client sees exactly that shape. One
|
|
46
|
+
contract, both sides — the frontend physically can't drift from the backend's output.
|
|
47
|
+
- **Return values, not boilerplate.** Returning a plain object/array/string sends JSON. Return a
|
|
48
|
+
\`Response\` for full control, or \`new Response(..., { status })\` for errors. Prefer explicit error
|
|
49
|
+
shapes (\`{ ok: false, error: "slot_unavailable" }\`) with the right status over generic 500s.
|
|
50
|
+
- **\`app.fetch(request)\` is the whole app** — \`(Request) => Response | Promise<Response>\`. Tests drive it
|
|
51
|
+
directly (\`await app.fetch(new Request("http://x/users/1"))\`); no server needs to be running.
|
|
52
|
+
- **Don't reach for a heavy ORM/HTTP layer.** Routing, validation, cookies, and the typed client are
|
|
53
|
+
built in. Parameterize DB queries; use \`timingSafeEqual\` (or WebCrypto) for secret comparison.
|
|
54
|
+
|
|
55
|
+
## The client is typed + never throws — ALWAYS use it for internal API calls
|
|
56
|
+
|
|
57
|
+
\`\`\`ts
|
|
58
|
+
import { client } from "@nifrajs/client"
|
|
59
|
+
import type { app } from "./app" // type-only import — server code never ships to the client
|
|
60
|
+
|
|
61
|
+
const api = client<typeof app>("https://api.example.com")
|
|
62
|
+
const res = await api.users({ id: "42" }).get()
|
|
63
|
+
if (res.ok) res.data // typed from the route's return type — zero codegen
|
|
64
|
+
else res.error // errors are returned, never thrown
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
**Never hand-roll \`fetch\` + ad-hoc response types for this app's own API** — that's exactly how a screen
|
|
68
|
+
drifts from the backend. \`client<typeof app>\` derives both the request inputs and \`res.data\` from the
|
|
69
|
+
route types, so \`tsc\` (and \`nifra build\`) catch any mismatch the moment a route changes. When building a
|
|
70
|
+
page that calls the API, reach for the client first.
|
|
71
|
+
`;
|
|
72
|
+
/** File-routing + the server-only gotcha — only for full-stack (site/isr) templates. */
|
|
73
|
+
function webRules(framework) {
|
|
74
|
+
const label = FRAMEWORK_LABEL[framework];
|
|
75
|
+
return `## The frontend: file routing + loaders (${label})
|
|
76
|
+
|
|
77
|
+
- Routes live in \`routes/\`. \`index.tsx\` → \`/\`, \`[id].tsx\` → \`/:id\`, \`_layout.tsx\` wraps a subtree,
|
|
78
|
+
\`_404.tsx\` / \`_error.tsx\` are the fallbacks.
|
|
79
|
+
- A route file may export: \`default\` (the page component), \`loader\` (server-only data), \`action\`
|
|
80
|
+
(server-only mutation), and \`meta\` (head tags). The \`loader\` returns data typed straight into the page.
|
|
81
|
+
- Swap UI frameworks by changing one import (\`@nifrajs/web-${framework}\` → another adapter); your route,
|
|
82
|
+
loader, and action code do not change.
|
|
83
|
+
- **Loaders \`throw redirect(url)\`, actions \`return redirect(url)\`.** Loader redirects abort the
|
|
84
|
+
render server-side; action redirects return a response so client form submits get \`X-Nifra-Redirect\`
|
|
85
|
+
and navigate without a full reload. Throwing in an action or returning in a loader is a silent bug
|
|
86
|
+
that produces a false "operation failed" error on the client.
|
|
87
|
+
|
|
88
|
+
## ⚠️ The one rule that bites everyone: never import server-only code at a route's top level
|
|
89
|
+
|
|
90
|
+
A route module's \`loader\`/\`action\` run **only on the server**, but the module is **also bundled for the
|
|
91
|
+
browser** (for the component) — and the loader is *not* stripped. So a top-level
|
|
92
|
+
\`import db from "./db"\` (or anything touching \`process.env\`, secrets, \`node:\` APIs) ships server code to
|
|
93
|
+
the client and crashes hydration. Reach server resources through \`ctx.api\` / \`ctx.env\` (injected via
|
|
94
|
+
\`createWebApp\`) inside the loader instead — never as a top-level import in a \`routes/\` file.
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
/** Per-ORM body of the DB rules: the query idiom, schema location, and migrate story all differ. */
|
|
98
|
+
function dbRulesBody(orm) {
|
|
99
|
+
if (orm === "prisma") {
|
|
100
|
+
return `A Prisma data layer is wired: \`prisma/schema.prisma\` (a starter \`Note\` model) + \`db/index.ts\` (a
|
|
101
|
+
singleton \`PrismaClient\`).
|
|
102
|
+
|
|
103
|
+
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
104
|
+
\`\`\`ts
|
|
105
|
+
import { server } from "@nifrajs/core"
|
|
106
|
+
import { t } from "@nifrajs/schema"
|
|
107
|
+
import { db } from "./db"
|
|
108
|
+
|
|
109
|
+
export const app = server()
|
|
110
|
+
.decorate("db", db)
|
|
111
|
+
.get("/notes", async (c) => c.db.note.findMany({ where: { deletedAt: null } }))
|
|
112
|
+
.post("/notes", { body: t.object({ title: t.string({ minLength: 1 }) }) }, async (c) =>
|
|
113
|
+
c.db.note.create({ data: { title: c.body.title } }),
|
|
114
|
+
)
|
|
115
|
+
\`\`\`
|
|
116
|
+
- **Migrations:** edit \`prisma/schema.prisma\`, then \`bun run db:migrate\` (\`prisma migrate dev\` — creates +
|
|
117
|
+
applies the migration and regenerates the client). \`bun run db:studio\` opens a DB browser.`;
|
|
118
|
+
}
|
|
119
|
+
if (orm === "kysely") {
|
|
120
|
+
return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape interface — keep it in
|
|
121
|
+
sync with your migrations) + \`db/index.ts\` (the typed client). You own the migrations (\`db/migrations/\`,
|
|
122
|
+
run by \`db/migrate.ts\`).
|
|
123
|
+
|
|
124
|
+
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
125
|
+
\`\`\`ts
|
|
126
|
+
import { server } from "@nifrajs/core"
|
|
127
|
+
import { t } from "@nifrajs/schema"
|
|
128
|
+
import { db } from "./db"
|
|
129
|
+
|
|
130
|
+
export const app = server()
|
|
131
|
+
.decorate("db", db)
|
|
132
|
+
.get("/notes", async (c) =>
|
|
133
|
+
c.db.selectFrom("notes").selectAll().where("deleted_at", "is", null).execute())
|
|
134
|
+
.post("/notes", { body: t.object({ title: t.string({ minLength: 1 }) }) }, async (c) =>
|
|
135
|
+
c.db.insertInto("notes").values({ title: c.body.title }).returningAll().executeTakeFirstOrThrow())
|
|
136
|
+
\`\`\`
|
|
137
|
+
- **Migrations:** add a file to \`db/migrations/\` (copy the \`0001_create_notes.ts\` shape), update the
|
|
138
|
+
\`db/schema.ts\` interface to match, then \`bun run db:migrate\`.`;
|
|
139
|
+
}
|
|
140
|
+
return `A Drizzle data layer is wired in \`db/\`: \`db/schema.ts\` (a starter \`notes\` table) + \`db/index.ts\` (the
|
|
141
|
+
typed client).
|
|
142
|
+
|
|
143
|
+
- **Wire it into the backend once**, then read \`c.db\` in handlers:
|
|
144
|
+
\`\`\`ts
|
|
145
|
+
import { server } from "@nifrajs/core"
|
|
146
|
+
import { t } from "@nifrajs/schema"
|
|
147
|
+
import { db, notes } from "./db"
|
|
148
|
+
|
|
149
|
+
export const app = server()
|
|
150
|
+
.decorate("db", db)
|
|
151
|
+
.get("/notes", async (c) => c.db.select().from(notes))
|
|
152
|
+
.post("/notes", { body: t.object({ title: t.string({ minLength: 1 }) }) }, async (c) =>
|
|
153
|
+
(await c.db.insert(notes).values({ title: c.body.title }).returning())[0],
|
|
154
|
+
)
|
|
155
|
+
\`\`\`
|
|
156
|
+
- **Migrations:** edit \`db/schema.ts\`, then \`bun run db:generate\` (writes SQL to \`db/migrations\`) +
|
|
157
|
+
\`bun run db:migrate\` (applies it). \`bun run db:studio\` opens a DB browser.`;
|
|
158
|
+
}
|
|
159
|
+
/** DB rules — only when scaffolded with `--db`. Teaches the wired data layer + the `c.db` seam. */
|
|
160
|
+
function dbRules(db) {
|
|
161
|
+
const p = DB_PRESETS[db];
|
|
162
|
+
return `## Database (${p.label})
|
|
163
|
+
|
|
164
|
+
${dbRulesBody(p.orm)} ${p.note}
|
|
165
|
+
|
|
166
|
+
- **Query is fully typed** — types flow from the schema/model; \`c.db\` is typed end to end.
|
|
167
|
+
- **Never top-level-import \`db\` into a \`routes/\` page file** — it's server-only; reach it via \`c.db\` on
|
|
168
|
+
the backend (or \`ctx.api\` from a loader). A top-level import ships it to the browser and breaks the build.`;
|
|
169
|
+
}
|
|
170
|
+
/** Auth rules — only when scaffolded with `--auth`. Teaches the better-auth mount + the typed guards. */
|
|
171
|
+
function authRules(_auth) {
|
|
172
|
+
return `## Authentication (better-auth)
|
|
173
|
+
|
|
174
|
+
better-auth is configured in \`auth.ts\` (email/password + sessions, backed by your scaffolded database via
|
|
175
|
+
its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifrajs/better-auth\`.
|
|
176
|
+
|
|
177
|
+
- **Mount it once** on the backend — it serves every auth endpoint under \`/api/auth/*\`:
|
|
178
|
+
\`\`\`ts
|
|
179
|
+
import { server } from "@nifrajs/core"
|
|
180
|
+
import { betterAuth } from "@nifrajs/better-auth"
|
|
181
|
+
import { auth } from "./auth"
|
|
182
|
+
export const app = server().use(betterAuth(auth))
|
|
183
|
+
\`\`\`
|
|
184
|
+
- **Generate the auth tables** once, and after changing the config: \`bunx @better-auth/cli@latest generate\`
|
|
185
|
+
writes them into your schema (\`db/schema.ts\` for Drizzle, \`prisma/schema.prisma\` for Prisma); then
|
|
186
|
+
\`bun run db:migrate\`.
|
|
187
|
+
- **Read / require a session** with the typed guards (pass the raw \`Request\` — \`c.req\` in a handler, or a
|
|
188
|
+
loader's \`request\`):
|
|
189
|
+
\`\`\`ts
|
|
190
|
+
import { getSession, requireSession } from "@nifrajs/better-auth"
|
|
191
|
+
app.get("/me", async (c) => (await getSession(auth, c.req))?.user ?? null)
|
|
192
|
+
app.get("/account", async (c) => {
|
|
193
|
+
const { user } = await requireSession(auth, c.req) // throws a 401 Response when signed out
|
|
194
|
+
return { id: user.id }
|
|
195
|
+
})
|
|
196
|
+
\`\`\`
|
|
197
|
+
- \`auth\` is **server-only** — never import it into a \`routes/\` page, and keep \`BETTER_AUTH_SECRET\` in \`.env\`.`;
|
|
198
|
+
}
|
|
199
|
+
const SECURITY_RULES = `## Production defaults (don't ship the happy path only)
|
|
200
|
+
|
|
201
|
+
- **Dependencies: install current, never pin from memory.** Your training data lags npm by months — do
|
|
202
|
+
not hand-write a version range or reach for a remembered (often deprecated or vulnerable) API. Add a
|
|
203
|
+
dependency with \`bun add <pkg>\` so it resolves the **latest stable**, then use *that* version's API —
|
|
204
|
+
if unsure of a signature, read \`node_modules/<pkg>\`'s types or run \`bun pm view <pkg> version\`, don't
|
|
205
|
+
guess. Never **downgrade** versions already in \`package.json\` (especially the pinned \`@nifrajs/*\` ones).
|
|
206
|
+
- Validate at every trust boundary (above). Reject unknown fields and out-of-range values.
|
|
207
|
+
- Constant-time comparison for secrets/tokens/HMACs (\`timingSafeEqual\` or WebCrypto), never \`===\`.
|
|
208
|
+
- Verify ownership/permission on any resource a handler reads or mutates — auth'd ≠ authorized.
|
|
209
|
+
- Idempotency on retryable side effects (webhooks, payments): short-circuit on a repeated key.
|
|
210
|
+
- Money in integer minor units; timestamps as UTC; parse client dates at the boundary.
|
|
211
|
+
- No secrets/PII in logs or error responses.
|
|
212
|
+
`;
|
|
213
|
+
const COMMANDS = {
|
|
214
|
+
api: `- \`bun install\` — install dependencies
|
|
215
|
+
- \`bun run dev\` — run the API (watch mode)
|
|
216
|
+
- \`bun test\` — drive \`app.fetch\` directly; no server required
|
|
217
|
+
- \`nifra check\` — typecheck + typed-client lint (run before you call work done)`,
|
|
218
|
+
site: `- \`bun install\` — install dependencies
|
|
219
|
+
- \`nifra dev\` — true-HMR dev server
|
|
220
|
+
- \`nifra build\` — content-hashed client bundle + manifest
|
|
221
|
+
- \`nifra start\` — serve the built app (SSR)
|
|
222
|
+
- \`nifra check\` — typecheck + typed-client lint (run before you call work done)`,
|
|
223
|
+
isr: `- \`bun install\` — install dependencies
|
|
224
|
+
- \`nifra dev\` — true-HMR dev server
|
|
225
|
+
- \`nifra build\` — build for your deploy target
|
|
226
|
+
- \`nifra start\` — serve the built app (SSR + ISR cache)
|
|
227
|
+
- \`nifra check\` — typecheck + typed-client lint (run before you call work done)`,
|
|
228
|
+
};
|
|
229
|
+
/** The agent's stop condition — what makes the anti-drift guarantees actually fire. */
|
|
230
|
+
const DONE = `## Definition of done — run \`nifra check\`
|
|
231
|
+
|
|
232
|
+
Before considering any change complete, run **\`nifra check\`**. It (1) typechecks — the frontend↔backend
|
|
233
|
+
contract is compiler-enforced, so the typed client deriving \`res.data\` from your routes turns any
|
|
234
|
+
mismatch into an error; (2) flags any hand-rolled \`fetch()\` to this app's own API (which bypasses that
|
|
235
|
+
check); and (3) flags a server-only import (a DB driver, \`node:\`/\`bun:\`, \`./db\`) at the top level of a
|
|
236
|
+
\`routes/\` page — that ships server code to the browser. Use \`nifra check --json\` for machine-readable
|
|
237
|
+
diagnostics. A failing \`nifra check\` means the work isn't done — fix it, don't ship around it.`;
|
|
238
|
+
/** Build the full `AGENTS.md` for a freshly scaffolded app. */
|
|
239
|
+
export function agentsMd(opts) {
|
|
240
|
+
const isWeb = opts.template === "site" || opts.template === "isr";
|
|
241
|
+
const what = isWeb
|
|
242
|
+
? `a full-stack nifra app — a typed \`server()\` backend plus file-routed, server-rendered ${FRAMEWORK_LABEL[opts.framework]} pages`
|
|
243
|
+
: `a nifra API — a typed, contract-first \`server()\` backend`;
|
|
244
|
+
// Each entry is a self-contained markdown block; join with a blank line between blocks. The web-rules
|
|
245
|
+
// block is empty for API templates — dropped before joining so there's no doubled blank line.
|
|
246
|
+
const blocks = [
|
|
247
|
+
`# 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.`,
|
|
248
|
+
`## Commands\n\n${COMMANDS[opts.template]}`,
|
|
249
|
+
API_RULES.trim(),
|
|
250
|
+
opts.db !== undefined ? dbRules(opts.db) : "",
|
|
251
|
+
opts.auth !== undefined ? authRules(opts.auth) : "",
|
|
252
|
+
isWeb ? webRules(opts.framework).trim() : "",
|
|
253
|
+
SECURITY_RULES.trim(),
|
|
254
|
+
DONE,
|
|
255
|
+
"## 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.",
|
|
256
|
+
];
|
|
257
|
+
return `${blocks.filter(Boolean).join("\n\n")}\n`;
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--auth better-auth` — wire authentication into a scaffolded app, the same "bundle the right tool,
|
|
3
|
+
* correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
|
|
4
|
+
* into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
|
|
5
|
+
* logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
|
|
6
|
+
* from `./db` via better-auth's adapter for the chosen ORM (`drizzleAdapter` / `prismaAdapter`). The
|
|
7
|
+
* agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
|
|
8
|
+
*
|
|
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 — wire better-auth's own dialect manually).
|
|
11
|
+
* One preset today: `better-auth`.
|
|
12
|
+
*/
|
|
13
|
+
import { type DbChoice } from "./db.js";
|
|
14
|
+
export declare const AUTH_CHOICES: readonly ["better-auth"];
|
|
15
|
+
export type AuthChoice = (typeof AUTH_CHOICES)[number];
|
|
16
|
+
/**
|
|
17
|
+
* Guard `--auth` against a DB whose ORM has no clean better-auth adapter. Throws an actionable error
|
|
18
|
+
* for Kysely (passing a raw dialect to better-auth is manual wiring we won't fake-scaffold). Called by
|
|
19
|
+
* the CLI before scaffolding so the failure is early and explains the fix.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertAuthableDb(db: DbChoice): void;
|
|
22
|
+
export interface AuthPreset {
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly note: string;
|
|
25
|
+
readonly deps: Readonly<Record<string, string>>;
|
|
26
|
+
}
|
|
27
|
+
export declare const AUTH_PRESETS: Readonly<Record<AuthChoice, AuthPreset>>;
|
|
28
|
+
/** Write `auth.ts` and append the auth env vars to the (db-preset-created) `.env.example`. */
|
|
29
|
+
export declare function writeAuthFiles(target: string, _auth: AuthChoice, db: DbChoice): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAc,KAAK,QAAQ,EAAY,MAAM,SAAS,CAAA;AAE7D,eAAO,MAAM,YAAY,0BAA2B,CAAA;AACpD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAKtD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAQnD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAChD;AAED,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,CAOjE,CAAA;AAiDD,8FAA8F;AAC9F,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,QAAQ,GACX,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--auth better-auth` — wire authentication into a scaffolded app, the same "bundle the right tool,
|
|
3
|
+
* correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
|
|
4
|
+
* into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
|
|
5
|
+
* logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
|
|
6
|
+
* from `./db` via better-auth's adapter for the chosen ORM (`drizzleAdapter` / `prismaAdapter`). The
|
|
7
|
+
* agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
|
|
8
|
+
*
|
|
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 — wire better-auth's own dialect manually).
|
|
11
|
+
* One preset today: `better-auth`.
|
|
12
|
+
*/
|
|
13
|
+
import { appendFile, writeFile } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { DB_PRESETS } from "./db.js";
|
|
16
|
+
export const AUTH_CHOICES = ["better-auth"];
|
|
17
|
+
/** ORMs better-auth wires via a drop-in adapter. Kysely is excluded — see {@link assertAuthableDb}. */
|
|
18
|
+
const AUTHABLE_ORMS = new Set(["drizzle", "prisma"]);
|
|
19
|
+
/**
|
|
20
|
+
* Guard `--auth` against a DB whose ORM has no clean better-auth adapter. Throws an actionable error
|
|
21
|
+
* for Kysely (passing a raw dialect to better-auth is manual wiring we won't fake-scaffold). Called by
|
|
22
|
+
* the CLI before scaffolding so the failure is early and explains the fix.
|
|
23
|
+
*/
|
|
24
|
+
export function assertAuthableDb(db) {
|
|
25
|
+
const { orm } = DB_PRESETS[db];
|
|
26
|
+
if (!AUTHABLE_ORMS.has(orm)) {
|
|
27
|
+
throw new Error(`--auth better-auth doesn't scaffold for --db ${db} (${orm} has no drop-in better-auth adapter). ` +
|
|
28
|
+
"Use a drizzle-* or prisma-* preset, or wire better-auth's database dialect manually in auth.ts.");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export const AUTH_PRESETS = {
|
|
32
|
+
"better-auth": {
|
|
33
|
+
label: "better-auth",
|
|
34
|
+
note: "email/password + sessions, mounted at /api/auth/* — add OAuth providers in auth.ts.",
|
|
35
|
+
// @nifrajs/better-auth mounts it; better-auth is the implementation (peer-installed alongside).
|
|
36
|
+
deps: { "@nifrajs/better-auth": "^0.1.0", "better-auth": "^1.2.0" },
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
/** The better-auth `database` adapter expression + its import line, for the scaffolded DB's ORM. */
|
|
40
|
+
function adapterFor(db) {
|
|
41
|
+
const { orm, dialect } = DB_PRESETS[db];
|
|
42
|
+
if (orm === "prisma") {
|
|
43
|
+
// Prisma's adapter `provider` is the datasource name ("postgresql"), not Drizzle's "pg".
|
|
44
|
+
const provider = dialect === "postgres" ? "postgresql" : "sqlite";
|
|
45
|
+
return {
|
|
46
|
+
importLine: `import { prismaAdapter } from "better-auth/adapters/prisma"`,
|
|
47
|
+
database: `prismaAdapter(db, { provider: "${provider}" })`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Drizzle (the only other authable ORM; Kysely is rejected upstream by assertAuthableDb).
|
|
51
|
+
const provider = dialect === "postgres" ? "pg" : "sqlite";
|
|
52
|
+
return {
|
|
53
|
+
importLine: `import { drizzleAdapter } from "better-auth/adapters/drizzle"`,
|
|
54
|
+
database: `drizzleAdapter(db, { provider: "${provider}" })`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** The generated `auth.ts` — a better-auth instance backed by the scaffolded DB client. */
|
|
58
|
+
function authModule(db) {
|
|
59
|
+
const { importLine, database } = adapterFor(db);
|
|
60
|
+
return `import { betterAuth as createBetterAuth } from "better-auth"
|
|
61
|
+
${importLine}
|
|
62
|
+
import { db } from "./db"
|
|
63
|
+
|
|
64
|
+
// better-auth stores users + sessions in your database (the same one in db/). After editing the config
|
|
65
|
+
// (e.g. adding OAuth providers), generate its tables: \`bunx @better-auth/cli generate\` writes the auth
|
|
66
|
+
// schema for your ORM, then \`bun run db:migrate\` applies it.
|
|
67
|
+
export const auth = createBetterAuth({
|
|
68
|
+
database: ${database},
|
|
69
|
+
emailAndPassword: { enabled: true },
|
|
70
|
+
// Set in .env — generate a strong value: openssl rand -base64 32
|
|
71
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
72
|
+
baseURL: process.env.BETTER_AUTH_URL,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export type Auth = typeof auth
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
const ENV = `
|
|
79
|
+
# better-auth — REQUIRED. Generate a 32+ char secret: openssl rand -base64 32
|
|
80
|
+
BETTER_AUTH_SECRET="change-me-before-production"
|
|
81
|
+
BETTER_AUTH_URL="http://localhost:3000"
|
|
82
|
+
`;
|
|
83
|
+
/** Write `auth.ts` and append the auth env vars to the (db-preset-created) `.env.example`. */
|
|
84
|
+
export async function writeAuthFiles(target, _auth, db) {
|
|
85
|
+
assertAuthableDb(db); // defensive: the CLI already checks, but never emit a broken adapter import
|
|
86
|
+
await writeFile(join(target, "auth.ts"), authModule(db));
|
|
87
|
+
// .env.example already exists (the DB preset wrote it; `--auth` requires `--db`); append, don't clobber.
|
|
88
|
+
await appendFile(join(target, ".env.example"), ENV);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,UAAU,EAA2B,MAAM,SAAS,CAAA;AAE7D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,aAAa,CAAU,CAAA;AAGpD,uGAAuG;AACvG,MAAM,aAAa,GAAqB,IAAI,GAAG,CAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;AAE3E;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAY;IAC3C,MAAM,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;IAC9B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,gDAAgD,EAAE,KAAK,GAAG,wCAAwC;YAChG,iGAAiG,CACpG,CAAA;IACH,CAAC;AACH,CAAC;AAQD,MAAM,CAAC,MAAM,YAAY,GAA6C;IACpE,aAAa,EAAE;QACb,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,qFAAqF;QAC3F,gGAAgG;QAChG,IAAI,EAAE,EAAE,sBAAsB,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE;KACpE;CACF,CAAA;AAED,oGAAoG;AACpG,SAAS,UAAU,CAAC,EAAY;IAC9B,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;IACvC,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QACrB,yFAAyF;QACzF,MAAM,QAAQ,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAA;QACjE,OAAO;YACL,UAAU,EAAE,6DAA6D;YACzE,QAAQ,EAAE,kCAAkC,QAAQ,MAAM;SAC3D,CAAA;IACH,CAAC;IACD,0FAA0F;IAC1F,MAAM,QAAQ,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAA;IACzD,OAAO;QACL,UAAU,EAAE,+DAA+D;QAC3E,QAAQ,EAAE,mCAAmC,QAAQ,MAAM;KAC5D,CAAA;AACH,CAAC;AAED,2FAA2F;AAC3F,SAAS,UAAU,CAAC,EAAY;IAC9B,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;IAC/C,OAAO;EACP,UAAU;;;;;;;cAOE,QAAQ;;;;;;;;CAQrB,CAAA;AACD,CAAC;AAED,MAAM,GAAG,GAAG;;;;CAIX,CAAA;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,KAAiB,EACjB,EAAY;IAEZ,gBAAgB,CAAC,EAAE,CAAC,CAAA,CAAC,4EAA4E;IACjG,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,yGAAyG;IACzG,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,GAAG,CAAC,CAAA;AACrD,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type AuthChoice } from "./auth.js";
|
|
3
|
+
import { type DbChoice } from "./db.js";
|
|
4
|
+
declare const TEMPLATES: {
|
|
5
|
+
readonly api: "../template";
|
|
6
|
+
readonly site: "../template-site";
|
|
7
|
+
readonly isr: "../template-isr";
|
|
8
|
+
};
|
|
9
|
+
export type TemplateName = keyof typeof TEMPLATES;
|
|
10
|
+
/** Frontend frameworks the `site` template can scaffold. React is the default (`template-site`); the
|
|
11
|
+
* rest live in `template-site-<framework>` siblings — same multi-target deploy story, different adapter
|
|
12
|
+
* + routes. */
|
|
13
|
+
declare const FRAMEWORKS: readonly ["react", "preact", "vue", "solid", "svelte"];
|
|
14
|
+
export type Framework = (typeof FRAMEWORKS)[number];
|
|
15
|
+
/**
|
|
16
|
+
* Deploy targets for the multi-target `site` template. Every target's build + server entry already ships
|
|
17
|
+
* in the template; choosing one repoints the canonical `build`/`deploy` scripts at it (the per-target
|
|
18
|
+
* `build:*` scripts stay, so you can still switch). nifra never runs the deploy or enters credentials —
|
|
19
|
+
* `deploy` shells out to the vendor CLI you've authed yourself.
|
|
20
|
+
*/
|
|
21
|
+
interface DeployPreset {
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly build: string;
|
|
24
|
+
/** `deploy` script; `NAME` is replaced with the app directory name. */
|
|
25
|
+
readonly deploy: string;
|
|
26
|
+
readonly steps: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a GitHub Actions workflow that builds on every push/PR and deploys `target` on a push to `main`.
|
|
30
|
+
* Reuses the canonical `bun run build` (the deploy preset repoints it), then runs the target's official
|
|
31
|
+
* deploy mechanism. Exported for unit tests. Throws on an unknown target.
|
|
32
|
+
*/
|
|
33
|
+
export declare function githubDeployWorkflow(target: string, appName: string): string;
|
|
34
|
+
export interface ScaffoldOptions {
|
|
35
|
+
/** Destination directory. */
|
|
36
|
+
readonly target: string;
|
|
37
|
+
/** Template to copy. Default `"api"`. */
|
|
38
|
+
readonly template?: TemplateName;
|
|
39
|
+
/** Frontend framework (site template only). Default `"react"`. */
|
|
40
|
+
readonly framework?: string;
|
|
41
|
+
/** Deploy target (site template only) — repoints the default `build`/`deploy` scripts. */
|
|
42
|
+
readonly deploy?: string;
|
|
43
|
+
/** Emit a CI deploy workflow for the chosen `--deploy` target. Only `"github"` today. */
|
|
44
|
+
readonly ci?: string;
|
|
45
|
+
/** Wire a data layer: `drizzle-{libsql,postgres,sqlite}` | `prisma-{postgres,sqlite}` | `kysely-postgres`. */
|
|
46
|
+
readonly db?: string;
|
|
47
|
+
/** Wire authentication: `better-auth` (requires `--db` — auth needs a database). */
|
|
48
|
+
readonly auth?: string;
|
|
49
|
+
/** Allow scaffolding into a non-empty directory (copies template, overwrites collisions). */
|
|
50
|
+
readonly force?: boolean;
|
|
51
|
+
/** Path to a local nifra monorepo — replaces `@nifrajs/*` semver deps with `file:` refs so
|
|
52
|
+
* the app runs against the local source before the packages are published to npm. */
|
|
53
|
+
readonly link?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ScaffoldResult {
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly template: TemplateName;
|
|
58
|
+
readonly framework?: Framework;
|
|
59
|
+
readonly deploy?: DeployPreset;
|
|
60
|
+
/** The CI provider a workflow was generated for, when `--ci` was passed. */
|
|
61
|
+
readonly ci?: "github";
|
|
62
|
+
/** Repo secrets the generated workflow needs (for the next-steps message). */
|
|
63
|
+
readonly ciSecrets?: readonly string[];
|
|
64
|
+
/** The Drizzle preset wired in, when `--db` was passed. */
|
|
65
|
+
readonly db?: DbChoice;
|
|
66
|
+
/** The auth preset wired in, when `--auth` was passed. */
|
|
67
|
+
readonly auth?: AuthChoice;
|
|
68
|
+
/** Local nifra monorepo path when `--link` was used; undefined when using published packages. */
|
|
69
|
+
readonly link?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Copy a template into `target` and finalize it (gitignore rename, package name, optional deploy preset).
|
|
73
|
+
* Throws on: unknown template, `--deploy` with a non-site template, unknown deploy target, or an existing
|
|
74
|
+
* destination. Pure enough to unit-test (no argv, no process.exit).
|
|
75
|
+
*/
|
|
76
|
+
export declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
77
|
+
interface ParsedArgs {
|
|
78
|
+
readonly target?: string;
|
|
79
|
+
readonly template?: TemplateName;
|
|
80
|
+
readonly framework?: string;
|
|
81
|
+
readonly deploy?: string;
|
|
82
|
+
readonly ci?: string;
|
|
83
|
+
readonly db?: string;
|
|
84
|
+
readonly auth?: string;
|
|
85
|
+
readonly force?: boolean;
|
|
86
|
+
readonly link?: string;
|
|
87
|
+
}
|
|
88
|
+
/** Parse `[dir] [--template|-t <t>] [--framework|-f <fw>] [--deploy|-d <target>] [--ci|-c github]
|
|
89
|
+
* [--db <preset>] [--auth <preset>] [--force] [--link <path>]`.
|
|
90
|
+
* `--framework`/`--deploy`/`--ci` all default the template to `site`. */
|
|
91
|
+
export declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
92
|
+
/**
|
|
93
|
+
* Run the CLI for `argv` and return the exit code + the message to print — no `process.exit`, `console`,
|
|
94
|
+
* or `process.argv`, so the whole flow (parse → scaffold → next-steps) is unit-testable in-process.
|
|
95
|
+
*/
|
|
96
|
+
export declare function run(argv: readonly string[]): Promise<{
|
|
97
|
+
code: 0 | 1;
|
|
98
|
+
message: string;
|
|
99
|
+
}>;
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|