@shakil-dev/shakil-stack 2.2.8 → 2.2.9
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 +96 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as
|
|
2
|
+
import{Command as Ke}from"commander";import De from"fs-extra";import ne from"path";import{fileURLToPath as Ye}from"url";import s from"fs-extra";import o from"path";import je from"crypto";import a from"chalk";import Be from"ora";import Ce from"inquirer";import{execSync as He}from"child_process";import st from"fs-extra";var d=(e,r=process.cwd())=>{try{return He(e,{stdio:"inherit",cwd:r}),!0}catch{return!1}},E=()=>{let e=process.env.npm_config_user_agent||"";return e.includes("pnpm")?"pnpm":e.includes("yarn")?"yarn":"pnpm"};var ae=e=>`import { Server } from 'http';
|
|
3
3
|
import app from './app.js';
|
|
4
4
|
import config from './app/config/index.js';
|
|
5
5
|
|
|
@@ -178,7 +178,7 @@ const notFound = (req: Request, res: Response, next: NextFunction) => {
|
|
|
178
178
|
};
|
|
179
179
|
|
|
180
180
|
export default notFound;
|
|
181
|
-
`,
|
|
181
|
+
`,de=`import { NextFunction, Request, RequestHandler, Response } from 'express';
|
|
182
182
|
|
|
183
183
|
const catchAsync = (fn: RequestHandler) => {
|
|
184
184
|
return async (req: Request, res: Response, next: NextFunction) => {
|
|
@@ -191,7 +191,7 @@ const catchAsync = (fn: RequestHandler) => {
|
|
|
191
191
|
};
|
|
192
192
|
|
|
193
193
|
export default catchAsync;
|
|
194
|
-
`,
|
|
194
|
+
`,ue=`class ApiError extends Error {
|
|
195
195
|
statusCode: number;
|
|
196
196
|
constructor(statusCode: number, message: string | undefined, stack = '') {
|
|
197
197
|
super(message);
|
|
@@ -216,7 +216,7 @@ export const sanitize = (data: any): any => {
|
|
|
216
216
|
}
|
|
217
217
|
return data;
|
|
218
218
|
};
|
|
219
|
-
`,
|
|
219
|
+
`,fe=`import { Request, Response, NextFunction } from 'express';
|
|
220
220
|
import { sanitize } from '../utils/sanitizer.js';
|
|
221
221
|
|
|
222
222
|
export const sanitizeRequest = (req: Request, res: Response, next: NextFunction) => {
|
|
@@ -225,7 +225,7 @@ export const sanitizeRequest = (req: Request, res: Response, next: NextFunction)
|
|
|
225
225
|
if (req.params) sanitize(req.params);
|
|
226
226
|
next();
|
|
227
227
|
};
|
|
228
|
-
`,
|
|
228
|
+
`,ge=`import { Response } from 'express';
|
|
229
229
|
|
|
230
230
|
type IResponse<T> = {
|
|
231
231
|
statusCode: number;
|
|
@@ -257,10 +257,12 @@ export default sendResponse;
|
|
|
257
257
|
datasource db {
|
|
258
258
|
provider = "postgresql"
|
|
259
259
|
}
|
|
260
|
-
`,
|
|
261
|
-
id String @id @default(
|
|
260
|
+
`,N=`model User {
|
|
261
|
+
id String @id @default(cuid())
|
|
262
262
|
email String @unique
|
|
263
263
|
name String
|
|
264
|
+
emailVerified Boolean @default(false)
|
|
265
|
+
image String?
|
|
264
266
|
createdAt DateTime @default(now())
|
|
265
267
|
updatedAt DateTime @updatedAt
|
|
266
268
|
accounts Account[]
|
|
@@ -268,17 +270,19 @@ datasource db {
|
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
model Session {
|
|
271
|
-
id String @id @default(
|
|
273
|
+
id String @id @default(cuid())
|
|
272
274
|
userId String
|
|
273
275
|
token String @unique
|
|
274
276
|
expiresAt DateTime
|
|
277
|
+
ipAddress String?
|
|
278
|
+
userAgent String?
|
|
275
279
|
createdAt DateTime @default(now())
|
|
276
280
|
updatedAt DateTime @updatedAt
|
|
277
281
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
model Account {
|
|
281
|
-
id String @id @default(
|
|
285
|
+
id String @id @default(cuid())
|
|
282
286
|
userId String
|
|
283
287
|
accountId String
|
|
284
288
|
providerId String
|
|
@@ -293,7 +297,16 @@ model Account {
|
|
|
293
297
|
updatedAt DateTime @updatedAt
|
|
294
298
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
295
299
|
}
|
|
296
|
-
|
|
300
|
+
|
|
301
|
+
model Verification {
|
|
302
|
+
id String @id @default(cuid())
|
|
303
|
+
identifier String
|
|
304
|
+
value String
|
|
305
|
+
expiresAt DateTime
|
|
306
|
+
createdAt DateTime @default(now())
|
|
307
|
+
updatedAt DateTime @updatedAt
|
|
308
|
+
}
|
|
309
|
+
`,O=`import dotenv from "dotenv";
|
|
297
310
|
import path from "path";
|
|
298
311
|
import process from "process";
|
|
299
312
|
import { defineConfig } from "prisma/config";
|
|
@@ -306,7 +319,7 @@ export default defineConfig({
|
|
|
306
319
|
url: process.env.DATABASE_URL,
|
|
307
320
|
},
|
|
308
321
|
});
|
|
309
|
-
`,
|
|
322
|
+
`,_=`import jwt, { JwtPayload, SignOptions } from 'jsonwebtoken';
|
|
310
323
|
|
|
311
324
|
const createToken = (payload: Record<string, unknown>, secret: string, options: SignOptions) => {
|
|
312
325
|
return jwt.sign(payload, secret, options);
|
|
@@ -322,7 +335,7 @@ const verifyToken = (token: string, secret: string) => {
|
|
|
322
335
|
};
|
|
323
336
|
|
|
324
337
|
export const jwtUtils = { createToken, verifyToken };
|
|
325
|
-
`,
|
|
338
|
+
`,q=`import { CookieOptions, Request, Response } from "express";
|
|
326
339
|
|
|
327
340
|
const setCookie = (res: Response, key: string, value: string, options: CookieOptions) => {
|
|
328
341
|
res.cookie(key, value, options);
|
|
@@ -418,12 +431,7 @@ import type { AnyZodObject } from 'zod';
|
|
|
418
431
|
const validateRequest = (schema: AnyZodObject) => {
|
|
419
432
|
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
420
433
|
try {
|
|
421
|
-
await schema.parseAsync(
|
|
422
|
-
body: req.body,
|
|
423
|
-
query: req.query,
|
|
424
|
-
params: req.params,
|
|
425
|
-
cookies: req.cookies,
|
|
426
|
-
});
|
|
434
|
+
await schema.parseAsync(req.body);
|
|
427
435
|
return next();
|
|
428
436
|
} catch (error) {
|
|
429
437
|
next(error);
|
|
@@ -453,7 +461,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
453
461
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
454
462
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
455
463
|
SOFTWARE.
|
|
456
|
-
|
|
464
|
+
`,$=`# Contributor Covenant Code of Conduct
|
|
457
465
|
|
|
458
466
|
## Our Pledge
|
|
459
467
|
|
|
@@ -527,7 +535,7 @@ version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
|
527
535
|
|
|
528
536
|
[homepage]: http://contributor-covenant.org
|
|
529
537
|
[version]: http://contributor-covenant.org/version/1/4
|
|
530
|
-
|
|
538
|
+
`,z=`node_modules/
|
|
531
539
|
.env
|
|
532
540
|
dist/
|
|
533
541
|
*.log
|
|
@@ -747,25 +755,21 @@ export type ILoginUserPayload = z.infer<typeof AuthValidation.loginValidationSch
|
|
|
747
755
|
`,J=`import { z } from "zod";
|
|
748
756
|
|
|
749
757
|
const registerValidationSchema = z.object({
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
754
|
-
}),
|
|
758
|
+
name: z.string().min(1, "Name is required"),
|
|
759
|
+
email: z.string().email("Invalid email address"),
|
|
760
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
755
761
|
});
|
|
756
762
|
|
|
757
763
|
const loginValidationSchema = z.object({
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
password: z.string().min(1, "Password is required"),
|
|
761
|
-
}),
|
|
764
|
+
email: z.string().email("Invalid email address"),
|
|
765
|
+
password: z.string().min(1, "Password is required"),
|
|
762
766
|
});
|
|
763
767
|
|
|
764
768
|
export const AuthValidation = {
|
|
765
769
|
registerValidationSchema,
|
|
766
770
|
loginValidationSchema,
|
|
767
771
|
};
|
|
768
|
-
`;var
|
|
772
|
+
`;var Q=`import axios from "axios";
|
|
769
773
|
|
|
770
774
|
export const httpClient = axios.create({
|
|
771
775
|
baseURL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1",
|
|
@@ -781,7 +785,7 @@ httpClient.interceptors.response.use(
|
|
|
781
785
|
return Promise.reject(error);
|
|
782
786
|
}
|
|
783
787
|
);
|
|
784
|
-
`,
|
|
788
|
+
`,K=`"use server";
|
|
785
789
|
import { cookies } from "next/headers";
|
|
786
790
|
|
|
787
791
|
export async function setTokenInCookies(name: string, token: string, maxAge?: number) {
|
|
@@ -900,7 +904,7 @@ export function LoginForm() {
|
|
|
900
904
|
setLoading(true);
|
|
901
905
|
setError(null);
|
|
902
906
|
try {
|
|
903
|
-
const response = await loginAction(
|
|
907
|
+
const response = await loginAction(data);
|
|
904
908
|
if (response.success) {
|
|
905
909
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
906
910
|
router.push("/dashboard");
|
|
@@ -980,7 +984,7 @@ export function RegisterForm() {
|
|
|
980
984
|
setLoading(true);
|
|
981
985
|
setError(null);
|
|
982
986
|
try {
|
|
983
|
-
const response = await registerAction(
|
|
987
|
+
const response = await registerAction(data);
|
|
984
988
|
if (response.success) {
|
|
985
989
|
queryClient.invalidateQueries({ queryKey: ["user"] });
|
|
986
990
|
router.push("/dashboard");
|
|
@@ -1040,28 +1044,62 @@ export default function ${e==="login"?"LoginPage":"RegisterPage"}() {
|
|
|
1040
1044
|
</div>
|
|
1041
1045
|
);
|
|
1042
1046
|
}
|
|
1043
|
-
|
|
1047
|
+
`,be=`
|
|
1048
|
+
"use client";
|
|
1049
|
+
|
|
1050
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
1051
|
+
import { ReactNode, useState } from "react";
|
|
1052
|
+
|
|
1053
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
1054
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
1055
|
+
|
|
1056
|
+
return (
|
|
1057
|
+
<QueryClientProvider client={queryClient}>
|
|
1058
|
+
{children}
|
|
1059
|
+
</QueryClientProvider>
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
`,we=`
|
|
1063
|
+
import { Providers } from "@/components/Providers";
|
|
1064
|
+
import "./globals.css";
|
|
1065
|
+
|
|
1066
|
+
export default function RootLayout({
|
|
1067
|
+
children,
|
|
1068
|
+
}: {
|
|
1069
|
+
children: React.ReactNode;
|
|
1070
|
+
}) {
|
|
1071
|
+
return (
|
|
1072
|
+
<html lang="en">
|
|
1073
|
+
<body>
|
|
1074
|
+
<Providers>{children}</Providers>
|
|
1075
|
+
</body>
|
|
1076
|
+
</html>
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
`;var se=async e=>{let r=e;r||(r=(await Ce.prompt([{type:"input",name:"projectName",message:"What is your project name?",default:"shakil-stack-app"}])).projectName),r||(console.log(a.red("\u274C Error: Project name is required.")),process.exit(1));let{packageManager:l,useShadcn:x,installDeps:m,setupPrisma:u,setupBetterAuth:f,setupApi:T,setupLogin:h,setupGit:oe}=await Ce.prompt([{type:"list",name:"packageManager",message:"Which package manager do you want to use?",choices:["pnpm","npm","yarn"],default:E()},{type:"confirm",name:"useShadcn",message:"Would you like to use shadcn/ui?",default:!0},{type:"confirm",name:"setupPrisma",message:"Would you like to setup Prisma?",default:!0},{type:"confirm",name:"setupBetterAuth",message:"Would you like to setup Better-Auth?",default:!0},{type:"confirm",name:"setupApi",message:"Would you like to setup API modules (AuthController, etc.)?",default:!0},{type:"confirm",name:"setupLogin",message:"Would you like to setup Frontend Login/Register pages?",default:!0},{type:"confirm",name:"installDeps",message:"Do you want to install dependencies automatically?",default:!0},{type:"confirm",name:"setupGit",message:"Would you like to setup Git with automatic version control?",default:!0}]),k=r==="."||r==="./",t=k?process.cwd():o.join(process.cwd(),r),p=k?o.basename(process.cwd()):r;!k&&s.existsSync(t)&&(console.log(a.red(`\u274C Error: Directory ${r} already exists.`)),process.exit(1)),console.log(a.cyan(`
|
|
1044
1080
|
\u{1F680} Initializing ${a.bold(p)} in ${k?"current directory":r}...
|
|
1045
|
-
`));let S=
|
|
1046
|
-
\u{1F5BC}\uFE0F Scaffolding Next.js frontend...`)),
|
|
1047
|
-
\u{1F3A8} Setting up shadcn/ui...`));try{
|
|
1048
|
-
\u26A0\uFE0F Warning: Failed to automate shadcn/ui init. You can run "npx shadcn@latest init" in the frontend folder.`))}}await s.outputFile(o.join(t,"backend","src","server.ts"),ae(p)),await s.outputFile(o.join(t,"backend","src","app.ts"),ie(p)),await s.outputFile(o.join(t,"backend","src","app","config","index.ts"),F),u&&await s.outputFile(o.join(t,"backend","src","app","lib","prisma.ts"),P),
|
|
1081
|
+
`));let S=Be("\u{1F6E0}\uFE0F Creating project structure...").start();try{await s.ensureDir(t),await s.ensureDir(o.join(t,"backend","src","app","config")),await s.ensureDir(o.join(t,"backend","src","app","lib")),await s.ensureDir(o.join(t,"backend","src","app","module")),await s.ensureDir(o.join(t,"backend","src","app","routes")),await s.ensureDir(o.join(t,"backend","src","app","middleware")),await s.ensureDir(o.join(t,"backend","src","app","utils")),await s.ensureDir(o.join(t,"backend","src","app","errorHelpers")),u&&await s.ensureDir(o.join(t,"backend","prisma","schema")),T&&await s.ensureDir(o.join(t,"backend","src","app","module","auth")),console.log(a.cyan(`
|
|
1082
|
+
\u{1F5BC}\uFE0F Scaffolding Next.js frontend...`)),d(`npx create-next-app@latest frontend --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-${l}`,t);let b=["config","hooks","lib","services","types"];T&&b.push("lib/axios"),h&&b.push("components/auth","app/(auth)/login","app/(auth)/register");for(let y of b)await s.ensureDir(o.join(t,"frontend","src",y));if(x){console.log(a.cyan(`
|
|
1083
|
+
\u{1F3A8} Setting up shadcn/ui...`));try{d("npx shadcn@latest init -d",o.join(t,"frontend")),console.log(a.cyan("\u{1F4E6} Adding common shadcn/ui components...")),d(`npx shadcn@latest add ${["button","card","input","label","textarea","dialog","dropdown-menu","table","tabs","checkbox"].join(" ")} -y`,o.join(t,"frontend")),console.log(a.green("\u2705 shadcn/ui and common components initialized successfully!\u2728"))}catch{console.log(a.yellow(`
|
|
1084
|
+
\u26A0\uFE0F Warning: Failed to automate shadcn/ui init. You can run "npx shadcn@latest init" in the frontend folder.`))}}await s.outputFile(o.join(t,"backend","src","server.ts"),ae(p)),await s.outputFile(o.join(t,"backend","src","app.ts"),ie(p)),await s.outputFile(o.join(t,"backend","src","app","config","index.ts"),F),u&&await s.outputFile(o.join(t,"backend","src","app","lib","prisma.ts"),P),f&&await s.outputFile(o.join(t,"backend","src","app","lib","auth.ts"),I),await s.outputFile(o.join(t,"backend","src","app","routes","index.ts"),ce),await s.outputFile(o.join(t,"backend","src","app","middleware","globalErrorHandler.ts"),pe),await s.outputFile(o.join(t,"backend","src","app","middleware","notFound.ts"),le),await s.outputFile(o.join(t,"backend","src","app","middleware","sanitizeRequest.ts"),fe),await s.outputFile(o.join(t,"backend","src","app","middleware","validateRequest.ts"),ke),await s.outputFile(o.join(t,"backend","src","app","utils","catchAsync.ts"),de),await s.outputFile(o.join(t,"backend","src","app","utils","sendResponse.ts"),ge),await s.outputFile(o.join(t,"backend","src","app","utils","sanitizer.ts"),me),await s.outputFile(o.join(t,"backend","src","app","errorHelpers","ApiError.ts"),ue),u&&(await s.outputFile(o.join(t,"backend","prisma","schema","schema.prisma"),U),f&&await s.outputFile(o.join(t,"backend","prisma","schema","auth.prisma"),N),await s.outputFile(o.join(t,"backend","prisma.config.ts"),O)),T&&(await s.outputFile(o.join(t,"backend","src","app","module","auth","auth.controller.ts"),M),await s.outputFile(o.join(t,"backend","src","app","module","auth","auth.service.ts"),V),await s.outputFile(o.join(t,"backend","src","app","module","auth","auth.route.ts"),W),await s.outputFile(o.join(t,"backend","src","app","module","auth","auth.interface.ts"),G),await s.outputFile(o.join(t,"backend","src","app","module","auth","auth.validation.ts"),J),await s.outputFile(o.join(t,"backend","src","app","utils","jwt.ts"),_),await s.outputFile(o.join(t,"backend","src","app","utils","cookie.ts"),q),await s.outputFile(o.join(t,"backend","src","app","utils","token.ts"),D)),T&&(await s.outputFile(o.join(t,"frontend","src","lib","axios","httpClient.ts"),Q),await s.outputFile(o.join(t,"frontend","src","lib","tokenUtils.ts"),K),await s.outputFile(o.join(t,"frontend","src","lib","cookieUtils.ts"),Y),await s.outputFile(o.join(t,"frontend","src","services","auth.actions.ts"),X)),h&&(await s.outputFile(o.join(t,"frontend","src","components","auth","login-form.tsx"),Z),await s.outputFile(o.join(t,"frontend","src","components","auth","register-form.tsx"),ee),await s.outputFile(o.join(t,"frontend","src","app","(auth)","login","page.tsx"),C("login")),await s.outputFile(o.join(t,"frontend","src","app","(auth)","register","page.tsx"),C("register")),await s.outputFile(o.join(t,"frontend","src","components","Providers.tsx"),be),await s.outputFile(o.join(t,"frontend","src","app","layout.tsx"),we)),await s.outputFile(o.join(t,"backend","tsconfig.json"),he),await s.outputFile(o.join(t,".gitignore"),z),await s.outputFile(o.join(t,"LICENSE"),L),await s.outputFile(o.join(t,"README.md"),H(p)),await s.outputFile(o.join(t,"CODE_OF_CONDUCT.md"),$);let A=je.randomBytes(32).toString("hex"),i=je.randomBytes(32).toString("hex"),w=`DATABASE_URL="postgresql://postgres:postgres@localhost:5432/${p}"
|
|
1049
1085
|
JWT_SECRET="${A}"
|
|
1050
1086
|
BETTER_AUTH_SECRET="${i}"
|
|
1051
1087
|
NODE_ENV="development"
|
|
1052
1088
|
PORT=8000
|
|
1053
1089
|
BETTER_AUTH_BASE_URL="http://localhost:8000"
|
|
1054
|
-
CLIENT_URL="http://localhost:3000"
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
`)
|
|
1090
|
+
CLIENT_URL="http://localhost:3000"`,Le="NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1";await s.outputFile(o.join(t,".env"),w),await s.outputFile(o.join(t,"frontend",".env.local"),Le),await s.outputFile(o.join(t,"package.json"),B(p)),l==="pnpm"&&await s.outputFile(o.join(t,"pnpm-workspace.yaml"),`packages:
|
|
1091
|
+
- 'backend'
|
|
1092
|
+
- 'frontend'
|
|
1093
|
+
`);let $e={name:`${p}-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:{...u?{"@prisma/adapter-pg":"^7.5.0","@prisma/client":"^7.5.0",pg:"^8.20.0"}:{},...f?{"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",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",...u?{"@types/pg":"^8.20.0",prisma:"^7.5.0"}:{},"@types/morgan":"^1.9.10","@types/jsdom":"^21.1.7",tsx:"^4.21.0",nodemon:"^3.1.14",tsup:"^8.5.1",typescript:"^5.9.3",eslint:"^9.21.0",prettier:"^3.5.2"}};if(await s.writeJson(o.join(t,"backend","package.json"),$e,{spaces:2}),S.succeed(a.green("\u2705 Project structure created! \u2728")),m&&(console.log(a.yellow(`
|
|
1094
|
+
\u{1F4E6} Finalizing dependencies with ${l}...
|
|
1095
|
+
`)),d(`${l} install`,t),d(`${l} install`,o.join(t,"backend")),T||h)){console.log(a.yellow(`
|
|
1058
1096
|
\u{1F4E6} Adding frontend auth dependencies...
|
|
1059
|
-
`));let y=["axios"];h&&y.push("react-hook-form","zod","@hookform/resolvers
|
|
1060
|
-
Git Initializing...`));try{let y=o.join(t,"frontend",".git");s.existsSync(y)&&await s.remove(y),
|
|
1097
|
+
`));let y=["axios"];h&&y.push("react-hook-form","zod","@hookform/resolvers","@tanstack/react-query");let ze=l==="pnpm"?`${l} add ${y.join(" ")} --filter ./frontend`:`${l} add ${y.join(" ")}`;d(ze,t)}if(oe){console.log(a.cyan(`
|
|
1098
|
+
Git Initializing...`));try{let y=o.join(t,"frontend",".git");s.existsSync(y)&&await s.remove(y),d("git init",t),d("git add .",t),d('git commit -m "Initial commit from shakil-stack"',t),console.log(a.green(" Git initialized successfully!"))}catch{console.log(a.yellow("\u26A0\uFE0F Warning: Failed to initialize Git."))}}console.log(a.cyan(`
|
|
1061
1099
|
\u{1F525} Your project is ready! Follow these steps to start:
|
|
1062
1100
|
`)),r!=="./"&&r!=="."&&console.log(a.white(` 1. ${a.bold(`cd ${r}`)}`)),console.log(a.white(` 2. Ensure your ${a.bold("PostgreSQL")} database is running.`)),console.log(a.white(` 3. Update ${a.bold(".env")} credentials if needed.`)),console.log(a.white(` 4. ${a.bold("pnpm dev:backend")} (Starts server & Prisma Studio)`)),console.log(a.white(` 5. ${a.bold("pnpm dev:frontend")} (Starts Next.js application)`)),console.log(a.green(`
|
|
1063
1101
|
Happy coding! \u{1F680}
|
|
1064
|
-
`))}catch(b){S.fail(a.red("\u274C Failed to initialize project.")),console.error(b),process.exit(1)}};import R from"fs-extra";import te from"path";import v from"chalk";import
|
|
1102
|
+
`))}catch(b){S.fail(a.red("\u274C Failed to initialize project.")),console.error(b),process.exit(1)}};import R from"fs-extra";import te from"path";import v from"chalk";import Ve from"ora";var Re=(e,r)=>`import { Request, Response } from 'express';
|
|
1065
1103
|
import httpStatus from 'http-status';
|
|
1066
1104
|
import catchAsync from '../../utils/catchAsync.js';
|
|
1067
1105
|
import sendResponse from '../../utils/sendResponse.js';
|
|
@@ -1080,7 +1118,7 @@ const create${e} = catchAsync(async (req: Request, res: Response) => {
|
|
|
1080
1118
|
export const ${e}Controller = {
|
|
1081
1119
|
create${e},
|
|
1082
1120
|
};
|
|
1083
|
-
`,
|
|
1121
|
+
`,Se=(e,r)=>`import { ${e} } from '@prisma/client';
|
|
1084
1122
|
import prisma from '../../lib/prisma.js';
|
|
1085
1123
|
|
|
1086
1124
|
const create${e}IntoDB = async (payload: any) => {
|
|
@@ -1091,7 +1129,7 @@ const create${e}IntoDB = async (payload: any) => {
|
|
|
1091
1129
|
export const ${e}Service = {
|
|
1092
1130
|
create${e}IntoDB,
|
|
1093
1131
|
};
|
|
1094
|
-
`,
|
|
1132
|
+
`,Ae=(e,r)=>`import { Router } from 'express';
|
|
1095
1133
|
import { ${e}Controller } from './${r}.controller.js';
|
|
1096
1134
|
|
|
1097
1135
|
const router = Router();
|
|
@@ -1099,10 +1137,10 @@ const router = Router();
|
|
|
1099
1137
|
router.post('/create-${r}', ${e}Controller.create${e});
|
|
1100
1138
|
|
|
1101
1139
|
export const ${e}Routes = router;
|
|
1102
|
-
`,
|
|
1140
|
+
`,Ee=e=>`export type I${e} = {
|
|
1103
1141
|
// Define interface
|
|
1104
1142
|
};
|
|
1105
|
-
`,
|
|
1143
|
+
`,Fe=e=>`import { z } from 'zod';
|
|
1106
1144
|
|
|
1107
1145
|
const create${e}ValidationSchema = z.object({
|
|
1108
1146
|
body: z.object({
|
|
@@ -1113,17 +1151,17 @@ const create${e}ValidationSchema = z.object({
|
|
|
1113
1151
|
export const ${e}Validations = {
|
|
1114
1152
|
create${e}ValidationSchema,
|
|
1115
1153
|
};
|
|
1116
|
-
`,
|
|
1117
|
-
`,
|
|
1154
|
+
`,Pe=e=>`export const ${e}SearchableFields = [];
|
|
1155
|
+
`,Ie=e=>`model ${e} {
|
|
1118
1156
|
id String @id @default(uuid())
|
|
1119
1157
|
name String
|
|
1120
1158
|
createdAt DateTime @default(now())
|
|
1121
1159
|
updatedAt DateTime @updatedAt
|
|
1122
1160
|
}
|
|
1123
|
-
`;var
|
|
1124
|
-
\u26A0\uFE0F Both backend/.env and root .env exist. Merging...`)),await c.remove(m)):(await c.move(m,u),console.log(
|
|
1125
|
-
\u{1F4DD} Moved .env from backend/ to root.`)))):c.existsSync(u)&&(
|
|
1126
|
-
${i}`)}h!==
|
|
1127
|
-
`),console.log(
|
|
1128
|
-
`))}x.succeed(
|
|
1129
|
-
Next Steps:`)),console.log(
|
|
1161
|
+
`;var Ue=async e=>{e||(console.log(v.red("\u274C Error: Module name is required.")),process.exit(1));let r=e.charAt(0).toUpperCase()+e.slice(1),l=e.toLowerCase(),x=R.existsSync("backend")?"backend":".",m=te.join(x,"src","app","module",r);R.existsSync(te.join(x,"src","app","module"))||(console.log(v.red("\u274C Error: This command must be run inside your shakil-stack project root or backend directory.")),process.exit(1)),R.existsSync(m)&&(console.log(v.red(`\u274C Error: Module ${r} already exists.`)),process.exit(1));let u=Ve(`\u{1F6E0}\uFE0F Generating module: ${v.cyan(r)}...`).start();try{await R.ensureDir(m);let f={"controller.ts":Re(r,l),"service.ts":Se(r,l),"route.ts":Ae(r,l),"interface.ts":Ee(r),"validation.ts":Fe(r),"constant.ts":Pe(r)};await R.outputFile(te.join(x,"prisma","schema",`${l}.prisma`),Ie(r));for(let[T,h]of Object.entries(f))await R.outputFile(te.join(m,`${l}.${T}`),h);u.succeed(v.green(`\u2705 Module ${r} generated successfully! \u2728`)),console.log(v.gray(`Created at: ${m}`))}catch(f){u.fail(v.red("\u274C Failed to generate module.")),console.error(f)}};import We from"fs-extra";import Ge from"chalk";var Ne=async()=>{let e=E(),r=We.existsSync("backend")?"backend":".";console.log(Ge.cyan(`\u{1F3D7}\uFE0F Building backend with ${e}...`)),d(`${e} run build`,r)};import Je from"fs-extra";import re from"chalk";var Oe=async e=>{let r=Je.existsSync("backend")?"backend":".";e==="generate"?(console.log(re.cyan("\u{1F504} Generating Prisma client...")),d("npx prisma generate",r)):e==="migrate"?(console.log(re.cyan("\u{1F680} Running Prisma migrations...")),d("npx prisma migrate dev",r)):console.log(re.red(`\u274C Error: Unknown prisma subcommand: ${e}`))};import c from"fs-extra";import n from"path";import g from"chalk";import Qe from"ora";var _e=async()=>{let e=process.cwd(),r=n.join(e,"backend"),l=n.basename(e);c.existsSync(r)||(console.log(g.red("\u274C Error: Not in a shakil-stack project root (backend folder not found).")),process.exit(1));let x=Qe("\u{1F504} Updating project structure...").start();try{let m=n.join(r,".env"),u=n.join(e,".env"),f="";c.existsSync(m)?(f=await c.readFile(m,"utf-8"),c.existsSync(u)?(console.log(g.yellow(`
|
|
1162
|
+
\u26A0\uFE0F Both backend/.env and root .env exist. Merging...`)),await c.remove(m)):(await c.move(m,u),console.log(g.yellow(`
|
|
1163
|
+
\u{1F4DD} Moved .env from backend/ to root.`)))):c.existsSync(u)&&(f=await c.readFile(u,"utf-8"));let T=['BETTER_AUTH_BASE_URL="http://localhost:8000"','CLIENT_URL="http://localhost:3000"','JWT_ACCESS_EXPIRES_IN="1h"','JWT_REFRESH_EXPIRES_IN="7d"'],h=f;for(let i of T){let w=i.split("=")[0];h.includes(w)||(h+=`
|
|
1164
|
+
${i}`)}h!==f&&(await c.outputFile(u,h.trim()+`
|
|
1165
|
+
`),console.log(g.green("\u2705 Updated root .env with missing variables.")));let oe=[{path:n.join(r,"src","app","config","index.ts"),content:F},{path:n.join(r,"src","app","lib","prisma.ts"),content:P},{path:n.join(r,"src","app","lib","auth.ts"),content:I},{path:n.join(r,"prisma","schema","schema.prisma"),content:U},{path:n.join(r,"prisma.config.ts"),content:O}];for(let i of oe)await c.outputFile(i.path,i.content);let k=n.join(r,"src","app","module","auth");await c.ensureDir(k);let t=[{path:n.join(k,"auth.controller.ts"),content:M},{path:n.join(k,"auth.service.ts"),content:V},{path:n.join(k,"auth.route.ts"),content:W},{path:n.join(k,"auth.interface.ts"),content:G},{path:n.join(k,"auth.validation.ts"),content:J},{path:n.join(r,"src","app","utils","jwt.ts"),content:_},{path:n.join(r,"src","app","utils","cookie.ts"),content:q},{path:n.join(r,"src","app","utils","token.ts"),content:D},{path:n.join(r,"prisma","schema","auth.prisma"),content:N}];for(let i of t)c.existsSync(i.path)||(await c.outputFile(i.path,i.content),console.log(g.cyan(`\u{1F195} Added ${n.basename(i.path)} to auth module.`)));let p=n.join(e,"frontend");if(c.existsSync(p)){let i=[{path:n.join(p,"src","lib","axios","httpClient.ts"),content:Q},{path:n.join(p,"src","lib","tokenUtils.ts"),content:K},{path:n.join(p,"src","lib","cookieUtils.ts"),content:Y},{path:n.join(p,"src","services","auth.actions.ts"),content:X},{path:n.join(p,"src","components","auth","login-form.tsx"),content:Z},{path:n.join(p,"src","components","auth","register-form.tsx"),content:ee},{path:n.join(p,"src","app","(auth)","login","page.tsx"),content:C("login")},{path:n.join(p,"src","app","(auth)","register","page.tsx"),content:C("register")}];for(let w of i)c.existsSync(w.path)||(await c.outputFile(w.path,w.content),console.log(g.cyan(`\u{1F195} Added ${n.basename(w.path)} to frontend.`)))}let S=n.basename(e),b=[{path:n.join(e,".gitignore"),content:z},{path:n.join(e,"LICENSE"),content:L},{path:n.join(e,"README.md"),content:H(S)},{path:n.join(e,"CODE_OF_CONDUCT.md"),content:$},{path:n.join(e,"package.json"),content:B(S)}];for(let i of b)c.existsSync(i.path)||(await c.outputFile(i.path,i.content),console.log(g.cyan(`\u{1F4C4} Added ${n.basename(i.path)} to root.`)));let A=n.join(r,".gitignore");if(c.existsSync(A)){let i=await c.readFile(A,"utf-8");i.includes(".env")&&(i=i.replace(/\.env\n?/,"").trim(),await c.outputFile(A,i+`
|
|
1166
|
+
`))}x.succeed(g.green("\u2705 Project updated successfully! \u2728")),console.log(g.cyan(`
|
|
1167
|
+
Next Steps:`)),console.log(g.white("1. Check your root .env file and update credentials if needed.")),console.log(g.white("2. Run 'shakil-stack prisma generate' to update the Prisma client."))}catch(m){x.fail(g.red("\u274C Failed to update project.")),console.error(m),process.exit(1)}};var Xe=Ye(import.meta.url),qe=ne.dirname(Xe),Ze=ne.resolve(qe,De.existsSync(ne.resolve(qe,"../../package.json"))?"../../package.json":"../package.json"),et=De.readJsonSync(Ze),j=new Ke;j.name("shakil-stack").description("Full-stack EchoNet-style project generator CLI").version(et.version);j.command("init").description("Initialize a new full-stack project").argument("[projectName]","Name of the project").action(e=>{se(e)});j.command("update").description("Update an existing project to the latest shakil-stack version").action(()=>{_e()});j.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"?Ue(r):console.log(`\u274C Error: Unknown generation type: ${e}`)});j.command("build").description("Build the backend for production").action(()=>{Ne()});j.command("prisma").description("Prisma utilities").argument("<subcommand>","generate | migrate").action(e=>{Oe(e)});process.argv.slice(2).length?j.parse(process.argv):se();
|