@rellcodes16/devkit 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 +128 -0
- package/bin/devkit.js +55 -0
- package/package.json +42 -0
- package/src/commands/createNode.js +219 -0
- package/src/commands/createReact.js +124 -0
- package/src/templates/node/index.js +666 -0
- package/src/templates/react/index.js +172 -0
- package/src/utils.js +40 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
export function nodePackageJson(appname, options) {
|
|
2
|
+
const deps = {
|
|
3
|
+
express: "^4.19.2",
|
|
4
|
+
cors: "^2.8.5",
|
|
5
|
+
dotenv: "^16.4.5",
|
|
6
|
+
morgan: "^1.10.0",
|
|
7
|
+
helmet: "^7.1.0",
|
|
8
|
+
"cookie-parser": "^1.4.6",
|
|
9
|
+
"express-rate-limit": "^7.3.1",
|
|
10
|
+
"express-mongo-sanitize": "^2.2.0",
|
|
11
|
+
"xss-clean": "^0.1.4",
|
|
12
|
+
"serve-favicon": "^2.5.0",
|
|
13
|
+
validator: "^13.12.0",
|
|
14
|
+
uuid: "^10.0.0",
|
|
15
|
+
"http-errors": "^2.0.0",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (options.mongo) deps["mongoose"] = "^8.4.3";
|
|
19
|
+
if (options.prisma) deps["@prisma/client"] = "^5.16.0";
|
|
20
|
+
if (options.turso) deps["@libsql/client"] = "^0.6.0";
|
|
21
|
+
if (options.pg) deps["postgres"] = "^3.4.4";
|
|
22
|
+
if (options.supabase) deps["@supabase/supabase-js"] = "^2.44.2";
|
|
23
|
+
if (options.redis) deps["ioredis"] = "^5.4.1";
|
|
24
|
+
|
|
25
|
+
if (options.drizzle) deps["drizzle-orm"] = "^0.32.0";
|
|
26
|
+
|
|
27
|
+
if (options.cloudinary) {
|
|
28
|
+
deps["cloudinary"] = "^2.3.1";
|
|
29
|
+
deps["multer"] = "^1.4.5-lts.1";
|
|
30
|
+
deps["multer-storage-cloudinary"] = "^4.0.0";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (options.socket) deps["socket.io"] = "^4.7.5";
|
|
34
|
+
|
|
35
|
+
const scripts = {
|
|
36
|
+
dev: "nodemon src/index.js",
|
|
37
|
+
start: "node src/index.js",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (options.prisma) {
|
|
41
|
+
scripts["db:migrate"] = "npx prisma migrate dev";
|
|
42
|
+
scripts["db:studio"] = "npx prisma studio";
|
|
43
|
+
scripts["db:generate"] = "npx prisma generate";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (options.drizzle) {
|
|
47
|
+
scripts["db:push"] = "npx drizzle-kit push";
|
|
48
|
+
scripts["db:studio"] = "npx drizzle-kit studio";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return JSON.stringify(
|
|
52
|
+
{
|
|
53
|
+
name: appname,
|
|
54
|
+
version: "1.0.0",
|
|
55
|
+
description: "",
|
|
56
|
+
main: "src/index.js",
|
|
57
|
+
type: "module",
|
|
58
|
+
scripts,
|
|
59
|
+
dependencies: deps,
|
|
60
|
+
devDependencies: { nodemon: "^3.1.4" },
|
|
61
|
+
},
|
|
62
|
+
null,
|
|
63
|
+
2
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function nodeIndexJs(options) {
|
|
68
|
+
const imports = [
|
|
69
|
+
`import express from "express";`,
|
|
70
|
+
`import cors from "cors";`,
|
|
71
|
+
`import morgan from "morgan";`,
|
|
72
|
+
`import helmet from "helmet";`,
|
|
73
|
+
`import dotenv from "dotenv";`,
|
|
74
|
+
`import cookieParser from "cookie-parser";`,
|
|
75
|
+
`import rateLimit from "express-rate-limit";`,
|
|
76
|
+
`import mongoSanitize from "express-mongo-sanitize";`,
|
|
77
|
+
`import xss from "xss-clean";`,
|
|
78
|
+
`import favicon from "serve-favicon";`,
|
|
79
|
+
`import { fileURLToPath } from "url";`,
|
|
80
|
+
`import path from "path";`,
|
|
81
|
+
`import routes from "./routes/index.js";`,
|
|
82
|
+
`import { errorHandler } from "./middleware/errorHandler.js";`,
|
|
83
|
+
`import { notFound } from "./middleware/notFound.js";`,
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
if (options.mongo) imports.push(`import connectDB from "./config/db.js";`);
|
|
87
|
+
if (options.prisma) imports.push(`import prisma from "./config/db.js";`);
|
|
88
|
+
if (options.turso) imports.push(`import { db } from "./config/db.js";`);
|
|
89
|
+
if (options.pg) imports.push(`import sql from "./config/db.js";`);
|
|
90
|
+
if (options.supabase) imports.push(`import supabase from "./config/db.js";`);
|
|
91
|
+
if (options.redis) imports.push(`import redis from "./config/redis.js";`);
|
|
92
|
+
|
|
93
|
+
if (options.socket) {
|
|
94
|
+
imports.push(
|
|
95
|
+
`import { createServer } from "http";`,
|
|
96
|
+
`import { Server } from "socket.io";`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const socketSetup = options.socket
|
|
101
|
+
? `
|
|
102
|
+
const httpServer = createServer(app);
|
|
103
|
+
const io = new Server(httpServer, {
|
|
104
|
+
cors: { origin: process.env.CLIENT_URL || "*", credentials: true },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
io.on("connection", (socket) => {
|
|
108
|
+
console.log("Client connected:", socket.id);
|
|
109
|
+
socket.on("disconnect", () => console.log("Client disconnected:", socket.id));
|
|
110
|
+
});
|
|
111
|
+
`
|
|
112
|
+
: "";
|
|
113
|
+
|
|
114
|
+
const listenTarget = options.socket ? "httpServer" : "app";
|
|
115
|
+
|
|
116
|
+
const dbConnect = options.mongo
|
|
117
|
+
? `\n// Connect to MongoDB\nawait connectDB();\n`
|
|
118
|
+
: "";
|
|
119
|
+
|
|
120
|
+
const rateLimitSetup = `
|
|
121
|
+
const limiter = rateLimit({
|
|
122
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
123
|
+
max: 100,
|
|
124
|
+
standardHeaders: true,
|
|
125
|
+
legacyHeaders: false,
|
|
126
|
+
message: { error: "Too many requests, please try again later." },
|
|
127
|
+
});`;
|
|
128
|
+
|
|
129
|
+
return `${imports.join("\n")}
|
|
130
|
+
|
|
131
|
+
dotenv.config();
|
|
132
|
+
|
|
133
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
134
|
+
const __dirname = path.dirname(__filename);
|
|
135
|
+
|
|
136
|
+
const app = express();
|
|
137
|
+
const PORT = process.env.PORT || 3000;
|
|
138
|
+
${socketSetup}${dbConnect}${rateLimitSetup}
|
|
139
|
+
|
|
140
|
+
// Security middleware
|
|
141
|
+
app.use(helmet());
|
|
142
|
+
app.use(mongoSanitize());
|
|
143
|
+
app.use(xss());
|
|
144
|
+
app.use("/api", limiter);
|
|
145
|
+
|
|
146
|
+
// Core middleware
|
|
147
|
+
app.use(cors({ origin: process.env.CLIENT_URL || "*", credentials: true }));
|
|
148
|
+
app.use(morgan(process.env.NODE_ENV === "production" ? "combined" : "dev"));
|
|
149
|
+
app.use(express.json({ limit: "10kb" }));
|
|
150
|
+
app.use(express.urlencoded({ extended: true }));
|
|
151
|
+
app.use(cookieParser());
|
|
152
|
+
app.use(favicon(path.join(__dirname, "../public", "favicon.ico")));
|
|
153
|
+
|
|
154
|
+
// Routes
|
|
155
|
+
app.use("/api", routes);
|
|
156
|
+
|
|
157
|
+
// 404 + error handler
|
|
158
|
+
app.use(notFound);
|
|
159
|
+
app.use(errorHandler);
|
|
160
|
+
|
|
161
|
+
${listenTarget}.listen(PORT, () => {
|
|
162
|
+
console.log(\`Server running on http://localhost:\${PORT}\`);
|
|
163
|
+
});
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function nodeEnv(options) {
|
|
168
|
+
const lines = [
|
|
169
|
+
`PORT=3000`,
|
|
170
|
+
`NODE_ENV=development`,
|
|
171
|
+
`CLIENT_URL=http://localhost:5173`,
|
|
172
|
+
``,
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
if (options.mongo) {
|
|
176
|
+
lines.push(`# MongoDB`);
|
|
177
|
+
lines.push(`MONGODB_URI=mongodb://localhost:27017/mydb`);
|
|
178
|
+
lines.push(``);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (options.prisma) {
|
|
182
|
+
lines.push(`# Database (Prisma)`);
|
|
183
|
+
lines.push(`DATABASE_URL=postgresql://user:password@localhost:5432/mydb`);
|
|
184
|
+
lines.push(``);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options.turso) {
|
|
188
|
+
lines.push(`# Turso (LibSQL)`);
|
|
189
|
+
lines.push(`TURSO_DATABASE_URL=libsql://your-db.turso.io`);
|
|
190
|
+
lines.push(`TURSO_AUTH_TOKEN=your-auth-token`);
|
|
191
|
+
lines.push(``);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (options.pg) {
|
|
195
|
+
lines.push(`# PostgreSQL`);
|
|
196
|
+
lines.push(`DATABASE_URL=postgresql://user:password@localhost:5432/mydb`);
|
|
197
|
+
lines.push(``);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (options.supabase) {
|
|
201
|
+
lines.push(`# Supabase`);
|
|
202
|
+
lines.push(`SUPABASE_URL=https://your-project.supabase.co`);
|
|
203
|
+
lines.push(`SUPABASE_ANON_KEY=your-anon-key`);
|
|
204
|
+
lines.push(`SUPABASE_SERVICE_ROLE_KEY=your-service-role-key`);
|
|
205
|
+
lines.push(``);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (options.redis) {
|
|
209
|
+
lines.push(`# Redis`);
|
|
210
|
+
lines.push(`REDIS_URL=redis://localhost:6379`);
|
|
211
|
+
lines.push(``);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.cloudinary) {
|
|
215
|
+
lines.push(`# Cloudinary`);
|
|
216
|
+
lines.push(`CLOUDINARY_CLOUD_NAME=your-cloud-name`);
|
|
217
|
+
lines.push(`CLOUDINARY_API_KEY=your-api-key`);
|
|
218
|
+
lines.push(`CLOUDINARY_API_SECRET=your-api-secret`);
|
|
219
|
+
lines.push(``);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
lines.push(`# Auth`);
|
|
223
|
+
lines.push(`JWT_SECRET=change-this-to-a-long-random-secret`);
|
|
224
|
+
lines.push(`JWT_EXPIRES_IN=7d`);
|
|
225
|
+
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function nodeGitignore() {
|
|
230
|
+
return `node_modules/
|
|
231
|
+
.env
|
|
232
|
+
.env.local
|
|
233
|
+
dist/
|
|
234
|
+
*.log
|
|
235
|
+
.DS_Store
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function nodeMongoConfig() {
|
|
240
|
+
return `import mongoose from "mongoose";
|
|
241
|
+
|
|
242
|
+
export default async function connectDB() {
|
|
243
|
+
try {
|
|
244
|
+
const conn = await mongoose.connect(process.env.MONGODB_URI);
|
|
245
|
+
console.log(\`MongoDB connected: \${conn.connection.host}\`);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error("MongoDB connection failed:", err.message);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function nodePrismaConfig() {
|
|
255
|
+
return `import { PrismaClient } from "@prisma/client";
|
|
256
|
+
|
|
257
|
+
const prisma = new PrismaClient({
|
|
258
|
+
log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export default prisma;
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Turso — raw SQL client only (no Drizzle unless --drizzle is also passed)
|
|
266
|
+
export function nodeTursoConfig() {
|
|
267
|
+
return `import { createClient } from "@libsql/client";
|
|
268
|
+
|
|
269
|
+
const db = createClient({
|
|
270
|
+
url: process.env.TURSO_DATABASE_URL,
|
|
271
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Usage: const { rows } = await db.execute("SELECT * FROM users WHERE id = ?", [id]);
|
|
275
|
+
export default db;
|
|
276
|
+
`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Turso + Drizzle (only when --drizzle flag is also set)
|
|
280
|
+
export function nodeTursoWithDrizzleConfig() {
|
|
281
|
+
return `import { createClient } from "@libsql/client";
|
|
282
|
+
import { drizzle } from "drizzle-orm/libsql";
|
|
283
|
+
import * as schema from "./schema.js";
|
|
284
|
+
|
|
285
|
+
const client = createClient({
|
|
286
|
+
url: process.env.TURSO_DATABASE_URL,
|
|
287
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
export const db = drizzle(client, { schema });
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function nodeTursoSchema() {
|
|
295
|
+
return `import { text, integer, sqliteTable } from "drizzle-orm/sqlite-core";
|
|
296
|
+
|
|
297
|
+
// Example table, replace or extend with your own models
|
|
298
|
+
export const users = sqliteTable("users", {
|
|
299
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
300
|
+
email: text("email").notNull().unique(),
|
|
301
|
+
name: text("name"),
|
|
302
|
+
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
|
|
303
|
+
});
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function nodeDrizzleConfig(options) {
|
|
308
|
+
const isPg = options.pg;
|
|
309
|
+
return `import { defineConfig } from "drizzle-kit";
|
|
310
|
+
import dotenv from "dotenv";
|
|
311
|
+
dotenv.config();
|
|
312
|
+
|
|
313
|
+
export default defineConfig({
|
|
314
|
+
schema: "./src/config/schema.js",
|
|
315
|
+
out: "./drizzle",
|
|
316
|
+
dialect: "${isPg ? "postgresql" : "turso"}",
|
|
317
|
+
dbCredentials: {
|
|
318
|
+
${isPg
|
|
319
|
+
? ` url: process.env.DATABASE_URL,`
|
|
320
|
+
: ` url: process.env.TURSO_DATABASE_URL,\n authToken: process.env.TURSO_AUTH_TOKEN,`}
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function nodePgConfig() {
|
|
327
|
+
return `import postgres from "postgres";
|
|
328
|
+
|
|
329
|
+
const sql = postgres(process.env.DATABASE_URL, {
|
|
330
|
+
max: 10, // connection pool size
|
|
331
|
+
idle_timeout: 20, // close idle connections after 20s
|
|
332
|
+
connect_timeout: 10, // fail fast if DB unreachable
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Usage: const users = await sql\`SELECT * FROM users WHERE id = \${id}\`;
|
|
336
|
+
export default sql;
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function nodePgWithDrizzleConfig() {
|
|
341
|
+
return `import postgres from "postgres";
|
|
342
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
343
|
+
import * as schema from "./schema.js";
|
|
344
|
+
|
|
345
|
+
const client = postgres(process.env.DATABASE_URL, { max: 10 });
|
|
346
|
+
export const db = drizzle(client, { schema });
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function nodePgSchema() {
|
|
351
|
+
return `import { pgTable, serial, text, varchar, timestamp } from "drizzle-orm/pg-core";
|
|
352
|
+
|
|
353
|
+
// Example table — replace or extend with your own models
|
|
354
|
+
export const users = pgTable("users", {
|
|
355
|
+
id: serial("id").primaryKey(),
|
|
356
|
+
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
357
|
+
name: text("name"),
|
|
358
|
+
createdAt: timestamp("created_at").defaultNow(),
|
|
359
|
+
});
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Supabase
|
|
364
|
+
export function nodeSupabaseConfig() {
|
|
365
|
+
return `import { createClient } from "@supabase/supabase-js";
|
|
366
|
+
|
|
367
|
+
// Public client — safe for read operations and auth flows
|
|
368
|
+
export const supabase = createClient(
|
|
369
|
+
process.env.SUPABASE_URL,
|
|
370
|
+
process.env.SUPABASE_ANON_KEY
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Admin client — full access, never expose to the browser
|
|
374
|
+
export const supabaseAdmin = createClient(
|
|
375
|
+
process.env.SUPABASE_URL,
|
|
376
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
export default supabase;
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Redis
|
|
384
|
+
export function nodeRedisConfig() {
|
|
385
|
+
return `import Redis from "ioredis";
|
|
386
|
+
|
|
387
|
+
const redis = new Redis(process.env.REDIS_URL, {
|
|
388
|
+
maxRetriesPerRequest: 3,
|
|
389
|
+
enableReadyCheck: true,
|
|
390
|
+
lazyConnect: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
redis.on("connect", () => console.log("Redis connected"));
|
|
394
|
+
redis.on("error", (err) => console.error("Redis error:", err.message));
|
|
395
|
+
|
|
396
|
+
// Helpers
|
|
397
|
+
export async function getCache(key) {
|
|
398
|
+
const data = await redis.get(key);
|
|
399
|
+
return data ? JSON.parse(data) : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function setCache(key, value, ttlSeconds = 300) {
|
|
403
|
+
await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function deleteCache(key) {
|
|
407
|
+
await redis.del(key);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export default redis;
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function nodePrismaSchema() {
|
|
415
|
+
return `generator client {
|
|
416
|
+
provider = "prisma-client-js"
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
datasource db {
|
|
420
|
+
provider = "postgresql"
|
|
421
|
+
url = env("DATABASE_URL")
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Add your models below
|
|
425
|
+
// model User {
|
|
426
|
+
// id String @id @default(uuid())
|
|
427
|
+
// email String @unique
|
|
428
|
+
// name String?
|
|
429
|
+
// createdAt DateTime @default(now())
|
|
430
|
+
// updatedAt DateTime @updatedAt
|
|
431
|
+
// }
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function nodeCloudinaryConfig() {
|
|
436
|
+
return `import { v2 as cloudinary } from "cloudinary";
|
|
437
|
+
import { CloudinaryStorage } from "multer-storage-cloudinary";
|
|
438
|
+
import multer from "multer";
|
|
439
|
+
|
|
440
|
+
cloudinary.config({
|
|
441
|
+
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
442
|
+
api_key: process.env.CLOUDINARY_API_KEY,
|
|
443
|
+
api_secret: process.env.CLOUDINARY_API_SECRET,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const storage = new CloudinaryStorage({
|
|
447
|
+
cloudinary,
|
|
448
|
+
params: {
|
|
449
|
+
folder: "uploads",
|
|
450
|
+
allowed_formats: ["jpg", "jpeg", "png", "webp", "gif"],
|
|
451
|
+
transformation: [{ width: 1200, crop: "limit" }],
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
export const upload = multer({ storage });
|
|
456
|
+
export default cloudinary;
|
|
457
|
+
`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function nodeErrorHandler() {
|
|
461
|
+
return `export function errorHandler(err, req, res, next) {
|
|
462
|
+
const status = err.status || err.statusCode || 500;
|
|
463
|
+
const message = err.message || "Internal Server Error";
|
|
464
|
+
|
|
465
|
+
if (process.env.NODE_ENV === "development") {
|
|
466
|
+
console.error(\`[ERROR \${status}]\`, err.stack);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
res.status(status).json({
|
|
470
|
+
success: false,
|
|
471
|
+
error: message,
|
|
472
|
+
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function nodeNotFound() {
|
|
479
|
+
return `export function notFound(req, res, next) {
|
|
480
|
+
res.status(404).json({
|
|
481
|
+
success: false,
|
|
482
|
+
error: \`Route not found — \${req.method} \${req.originalUrl}\`,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function nodeAsyncHandler() {
|
|
489
|
+
return `/**
|
|
490
|
+
* Wraps an async route handler and forwards errors to Express error middleware.
|
|
491
|
+
* Eliminates try/catch boilerplate in every controller.
|
|
492
|
+
*
|
|
493
|
+
* Usage:
|
|
494
|
+
* router.get("/", asyncHandler(async (req, res) => { ... }));
|
|
495
|
+
*/
|
|
496
|
+
export const asyncHandler = (fn) => (req, res, next) =>
|
|
497
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function nodeRoutes() {
|
|
502
|
+
return `import { Router } from "express";
|
|
503
|
+
|
|
504
|
+
const router = Router();
|
|
505
|
+
|
|
506
|
+
router.get("/health", (req, res) => {
|
|
507
|
+
res.json({ success: true, status: "ok", timestamp: new Date().toISOString() });
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
export default router;
|
|
511
|
+
`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function nodeUserModel(withCloudinary = false) {
|
|
515
|
+
const avatarField = withCloudinary
|
|
516
|
+
? ` avatar: {
|
|
517
|
+
url: { type: String, default: "" },
|
|
518
|
+
publicId: { type: String, default: "" },
|
|
519
|
+
},`
|
|
520
|
+
: "";
|
|
521
|
+
|
|
522
|
+
return `import mongoose from "mongoose";
|
|
523
|
+
import validator from "validator";
|
|
524
|
+
import { v4 as uuidv4 } from "uuid";
|
|
525
|
+
|
|
526
|
+
const userSchema = new mongoose.Schema(
|
|
527
|
+
{
|
|
528
|
+
_id: { type: String, default: uuidv4 },
|
|
529
|
+
name: {
|
|
530
|
+
type: String,
|
|
531
|
+
required: [true, "Name is required"],
|
|
532
|
+
trim: true,
|
|
533
|
+
minlength: [2, "Name must be at least 2 characters"],
|
|
534
|
+
},
|
|
535
|
+
email: {
|
|
536
|
+
type: String,
|
|
537
|
+
required: [true, "Email is required"],
|
|
538
|
+
unique: true,
|
|
539
|
+
lowercase: true,
|
|
540
|
+
validate: [validator.isEmail, "Please provide a valid email"],
|
|
541
|
+
},
|
|
542
|
+
password: {
|
|
543
|
+
type: String,
|
|
544
|
+
required: [true, "Password is required"],
|
|
545
|
+
minlength: [8, "Password must be at least 8 characters"],
|
|
546
|
+
select: false,
|
|
547
|
+
},
|
|
548
|
+
role: {
|
|
549
|
+
type: String,
|
|
550
|
+
enum: ["user", "admin"],
|
|
551
|
+
default: "user",
|
|
552
|
+
},
|
|
553
|
+
${avatarField}
|
|
554
|
+
isActive: { type: Boolean, default: true },
|
|
555
|
+
},
|
|
556
|
+
{ timestamps: true }
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
// Strip password from JSON responses
|
|
560
|
+
userSchema.set("toJSON", {
|
|
561
|
+
transform: (doc, ret) => {
|
|
562
|
+
delete ret.password;
|
|
563
|
+
return ret;
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
const User = mongoose.model("User", userSchema);
|
|
568
|
+
export default User;
|
|
569
|
+
`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function nodeReadme(appname, options) {
|
|
573
|
+
const stackLines = [
|
|
574
|
+
"- Node.js + Express",
|
|
575
|
+
"- Helmet (security headers)",
|
|
576
|
+
"- CORS",
|
|
577
|
+
"- Morgan (logging)",
|
|
578
|
+
"- express-rate-limit",
|
|
579
|
+
"- express-mongo-sanitize",
|
|
580
|
+
"- xss-clean",
|
|
581
|
+
"- cookie-parser",
|
|
582
|
+
"- dotenv",
|
|
583
|
+
"- validator",
|
|
584
|
+
"- uuid",
|
|
585
|
+
"- serve-favicon",
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
if (options.mongo) stackLines.push("- Mongoose (MongoDB)");
|
|
589
|
+
if (options.prisma) stackLines.push("- Prisma (PostgreSQL)");
|
|
590
|
+
if (options.turso) stackLines.push(options.drizzle ? "- Turso (LibSQL) + Drizzle ORM" : "- Turso (LibSQL) — raw SQL client");
|
|
591
|
+
if (options.pg) stackLines.push(options.drizzle ? "- postgres.js (PostgreSQL) + Drizzle ORM" : "- postgres.js (PostgreSQL)");
|
|
592
|
+
if (options.supabase) stackLines.push("- Supabase JS client");
|
|
593
|
+
if (options.redis) stackLines.push("- Redis (ioredis)");
|
|
594
|
+
if (options.cloudinary) stackLines.push("- Cloudinary + Multer");
|
|
595
|
+
if (options.socket) stackLines.push("- Socket.io");
|
|
596
|
+
|
|
597
|
+
const dbSteps = [];
|
|
598
|
+
if (options.mongo) dbSteps.push("# Set MONGODB_URI to your local or Atlas connection string");
|
|
599
|
+
if (options.prisma) dbSteps.push("npx prisma migrate dev # after setting DATABASE_URL");
|
|
600
|
+
if (options.turso && options.drizzle) dbSteps.push("npx drizzle-kit push # after setting TURSO credentials");
|
|
601
|
+
if (options.pg && options.drizzle) dbSteps.push("npx drizzle-kit push # after setting DATABASE_URL");
|
|
602
|
+
if (options.supabase) dbSteps.push("# Set SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY");
|
|
603
|
+
if (options.redis) dbSteps.push("# Make sure Redis is running: redis-server");
|
|
604
|
+
|
|
605
|
+
const modelSection = options.mongo ? " models/ # Mongoose models\n " : "";
|
|
606
|
+
|
|
607
|
+
const configLines = [" config/", " db.js # Database connection"];
|
|
608
|
+
if (options.redis) configLines.push(" redis.js # Redis client + cache helpers");
|
|
609
|
+
if (options.cloudinary) configLines.push(" cloudinary.js # Cloudinary config + multer");
|
|
610
|
+
if (options.drizzle) configLines.push(" schema.js # Drizzle table definitions");
|
|
611
|
+
|
|
612
|
+
const envRows = [
|
|
613
|
+
"| `PORT` | Server port (default 3000) |",
|
|
614
|
+
"| `NODE_ENV` | Environment (development / production) |",
|
|
615
|
+
"| `CLIENT_URL` | Allowed CORS origin |",
|
|
616
|
+
options.mongo && "| `MONGODB_URI` | MongoDB connection string |",
|
|
617
|
+
(options.prisma || options.pg) && "| `DATABASE_URL` | PostgreSQL connection string |",
|
|
618
|
+
options.turso && "| `TURSO_DATABASE_URL` | Turso database URL |",
|
|
619
|
+
options.turso && "| `TURSO_AUTH_TOKEN` | Turso auth token |",
|
|
620
|
+
options.supabase && "| `SUPABASE_URL` | Supabase project URL |",
|
|
621
|
+
options.supabase && "| `SUPABASE_ANON_KEY` | Supabase anon/public key |",
|
|
622
|
+
options.supabase && "| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key (server only) |",
|
|
623
|
+
options.redis && "| `REDIS_URL` | Redis connection URL |",
|
|
624
|
+
options.cloudinary && "| `CLOUDINARY_CLOUD_NAME` | Cloudinary cloud name |",
|
|
625
|
+
options.cloudinary && "| `CLOUDINARY_API_KEY` | Cloudinary API key |",
|
|
626
|
+
options.cloudinary && "| `CLOUDINARY_API_SECRET` | Cloudinary API secret |",
|
|
627
|
+
"| `JWT_SECRET` | JWT signing secret |",
|
|
628
|
+
"| `JWT_EXPIRES_IN` | JWT expiry (e.g. 7d) |",
|
|
629
|
+
].filter(Boolean).join("\n");
|
|
630
|
+
|
|
631
|
+
return `# ${appname}
|
|
632
|
+
|
|
633
|
+
Scaffolded with [devkit](https://github.com/rellcodes16/devkit).
|
|
634
|
+
|
|
635
|
+
## Stack
|
|
636
|
+
|
|
637
|
+
${stackLines.join("\n")}
|
|
638
|
+
|
|
639
|
+
## Getting started
|
|
640
|
+
|
|
641
|
+
\`\`\`bash
|
|
642
|
+
cp .env.example .env
|
|
643
|
+
${dbSteps.join("\n")}
|
|
644
|
+
npm run dev
|
|
645
|
+
\`\`\`
|
|
646
|
+
|
|
647
|
+
## Project structure
|
|
648
|
+
|
|
649
|
+
\`\`\`
|
|
650
|
+
src/
|
|
651
|
+
routes/ # Express routers
|
|
652
|
+
controllers/ # Route handler logic
|
|
653
|
+
middleware/ # errorHandler, notFound, asyncHandler
|
|
654
|
+
${modelSection}${configLines.join("\n")}
|
|
655
|
+
index.js # App entry point
|
|
656
|
+
public/
|
|
657
|
+
favicon.ico
|
|
658
|
+
\`\`\`
|
|
659
|
+
|
|
660
|
+
## Environment variables
|
|
661
|
+
|
|
662
|
+
| Variable | Description |
|
|
663
|
+
|---|---|
|
|
664
|
+
${envRows}
|
|
665
|
+
`;
|
|
666
|
+
}
|