stackkit 0.3.4 → 0.3.6

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.
Files changed (203) hide show
  1. package/README.md +50 -42
  2. package/dist/cli/add.js +122 -56
  3. package/dist/cli/create.d.ts +2 -0
  4. package/dist/cli/create.js +271 -95
  5. package/dist/cli/doctor.js +1 -0
  6. package/dist/cli/list.d.ts +1 -1
  7. package/dist/cli/list.js +6 -4
  8. package/dist/index.js +234 -191
  9. package/dist/lib/constants.d.ts +4 -0
  10. package/dist/lib/constants.js +4 -0
  11. package/dist/lib/discovery/module-discovery.d.ts +4 -0
  12. package/dist/lib/discovery/module-discovery.js +56 -0
  13. package/dist/lib/generation/code-generator.d.ts +11 -2
  14. package/dist/lib/generation/code-generator.js +42 -3
  15. package/dist/lib/generation/generator-utils.js +3 -1
  16. package/dist/lib/pm/package-manager.js +16 -13
  17. package/dist/lib/ui/logger.js +3 -2
  18. package/dist/lib/utils/path-resolver.d.ts +2 -0
  19. package/dist/lib/utils/path-resolver.js +8 -0
  20. package/dist/meta.json +8312 -0
  21. package/modules/auth/better-auth/files/{shared → express}/config/env.ts +48 -52
  22. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +20 -1
  23. package/modules/auth/better-auth/files/express/modules/auth.controller.ts +349 -0
  24. package/modules/auth/better-auth/files/express/modules/{auth/auth.route.ts → auth.route.ts} +12 -7
  25. package/modules/auth/better-auth/files/express/modules/auth.service.ts +664 -0
  26. package/modules/auth/better-auth/files/express/modules/{auth/auth.type.ts → auth.type.ts} +22 -9
  27. package/modules/auth/better-auth/files/{shared/mongoose/auth/constants.ts → express/mongo-modules/auth.constants.ts} +0 -1
  28. package/modules/auth/better-auth/files/{shared/mongoose/auth/helper.ts → express/mongo-modules/auth.helper.ts} +11 -1
  29. package/modules/auth/better-auth/files/express/types/express.d.ts +11 -0
  30. package/modules/auth/better-auth/files/nextjs/api-route.ts +74 -0
  31. package/modules/auth/better-auth/files/nextjs/dashboard/pages/(user)/page.tsx +6 -0
  32. package/modules/auth/better-auth/files/nextjs/dashboard/pages/admin/page.tsx +6 -0
  33. package/modules/auth/better-auth/files/nextjs/dashboard/pages/layout.tsx +48 -0
  34. package/modules/auth/better-auth/files/nextjs/dashboard/pages/my-profile/page.tsx +5 -0
  35. package/modules/auth/better-auth/files/nextjs/features/services/auth.service.ts +102 -0
  36. package/modules/auth/better-auth/files/nextjs/layout/layout.tsx +13 -0
  37. package/modules/auth/better-auth/files/nextjs/lib/axios/http.ts +158 -0
  38. package/modules/auth/better-auth/files/nextjs/lib/env.ts +35 -0
  39. package/modules/auth/better-auth/files/nextjs/lib/utils/auth.ts +75 -0
  40. package/modules/auth/better-auth/files/nextjs/lib/utils/cookie.ts +29 -0
  41. package/modules/auth/better-auth/files/nextjs/lib/utils/jwt.ts +28 -0
  42. package/modules/auth/better-auth/files/nextjs/lib/utils/token.ts +49 -0
  43. package/modules/auth/better-auth/files/nextjs/pages/forgot-password/page.tsx +5 -0
  44. package/modules/auth/better-auth/files/nextjs/pages/layout.tsx +11 -0
  45. package/modules/auth/better-auth/files/nextjs/pages/login/page.tsx +9 -0
  46. package/modules/auth/better-auth/files/nextjs/pages/register/page.tsx +5 -0
  47. package/modules/auth/better-auth/files/nextjs/pages/reset-password/page.tsx +10 -0
  48. package/modules/auth/better-auth/files/nextjs/pages/verify-email/page.tsx +10 -0
  49. package/modules/auth/better-auth/files/nextjs/proxy.ts +157 -22
  50. package/modules/auth/better-auth/files/nextjs/theme/providers/theme-provider.tsx +11 -0
  51. package/modules/auth/better-auth/files/nextjs/types/api.types.ts +18 -0
  52. package/modules/auth/better-auth/files/react/components/protected-route.tsx +39 -0
  53. package/modules/auth/better-auth/files/react/components/route-guards.tsx +13 -0
  54. package/modules/auth/better-auth/files/react/dashboard/admin/pages/overview.tsx +3 -0
  55. package/modules/auth/better-auth/files/react/dashboard/pages/overview.tsx +3 -0
  56. package/modules/auth/better-auth/files/react/features/pages/forgot-password.tsx +5 -0
  57. package/modules/auth/better-auth/files/react/features/pages/login.tsx +5 -0
  58. package/modules/auth/better-auth/files/react/features/pages/my-profile.tsx +5 -0
  59. package/modules/auth/better-auth/files/react/features/pages/oauth-callback.tsx +59 -0
  60. package/modules/auth/better-auth/files/react/features/pages/register.tsx +5 -0
  61. package/modules/auth/better-auth/files/react/features/pages/reset-password.tsx +10 -0
  62. package/modules/auth/better-auth/files/react/features/pages/verify-email.tsx +10 -0
  63. package/modules/auth/better-auth/files/react/layout/dashboard-layout.tsx +54 -0
  64. package/modules/auth/better-auth/files/react/lib/axios/http.ts +68 -0
  65. package/modules/auth/better-auth/files/react/lib/env.ts +25 -0
  66. package/modules/auth/better-auth/files/react/router.tsx +73 -0
  67. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider-context.ts +13 -0
  68. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider.tsx +51 -0
  69. package/modules/auth/better-auth/files/react/theme/hooks/use-theme.ts +8 -0
  70. package/modules/auth/better-auth/files/shared/features/components/change-password-dialog.tsx +113 -0
  71. package/modules/auth/better-auth/files/shared/features/components/forgot-password-form.tsx +84 -0
  72. package/modules/auth/better-auth/files/shared/features/components/login-form.tsx +134 -0
  73. package/modules/auth/better-auth/files/shared/features/components/my-profile.tsx +147 -0
  74. package/modules/auth/better-auth/files/shared/features/components/profile-form.tsx +205 -0
  75. package/modules/auth/better-auth/files/shared/features/components/register-form.tsx +100 -0
  76. package/modules/auth/better-auth/files/shared/features/components/reset-password-form.tsx +111 -0
  77. package/modules/auth/better-auth/files/shared/features/components/social-login-buttons.tsx +47 -0
  78. package/modules/auth/better-auth/files/shared/features/components/user-profile-menu.tsx +106 -0
  79. package/modules/auth/better-auth/files/shared/features/components/verify-email-form.tsx +110 -0
  80. package/modules/auth/better-auth/files/shared/features/queries/auth.mutations.tsx +312 -0
  81. package/modules/auth/better-auth/files/shared/features/queries/auth.querie.ts +19 -0
  82. package/modules/auth/better-auth/files/shared/features/services/auth.api.ts +81 -0
  83. package/modules/auth/better-auth/files/shared/features/types/auth.type.ts +47 -0
  84. package/modules/auth/better-auth/files/shared/features/validators/change-password.validator.ts +18 -0
  85. package/modules/auth/better-auth/files/shared/features/validators/forgot.validator.ts +7 -0
  86. package/modules/auth/better-auth/files/shared/features/validators/login.validator.ts +14 -0
  87. package/modules/auth/better-auth/files/shared/features/validators/profile.validator.ts +8 -0
  88. package/modules/auth/better-auth/files/shared/features/validators/register.validator.ts +9 -0
  89. package/modules/auth/better-auth/files/shared/features/validators/reset.validator.ts +9 -0
  90. package/modules/auth/better-auth/files/shared/features/validators/verify.validator.ts +8 -0
  91. package/modules/auth/better-auth/files/shared/lib/auth-client.ts +2 -1
  92. package/modules/auth/better-auth/files/shared/lib/auth.ts +10 -29
  93. package/modules/auth/better-auth/files/shared/lib/constant/dashboard.ts +90 -0
  94. package/modules/auth/better-auth/files/shared/prisma/enums.prisma +0 -1
  95. package/modules/auth/better-auth/files/shared/theme/mode-toggle.tsx +30 -0
  96. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-header.tsx +94 -0
  97. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-sidebar.tsx +255 -0
  98. package/modules/auth/better-auth/files/shared/ui/shadcn/components/footer.tsx +35 -0
  99. package/modules/auth/better-auth/files/shared/ui/shadcn/components/navbar.tsx +145 -0
  100. package/modules/auth/better-auth/files/shared/ui/shadcn/form-field/input-field.tsx +440 -0
  101. package/modules/auth/better-auth/files/shared/utils/email.ts +20 -18
  102. package/modules/auth/better-auth/generator.json +174 -53
  103. package/modules/auth/better-auth/module.json +2 -2
  104. package/modules/components/files/shared/hooks/use-file-upload.ts +412 -0
  105. package/modules/components/files/shared/lib/utils/url-helpers.ts +110 -0
  106. package/modules/components/files/shared/shadcn/dashboard/data-table-column-selector.tsx +52 -0
  107. package/modules/components/files/shared/shadcn/dashboard/data-table-footer.tsx +156 -0
  108. package/modules/components/files/shared/shadcn/dashboard/data-table.tsx +405 -0
  109. package/modules/components/files/shared/shadcn/global/form-field/input-field.tsx +440 -0
  110. package/modules/components/files/shared/shadcn/global/form-field/media-uploader-field.tsx +745 -0
  111. package/modules/components/files/shared/shadcn/global/form-field/multi-select-field.tsx +207 -0
  112. package/modules/components/files/shared/shadcn/global/form-field/select-field.tsx +247 -0
  113. package/modules/components/files/shared/shadcn/global/form-field/textarea-field.tsx +277 -0
  114. package/modules/components/files/shared/shadcn/global/form-field/tiptap-editor-field.tsx +35 -0
  115. package/modules/components/files/shared/shadcn/global/no-results.tsx +41 -0
  116. package/modules/components/files/shared/shadcn/tiptap-editor/editor-menu-bar.tsx +217 -0
  117. package/modules/components/files/shared/shadcn/tiptap-editor/tiptap-editor.tsx +104 -0
  118. package/modules/components/files/shared/url/load-more.tsx +93 -0
  119. package/modules/components/files/shared/url/search-bar.tsx +131 -0
  120. package/modules/components/files/shared/url/sort-select.tsx +118 -0
  121. package/modules/components/files/shared/url/url-tabs.tsx +77 -0
  122. package/modules/components/generator.json +109 -0
  123. package/modules/components/module.json +11 -0
  124. package/modules/database/mongoose/generator.json +3 -14
  125. package/modules/database/mongoose/module.json +2 -2
  126. package/modules/database/prisma/generator.json +6 -12
  127. package/modules/database/prisma/module.json +2 -2
  128. package/modules/storage/cloudinary/files/express/config/env.ts +65 -0
  129. package/modules/storage/cloudinary/files/express/config/media.ts +103 -0
  130. package/modules/storage/cloudinary/files/express/modules/media/media.controller.ts +59 -0
  131. package/modules/storage/cloudinary/files/express/modules/media/media.route.ts +29 -0
  132. package/modules/storage/cloudinary/files/express/modules/media/media.service.ts +113 -0
  133. package/modules/storage/cloudinary/files/express/modules/media/media.type.ts +32 -0
  134. package/modules/storage/cloudinary/generator.json +34 -0
  135. package/modules/storage/cloudinary/module.json +11 -0
  136. package/modules/ui/shadcn/generator.json +21 -0
  137. package/modules/ui/shadcn/module.json +11 -0
  138. package/package.json +24 -26
  139. package/templates/express/README.md +11 -16
  140. package/templates/express/src/config/env.ts +7 -5
  141. package/templates/nextjs/README.md +13 -18
  142. package/templates/nextjs/app/favicon.ico +0 -0
  143. package/templates/nextjs/app/layout.tsx +6 -4
  144. package/templates/nextjs/components/providers/query-provider.tsx +3 -0
  145. package/templates/nextjs/env.example +3 -1
  146. package/templates/nextjs/lib/axios/http.ts +23 -0
  147. package/templates/nextjs/lib/env.ts +7 -5
  148. package/templates/nextjs/package.json +2 -1
  149. package/templates/nextjs/template.json +1 -2
  150. package/templates/react/README.md +9 -14
  151. package/templates/react/index.html +1 -1
  152. package/templates/react/package.json +1 -1
  153. package/templates/react/src/assets/favicon.ico +0 -0
  154. package/templates/react/src/components/providers/query-provider.tsx +38 -0
  155. package/templates/react/src/{shared/components → components}/seo.tsx +4 -8
  156. package/templates/react/src/lib/axios/http.ts +24 -0
  157. package/templates/react/src/main.tsx +8 -11
  158. package/templates/react/src/{features/about/pages → pages}/about.tsx +1 -1
  159. package/templates/react/src/{features/home/pages → pages}/home.tsx +1 -1
  160. package/templates/react/src/router.tsx +6 -6
  161. package/templates/react/src/vite-env.d.ts +2 -1
  162. package/templates/react/template.json +0 -1
  163. package/templates/react/tsconfig.app.json +6 -0
  164. package/templates/react/tsconfig.json +7 -1
  165. package/templates/react/vite.config.ts +12 -0
  166. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +0 -3
  167. package/modules/auth/authjs/files/nextjs/proxy.ts +0 -1
  168. package/modules/auth/authjs/files/shared/lib/auth.ts +0 -119
  169. package/modules/auth/authjs/files/shared/prisma/schema.prisma +0 -61
  170. package/modules/auth/authjs/generator.json +0 -64
  171. package/modules/auth/authjs/module.json +0 -13
  172. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +0 -264
  173. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +0 -537
  174. package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +0 -24
  175. package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +0 -4
  176. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +0 -41
  177. package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +0 -74
  178. package/templates/express/node_modules/.bin/acorn +0 -17
  179. package/templates/express/node_modules/.bin/eslint +0 -17
  180. package/templates/express/node_modules/.bin/tsc +0 -17
  181. package/templates/express/node_modules/.bin/tsserver +0 -17
  182. package/templates/express/node_modules/.bin/tsx +0 -17
  183. package/templates/nextjs/lib/api/http.ts +0 -40
  184. package/templates/nextjs/next-env.d.ts +0 -6
  185. package/templates/react/dist/assets/index-D4AHT4dU.js +0 -193
  186. package/templates/react/dist/assets/index-rpwj5ZOX.css +0 -1
  187. package/templates/react/dist/index.html +0 -14
  188. package/templates/react/dist/vite.svg +0 -1
  189. package/templates/react/public/vite.svg +0 -1
  190. package/templates/react/src/app/layouts/dashboard-layout.tsx +0 -8
  191. package/templates/react/src/app/layouts/public-layout.tsx +0 -5
  192. package/templates/react/src/app/providers.tsx +0 -20
  193. package/templates/react/src/app/router.tsx +0 -21
  194. package/templates/react/src/assets/react.svg +0 -1
  195. package/templates/react/src/shared/api/http.ts +0 -39
  196. package/templates/react/src/shared/components/loading.tsx +0 -8
  197. package/templates/react/src/shared/lib/query-client.ts +0 -12
  198. package/templates/react/src/utils/storage.ts +0 -35
  199. package/templates/react/src/utils/utils.ts +0 -3
  200. /package/templates/nextjs/app/{page.tsx → (public)/(root)/page.tsx} +0 -0
  201. /package/templates/react/src/{shared/components → components}/error-boundary.tsx +0 -0
  202. /package/templates/react/src/{shared/components → components}/layout.tsx +0 -0
  203. /package/templates/react/src/{shared/pages → pages}/not-found.tsx +0 -0
