@shakil-dev/shakil-stack 2.1.2 → 2.2.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/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env node
2
+ import{Command as ae}from"commander";import Q from"fs-extra";import w from"path";import{fileURLToPath as ie}from"url";import o from"fs-extra";import s from"path";import n from"chalk";import ee from"ora";import M from"inquirer";import{execSync as X}from"child_process";import ue from"fs-extra";var i=(e,r=process.cwd())=>{try{return X(e,{stdio:"inherit",cwd:r}),!0}catch{return!1}},f=()=>{let e=process.env.npm_config_user_agent||"";return e.includes("pnpm")?"pnpm":e.includes("yarn")?"yarn":"npm"};var j=e=>`import { Server } from 'http';
3
+ import app from './app.js';
4
+ import config from './app/config/index.js';
5
+
6
+ async function bootstrap() {
7
+ try {
8
+ const server: Server = app.listen(config.port, () => {
9
+ console.log(\`\u{1F680} Server is running on http://localhost:\${config.port}\`);
10
+ });
11
+ } catch (error) {
12
+ console.error('Failed to start server:', error);
13
+ }
14
+ }
15
+
16
+ bootstrap();
17
+ `,T=e=>`import cors from 'cors';
18
+ import express, { Application, Request, Response } from 'express';
19
+ import httpStatus from 'http-status';
20
+ import globalErrorHandler from './app/middleware/globalErrorHandler.js';
21
+ import notFound from './app/middleware/notFound.js';
22
+ import router from './app/routes/index.js';
23
+ import cookieParser from 'cookie-parser';
24
+ import morgan from 'morgan';
25
+ import helmet from 'helmet';
26
+ import { rateLimit } from 'express-rate-limit';
27
+ import { sanitizeRequest } from './app/middleware/sanitizeRequest.js';
28
+
29
+ const app: Application = express();
30
+
31
+ app.use(helmet());
32
+ app.use(cors({
33
+ origin: ["http://localhost:3000", "http://127.0.0.1:3000"],
34
+ credentials: true,
35
+ methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
36
+ allowedHeaders: ["Content-Type", "Authorization", "Cookie"]
37
+ }));
38
+
39
+ const authLimiter = rateLimit({
40
+ windowMs: 15 * 60 * 1000,
41
+ limit: 100,
42
+ standardHeaders: 'draft-7',
43
+ legacyHeaders: false,
44
+ message: "Too many requests from this IP, please try again after 15 minutes"
45
+ });
46
+
47
+ app.use('/api/v1/auth', authLimiter);
48
+
49
+ app.use(cookieParser());
50
+ app.use(express.json({ limit: '10mb' }));
51
+ app.use(express.urlencoded({ extended: true, limit: '10mb' }));
52
+ app.use(sanitizeRequest);
53
+ app.use(morgan("dev"));
54
+
55
+ app.use('/api/v1', router);
56
+
57
+ app.get('/', (req: Request, res: Response) => {
58
+ res.status(httpStatus.OK).json({
59
+ success: true,
60
+ message: 'Welcome to ${e} API',
61
+ });
62
+ });
63
+
64
+ app.use(globalErrorHandler);
65
+ app.use(notFound);
66
+
67
+ export default app;
68
+ `,S=`import dotenv from 'dotenv';
69
+ import path from 'path';
70
+
71
+ dotenv.config({ path: path.join(process.cwd(), '.env') });
72
+
73
+ export default {
74
+ env: process.env.NODE_ENV,
75
+ port: process.env.PORT || 8000,
76
+ database_url: process.env.DATABASE_URL,
77
+ jwt_secret: process.env.JWT_SECRET,
78
+ };
79
+ `,R=`import "dotenv/config";
80
+ import { PrismaClient } from "@prisma/client";
81
+ import pkg from 'pg';
82
+ import { PrismaPg } from '@prisma/adapter-pg';
83
+ import config from '../config/index.js';
84
+
85
+ const { Pool } = pkg;
86
+ const connectionString = config.database_url as string;
87
+ const pool = new Pool({ connectionString });
88
+ const adapter = new PrismaPg(pool as any);
89
+ const prisma = new PrismaClient({ adapter });
90
+
91
+ export default prisma;
92
+ export { prisma };
93
+ `,v=`import { betterAuth } from "better-auth";
94
+ import { prismaAdapter } from "better-auth/adapters/prisma";
95
+ import config from "../config/index.js";
96
+ import { prisma } from "./prisma.js";
97
+
98
+ export const auth = betterAuth({
99
+ database: prismaAdapter(prisma, {
100
+ provider: "postgresql",
101
+ }),
102
+ secret: config.jwt_secret,
103
+ baseURL: "http://localhost:8000",
104
+ trustedOrigins: ["http://localhost:3000"],
105
+ emailAndPassword: {
106
+ enabled: true,
107
+ },
108
+ });
109
+ `,D=`import { Router } from 'express';
110
+ const router = Router();
111
+ export default router;
112
+ `,A=`import { ErrorRequestHandler } from 'express';
113
+ import config from '../config/index.js';
114
+
115
+ const globalErrorHandler: ErrorRequestHandler = (error, req, res, next) => {
116
+ res.status(500).json({
117
+ success: false,
118
+ message: error.message || 'Something went wrong!',
119
+ stack: config.env !== 'production' ? error?.stack : undefined,
120
+ });
121
+ };
122
+
123
+ export default globalErrorHandler;
124
+ `,F=`import { Request, Response, NextFunction } from 'express';
125
+ import httpStatus from 'http-status';
126
+
127
+ const notFound = (req: Request, res: Response, next: NextFunction) => {
128
+ res.status(httpStatus.NOT_FOUND).json({
129
+ success: false,
130
+ message: 'API Not Found',
131
+ });
132
+ };
133
+
134
+ export default notFound;
135
+ `,P=`import { NextFunction, Request, RequestHandler, Response } from 'express';
136
+
137
+ const catchAsync = (fn: RequestHandler) => {
138
+ return async (req: Request, res: Response, next: NextFunction) => {
139
+ try {
140
+ await fn(req, res, next);
141
+ } catch (error) {
142
+ next(error);
143
+ }
144
+ };
145
+ };
146
+
147
+ export default catchAsync;
148
+ `,$=`class ApiError extends Error {
149
+ statusCode: number;
150
+ constructor(statusCode: number, message: string | undefined, stack = '') {
151
+ super(message);
152
+ this.statusCode = statusCode;
153
+ if (stack) this.stack = stack;
154
+ else Error.captureStackTrace(this, this.constructor);
155
+ }
156
+ }
157
+ export default ApiError;
158
+ `,q=`import { JSDOM } from 'jsdom';
159
+ import createDOMPurify from 'dompurify';
160
+
161
+ const window = new JSDOM('').window;
162
+ const DOMPurify = createDOMPurify(window as any);
163
+
164
+ export const sanitize = (data: any): any => {
165
+ if (typeof data === 'string') return DOMPurify.sanitize(data);
166
+ if (typeof data === 'object' && data !== null) {
167
+ for (const key in data) {
168
+ if (Object.prototype.hasOwnProperty.call(data, key)) data[key] = sanitize(data[key]);
169
+ }
170
+ }
171
+ return data;
172
+ };
173
+ `,E=`import { Request, Response, NextFunction } from 'express';
174
+ import { sanitize } from '../utils/sanitizer.js';
175
+
176
+ export const sanitizeRequest = (req: Request, res: Response, next: NextFunction) => {
177
+ if (req.body) sanitize(req.body);
178
+ if (req.query) sanitize(req.query);
179
+ if (req.params) sanitize(req.params);
180
+ next();
181
+ };
182
+ `,C=`import { Response } from 'express';
183
+
184
+ type IResponse<T> = {
185
+ statusCode: number;
186
+ success: boolean;
187
+ message?: string | null;
188
+ meta?: {
189
+ limit: number;
190
+ page: number;
191
+ total: number;
192
+ };
193
+ data: T;
194
+ };
195
+
196
+ const sendResponse = <T>(res: Response, data: IResponse<T>) => {
197
+ res.status(data.statusCode).json({
198
+ success: data.success,
199
+ message: data.message || null,
200
+ meta: data.meta || null,
201
+ data: data.data || null,
202
+ });
203
+ };
204
+
205
+ export default sendResponse;
206
+ `,z=`generator client {
207
+ provider = "prisma-client-js"
208
+ previewFeatures = ["prismaSchemaFolder"]
209
+ }
210
+
211
+ datasource db {
212
+ provider = "postgresql"
213
+ }
214
+ `,I=`model User {
215
+ id String @id @default(uuid())
216
+ email String @unique
217
+ name String
218
+ createdAt DateTime @default(now())
219
+ updatedAt DateTime @updatedAt
220
+ accounts Account[]
221
+ sessions Session[]
222
+ }
223
+
224
+ model Session {
225
+ id String @id @default(uuid())
226
+ userId String
227
+ token String @unique
228
+ expiresAt DateTime
229
+ createdAt DateTime @default(now())
230
+ updatedAt DateTime @updatedAt
231
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
232
+ }
233
+
234
+ model Account {
235
+ id String @id @default(uuid())
236
+ userId String
237
+ accountId String
238
+ providerId String
239
+ accessToken String?
240
+ refreshToken String?
241
+ idToken String?
242
+ accessTokenExpiresAt DateTime?
243
+ refreshTokenExpiresAt DateTime?
244
+ scope String?
245
+ password String?
246
+ createdAt DateTime @default(now())
247
+ updatedAt DateTime @updatedAt
248
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
249
+ }
250
+ `,O=`import "dotenv/config";
251
+ import { defineConfig } from "prisma/config";
252
+ import process from "process";
253
+
254
+ export default defineConfig({
255
+ schema: "prisma/schema",
256
+ datasource: {
257
+ url: process.env.DATABASE_URL,
258
+ },
259
+ });
260
+ `,_=`{
261
+ "compilerOptions": {
262
+ "target": "ES2022",
263
+ "module": "NodeNext",
264
+ "moduleResolution": "NodeNext",
265
+ "outDir": "./dist",
266
+ "rootDir": "./src",
267
+ "strict": true,
268
+ "esModuleInterop": true,
269
+ "skipLibCheck": true,
270
+ "forceConsistentCasingInFileNames": true,
271
+ "baseUrl": ".",
272
+ "paths": {
273
+ "@/*": ["src/*"]
274
+ }
275
+ },
276
+ "include": ["src/**/*"],
277
+ "exclude": ["node_modules", "dist"]
278
+ }
279
+ `;var b=async e=>{let r=e;r||(r=(await M.prompt([{type:"input",name:"projectName",message:"What is your project name?",default:"shakil-stack-app"}])).projectName),r||(console.log(n.red("\u274C Error: Project name is required.")),process.exit(1));let{packageManager:a,useShadcn:g,installDeps:c}=await M.prompt([{type:"list",name:"packageManager",message:"Which package manager do you want to use?",choices:["pnpm","npm","yarn"],default:f()},{type:"confirm",name:"useShadcn",message:"Would you like to use shadcn/ui?",default:!0},{type:"confirm",name:"installDeps",message:"Do you want to install dependencies automatically?",default:!0}]),t=s.join(process.cwd(),r);o.existsSync(t)&&(console.log(n.red(`\u274C Error: Directory ${r} already exists.`)),process.exit(1)),console.log(n.cyan(`
280
+ \u{1F680} Initializing ${n.bold(r)}...
281
+ `));let m=ee("\u{1F6E0}\uFE0F Creating project structure...").start();try{await o.ensureDir(t),await o.ensureDir(s.join(t,"backend","src","app","config")),await o.ensureDir(s.join(t,"backend","src","app","lib")),await o.ensureDir(s.join(t,"backend","src","app","module")),await o.ensureDir(s.join(t,"backend","src","app","routes")),await o.ensureDir(s.join(t,"backend","src","app","middleware")),await o.ensureDir(s.join(t,"backend","src","app","utils")),await o.ensureDir(s.join(t,"backend","src","app","errorHelpers")),await o.ensureDir(s.join(t,"backend","prisma","schema")),console.log(n.cyan(`
282
+ \u{1F5BC}\uFE0F Scaffolding Next.js frontend...`)),i(`npx create-next-app@latest frontend --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-${a}`,t);let d=["config","hooks","lib","services","types"];for(let y of d)await o.ensureDir(s.join(t,"frontend","src",y));if(g){console.log(n.cyan(`
283
+ \u{1F3A8} Setting up shadcn/ui...`));try{i("npx shadcn@latest init -d",s.join(t,"frontend")),console.log(n.cyan("\u{1F4E6} Adding common shadcn/ui components...")),i(`npx shadcn@latest add ${["button","card","input","label","textarea","dialog","dropdown-menu","table","tabs","checkbox"].join(" ")} -y`,s.join(t,"frontend")),console.log(n.green("\u2705 shadcn/ui and common components initialized successfully!\u2728"))}catch{console.log(n.yellow(`
284
+ \u26A0\uFE0F Warning: Failed to automate shadcn/ui init. You can run "npx shadcn@latest init" in the frontend folder.`))}}await o.outputFile(s.join(t,"backend","src","server.ts"),j(r)),await o.outputFile(s.join(t,"backend","src","app.ts"),T(r)),await o.outputFile(s.join(t,"backend","src","app","config","index.ts"),S),await o.outputFile(s.join(t,"backend","src","app","lib","prisma.ts"),R),await o.outputFile(s.join(t,"backend","src","app","lib","auth.ts"),v),await o.outputFile(s.join(t,"backend","src","app","routes","index.ts"),D),await o.outputFile(s.join(t,"backend","src","app","middleware","globalErrorHandler.ts"),A),await o.outputFile(s.join(t,"backend","src","app","middleware","notFound.ts"),F),await o.outputFile(s.join(t,"backend","src","app","middleware","sanitizeRequest.ts"),E),await o.outputFile(s.join(t,"backend","src","app","utils","catchAsync.ts"),P),await o.outputFile(s.join(t,"backend","src","app","utils","sendResponse.ts"),C),await o.outputFile(s.join(t,"backend","src","app","utils","sanitizer.ts"),q),await o.outputFile(s.join(t,"backend","src","app","errorHelpers","ApiError.ts"),$),await o.outputFile(s.join(t,"backend","prisma","schema","base.prisma"),z),await o.outputFile(s.join(t,"backend","prisma","schema","user.prisma"),I),await o.outputFile(s.join(t,"backend","prisma.config.ts"),O),await o.outputFile(s.join(t,"backend","tsconfig.json"),_),await o.outputFile(s.join(t,"backend",".gitignore"),`node_modules
285
+ dist
286
+ .env`),await o.outputFile(s.join(t,"backend",".env"),`DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
287
+ JWT_SECRET="your-secret-key"`);let h={name:`${r}-backend`,version:"1.0.0",type:"module",scripts:{test:'echo "Error: no test specified" && exit 1',dev:"nodemon --exec tsx src/server.ts",build:"prisma generate && tsup src/server.ts --format esm --platform node --target node20 --outDir dist --external pg-native",postinstall:"prisma generate",start:"node dist/server.js","prisma:generate":"prisma generate","prisma:migrate":"prisma migrate dev","prisma:studio":"prisma studio",seed:"tsx prisma/seed.ts",setup:"pnpm install && pnpm add @prisma/adapter-pg pg && pnpm add -D @types/pg && pnpm prisma:generate",predev:"pnpm run prisma:generate",init:"pnpm run prisma:generate && pnpm run prisma:migrate --name init",lint:"eslint src/**/*.ts","lint:fix":"eslint src/**/*.ts --fix",format:"prettier --write .",push:"prisma db push",pull:"prisma db pull"},dependencies:{"@prisma/adapter-pg":"^7.5.0","@prisma/client":"^7.5.0","better-auth":"^1.5.6","cookie-parser":"^1.4.7",cors:"^2.8.6",dompurify:"^3.3.3",dotenv:"^17.3.1",express:"^5.2.1","express-rate-limit":"^8.3.1",helmet:"^8.1.0","http-status":"^2.1.0",jsdom:"^29.0.1",jsonwebtoken:"^9.0.3",morgan:"^1.10.1",pg:"^8.20.0",winston:"^3.19.0",zod:"^4.3.6"},devDependencies:{"@types/cookie-parser":"^1.4.10","@types/cors":"^2.8.19","@types/express":"^5.0.6","@types/node":"^20.19.37","@types/pg":"^8.20.0","@types/morgan":"^1.9.10","@types/jsdom":"^21.1.7",prisma:"^7.5.0",tsx:"^4.21.0",nodemon:"^3.1.14",tsup:"^8.5.1",typescript:"^5.9.3",eslint:"^9.21.0",prettier:"^3.5.2"}};await o.writeJson(s.join(t,"backend","package.json"),h,{spaces:2}),m.succeed(n.green("\u2705 Project structure created! \u2728")),c&&(console.log(n.yellow(`
288
+ \u{1F4E6} Finalizing dependencies with ${a}...
289
+ `)),i(`${a} install`,s.join(t,"backend"))),console.log(n.cyan("To get started:")),console.log(n.white(` cd ${r}`)),console.log(n.white(` cd backend && ${a} dev
290
+ `)),console.log(n.white(` cd frontend && ${a} dev
291
+ `))}catch(d){m.fail(n.red("\u274C Failed to initialize project.")),console.error(d),process.exit(1)}};import l from"fs-extra";import x from"path";import p from"chalk";import re from"ora";var H=(e,r)=>`import { Request, Response } from 'express';
292
+ import httpStatus from 'http-status';
293
+ import catchAsync from '../../utils/catchAsync.js';
294
+ import sendResponse from '../../utils/sendResponse.js';
295
+ import { ${e}Service } from './${r}.service.js';
296
+
297
+ const create${e} = catchAsync(async (req: Request, res: Response) => {
298
+ const result = await ${e}Service.create${e}IntoDB(req.body);
299
+ sendResponse(res, {
300
+ statusCode: httpStatus.OK,
301
+ success: true,
302
+ message: '${e} created successfully',
303
+ data: result,
304
+ });
305
+ });
306
+
307
+ export const ${e}Controller = {
308
+ create${e},
309
+ };
310
+ `,L=(e,r)=>`import { ${e} } from '@prisma/client';
311
+ import prisma from '../../lib/prisma.js';
312
+
313
+ const create${e}IntoDB = async (payload: any) => {
314
+ // Logic here
315
+ return payload;
316
+ };
317
+
318
+ export const ${e}Service = {
319
+ create${e}IntoDB,
320
+ };
321
+ `,N=(e,r)=>`import { Router } from 'express';
322
+ import { ${e}Controller } from './${r}.controller.js';
323
+
324
+ const router = Router();
325
+
326
+ router.post('/create-${r}', ${e}Controller.create${e});
327
+
328
+ export const ${e}Routes = router;
329
+ `,U=e=>`export type I${e} = {
330
+ // Define interface
331
+ };
332
+ `,B=e=>`import { z } from 'zod';
333
+
334
+ const create${e}ValidationSchema = z.object({
335
+ body: z.object({
336
+ // Define schema
337
+ }),
338
+ });
339
+
340
+ export const ${e}Validations = {
341
+ create${e}ValidationSchema,
342
+ };
343
+ `,J=e=>`export const ${e}SearchableFields = [];
344
+ `,W=e=>`model ${e} {
345
+ id String @id @default(uuid())
346
+ name String
347
+ createdAt DateTime @default(now())
348
+ updatedAt DateTime @updatedAt
349
+ }
350
+ `;var V=async e=>{e||(console.log(p.red("\u274C Error: Module name is required.")),process.exit(1));let r=e.charAt(0).toUpperCase()+e.slice(1),a=e.toLowerCase(),g=l.existsSync("backend")?"backend":".",c=x.join(g,"src","app","module",r);l.existsSync(x.join(g,"src","app","module"))||(console.log(p.red("\u274C Error: This command must be run inside your shakil-stack project root or backend directory.")),process.exit(1)),l.existsSync(c)&&(console.log(p.red(`\u274C Error: Module ${r} already exists.`)),process.exit(1));let t=re(`\u{1F6E0}\uFE0F Generating module: ${p.cyan(r)}...`).start();try{await l.ensureDir(c);let m={"controller.ts":H(r,a),"service.ts":L(r,a),"route.ts":N(r,a),"interface.ts":U(r),"validation.ts":B(r),"constant.ts":J(r)};await l.outputFile(x.join(g,"prisma","schema",`${a}.prisma`),W(r));for(let[d,h]of Object.entries(m))await l.outputFile(x.join(c,`${a}.${d}`),h);t.succeed(p.green(`\u2705 Module ${r} generated successfully! \u2728`)),console.log(p.gray(`Created at: ${c}`))}catch(m){t.fail(p.red("\u274C Failed to generate module.")),console.error(m)}};import se from"fs-extra";import oe from"chalk";var G=async()=>{let e=f(),r=se.existsSync("backend")?"backend":".";console.log(oe.cyan(`\u{1F3D7}\uFE0F Building backend with ${e}...`)),i(`${e} run build`,r)};import ne from"fs-extra";import k from"chalk";var K=async e=>{let r=ne.existsSync("backend")?"backend":".";e==="generate"?(console.log(k.cyan("\u{1F504} Generating Prisma client...")),i("npx prisma generate",r)):e==="migrate"?(console.log(k.cyan("\u{1F680} Running Prisma migrations...")),i("npx prisma migrate dev",r)):console.log(k.red(`\u274C Error: Unknown prisma subcommand: ${e}`))};var pe=ie(import.meta.url),Y=w.dirname(pe),ce=w.resolve(Y,Q.existsSync(w.resolve(Y,"../../package.json"))?"../../package.json":"../package.json"),me=Q.readJsonSync(ce),u=new ae;u.name("shakil-stack").description("Full-stack EchoNet-style project generator CLI").version(me.version);u.command("init").description("Initialize a new full-stack project").argument("[projectName]","Name of the project").action(e=>{b(e)});u.command("generate").alias("g").description("Generate a new module").argument("<type>","Type of generation (module)").argument("<name>","Name of the module").action((e,r)=>{e==="module"?V(r):console.log(`\u274C Error: Unknown generation type: ${e}`)});u.command("build").description("Build the backend for production").action(()=>{G()});u.command("prisma").description("Prisma utilities").argument("<subcommand>","generate | migrate").action(e=>{K(e)});process.argv.slice(2).length?u.parse(process.argv):b();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shakil-dev/shakil-stack",
3
- "version": "2.1.2",
3
+ "version": "2.2.2",
4
4
  "description": "Full-stack EchoNet-style project generator CLI",
