bunforge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # bunforge
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.11. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
16
+
17
+ # bunforge
18
+
19
+ A zero-config CLI scaffolder for Bun projects with optional Prisma + JWT auth.
20
+
21
+ ## Install & run
22
+ ````bash
23
+ # From within the bunforge repo
24
+ bun start
25
+ ````
26
+
27
+ ## What it does
28
+ ````
29
+ bunforge
30
+ ├── asks for a project name
31
+ ├── runs bun init -y
32
+ ├── [optional] installs & configures Prisma (PostgreSQL + pg adapter)
33
+ │ ├── bun add prisma @types/pg @prisma/client @prisma/adapter-pg pg dotenv
34
+ │ ├── bunx prisma init
35
+ │ ├── writes src/db.ts (PrismaClient with PrismaPg adapter)
36
+ │ └── appends users model + ROLE enum to prisma/schema.prisma
37
+ └── [optional, requires Prisma] scaffolds JWT auth
38
+ ├── bun add express @types/express jsonwebtoken @types/jsonwebtoken zod
39
+ ├── writes src/Types/types.ts (SignupSchema, SigninSchema via Zod)
40
+ └── writes src/index.ts (Express server, /api/auth/signup & /api/auth/login)
41
+ ````
42
+
43
+ ## Post-scaffold workflow
44
+ ````bash
45
+ cd <your-project>
46
+
47
+ # 1. Fill in your real DB connection
48
+ nano .env # set DATABASE_URL, JWT_SECRET
49
+
50
+ # 2. Run first migration
51
+ bunx prisma migrate dev --name init
52
+
53
+ # 3. Start dev server
54
+ bun run src/index.ts
55
+ ````
56
+
57
+ ## API
58
+
59
+ | Method | Path | Body | Response |
60
+ |--------|-------------------|-------------------------------|------------------------------|
61
+ | POST | /api/auth/signup | `{ email, password }` | `{ success: true }` |
62
+ | POST | /api/auth/login | `{ email, password }` | `{ success, data: { token, user } }` |
63
+
64
+ ## Protecting routes
65
+ ````typescript
66
+ import { authMiddleware } from "./index";
67
+
68
+ app.get("/api/me", authMiddleware(), (req, res) => {
69
+ res.json({ id: req.id, role: req.role });
70
+ });
71
+ ````
package/bun.lock ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "dependencies": {
7
+ "@clack/prompts": "^1.2.0",
8
+ "execa": "^9.6.1",
9
+ "picocolors": "^1.1.1",
10
+ },
11
+ "devDependencies": {
12
+ "@types/bun": "latest",
13
+ "@types/node": "^25.5.0",
14
+ },
15
+ "peerDependencies": {
16
+ "typescript": "^5",
17
+ },
18
+ },
19
+ },
20
+ "packages": {
21
+ "@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="],
22
+
23
+ "@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
24
+
25
+ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
26
+
27
+ "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
28
+
29
+ "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
30
+
31
+ "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
32
+
33
+ "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
34
+
35
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
36
+
37
+ "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
38
+
39
+ "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="],
40
+
41
+ "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="],
42
+
43
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="],
44
+
45
+ "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
46
+
47
+ "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
48
+
49
+ "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
50
+
51
+ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
52
+
53
+ "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
54
+
55
+ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
56
+
57
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
58
+
59
+ "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
60
+
61
+ "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
62
+
63
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
64
+
65
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
66
+
67
+ "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
68
+
69
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
70
+
71
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
72
+
73
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
74
+
75
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
76
+
77
+ "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
78
+
79
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
80
+
81
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
82
+
83
+ "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
84
+
85
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
86
+
87
+ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
88
+
89
+ "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
90
+ }
91
+ }
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ console.log("Hello via Bun!");
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "bunforge",
3
+ "version": "1.0.0",
4
+ "module": "src/index.ts",
5
+ "bin": {
6
+ "bunforge": "./src/index.ts"
7
+ },
8
+ "scripts": {
9
+ "start": "bun src/index.ts",
10
+ "dev": "bun --watch src/index.ts"
11
+ },
12
+ "dependencies": {
13
+ "@clack/prompts": "^0.9.0",
14
+ "execa": "^9.5.2",
15
+ "picocolors": "^1.1.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.0.0"
19
+ }
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bun
2
+ import { join } from "path";
3
+ import { mkdir } from "fs/promises";
4
+ import { execa } from "execa";
5
+ import * as p from "@clack/prompts";
6
+ import pc from "picocolors";
7
+
8
+ import { getProjectName, askPrisma, askAuth } from "./prompts.js";
9
+ import { setupPrisma } from "./tasks/prisma.js";
10
+ import { setupAuth } from "./tasks/auth.js";
11
+
12
+ p.intro(pc.bgCyan(pc.black(" bunforge ")) + " — Bun project scaffolder");
13
+
14
+ const name = await getProjectName();
15
+ const projectRoot = join(process.cwd(), name);
16
+
17
+ // ── 1. bun init ──────────────────────────────────────────────────────────────
18
+ const s = p.spinner();
19
+ s.start("Initialising bun project…");
20
+ await mkdir(projectRoot, { recursive: true });
21
+ await execa("bun", ["init", "-y"], { cwd: projectRoot });
22
+ s.stop("Bun project ready");
23
+
24
+ // ── 2. Prisma ────────────────────────────────────────────────────────────────
25
+ const wantsPrisma = await askPrisma();
26
+ if (wantsPrisma) {
27
+ s.start("Setting up Prisma…");
28
+ await setupPrisma(projectRoot);
29
+ s.stop("Prisma ready");
30
+ }
31
+
32
+ // ── 3. Auth ──────────────────────────────────────────────────────────────────
33
+ let wantsAuth = false;
34
+ if (wantsPrisma) {
35
+ wantsAuth = await askAuth();
36
+ if (wantsAuth) {
37
+ s.start("Scaffolding auth…");
38
+ await setupAuth(projectRoot);
39
+ s.stop("Auth ready");
40
+ }
41
+ }
42
+
43
+ // ── 4. .env ──────────────────────────────────────────────────────────────────
44
+ await Bun.write(
45
+ join(projectRoot, ".env"),
46
+ `DATABASE_URL="postgresql://user:password@localhost:5432/mydb"\nJWT_SECRET="supersecret"\nPORT=3000\n`
47
+ );
48
+
49
+ // ── 5. .gitignore ────────────────────────────────────────────────────────────
50
+ await Bun.write(
51
+ join(projectRoot, ".gitignore"),
52
+ `node_modules\n.env\ngenerated\ndist\n`
53
+ );
54
+
55
+ // ── Done ─────────────────────────────────────────────────────────────────────
56
+ p.outro(
57
+ pc.green("✔ Done! ") +
58
+ `cd ${name}` +
59
+ (wantsPrisma ? " → update .env → bunx prisma migrate dev --name init -> bunx prisma generate " : "")
60
+ );
package/src/prompts.ts ADDED
@@ -0,0 +1,31 @@
1
+ import * as p from "@clack/prompts";
2
+ import pc from "picocolors";
3
+
4
+ export async function getProjectName(): Promise<string> {
5
+ const name = await p.text({
6
+ message: "Project name?",
7
+ placeholder: "my-bun-app",
8
+ validate: (v) => (!v ? "Name is required" : undefined),
9
+ });
10
+ if (p.isCancel(name)) cancel();
11
+ return name as string;
12
+ }
13
+
14
+ export async function askPrisma(): Promise<boolean> {
15
+ const use = await p.confirm({ message: "Add Prisma (PostgreSQL)?" });
16
+ if (p.isCancel(use)) cancel();
17
+ return use as boolean;
18
+ }
19
+
20
+ export async function askAuth(): Promise<boolean> {
21
+ const use = await p.confirm({
22
+ message: "Add JWT auth routes (requires Prisma)?",
23
+ });
24
+ if (p.isCancel(use)) cancel();
25
+ return use as boolean;
26
+ }
27
+
28
+ function cancel(): never {
29
+ p.cancel("Cancelled.");
30
+ process.exit(0);
31
+ }
@@ -0,0 +1,119 @@
1
+ import { execa } from "execa";
2
+ import { writeFileDeep } from "../utils/files.js";
3
+
4
+ const TYPES_TS = `import z from "zod";
5
+
6
+ export const SignupSchema = z.object({
7
+ email: z.string().email(),
8
+ password: z.string(),
9
+ });
10
+
11
+ export const SigninSchema = z.object({
12
+ email: z.string().email(),
13
+ password: z.string(),
14
+ });
15
+ `;
16
+
17
+ const INDEX_TS = `import express, {
18
+ type NextFunction,
19
+ type Request,
20
+ type Response,
21
+ } from "express";
22
+ import { prisma } from "./prisma/db";
23
+ import jwt, { type JwtPayload } from "jsonwebtoken";
24
+ import { SignupSchema, SigninSchema } from "./Types/types";
25
+
26
+ const app = express();
27
+ const SECRET = process.env.JWT_SECRET ?? "changeme_in_prod";
28
+
29
+ app.use(express.json());
30
+
31
+ // ── helpers ──────────────────────────────────────────────────────────────────
32
+ function ferr(msg: string, code: number, res: Response) {
33
+ return res.status(code).json({ success: false, data: null, error: msg });
34
+ }
35
+
36
+ interface AuthRequest extends Request {
37
+ id?: string;
38
+ role?: string;
39
+ }
40
+
41
+ export function authMiddleware() {
42
+ return (req: AuthRequest, res: Response, next: NextFunction) => {
43
+ try {
44
+ const token = req.headers.authorization?.split(" ")[1];
45
+ if (!token) return ferr("UNAUTHORIZED", 401, res);
46
+ const payload = jwt.verify(token, SECRET) as JwtPayload;
47
+ req.id = payload.id;
48
+ req.role = payload.role;
49
+ next();
50
+ } catch {
51
+ return ferr("UNAUTHORIZED", 401, res);
52
+ }
53
+ };
54
+ }
55
+
56
+ // ── routes ───────────────────────────────────────────────────────────────────
57
+ app.post("/api/auth/signup", async (req: Request, res: Response) => {
58
+ const parsed = SignupSchema.safeParse(req.body);
59
+ if (!parsed.success) return ferr("INVALID_REQUEST", 400, res);
60
+
61
+ const exists = await prisma.users.findUnique({
62
+ where: { email: parsed.data.email },
63
+ });
64
+ if (exists) return ferr("EMAIL_ALREADY_EXISTS", 400, res);
65
+
66
+ await prisma.users.create({
67
+ data: { email: parsed.data.email, password: parsed.data.password },
68
+ });
69
+
70
+ return res.status(201).json({ success: true, error: null });
71
+ });
72
+
73
+ app.post("/api/auth/login", async (req: Request, res: Response) => {
74
+ const parsed = SigninSchema.safeParse(req.body);
75
+ if (!parsed.success) return ferr("INVALID_REQUEST", 400, res);
76
+
77
+ const user = await prisma.users.findUnique({
78
+ where: { email: parsed.data.email },
79
+ });
80
+ if (!user) return ferr("INVALID_CREDENTIALS", 401, res);
81
+
82
+ const token = jwt.sign({ id: user.id, role: user.role }, SECRET);
83
+
84
+ return res.status(200).json({
85
+ success: true,
86
+ data: { token, user: { id: user.id, email: user.email } },
87
+ error: null,
88
+ });
89
+ });
90
+
91
+ // ── start ────────────────────────────────────────────────────────────────────
92
+ const PORT = process.env.PORT ?? 3000;
93
+ app.listen(PORT, () => console.log(\`🚀 Server running on port \${PORT}\`));
94
+
95
+ `;
96
+
97
+ export async function setupAuth(projectRoot: string): Promise<void> {
98
+ // Install deps
99
+ await execa(
100
+ "bun",
101
+ [
102
+ "add",
103
+ "express",
104
+ "@types/express",
105
+ "jsonwebtoken",
106
+ "@types/jsonwebtoken",
107
+ "zod",
108
+ ],
109
+ { cwd: projectRoot, stdio: "inherit" }
110
+ );
111
+
112
+ // Write Types/types.ts
113
+ await writeFileDeep(projectRoot, "Types/types.ts", TYPES_TS);
114
+
115
+ // Write src/index.ts
116
+ await writeFileDeep(projectRoot, "index.ts", INDEX_TS);
117
+
118
+ console.log("\n✅ Auth routes scaffolded in src/index.ts");
119
+ }
@@ -0,0 +1,70 @@
1
+ import { execa } from "execa";
2
+ import { writeFileDeep } from "../utils/files.js";
3
+
4
+ const DB_TS = `import "dotenv/config";
5
+ import { PrismaPg } from "@prisma/adapter-pg";
6
+ import { PrismaClient } from "../generated/prisma/client";
7
+
8
+ const connectionString = \`\${process.env.DATABASE_URL}\`;
9
+ const adapter = new PrismaPg({ connectionString });
10
+ const prisma = new PrismaClient({ adapter });
11
+
12
+ export { prisma };
13
+ `;
14
+
15
+ const SCHEMA_ADDITION = `
16
+ enum ROLE {
17
+ customer
18
+ admin
19
+ }
20
+
21
+ model users {
22
+ id String @id @db.VarChar(255) @default(uuid())
23
+ email String @unique @db.VarChar(255)
24
+ password String @db.VarChar(255)
25
+ role ROLE @default(customer)
26
+ }
27
+ `;
28
+
29
+ export async function setupPrisma(projectRoot: string): Promise<void> {
30
+ // 1. Install deps
31
+ await execa(
32
+ "bun",
33
+ [
34
+ "add",
35
+ "prisma",
36
+ "@types/pg",
37
+ "--save-dev",
38
+ "@prisma/client",
39
+ "@prisma/adapter-pg",
40
+ "pg",
41
+ "dotenv",
42
+ ],
43
+ { cwd: projectRoot, stdio: "inherit" }
44
+ );
45
+
46
+ // 2. Init prisma
47
+ await execa("bunx", ["prisma", "init"], {
48
+ cwd: projectRoot,
49
+ stdio: "inherit",
50
+ });
51
+
52
+ // 3. Write src/db.ts
53
+ await writeFileDeep(projectRoot, "prisma/db.ts", DB_TS);
54
+
55
+ // 4. Append models to prisma/schema.prisma
56
+ const schemaPath = `${projectRoot}/prisma/schema.prisma`;
57
+ const existing = await Bun.file(schemaPath).text();
58
+
59
+ // Add postgresql adapter preview feature
60
+ const updatedSchema = existing
61
+ .replace(
62
+ /generator client \{([^}]*)\}/,
63
+ `generator client {$1 previewFeatures = ["driverAdapters"]\n}`
64
+ )
65
+ .concat(SCHEMA_ADDITION);
66
+
67
+ await Bun.write(schemaPath, updatedSchema);
68
+
69
+ console.log("\n✅ Prisma set up. Don't forget to set DATABASE_URL in .env");
70
+ }
@@ -0,0 +1,12 @@
1
+ import { mkdir, writeFile } from "fs/promises";
2
+ import { join, dirname } from "path";
3
+
4
+ export async function writeFileDeep(
5
+ projectRoot: string,
6
+ relPath: string,
7
+ content: string
8
+ ): Promise<void> {
9
+ const fullPath = join(projectRoot, relPath);
10
+ await mkdir(dirname(fullPath), { recursive: true });
11
+ await writeFile(fullPath, content, "utf-8");
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ // Environment setup & latest features
8
+ "lib": ["ESNext"],
9
+ "moduleDetection": "force",
10
+ "jsx": "react-jsx",
11
+ "allowJs": true,
12
+
13
+ // Bundler mode
14
+ "moduleResolution": "bundler",
15
+ "allowImportingTsExtensions": true,
16
+ "verbatimModuleSyntax": true,
17
+ "noEmit": true,
18
+
19
+ // Best practices
20
+ "strict": true,
21
+ "skipLibCheck": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedIndexedAccess": true,
24
+ "noImplicitOverride": true,
25
+
26
+ // Some stricter flags (disabled by default)
27
+ "noUnusedLocals": false,
28
+ "noUnusedParameters": false,
29
+ "noPropertyAccessFromIndexSignature": false
30
+ }
31
+ }