@@ -0,0 +1,65 @@
1
+ import dotenv from "dotenv";
2
+ import status from "http-status";
3
+ import path from "path";
4
+ import { AppError } from "../shared/errors/app-error";
5
+
6
+ dotenv.config({ path: path.join(process.cwd(), ".env") });
7
+
8
+ interface EnvConfig {
9
+ NODE_ENV: string;
10
+ PORT: string;
11
+ APP_NAME?: string;
12
+ APP_URL: string;
13
+ {{#if database}}
14
+ DATABASE_URL: string;
15
+ {{/if}}
16
+ CLOUDINARY: {
17
+ CLOUDINARY_CLOUD_NAME: string;
18
+ CLOUDINARY_API_KEY: string;
19
+ CLOUDINARY_API_SECRET: string;
20
+ CLOUDINARY_UPLOAD_PRESET?: string;
21
+ };
22
+ }
23
+
24
+ const loadEnvVars = (): EnvConfig => {
25
+ const requiredEnvVars = [
26
+ "NODE_ENV",
27
+ "PORT",
28
+ "APP_NAME",
29
+ "APP_URL",
30
+ {{#if database}}
31
+ "DATABASE_URL",
32
+ {{/if}}
33
+ "CLOUDINARY_CLOUD_NAME",
34
+ "CLOUDINARY_API_KEY",
35
+ "CLOUDINARY_API_SECRET",
36
+ ];
37
+
38
+ requiredEnvVars.forEach((varName) => {
39
+ if (!process.env[varName]) {
40
+ throw new AppError(
41
+ status.INTERNAL_SERVER_ERROR,
42
+ `Environment variable ${varName} is required but not set in .env file.`,
43
+ );
44
+ }
45
+ });
46
+
47
+ return {
48
+ NODE_ENV: process.env.NODE_ENV as string,
49
+ PORT: process.env.PORT as string,
50
+ APP_NAME: process.env.APP_NAME ?? "Your App",
51
+ APP_URL: process.env.APP_URL as string,
52
+ {{#if database}}
53
+ DATABASE_URL: process.env.DATABASE_URL as string,
54
+ {{/if}}
55
+ CLOUDINARY: {
56
+ CLOUDINARY_CLOUD_NAME: process.env.CLOUDINARY_CLOUD_NAME as string,
57
+ CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY as string,
58
+ CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET as string,
59
+ CLOUDINARY_UPLOAD_PRESET:
60
+ process.env.CLOUDINARY_UPLOAD_PRESET as string,
61
+ },
62
+ };
63
+ };
64
+
65
+ export const envVars = loadEnvVars();
@@ -0,0 +1,103 @@
1
+ import { v2 as cloudinary, UploadApiResponse } from "cloudinary";
2
+ import status from "http-status";
3
+ import { AppError } from "../shared/errors/app-error";
4
+ import { envVars } from "./env";
5
+
6
+ cloudinary.config({
7
+ cloud_name: envVars.CLOUDINARY.CLOUDINARY_CLOUD_NAME,
8
+ api_key: envVars.CLOUDINARY.CLOUDINARY_API_KEY,
9
+ api_secret: envVars.CLOUDINARY.CLOUDINARY_API_SECRET,
10
+ });
11
+
12
+ export const uploadFileToCloudinary = async (
13
+ buffer: Buffer,
14
+ fileName: string,
15
+ ): Promise<UploadApiResponse> => {
16
+ if (!buffer || !fileName) {
17
+ throw new AppError(
18
+ status.BAD_REQUEST,
19
+ "File buffer and file name are required for upload",
20
+ );
21
+ }
22
+
23
+ const extension = fileName.split(".").pop()?.toLocaleLowerCase();
24
+
25
+ const fileNameWithoutExtension = fileName
26
+ .split(".")
27
+ .slice(0, -1)
28
+ .join(".")
29
+ .toLowerCase()
30
+ .replace(/\s+/g, "-")
31
+ // eslint-disable-next-line no-useless-escape
32
+ .replace(/[^a-z0-9\-]/g, "");
33
+
34
+ const uniqueName =
35
+ Math.random().toString(36).substring(2) +
36
+ "-" +
37
+ Date.now() +
38
+ "-" +
39
+ fileNameWithoutExtension;
40
+
41
+ const folder =
42
+ extension === "pdf" ? "pdfs" : extension === "mp4" ? "videos" : "images";
43
+
44
+ return new Promise((resolve, reject) => {
45
+ cloudinary.uploader
46
+ .upload_stream(
47
+ {
48
+ resource_type: "auto",
49
+ public_id: `app/${folder}/${uniqueName}`,
50
+ folder: `app/${folder}`,
51
+ },
52
+ (error, result) => {
53
+ if (error) {
54
+ return reject(
55
+ new AppError(
56
+ status.INTERNAL_SERVER_ERROR,
57
+ "Failed to upload file to Cloudinary",
58
+ ),
59
+ );
60
+ }
61
+ resolve(result as UploadApiResponse);
62
+ },
63
+ )
64
+ .end(buffer);
65
+ });
66
+ };
67
+
68
+ export const deleteFileFromCloudinary = async (url: string) => {
69
+ try {
70
+ const regex = /\/v\d+\/(.+?)(?:\.[a-zA-Z0-9]+)+$/;
71
+
72
+ const match = url.match(regex);
73
+
74
+ if (match && match[1]) {
75
+ const publicId = match[1];
76
+
77
+ // try several resource types to ensure deletion for image/video/raw
78
+ const tryTypes: Array<"image" | "video" | "raw"> = [
79
+ "image",
80
+ "video",
81
+ "raw",
82
+ ];
83
+
84
+ for (const t of tryTypes) {
85
+ try {
86
+ await cloudinary.uploader.destroy(publicId, { resource_type: t });
87
+ } catch {
88
+ // continue trying other types
89
+ }
90
+ }
91
+
92
+ console.log(`File ${publicId} deleted from cloudinary`);
93
+ }
94
+ } catch (error) {
95
+ console.error("Error deleting file from Cloudinary:", error);
96
+ throw new AppError(
97
+ status.INTERNAL_SERVER_ERROR,
98
+ "Failed to delete file from Cloudinary",
99
+ );
100
+ }
101
+ };
102
+
103
+ export const cloudinaryUpload = cloudinary;
@@ -0,0 +1,59 @@
1
+ import { Request, Response } from "express";
2
+ import status from "http-status";
3
+ import { catchAsync } from "../../shared/utils/catch-async";
4
+ import { sendResponse } from "../../shared/utils/send-response";
5
+ import { mediaService } from "./media.service";
6
+ import {
7
+ MediaSignInput,
8
+ mediaSignSchema,
9
+ mediaUploadDeleteSchema,
10
+ mediaUploadPresignSchema,
11
+ } from "./media.type";
12
+
13
+ const signMedia = catchAsync(async (req: Request, res: Response) => {
14
+ const publicId = req.params.publicId as string;
15
+ const transformation = (req.query.transformation as string) || undefined;
16
+
17
+ const payload = mediaSignSchema.parse({
18
+ publicId,
19
+ transformation,
20
+ }) as MediaSignInput;
21
+ const result = await mediaService.signMedia(payload);
22
+
23
+ sendResponse(res, {
24
+ status: status.OK,
25
+ success: true,
26
+ message: "Cloudinary URL generated",
27
+ data: result,
28
+ });
29
+ });
30
+
31
+ const createPresign = catchAsync(async (req: Request, res: Response) => {
32
+ const payload = mediaUploadPresignSchema.parse(req.body);
33
+ const result = await mediaService.createMediaPresign(payload);
34
+
35
+ sendResponse(res, {
36
+ status: status.OK,
37
+ success: true,
38
+ message: "Presigned upload created",
39
+ data: result,
40
+ });
41
+ });
42
+
43
+ const deleteUploads = catchAsync(async (req: Request, res: Response) => {
44
+ const payload = mediaUploadDeleteSchema.parse(req.body);
45
+ const result = await mediaService.deleteMediaUploads(payload);
46
+
47
+ sendResponse(res, {
48
+ status: status.OK,
49
+ success: true,
50
+ message: "Uploads deleted",
51
+ data: result,
52
+ });
53
+ });
54
+
55
+ export const mediaController = {
56
+ signMedia,
57
+ createPresign,
58
+ deleteUploads,
59
+ };
@@ -0,0 +1,29 @@
1
+ {{#if database == "prisma"}}
2
+ import { Role } from "@prisma/client";
3
+ {{/if}}
4
+ {{#if database == "mongoose"}}
5
+ import { Role } from "../../modules/auth/auth.constants";
6
+ {{/if}}
7
+ import { Router } from "express";
8
+ import { authorize } from "../../shared/middlewares/authorize.middleware";
9
+ import { mediaController } from "./media.controller";
10
+
11
+ const router = Router();
12
+
13
+ router.get(
14
+ "/:publicId/transform",
15
+ authorize(Role.ADMIN, Role.USER),
16
+ mediaController.signMedia,
17
+ );
18
+ router.post(
19
+ "/upload/presign",
20
+ authorize(Role.ADMIN, Role.USER),
21
+ mediaController.createPresign,
22
+ );
23
+ router.post(
24
+ "/upload/delete",
25
+ authorize(Role.ADMIN, Role.USER),
26
+ mediaController.deleteUploads,
27
+ );
28
+
29
+ export const mediaRoutes = router;
@@ -0,0 +1,113 @@
1
+ import { envVars } from "../../config/env";
2
+ import { cloudinaryUpload, deleteFileFromCloudinary } from "../../config/media";
3
+ import {
4
+ MediaSignInput,
5
+ MediaUploadDeleteInput,
6
+ MediaUploadPresignInput,
7
+ } from "./media.type";
8
+
9
+ const signMedia = async (payload: MediaSignInput) => {
10
+ const { publicId, transformation } = payload;
11
+
12
+ const options = transformation ? { transformation } : undefined;
13
+
14
+ const url = cloudinaryUpload.url(publicId, options);
15
+
16
+ return { url };
17
+ };
18
+
19
+ const createMediaPresign = async (payload: MediaUploadPresignInput) => {
20
+ const timestamp = Math.floor(Date.now() / 1000);
21
+ const paramsToSign: Record<string, string | number> = { timestamp };
22
+
23
+ if (payload.folder) paramsToSign.folder = payload.folder;
24
+ if (payload.publicId) paramsToSign.public_id = payload.publicId;
25
+ if (payload.resourceType && payload.resourceType !== "auto")
26
+ paramsToSign.resource_type = payload.resourceType;
27
+
28
+ const resourceType = payload.resourceType || "auto";
29
+
30
+ // Unsigned flow: return upload_preset if configured
31
+ if (payload.unsigned) {
32
+ const upload_preset =
33
+ envVars.CLOUDINARY.CLOUDINARY_UPLOAD_PRESET ?? undefined;
34
+ const uploadUrl = `https://api.cloudinary.com/v1_1/${envVars.CLOUDINARY.CLOUDINARY_CLOUD_NAME}/${resourceType}/upload`;
35
+
36
+ return {
37
+ uploadUrl,
38
+ upload_preset,
39
+ unsigned: true,
40
+ folder: payload.folder,
41
+ publicId: payload.publicId,
42
+ resourceType: payload.resourceType,
43
+ };
44
+ }
45
+
46
+ const signature = cloudinaryUpload.utils.api_sign_request(
47
+ paramsToSign,
48
+ envVars.CLOUDINARY.CLOUDINARY_API_SECRET,
49
+ );
50
+
51
+ const uploadUrl = `https://api.cloudinary.com/v1_1/${envVars.CLOUDINARY.CLOUDINARY_CLOUD_NAME}/${resourceType}/upload`;
52
+
53
+ return {
54
+ uploadUrl,
55
+ api_key: envVars.CLOUDINARY.CLOUDINARY_API_KEY,
56
+ timestamp,
57
+ signature,
58
+ folder: payload.folder,
59
+ publicId: payload.publicId,
60
+ resourceType: payload.resourceType,
61
+ unsigned: false,
62
+ };
63
+ };
64
+
65
+ const deleteMediaUploads = async (payload: MediaUploadDeleteInput) => {
66
+ const keys = payload.keys ?? [];
67
+ const urls = payload.urls ?? [];
68
+
69
+ const deleted: string[] = [];
70
+ const failed: string[] = [];
71
+
72
+ for (const key of keys) {
73
+ try {
74
+ // try destroy across possible types
75
+ const tryTypes: Array<"image" | "video" | "raw"> = [
76
+ "image",
77
+ "video",
78
+ "raw",
79
+ ];
80
+ let ok = false;
81
+ for (const t of tryTypes) {
82
+ try {
83
+ await cloudinaryUpload.uploader.destroy(key, { resource_type: t });
84
+ ok = true;
85
+ break;
86
+ } catch {
87
+ // try next
88
+ }
89
+ }
90
+ if (ok) deleted.push(key);
91
+ else failed.push(key);
92
+ } catch {
93
+ failed.push(key);
94
+ }
95
+ }
96
+
97
+ for (const url of urls) {
98
+ try {
99
+ await deleteFileFromCloudinary(url);
100
+ deleted.push(url);
101
+ } catch {
102
+ failed.push(url);
103
+ }
104
+ }
105
+
106
+ return { deleted, failed };
107
+ };
108
+
109
+ export const mediaService = {
110
+ signMedia,
111
+ createMediaPresign,
112
+ deleteMediaUploads,
113
+ };
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ export const mediaSignSchema = z.object({
4
+ publicId: z.string().min(1),
5
+ transformation: z.string().min(1).optional(),
6
+ });
7
+
8
+ export const mediaUploadPresignSchema = z.object({
9
+ folder: z.string().min(1).optional(),
10
+ publicId: z.string().min(1).optional(),
11
+ resourceType: z
12
+ .enum(["image", "video", "raw", "auto"] as const)
13
+ .optional()
14
+ .default("auto"),
15
+ unsigned: z.boolean().optional().default(false),
16
+ });
17
+
18
+ export const mediaUploadDeleteSchema = z
19
+ .object({
20
+ keys: z.array(z.string().min(1)).optional(),
21
+ urls: z.array(z.string().min(1)).optional(),
22
+ })
23
+ .refine(
24
+ (value) => (value.keys?.length ?? 0) > 0 || (value.urls?.length ?? 0) > 0,
25
+ {
26
+ message: "keys or urls required",
27
+ },
28
+ );
29
+
30
+ export type MediaSignInput = z.infer<typeof mediaSignSchema>;
31
+ export type MediaUploadPresignInput = z.infer<typeof mediaUploadPresignSchema>;
32
+ export type MediaUploadDeleteInput = z.infer<typeof mediaUploadDeleteSchema>;
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "cloudinary",
3
+ "type": "storage",
4
+ "priority": 5,
5
+ "operations": [
6
+ {
7
+ "type": "create-file",
8
+ "source": "express/config/*",
9
+ "destination": "src/config/*",
10
+ "condition": { "framework": "express" }
11
+ },
12
+ {
13
+ "type": "create-file",
14
+ "source": "express/modules/media/*",
15
+ "destination": "src/modules/media/*",
16
+ "condition": { "framework": "express" }
17
+ },
18
+ {
19
+ "type": "add-dependency",
20
+ "dependencies": {
21
+ "cloudinary": "^1.39.0"
22
+ }
23
+ },
24
+ {
25
+ "type": "add-env",
26
+ "envVars": {
27
+ "CLOUDINARY_CLOUD_NAME": "your_cloud_name",
28
+ "CLOUDINARY_API_KEY": "your_api_key",
29
+ "CLOUDINARY_API_SECRET": "your_api_secret",
30
+ "CLOUDINARY_UPLOAD_PRESET": "your_upload_preset"
31
+ }
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "cloudinary",
3
+ "displayName": "Cloudinary Storage",
4
+ "category": "storage",
5
+ "description": "Cloudinary provider for uploads",
6
+ "supportedFrameworks": ["express"],
7
+ "compatibility": {
8
+ "frameworks": ["express"],
9
+ "languages": ["typescript", "javascript"]
10
+ }
11
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "shadcn",
3
+ "type": "ui",
4
+ "priority": 5,
5
+ "operations": [
6
+ {
7
+ "type": "run-command",
8
+ "command": "{{packageManager=='pnpm' ? 'pnpm dlx shadcn@latest init -t next -d -y' : packageManager=='yarn' ? 'yarn dlx shadcn@latest init -t next -d -y' : packageManager=='bun' ? 'bunx shadcn@latest init -t next -d -y' : 'npx shadcn@latest init -t next -d -y'}}",
9
+ "condition": { "framework": "nextjs" }
10
+ },
11
+ {
12
+ "type": "run-command",
13
+ "command": "{{packageManager=='pnpm' ? 'pnpm dlx shadcn@latest init -t vite -d -y' : packageManager=='yarn' ? 'yarn dlx shadcn@latest init -t vite -d -y' : packageManager=='bun' ? 'bunx shadcn@latest init -t vite -d -y' : 'npx shadcn@latest init -t vite -d -y'}}",
14
+ "condition": { "framework": "react" }
15
+ },
16
+ {
17
+ "type": "run-command",
18
+ "command": "{{packageManager=='pnpm' ? 'pnpm dlx shadcn@latest add button -y' : packageManager=='yarn' ? 'yarn dlx shadcn@latest add button -y' : packageManager=='bun' ? 'bun dlx shadcn@latest add button -y' : 'npx shadcn@latest add button -y'}}"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "shadcn",
3
+ "displayName": "Shadcn UI",
4
+ "category": "ui",
5
+ "description": "Shadcn-style component set + tailwind helpers",
6
+ "supportedFrameworks": ["react", "nextjs"],
7
+ "compatibility": {
8
+ "frameworks": ["react", "nextjs"],
9
+ "languages": ["typescript", "javascript"]
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackkit",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Production-ready CLI to create and extend JavaScript or TypeScript apps with modular stacks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "scripts": {
26
26
  "dev": "tsc --watch",
27
- "build": "npm run clean && tsc && npm run copy-assets",
27
+ "bundle": "node ./scripts/bundle.js",
28
+ "build": "npm run clean && tsc && npm run copy-assets && npm run bundle",
28
29
  "copy-assets": "node ./scripts/copy-assets.js",
29
30
  "clean": "node ./scripts/clean.js",
30
31
  "typecheck": "tsc --noEmit",
@@ -52,32 +53,29 @@
52
53
  ],
53
54
  "author": "Tariqul Islam",
54
55
  "license": "MIT",
55
- "engines": {
56
- "node": ">=18"
57
- },
58
- "dependencies": {
59
- "chalk": "^4.1.2",
60
- "commander": "^12.0.0",
61
- "detect-package-manager": "^3.0.1",
62
- "execa": "^5.1.1",
63
- "fs-extra": "^11.2.0",
64
- "inquirer": "^9.3.8",
65
- "ora": "^5.4.1",
66
- "validate-npm-package-name": "^5.0.1",
67
- "@babel/core": "^7.28.5",
68
- "@babel/preset-env": "^7.28.5",
69
- "@babel/preset-typescript": "^7.28.5",
70
- "@babel/preset-react": "^7.28.5",
71
- "recast": "^0.20.5",
72
- "@babel/parser": "^7.28.5",
73
- "@babel/plugin-transform-typescript": "^7.28.5",
74
- "@babel/plugin-transform-react-jsx": "^7.27.1"
75
- },
76
56
  "devDependencies": {
57
+ "@babel/core": "^7.29.0",
58
+ "@babel/parser": "^7.29.2",
59
+ "@babel/plugin-transform-typescript": "^7.28.6",
60
+ "@babel/preset-env": "^7.29.2",
61
+ "@babel/preset-typescript": "^7.28.5",
77
62
  "@types/fs-extra": "^11.0.4",
78
- "@types/inquirer": "^9.0.7",
79
- "@types/node": "^25.0.8",
63
+ "@types/node": "^25.5.0",
64
+ "@types/prompts": "^2.4.9",
80
65
  "@types/validate-npm-package-name": "^4.0.2",
81
- "typescript": "^5.3.3"
66
+ "chalk": "^5.6.2",
67
+ "commander": "^14.0.3",
68
+ "detect-package-manager": "^3.0.2",
69
+ "esbuild": "^0.27.4",
70
+ "execa": "^9.6.1",
71
+ "fs-extra": "^11.3.4",
72
+ "ora": "^9.3.0",
73
+ "prompts": "^2.4.2",
74
+ "recast": "^0.23.11",
75
+ "typescript": "^6.0.2",
76
+ "validate-npm-package-name": "^7.0.2"
77
+ },
78
+ "engines": {
79
+ "node": ">=18"
82
80
  }
83
81
  }
@@ -1,6 +1,6 @@
1
- # Express Template
1
+ # Express API
2
2
 
3
- Production-ready Express (TypeScript) starter for building REST APIs and backend services.
3
+ Production-ready Express (TypeScript) starter for building REST APIs, generated by [StackKit](https://github.com/tariqul420/stackkit).
4
4
 
5
5
  ## Requirements
6
6
 
@@ -23,13 +23,15 @@ npm run dev
23
23
 
24
24
  ## Scripts
25
25
 
26
- - `npm run dev` - Start development server
27
- - `npm run build` - Build TypeScript
28
- - `npm start` - Start production server
26
+ | Command | Description |
27
+ | ------------ | ------------------------ |
28
+ | `pnpm dev` | Start development server |
29
+ | `pnpm build` | Compile TypeScript |
30
+ | `pnpm start` | Start production server |
29
31
 
30
32
  ## Environment
31
33
 
32
- Use a `.env` file or environment variables for configuration. See `.env.example` for available keys.
34
+ Copy `.env.example` to `.env` and fill in your credentials. Do not commit secrets.
33
35
 
34
36
  ## Recommended Folder & File Structure
35
37
 
@@ -47,8 +49,8 @@ express-api/
47
49
 
48
50
  │ ├── database/
49
51
  │ │ └── prisma.ts # PrismaClient singleton
50
-
51
- │ ├── lib/
52
+
53
+ │ ├── lib/
52
54
  │ └── auth.ts # Auth server config
53
55
 
54
56
  │ ├── shared/
@@ -114,11 +116,4 @@ express-api/
114
116
 
115
117
  ---
116
118
 
117
- ## Generated with StackKit
118
-
119
- This project was scaffolded using **StackKit** — a CLI toolkit for building production-ready applications.
120
-
121
- - Generated via: `npx stackkit@latest create`
122
-
123
- Learn more about StackKit:
124
- https://github.com/tariqul420/stackkit
119
+ Generated with [StackKit](https://github.com/tariqul420/stackkit) — `npx stackkit@latest create`
@@ -8,12 +8,13 @@ dotenv.config({ path: path.join(process.cwd(), ".env") });
8
8
  interface EnvConfig {
9
9
  NODE_ENV: string;
10
10
  PORT: string;
11
+ APP_NAME: string;
11
12
  APP_URL: string;
12
13
  FRONTEND_URL: string;
13
14
  }
14
15
 
15
16
  const loadEnvVars = (): EnvConfig => {
16
- const requiredEnvVars = ["NODE_ENV", "PORT", "APP_URL", "FRONTEND_URL"];
17
+ const requiredEnvVars = ["NODE_ENV", "PORT", "APP_NAME", "APP_URL", "FRONTEND_URL"];
17
18
 
18
19
  requiredEnvVars.forEach((varName) => {
19
20
  if (!process.env[varName]) {
@@ -25,10 +26,11 @@ const loadEnvVars = (): EnvConfig => {
25
26
  });
26
27
 
27
28
  return {
28
- NODE_ENV: process.env.NODE_ENV as string,
29
- PORT: process.env.PORT as string,
30
- APP_URL: process.env.APP_URL as string,
31
- FRONTEND_URL: process.env.FRONTEND_URL as string,
29
+ APP_NAME: process.env.APP_NAME || "App Name",
30
+ NODE_ENV: process.env.NODE_ENV || "development",
31
+ PORT: process.env.PORT || "3000",
32
+ APP_URL: process.env.APP_URL || "http://localhost:5000",
33
+ FRONTEND_URL: process.env.FRONTEND_URL || "http://localhost:3000",
32
34
  };
33
35
  };
34
36