5
5
  "keywords": [
6
6
  "shakil-stack",
@@ -13,9 +13,10 @@
13
13
  "generator",
14
14
  "cli"
15
15
  ],
16
- "main": "./bin/index.js",
16
+ "main": "dist/index.js",
17
+ "type": "module",
17
18
  "bin": {
18
- "shakil-stack": "bin/index.js"
19
+ "shakil-stack": "dist/index.js"
19
20
  },
20
21
  "license": "MIT",
21
22
  "repository": {
@@ -29,14 +30,25 @@
29
30
  "publishConfig": {
30
31
  "access": "public"
31
32
  },
32
- "scripts": {
33
- "start": "node ./bin/index.js"
34
- },
35
33
  "dependencies": {
36
34
  "chalk": "^4.1.2",
37
35
  "commander": "^11.1.0",
38
36
  "fs-extra": "^11.2.0",
39
- "ora": "^5.4.1",
40
- "inquirer": "^8.2.6"
37
+ "inquirer": "^8.2.6",
38
+ "ora": "^5.4.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/fs-extra": "^11.0.4",
42
+ "@types/inquirer": "^9.0.9",
43
+ "@types/node": "^25.5.0",
44
+ "@types/ora": "^3.2.0",
45
+ "tsup": "^8.5.1",
46
+ "tsx": "^4.21.0",
47
+ "typescript": "^6.0.2"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup src/bin/index.ts --format esm --outDir dist --clean --minify --target node20",
51
+ "dev": "tsx src/bin/index.ts",
52
+ "start": "node dist/bin/index.js"
41
53
  }
42
- }
54
+ }
@@ -1,44 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- permissions:
9
- contents: write
10
-
11
- jobs:
12
- publish:
13
- runs-on: ubuntu-latest
14
- steps:
15
- - name: Checkout Code
16
- uses: actions/checkout@v4
17
- with:
18
- fetch-depth: 0
19
-
20
- - name: Setup Node.js
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: '20'
24
- registry-url: 'https://registry.npmjs.org'
25
-
26
- - name: Install Dependencies
27
- run: npm ci
28
-
29
- - name: Set Git Identity
30
- run: |
31
- git config --global user.name "github-actions[bot]"
32
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
33
- git add . # Ensure any changes like package-lock.json are staged
34
-
35
- - name: Bump Version
36
- run: npm version patch
37
-
38
- - name: Publish to NPM
39
- run: npm publish --access public
40
- env:
41
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
42
-
43
- - name: Push Version Change to GitHub
44
- run: git push origin main --tags
package/bin/index.js DELETED
@@ -1,729 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
- const { Command } = require('commander');
7
- const inquirer = require('inquirer');
8
- const chalk = require('chalk');
9
- const ora = require('ora');
10
-
11
- const program = new Command();
12
-
13
- // --- Utils ---
14
- const getPackageManager = () => {
15
- if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
16
- if (fs.existsSync('yarn.lock')) return 'yarn';
17
- return 'npm';
18
- };
19
-
20
- const runCommand = (command, cwd = process.cwd()) => {
21
- try {
22
- execSync(command, { stdio: 'inherit', cwd });
23
- } catch (err) {
24
- // console.error(chalk.red(`Failed to execute: ${command}`));
25
- }
26
- };
27
-
28
- // --- Command: Init ---
29
- const initProject = async (name) => {
30
- console.log(chalk.cyan('\n🚀 Initializing Shakil-Stack Project Generator...\n'));
31
-
32
- const answers = await inquirer.prompt([
33
- {
34
- type: 'input',
35
- name: 'projectName',
36
- message: 'What is your project name?',
37
- default: name || 'my-new-project',
38
- validate: (input) => (input ? true : 'Project name is required'),
39
- },
40
- {
41
- type: 'list',
42
- name: 'packageManager',
43
- message: 'Which package manager would you like to use?',
44
- choices: ['pnpm', 'npm', 'yarn'],
45
- default: 'pnpm',
46
- },
47
- {
48
- type: 'confirm',
49
- name: 'useShadcn',
50
- message: 'Would you like to use shadcn/ui for components?',
51
- default: true,
52
- },
53
- {
54
- type: 'confirm',
55
- name: 'installDeps',
56
- message: 'Would you like to install dependencies now?',
57
- default: true,
58
- },
59
- ]);
60
-
61
- const { projectName, packageManager, installDeps, useShadcn } = answers;
62
- const projectPath = path.resolve(process.cwd(), projectName);
63
-
64
- if (fs.existsSync(projectPath)) {
65
- console.log(chalk.red(`\n❌ Error: Directory ${projectName} already exists.\n`));
66
- process.exit(1);
67
- }
68
-
69
- try {
70
- await fs.ensureDir(projectPath);
71
- const spinner = ora(`🚀 Creating project: ${chalk.cyan(projectName)}...`).start();
72
-
73
- // Backend Folder Structure
74
- const backendDirs = [
75
- 'prisma',
76
- 'src/app/config',
77
- 'src/app/errorHelpers',
78
- 'src/app/interfaces',
79
- 'src/app/lib',
80
- 'src/app/middleware',
81
- 'src/app/module',
82
- 'src/app/routes',
83
- 'src/app/utils',
84
- ];
85
-
86
- spinner.text = `📂 Creating backend folder structure...`;
87
- for (const dir of backendDirs) {
88
- await fs.ensureDir(path.join(projectPath, 'backend', dir));
89
- }
90
-
91
- // Frontend (create-next-app)
92
- spinner.text = `📦 Running create-next-app for frontend...`;
93
- spinner.stop();
94
- const nextAppCmd = `npx create-next-app@latest frontend --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-${packageManager} --no-git`;
95
- try {
96
- execSync(nextAppCmd, { cwd: projectPath, stdio: 'inherit' });
97
- } catch (err) {
98
- console.log(chalk.yellow('\n⚠️ Warning: Failed to generate frontend via create-next-app. Building manually...'));
99
- await fs.ensureDir(path.join(projectPath, 'frontend'));
100
- }
101
-
102
- // Frontend extra folders
103
- const frontendExtraFolders = ['config', 'hooks', 'lib', 'services', 'types'];
104
- for (const folder of frontendExtraFolders) {
105
- await fs.ensureDir(path.join(projectPath, 'frontend', 'src', folder));
106
- }
107
-
108
- // Shadcn/UI initialization
109
- if (useShadcn) {
110
- console.log(chalk.cyan('\n🎨 Setting up shadcn/ui...'));
111
- try {
112
- // Using non-interactive init with default settings
113
- // -d flag uses default options: TypeScript, Tailwind, Lucide Icons, Slate color, css variables
114
- execSync(`npx shadcn@latest init -d`, {
115
- cwd: path.join(projectPath, 'frontend'),
116
- stdio: 'inherit'
117
- });
118
-
119
- console.log(chalk.cyan('📦 Adding common shadcn/ui components...'));
120
- const commonComponents = ['button', 'card', 'input', 'label', 'textarea', 'dialog', 'dropdown-menu', 'table', 'tabs', 'checkbox'];
121
- execSync(`npx shadcn@latest add ${commonComponents.join(' ')} -y`, {
122
- cwd: path.join(projectPath, 'frontend'),
123
- stdio: 'inherit'
124
- });
125
-
126
- console.log(chalk.green('✅ shadcn/ui and common components initialized successfully!✨'));
127
- } catch (err) {
128
- console.log(chalk.yellow('\n⚠️ Warning: Failed to automate shadcn/ui init. You can run "npx shadcn@latest init" in the frontend folder.'));
129
- }
130
- }
131
-
132
- spinner.start(`📂 Finalizing root files and backend code...`);
133
-
134
- // Root Files
135
- await fs.outputFile(path.join(projectPath, '.env'), 'DATABASE_URL="postgresql://user:password@localhost:5432/mydb"\nPORT=8000\nNODE_ENV=development\nJWT_SECRET="your-secret-key"');
136
- await fs.outputFile(path.join(projectPath, '.gitignore'), 'node_modules\n.env\ndist\n*.db\n.next\n.DS_Store');
137
- await fs.outputFile(path.join(projectPath, 'README.md'), `# ${projectName}\n\nGenerated with Full Shakil-Stack CLI.`);
138
-
139
- // Backend Files Templates
140
- const serverTs = `import { Server } from 'http';
141
- import app from './app.js';
142
- import config from './app/config/index.js';
143
-
144
- let server: Server;
145
-
146
- async function bootstrap() {
147
- try {
148
- server = app.listen(config.port, () => {
149
- console.log(\`${projectName} server is listening on port \${config.port}\`);
150
- });
151
- } catch (error) {
152
- console.error('Failed to start server:', error);
153
- }
154
- }
155
-
156
- bootstrap();
157
- `;
158
-
159
- const appTs = `import cors from 'cors';
160
- import express, { Application, Request, Response } from 'express';
161
- import httpStatus from 'http-status';
162
- import globalErrorHandler from './app/middleware/globalErrorHandler.js';
163
- import notFound from './app/middleware/notFound.js';
164
- import router from './app/routes/index.js';
165
- import cookieParser from 'cookie-parser';
166
- import morgan from 'morgan';
167
- import helmet from 'helmet';
168
- import { rateLimit } from 'express-rate-limit';
169
- import { sanitizeRequest } from './app/middleware/sanitizeRequest.js';
170
-
171
- const app: Application = express();
172
-
173
- app.use(helmet());
174
- app.use(cors({
175
- origin: ["http://localhost:3000", "http://127.0.0.1:3000"],
176
- credentials: true,
177
- methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
178
- allowedHeaders: ["Content-Type", "Authorization", "Cookie"]
179
- }));
180
-
181
- const authLimiter = rateLimit({
182
- windowMs: 15 * 60 * 1000,
183
- limit: 100,
184
- standardHeaders: 'draft-7',
185
- legacyHeaders: false,
186
- message: "Too many requests from this IP, please try again after 15 minutes"
187
- });
188
-
189
- app.use('/api/v1/auth', authLimiter);
190
-
191
- app.use(cookieParser());
192
- app.use(express.json({ limit: '10mb' }));
193
- app.use(express.urlencoded({ extended: true, limit: '10mb' }));
194
- app.use(sanitizeRequest);
195
- app.use(morgan("dev"));
196
-
197
- app.use('/api/v1', router);
198
-
199
- app.get('/', (req: Request, res: Response) => {
200
- res.status(httpStatus.OK).json({
201
- success: true,
202
- message: 'Welcome to ${projectName} API',
203
- });
204
- });
205
-
206
- app.use(globalErrorHandler);
207
- app.use(notFound);
208
-
209
- export default app;
210
- `;
211
-
212
- const configTs = `import dotenv from 'dotenv';
213
- import path from 'path';
214
-
215
- dotenv.config({ path: path.join(process.cwd(), '.env') });
216
-
217
- export default {
218
- env: process.env.NODE_ENV,
219
- port: process.env.PORT || 8000,
220
- database_url: process.env.DATABASE_URL,
221
- jwt_secret: process.env.JWT_SECRET,
222
- };
223
- `;
224
-
225
- const prismaTs = `import "dotenv/config";
226
- import { PrismaClient } from "@prisma/client";
227
- import pkg from 'pg';
228
- import { PrismaPg } from '@prisma/adapter-pg';
229
- import config from '../config/index.js';
230
-
231
- const { Pool } = pkg;
232
- const connectionString = config.database_url as string;
233
- const pool = new Pool({ connectionString });
234
- const adapter = new PrismaPg(pool as any);
235
- const prisma = new PrismaClient({ adapter });
236
-
237
- export default prisma;
238
- export { prisma };
239
- `;
240
-
241
- const authTs = `import { betterAuth } from "better-auth";
242
- import { prismaAdapter } from "better-auth/adapters/prisma";
243
- import config from "../config/index.js";
244
- import { prisma } from "./prisma.js";
245
-
246
- export const auth = betterAuth({
247
- database: prismaAdapter(prisma, {
248
- provider: "postgresql",
249
- }),
250
- secret: config.jwt_secret,
251
- baseURL: "http://localhost:8000",
252
- trustedOrigins: ["http://localhost:3000"],
253
- emailAndPassword: {
254
- enabled: true,
255
- },
256
- });
257
- `;
258
-
259
- const routesTs = `import { Router } from 'express';
260
- const router = Router();
261
- export default router;
262
- `;
263
-
264
- const globalErrorHandlerTs = `import { ErrorRequestHandler } from 'express';
265
- import config from '../config/index.js';
266
-
267
- const globalErrorHandler: ErrorRequestHandler = (error, req, res, next) => {
268
- res.status(500).json({
269
- success: false,
270
- message: error.message || 'Something went wrong!',
271
- stack: config.env !== 'production' ? error?.stack : undefined,
272
- });
273
- };
274
-
275
- export default globalErrorHandler;
276
- `;
277
-
278
- const notFoundTs = `import { Request, Response, NextFunction } from 'express';
279
- import httpStatus from 'http-status';
280
-
281
- const notFound = (req: Request, res: Response, next: NextFunction) => {
282
- res.status(httpStatus.NOT_FOUND).json({
283
- success: false,
284
- message: 'API Not Found',
285
- });
286
- };
287
-
288
- export default notFound;
289
- `;
290
-
291
- const catchAsyncTs = `import { NextFunction, Request, RequestHandler, Response } from 'express';
292
-
293
- const catchAsync = (fn: RequestHandler) => {
294
- return async (req: Request, res: Response, next: NextFunction) => {
295
- try {
296
- await fn(req, res, next);
297
- } catch (error) {
298
- next(error);
299
- }
300
- };
301
- };
302
-
303
- export default catchAsync;
304
- `;
305
-
306
- const apiErrorTs = `class ApiError extends Error {
307
- statusCode: number;
308
- constructor(statusCode: number, message: string | undefined, stack = '') {
309
- super(message);
310
- this.statusCode = statusCode;
311
- if (stack) this.stack = stack;
312
- else Error.captureStackTrace(this, this.constructor);
313
- }
314
- }
315
- export default ApiError;
316
- `;
317
-
318
- const sanitizerTs = `import { JSDOM } from 'jsdom';
319
- import createDOMPurify from 'dompurify';
320
-
321
- const window = new JSDOM('').window;
322
- const DOMPurify = createDOMPurify(window as any);
323
-
324
- export const sanitize = (data: any): any => {
325
- if (typeof data === 'string') return DOMPurify.sanitize(data);
326
- if (typeof data === 'object' && data !== null) {
327
- for (const key in data) {
328
- if (Object.prototype.hasOwnProperty.call(data, key)) data[key] = sanitize(data[key]);
329
- }
330
- }
331
- return data;
332
- };
333
- `;
334
-
335
- const sanitizeRequestTs = `import { Request, Response, NextFunction } from 'express';
336
- import { sanitize } from '../utils/sanitizer.js';
337
-
338
- export const sanitizeRequest = (req: Request, res: Response, next: NextFunction) => {
339
- if (req.body) sanitize(req.body);
340
- if (req.query) sanitize(req.query);
341
- if (req.params) sanitize(req.params);
342
- next();
343
- };
344
- `;
345
-
346
- const basePrisma = `generator client {
347
- provider = "prisma-client-js"
348
- previewFeatures = ["prismaSchemaFolder"]
349
- }
350
-
351
- datasource db {
352
- provider = "postgresql"
353
- }
354
- `;
355
-
356
- const userPrisma = `model User {
357
- id String @id @default(uuid())
358
- email String @unique
359
- name String
360
- createdAt DateTime @default(now())
361
- updatedAt DateTime @updatedAt
362
- accounts Account[]
363
- sessions Session[]
364
- }
365
-
366
- model Session {
367
- id String @id @default(uuid())
368
- userId String
369
- token String @unique
370
- expiresAt DateTime
371
- createdAt DateTime @default(now())
372
- updatedAt DateTime @updatedAt
373
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
374
- }
375
-
376
- model Account {
377
- id String @id @default(uuid())
378
- userId String
379
- accountId String
380
- providerId String
381
- accessToken String?
382
- refreshToken String?
383
- idToken String?
384
- accessTokenExpiresAt DateTime?
385
- refreshTokenExpiresAt DateTime?
386
- scope String?
387
- password String?
388
- createdAt DateTime @default(now())
389
- updatedAt DateTime @updatedAt
390
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
391
- }
392
- `;
393
-
394
- const prismaConfigTs = `import "dotenv/config";
395
- import { defineConfig } from "prisma/config";
396
- import process from "process";
397
-
398
- export default defineConfig({
399
- schema: "prisma/schema",
400
- datasource: {
401
- url: process.env.DATABASE_URL,
402
- },
403
- });
404
- `;
405
-
406
- const tsconfigTs = `{
407
- "compilerOptions": {
408
- "target": "ES2022",
409
- "module": "NodeNext",
410
- "moduleResolution": "NodeNext",
411
- "outDir": "./dist",
412
- "rootDir": "./src",
413
- "strict": true,
414
- "esModuleInterop": true,
415
- "skipLibCheck": true,
416
- "forceConsistentCasingInFileNames": true,
417
- "baseUrl": ".",
418
- "paths": {
419
- "@/*": ["src/*"]
420
- }
421
- },
422
- "include": ["src/**/*"],
423
- "exclude": ["node_modules", "dist"]
424
- }
425
- `;
426
-
427
- const sendResponseTs = `import { Response } from 'express';
428
-
429
- type IResponse<T> = {
430
- statusCode: number;
431
- success: boolean;
432
- message?: string | null;
433
- meta?: {
434
- limit: number;
435
- page: number;
436
- total: number;
437
- };
438
- data: T;
439
- };
440
-
441
- const sendResponse = <T>(res: Response, data: IResponse<T>) => {
442
- res.status(data.statusCode).json({
443
- success: data.success,
444
- message: data.message || null,
445
- meta: data.meta || null,
446
- data: data.data || null,
447
- });
448
- };
449
-
450
- export default sendResponse;
451
- `;
452
-
453
- // Writing Backend Files
454
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'server.ts'), serverTs);
455
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app.ts'), appTs);
456
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'config', 'index.ts'), configTs);
457
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'lib', 'prisma.ts'), prismaTs);
458
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'lib', 'auth.ts'), authTs);
459
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'routes', 'index.ts'), routesTs);
460
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'middleware', 'globalErrorHandler.ts'), globalErrorHandlerTs);
461
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'middleware', 'notFound.ts'), notFoundTs);
462
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'middleware', 'sanitizeRequest.ts'), sanitizeRequestTs);
463
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'catchAsync.ts'), catchAsyncTs);
464
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'sendResponse.ts'), sendResponseTs);
465
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'utils', 'sanitizer.ts'), sanitizerTs);
466
- await fs.outputFile(path.join(projectPath, 'backend', 'src', 'app', 'errorHelpers', 'ApiError.ts'), apiErrorTs);
467
- await fs.outputFile(path.join(projectPath, 'backend', 'prisma', 'schema', 'base.prisma'), basePrisma);
468
- await fs.outputFile(path.join(projectPath, 'backend', 'prisma', 'schema', 'user.prisma'), userPrisma);
469
- await fs.outputFile(path.join(projectPath, 'backend', 'prisma.config.ts'), prismaConfigTs);
470
- await fs.outputFile(path.join(projectPath, 'backend', 'tsconfig.json'), tsconfigTs);
471
- await fs.outputFile(path.join(projectPath, 'backend', '.gitignore'), 'node_modules\ndist\n.env');
472
- await fs.outputFile(path.join(projectPath, 'backend', '.env'), 'DATABASE_URL="postgresql://user:password@localhost:5432/mydb"\nJWT_SECRET="your-secret-key"');
473
-
474
- const backendPkg = {
475
- name: `${projectName}-backend`,
476
- version: '1.0.0',
477
- type: "module",
478
- scripts: {
479
- "test": "echo \"Error: no test specified\" && exit 1",
480
- "dev": "nodemon --exec tsx src/server.ts",
481
- "build": "prisma generate && tsup src/server.ts --format esm --platform node --target node20 --outDir dist --external pg-native",
482
- "postinstall": "prisma generate",
483
- "start": "node dist/server.js",
484
- "prisma:generate": "prisma generate",
485
- "prisma:migrate": "prisma migrate dev",
486
- "prisma:studio": "prisma studio",
487
- "seed": "tsx prisma/seed.ts",
488
- "setup": "pnpm install && pnpm add @prisma/adapter-pg pg && pnpm add -D @types/pg && pnpm prisma:generate",
489
- "predev": "pnpm run prisma:generate",
490
- "init": "pnpm run prisma:generate && pnpm run prisma:migrate --name init",
491
- "lint": "eslint src/**/*.ts",
492
- "lint:fix": "eslint src/**/*.ts --fix",
493
- "format": "prettier --write .",
494
- "push": "prisma db push",
495
- "pull": "prisma db pull"
496
- },
497
- dependencies: {
498
- "@prisma/adapter-pg": "^7.5.0",
499
- "@prisma/client": "^7.5.0",
500
- "better-auth": "^1.5.6",
501
- "cookie-parser": "^1.4.7",
502
- "cors": "^2.8.6",
503
- "dompurify": "^3.3.3",
504
- "dotenv": "^17.3.1",
505
- "express": "^5.2.1",
506
- "express-rate-limit": "^8.3.1",
507
- "helmet": "^8.1.0",
508
- "http-status": "^2.1.0",
509
- "jsdom": "^29.0.1",
510
- "jsonwebtoken": "^9.0.3",
511
- "morgan": "^1.10.1",
512
- "pg": "^8.20.0",
513
- "winston": "^3.19.0",
514
- "zod": "^4.3.6"
515
- },
516
- devDependencies: {
517
- "@types/cookie-parser": "^1.4.10",
518
- "@types/cors": "^2.8.19",
519
- "@types/express": "^5.0.6",
520
- "@types/node": "^20.19.37",
521
- "@types/pg": "^8.20.0",
522
- "@types/morgan": "^1.9.10",
523
- "@types/jsdom": "^21.1.7",
524
- "prisma": "^7.5.0",
525
- "tsx": "^4.21.0",
526
- "nodemon": "^3.1.14",
527
- "tsup": "^8.5.1",
528
- "typescript": "^5.9.3",
529
- "eslint": "^9.21.0",
530
- "prettier": "^3.5.2"
531
- }
532
- };
533
- await fs.writeJson(path.join(projectPath, 'backend', 'package.json'), backendPkg, { spaces: 2 });
534
-
535
- spinner.succeed(chalk.green(`✅ Project structure created! ✨`));
536
-
537
- if (installDeps) {
538
- console.log(chalk.yellow(`\n📦 Finalizing dependencies with ${packageManager}...\n`));
539
- runCommand(`cd "${path.join(projectPath, 'backend')}" && ${packageManager} install`);
540
- }
541
-
542
- console.log(chalk.cyan(`To get started:`));
543
- console.log(chalk.white(` cd ${projectName}`));
544
- console.log(chalk.white(` cd backend && ${packageManager} dev\n`));
545
- console.log(chalk.white(` cd frontend && ${packageManager} dev\n`));
546
-
547
- } catch (error) {
548
- console.error(error);
549
- process.exit(1);
550
- }
551
- };
552
-
553
- // --- Command: Generate Module ---
554
- const generateModule = async (name) => {
555
- if (!name) {
556
- console.log(chalk.red('❌ Error: Module name is required.'));
557
- process.exit(1);
558
- }
559
-
560
- const moduleName = name.charAt(0).toUpperCase() + name.slice(1);
561
- const lowercaseName = name.toLowerCase();
562
-
563
- // Check if inside a shakil-stack project
564
- const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
565
- const moduleDir = path.join(backendRoot, 'src', 'app', 'module', moduleName);
566
-
567
- if (!fs.existsSync(path.join(backendRoot, 'src', 'app', 'module'))) {
568
- console.log(chalk.red('❌ Error: This command must be run inside your shakil-stack project root or backend directory.'));
569
- process.exit(1);
570
- }
571
-
572
- if (fs.existsSync(moduleDir)) {
573
- console.log(chalk.red(`❌ Error: Module ${moduleName} already exists.`));
574
- process.exit(1);
575
- }
576
-
577
- const spinner = ora(`🛠️ Generating module: ${chalk.cyan(moduleName)}...`).start();
578
-
579
- try {
580
- await fs.ensureDir(moduleDir);
581
-
582
- const files = {
583
- 'controller.ts': `import { Request, Response } from 'express';
584
- import httpStatus from 'http-status';
585
- import catchAsync from '../../utils/catchAsync.js';
586
- import sendResponse from '../../utils/sendResponse.js';
587
- import { ${moduleName}Service } from './${lowercaseName}.service.js';
588
-
589
- const create${moduleName} = catchAsync(async (req: Request, res: Response) => {
590
- const result = await ${moduleName}Service.create${moduleName}IntoDB(req.body);
591
- sendResponse(res, {
592
- statusCode: httpStatus.OK,
593
- success: true,
594
- message: '${moduleName} created successfully',
595
- data: result,
596
- });
597
- });
598
-
599
- export const ${moduleName}Controller = {
600
- create${moduleName},
601
- };
602
- `,
603
- 'service.ts': `import { ${moduleName} } from '@prisma/client';
604
- import prisma from '../../lib/prisma.js';
605
-
606
- const create${moduleName}IntoDB = async (payload: any) => {
607
- // Logic here
608
- return payload;
609
- };
610
-
611
- export const ${moduleName}Service = {
612
- create${moduleName}IntoDB,
613
- };
614
- `,
615
- 'route.ts': `import { Router } from 'express';
616
- import { ${moduleName}Controller } from './${lowercaseName}.controller.js';
617
-
618
- const router = Router();
619
-
620
- router.post('/create-${lowercaseName}', ${moduleName}Controller.create${moduleName});
621
-
622
- export const ${moduleName}Routes = router;
623
- `,
624
- 'interface.ts': `export type I${moduleName} = {
625
- // Define interface
626
- };
627
- `,
628
- 'validation.ts': `import { z } from 'zod';
629
-
630
- const create${moduleName}ValidationSchema = z.object({
631
- body: z.object({
632
- // Define schema
633
- }),
634
- });
635
-
636
- export const ${moduleName}Validations = {
637
- create${moduleName}ValidationSchema,
638
- };
639
- `,
640
- 'constant.ts': `export const ${moduleName}SearchableFields = [];
641
- `,
642
- };
643
-
644
- // Add Prisma schema for the module
645
- const modulePrisma = `model ${moduleName} {
646
- id String @id @default(uuid())
647
- name String
648
- createdAt DateTime @default(now())
649
- updatedAt DateTime @updatedAt
650
- }
651
- `;
652
- await fs.outputFile(path.join(backendRoot, 'prisma', 'schema', `${lowercaseName}.prisma`), modulePrisma);
653
-
654
- for (const [ext, content] of Object.entries(files)) {
655
- await fs.outputFile(path.join(moduleDir, `${lowercaseName}.${ext}`), content);
656
- }
657
-
658
- spinner.succeed(chalk.green(`✅ Module ${moduleName} generated successfully! ✨`));
659
- console.log(chalk.gray(`Created at: ${moduleDir}`));
660
-
661
- } catch (error) {
662
- spinner.fail(chalk.red('❌ Failed to generate module.'));
663
- console.error(error);
664
- }
665
- };
666
-
667
- // --- CLI Structure ---
668
- const packageJson = require('../package.json');
669
-
670
- program
671
- .name('shakil-stack')
672
- .description('Full-stack EchoNet-style project generator CLI')
673
- .version(packageJson.version);
674
-
675
- program
676
- .command('init')
677
- .description('Initialize a new full-stack project')
678
- .argument('[projectName]', 'Name of the project')
679
- .action((projectName) => {
680
- initProject(projectName);
681
- });
682
-
683
- program
684
- .command('generate')
685
- .alias('g')
686
- .description('Generate a new module')
687
- .argument('<type>', 'Type of generation (module)')
688
- .argument('<name>', 'Name of the module')
689
- .action((type, name) => {
690
- if (type === 'module') {
691
- generateModule(name);
692
- } else {
693
- console.log(chalk.red(`❌ Error: Unknown generation type: ${type}`));
694
- }
695
- });
696
-
697
- program
698
- .command('build')
699
- .description('Build the backend for production')
700
- .action(() => {
701
- const pm = getPackageManager();
702
- const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
703
- console.log(chalk.cyan(`🏗️ Building backend with ${pm}...`));
704
- runCommand(`${pm} run build`, backendRoot);
705
- });
706
-
707
- program
708
- .command('prisma')
709
- .description('Prisma utilities')
710
- .argument('<subcommand>', 'generate | migrate')
711
- .action((subcommand) => {
712
- const backendRoot = fs.existsSync('backend') ? 'backend' : '.';
713
- if (subcommand === 'generate') {
714
- console.log(chalk.cyan('🔄 Generating Prisma client...'));
715
- runCommand('npx prisma generate', backendRoot);
716
- } else if (subcommand === 'migrate') {
717
- console.log(chalk.cyan('🚀 Running Prisma migrations...'));
718
- runCommand('npx prisma migrate dev', backendRoot);
719
- } else {
720
- console.log(chalk.red(`❌ Error: Unknown prisma subcommand: ${subcommand}`));
721
- }
722
- });
723
-
724
- // Handle default action (no command)
725
- if (!process.argv.slice(2).length) {
726
- initProject();
727
- } else {
728
- program.parse(process.argv);
729
- }