nexstruct 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/AGENTS.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/package.json +99 -0
  5. package/scaffold/generator.js +409 -0
  6. package/scaffold/index.js +20 -0
  7. package/scaffold/prompts.js +108 -0
  8. package/templates/api/axios/src/api/axios/client.api.ts +30 -0
  9. package/templates/api/axios/src/api/axios/users.api.ts +15 -0
  10. package/templates/api/fetch/src/api/fetch/client.api.ts +68 -0
  11. package/templates/api/fetch/src/api/fetch/users.api.ts +15 -0
  12. package/templates/api/trpc/src/api/trpc/client.api.ts +4 -0
  13. package/templates/api/trpc/src/api/trpc/router.api.ts +15 -0
  14. package/templates/api/trpc/src/api/trpc/server.client.api.ts +4 -0
  15. package/templates/api/trpc/src/providers/trpc.provider.tsx +24 -0
  16. package/templates/auth/clerk/src/auth/clerk/auth.service.ts +4 -0
  17. package/templates/auth/clerk/src/hooks/use-auth.hook.ts +13 -0
  18. package/templates/auth/clerk/src/middleware.ts +7 -0
  19. package/templates/auth/clerk/src/providers/auth.provider.tsx +6 -0
  20. package/templates/auth/next-auth/src/app/api/auth/[...nextauth]/route.ts +5 -0
  21. package/templates/auth/next-auth/src/auth/next-auth/auth.service.ts +45 -0
  22. package/templates/auth/next-auth/src/hooks/use-session.hook.ts +13 -0
  23. package/templates/auth/next-auth/src/providers/session.provider.tsx +6 -0
  24. package/templates/forms/formik/src/components/forms/login-form.component.tsx +30 -0
  25. package/templates/forms/formik/src/forms/formik/hooks/use-form-config.hook.ts +7 -0
  26. package/templates/forms/formik/src/forms/formik/schemas/example.schema.ts +8 -0
  27. package/templates/forms/react-hook-form/src/components/forms/login-form.component.tsx +27 -0
  28. package/templates/forms/react-hook-form/src/forms/react-hook-form/hooks/use-form.hook.ts +13 -0
  29. package/templates/forms/react-hook-form/src/forms/react-hook-form/schemas/example.schema.ts +15 -0
  30. package/templates/nextjs-base/next.config.ts +5 -0
  31. package/templates/nextjs-base/postcss.config.mjs +9 -0
  32. package/templates/nextjs-base/src/app/_components/navbar.tsx +88 -0
  33. package/templates/nextjs-base/src/app/_components/sidebar.tsx +223 -0
  34. package/templates/nextjs-base/src/app/error.tsx +39 -0
  35. package/templates/nextjs-base/src/app/globals.css +71 -0
  36. package/templates/nextjs-base/src/app/layout.tsx +21 -0
  37. package/templates/nextjs-base/src/app/loading.tsx +13 -0
  38. package/templates/nextjs-base/src/app/not-found.tsx +22 -0
  39. package/templates/nextjs-base/src/app/page.tsx +10 -0
  40. package/templates/nextjs-base/tailwind.config.ts +69 -0
  41. package/templates/shared/src/components/common/theme-toggle.component.tsx +31 -0
  42. package/templates/shared/src/components/common/toast/custom-message.component.tsx +18 -0
  43. package/templates/shared/src/components/common/toast/index.ts +8 -0
  44. package/templates/shared/src/components/common/toast/toast-message.component.tsx +112 -0
  45. package/templates/shared/src/hooks/use-debounce.hook.ts +12 -0
  46. package/templates/shared/src/hooks/use-fetch.hook.ts +42 -0
  47. package/templates/shared/src/hooks/use-intersection-observer.hook.ts +39 -0
  48. package/templates/shared/src/hooks/use-local-storage.hook.ts +30 -0
  49. package/templates/shared/src/hooks/use-media-query.hook.ts +26 -0
  50. package/templates/shared/src/hooks/use-toggle.hook.ts +12 -0
  51. package/templates/shared/src/lib/utils.util.ts +361 -0
  52. package/templates/shared/src/providers/theme.provider.tsx +17 -0
  53. package/templates/shared/src/providers/toast.provider.tsx +32 -0
  54. package/templates/shared/src/types/common.type.ts +34 -0
  55. package/templates/state/context/src/store/context/auth.context.tsx +47 -0
  56. package/templates/state/context/src/store/context/counter.context.tsx +41 -0
  57. package/templates/state/context/src/store/context/index.ts +2 -0
  58. package/templates/state/redux/src/providers/redux.provider.tsx +7 -0
  59. package/templates/state/redux/src/store/redux/hooks.store.ts +5 -0
  60. package/templates/state/redux/src/store/redux/index.ts +4 -0
  61. package/templates/state/redux/src/store/redux/slices/api.slice.ts +8 -0
  62. package/templates/state/redux/src/store/redux/slices/counter.slice.ts +24 -0
  63. package/templates/state/redux/src/store/redux/store.store.ts +13 -0
  64. package/templates/state/zustand/src/store/zustand/counter.store.ts +15 -0
  65. package/templates/state/zustand/src/store/zustand/index.ts +2 -0
  66. package/templates/state/zustand/src/store/zustand/user.store.ts +32 -0
  67. package/templates/ui/antd/COMPONENT_GUIDE.md +326 -0
  68. package/templates/ui/antd/src/app/examples/dialog/page.tsx +205 -0
  69. package/templates/ui/antd/src/app/examples/form/page.tsx +160 -0
  70. package/templates/ui/antd/src/app/examples/layout.tsx +125 -0
  71. package/templates/ui/antd/src/app/examples/page.tsx +64 -0
  72. package/templates/ui/antd/src/app/examples/table/page.tsx +118 -0
  73. package/templates/ui/antd/src/app/page.tsx +283 -0
  74. package/templates/ui/antd/src/components/common/DynamicTable/dynamic-table.component.tsx +79 -0
  75. package/templates/ui/antd/src/components/common/button/action-button.component.tsx +63 -0
  76. package/templates/ui/antd/src/components/common/dialog/dialog-wrapper.component.tsx +63 -0
  77. package/templates/ui/antd/src/components/common/fields/assets/components/check-field.component.tsx +55 -0
  78. package/templates/ui/antd/src/components/common/fields/assets/components/date-picker-field.component.tsx +80 -0
  79. package/templates/ui/antd/src/components/common/fields/assets/components/limit-field.component.tsx +26 -0
  80. package/templates/ui/antd/src/components/common/fields/assets/components/multi-check-field.component.tsx +56 -0
  81. package/templates/ui/antd/src/components/common/fields/assets/components/number-field.component.tsx +100 -0
  82. package/templates/ui/antd/src/components/common/fields/assets/components/otp-field.component.tsx +63 -0
  83. package/templates/ui/antd/src/components/common/fields/assets/components/password-field.component.tsx +106 -0
  84. package/templates/ui/antd/src/components/common/fields/assets/components/phone-number-field.component.tsx +78 -0
  85. package/templates/ui/antd/src/components/common/fields/assets/components/radio-field.component.tsx +55 -0
  86. package/templates/ui/antd/src/components/common/fields/assets/components/range-date-picker.component.tsx +66 -0
  87. package/templates/ui/antd/src/components/common/fields/assets/components/search-field.component.tsx +24 -0
  88. package/templates/ui/antd/src/components/common/fields/assets/components/select-field.component.tsx +82 -0
  89. package/templates/ui/antd/src/components/common/fields/assets/components/single-check-field.component.tsx +50 -0
  90. package/templates/ui/antd/src/components/common/fields/assets/components/single-select-field.component.tsx +86 -0
  91. package/templates/ui/antd/src/components/common/fields/assets/components/string-number-field.component.tsx +80 -0
  92. package/templates/ui/antd/src/components/common/fields/assets/components/switch-field.component.tsx +62 -0
  93. package/templates/ui/antd/src/components/common/fields/assets/components/text-area-field.component.tsx +85 -0
  94. package/templates/ui/antd/src/components/common/fields/assets/components/text-field.component.tsx +88 -0
  95. package/templates/ui/antd/src/components/common/fields/assets/interface/input-props.type.ts +233 -0
  96. package/templates/ui/antd/src/components/common/fields/cusInputField.component.tsx +40 -0
  97. package/templates/ui/antd/src/components/common/pagination/pagination.component.tsx +27 -0
  98. package/templates/ui/antd/src/components/ui/avatar.component.tsx +8 -0
  99. package/templates/ui/antd/src/components/ui/badge.component.tsx +8 -0
  100. package/templates/ui/antd/src/components/ui/button.component.tsx +8 -0
  101. package/templates/ui/antd/src/components/ui/card.component.tsx +8 -0
  102. package/templates/ui/antd/src/components/ui/checkbox.component.tsx +8 -0
  103. package/templates/ui/antd/src/components/ui/dialog.component.tsx +9 -0
  104. package/templates/ui/antd/src/components/ui/dropdown-menu.component.tsx +10 -0
  105. package/templates/ui/antd/src/components/ui/form.component.tsx +12 -0
  106. package/templates/ui/antd/src/components/ui/input.component.tsx +13 -0
  107. package/templates/ui/antd/src/components/ui/label.component.tsx +18 -0
  108. package/templates/ui/antd/src/components/ui/popover.component.tsx +8 -0
  109. package/templates/ui/antd/src/components/ui/progress.component.tsx +8 -0
  110. package/templates/ui/antd/src/components/ui/radio-group.component.tsx +10 -0
  111. package/templates/ui/antd/src/components/ui/scroll-area.component.tsx +25 -0
  112. package/templates/ui/antd/src/components/ui/select.component.tsx +8 -0
  113. package/templates/ui/antd/src/components/ui/separator.component.tsx +8 -0
  114. package/templates/ui/antd/src/components/ui/sheet.component.tsx +8 -0
  115. package/templates/ui/antd/src/components/ui/switch.component.tsx +8 -0
  116. package/templates/ui/antd/src/components/ui/table.component.tsx +8 -0
  117. package/templates/ui/antd/src/components/ui/tabs.component.tsx +8 -0
  118. package/templates/ui/antd/src/components/ui/textarea.component.tsx +9 -0
  119. package/templates/ui/antd/src/components/ui/tooltip.component.tsx +8 -0
  120. package/templates/ui/antd/src/lib/theme.util.ts +40 -0
  121. package/templates/ui/antd/src/providers/antd.provider.tsx +13 -0
  122. package/templates/ui/mui/src/app/examples/layout.tsx +113 -0
  123. package/templates/ui/mui/src/app/examples/page.tsx +716 -0
  124. package/templates/ui/mui/src/app/page.tsx +298 -0
  125. package/templates/ui/mui/src/components/common/DynamicTable/dynamic-table.component.tsx +131 -0
  126. package/templates/ui/mui/src/components/common/button/action-button.component.tsx +57 -0
  127. package/templates/ui/mui/src/components/common/dialog/dialog-wrapper.component.tsx +55 -0
  128. package/templates/ui/mui/src/components/common/fields/assets/components/check-field.component.tsx +51 -0
  129. package/templates/ui/mui/src/components/common/fields/assets/components/date-picker-field.component.tsx +50 -0
  130. package/templates/ui/mui/src/components/common/fields/assets/components/multi-check-field.component.tsx +14 -0
  131. package/templates/ui/mui/src/components/common/fields/assets/components/number-field.component.tsx +59 -0
  132. package/templates/ui/mui/src/components/common/fields/assets/components/password-field.component.tsx +87 -0
  133. package/templates/ui/mui/src/components/common/fields/assets/components/phone-number-field.component.tsx +48 -0
  134. package/templates/ui/mui/src/components/common/fields/assets/components/radio-field.component.tsx +37 -0
  135. package/templates/ui/mui/src/components/common/fields/assets/components/search-field.component.tsx +41 -0
  136. package/templates/ui/mui/src/components/common/fields/assets/components/select-field.component.tsx +77 -0
  137. package/templates/ui/mui/src/components/common/fields/assets/components/single-check-field.component.tsx +39 -0
  138. package/templates/ui/mui/src/components/common/fields/assets/components/single-select-field.component.tsx +56 -0
  139. package/templates/ui/mui/src/components/common/fields/assets/components/string-number-field.component.tsx +52 -0
  140. package/templates/ui/mui/src/components/common/fields/assets/components/switch-field.component.tsx +35 -0
  141. package/templates/ui/mui/src/components/common/fields/assets/components/text-area-field.component.tsx +46 -0
  142. package/templates/ui/mui/src/components/common/fields/assets/components/text-field.component.tsx +51 -0
  143. package/templates/ui/mui/src/components/common/fields/assets/interface/input-props.type.ts +193 -0
  144. package/templates/ui/mui/src/components/common/fields/cusInputField.component.tsx +34 -0
  145. package/templates/ui/mui/src/components/common/pagination/pagination.component.tsx +59 -0
  146. package/templates/ui/mui/src/components/ui/avatar.component.tsx +19 -0
  147. package/templates/ui/mui/src/components/ui/badge.component.tsx +18 -0
  148. package/templates/ui/mui/src/components/ui/button.component.tsx +22 -0
  149. package/templates/ui/mui/src/components/ui/card.component.tsx +39 -0
  150. package/templates/ui/mui/src/components/ui/checkbox.component.tsx +21 -0
  151. package/templates/ui/mui/src/components/ui/dialog.component.tsx +38 -0
  152. package/templates/ui/mui/src/components/ui/dropdown-menu.component.tsx +43 -0
  153. package/templates/ui/mui/src/components/ui/form.component.tsx +98 -0
  154. package/templates/ui/mui/src/components/ui/input.component.tsx +15 -0
  155. package/templates/ui/mui/src/components/ui/label.component.tsx +15 -0
  156. package/templates/ui/mui/src/components/ui/popover.component.tsx +20 -0
  157. package/templates/ui/mui/src/components/ui/progress.component.tsx +19 -0
  158. package/templates/ui/mui/src/components/ui/radio-group.component.tsx +25 -0
  159. package/templates/ui/mui/src/components/ui/scroll-area.component.tsx +27 -0
  160. package/templates/ui/mui/src/components/ui/select.component.tsx +26 -0
  161. package/templates/ui/mui/src/components/ui/separator.component.tsx +11 -0
  162. package/templates/ui/mui/src/components/ui/sheet.component.tsx +44 -0
  163. package/templates/ui/mui/src/components/ui/switch.component.tsx +23 -0
  164. package/templates/ui/mui/src/components/ui/table.component.tsx +34 -0
  165. package/templates/ui/mui/src/components/ui/tabs.component.tsx +38 -0
  166. package/templates/ui/mui/src/components/ui/textarea.component.tsx +18 -0
  167. package/templates/ui/mui/src/components/ui/tooltip.component.tsx +24 -0
  168. package/templates/ui/mui/src/lib/theme.util.ts +73 -0
  169. package/templates/ui/mui/src/providers/mui.provider.tsx +13 -0
  170. package/templates/ui/shadcn/COMPONENT_GUIDE.md +306 -0
  171. package/templates/ui/shadcn/src/app/examples/dialog/page.tsx +122 -0
  172. package/templates/ui/shadcn/src/app/examples/form/page.tsx +107 -0
  173. package/templates/ui/shadcn/src/app/examples/layout.tsx +24 -0
  174. package/templates/ui/shadcn/src/app/examples/page.tsx +30 -0
  175. package/templates/ui/shadcn/src/app/examples/table/page.tsx +77 -0
  176. package/templates/ui/shadcn/src/app/page.tsx +20 -0
  177. package/templates/ui/shadcn/src/components/common/DynamicTable/dynamic-table.component.tsx +136 -0
  178. package/templates/ui/shadcn/src/components/common/button/action-button.component.tsx +68 -0
  179. package/templates/ui/shadcn/src/components/common/dialog/dialog-wrapper.component.tsx +58 -0
  180. package/templates/ui/shadcn/src/components/common/fields/assets/components/check-field.component.tsx +52 -0
  181. package/templates/ui/shadcn/src/components/common/fields/assets/components/date-picker-field.component.tsx +62 -0
  182. package/templates/ui/shadcn/src/components/common/fields/assets/components/dynamic-file-upload-field.component.tsx +152 -0
  183. package/templates/ui/shadcn/src/components/common/fields/assets/components/limit-field.component.tsx +73 -0
  184. package/templates/ui/shadcn/src/components/common/fields/assets/components/multi-check-field.component.tsx +46 -0
  185. package/templates/ui/shadcn/src/components/common/fields/assets/components/number-field.component.tsx +124 -0
  186. package/templates/ui/shadcn/src/components/common/fields/assets/components/otp-field.component.tsx +61 -0
  187. package/templates/ui/shadcn/src/components/common/fields/assets/components/password-field.component.tsx +110 -0
  188. package/templates/ui/shadcn/src/components/common/fields/assets/components/phone-number-field.component.tsx +90 -0
  189. package/templates/ui/shadcn/src/components/common/fields/assets/components/radio-field.component.tsx +41 -0
  190. package/templates/ui/shadcn/src/components/common/fields/assets/components/range-date-picker.component.tsx +71 -0
  191. package/templates/ui/shadcn/src/components/common/fields/assets/components/rich-text-editor.component.tsx +91 -0
  192. package/templates/ui/shadcn/src/components/common/fields/assets/components/search-field.component.tsx +34 -0
  193. package/templates/ui/shadcn/src/components/common/fields/assets/components/select-field.component.tsx +231 -0
  194. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-check-field.component.tsx +42 -0
  195. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-select-field.component.tsx +82 -0
  196. package/templates/ui/shadcn/src/components/common/fields/assets/components/string-number-field.component.tsx +68 -0
  197. package/templates/ui/shadcn/src/components/common/fields/assets/components/switch-field.component.tsx +61 -0
  198. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-field.component.tsx +62 -0
  199. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-with-file.component.tsx +142 -0
  200. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-field.component.tsx +80 -0
  201. package/templates/ui/shadcn/src/components/common/fields/assets/components/tiny-editor.component.tsx +51 -0
  202. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-profile-picture.component.tsx +103 -0
  203. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-video-file.component.tsx +86 -0
  204. package/templates/ui/shadcn/src/components/common/fields/assets/interface/input-props.type.ts +198 -0
  205. package/templates/ui/shadcn/src/components/common/fields/cusInputField.component.tsx +52 -0
  206. package/templates/ui/shadcn/src/components/common/pagination/pagination.component.tsx +68 -0
  207. package/templates/ui/shadcn/src/components/ui/avatar.component.tsx +37 -0
  208. package/templates/ui/shadcn/src/components/ui/badge.component.tsx +28 -0
  209. package/templates/ui/shadcn/src/components/ui/button.component.tsx +52 -0
  210. package/templates/ui/shadcn/src/components/ui/card.component.tsx +46 -0
  211. package/templates/ui/shadcn/src/components/ui/checkbox.component.tsx +25 -0
  212. package/templates/ui/shadcn/src/components/ui/dialog.component.tsx +98 -0
  213. package/templates/ui/shadcn/src/components/ui/dropdown-menu.component.tsx +163 -0
  214. package/templates/ui/shadcn/src/components/ui/form.component.tsx +110 -0
  215. package/templates/ui/shadcn/src/components/ui/input-otp.component.tsx +64 -0
  216. package/templates/ui/shadcn/src/components/ui/input.component.tsx +23 -0
  217. package/templates/ui/shadcn/src/components/ui/label.component.tsx +23 -0
  218. package/templates/ui/shadcn/src/components/ui/popover.component.tsx +27 -0
  219. package/templates/ui/shadcn/src/components/ui/progress.component.tsx +22 -0
  220. package/templates/ui/shadcn/src/components/ui/radio-group.component.tsx +33 -0
  221. package/templates/ui/shadcn/src/components/ui/scroll-area.component.tsx +37 -0
  222. package/templates/ui/shadcn/src/components/ui/select.component.tsx +139 -0
  223. package/templates/ui/shadcn/src/components/ui/separator.component.tsx +23 -0
  224. package/templates/ui/shadcn/src/components/ui/sheet.component.tsx +89 -0
  225. package/templates/ui/shadcn/src/components/ui/switch.component.tsx +26 -0
  226. package/templates/ui/shadcn/src/components/ui/table.component.tsx +71 -0
  227. package/templates/ui/shadcn/src/components/ui/tabs.component.tsx +52 -0
  228. package/templates/ui/shadcn/src/components/ui/textarea.component.tsx +20 -0
  229. package/templates/ui/shadcn/src/components/ui/tooltip.component.tsx +25 -0
@@ -0,0 +1,409 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TEMPLATES_DIR = path.resolve(__dirname, '../templates');
9
+
10
+ const TEMPLATE_MAP = {
11
+ ui: {
12
+ shadcn: 'ui/shadcn',
13
+ mui: 'ui/mui',
14
+ antd: 'ui/antd',
15
+ },
16
+ state: {
17
+ redux: 'state/redux',
18
+ zustand: 'state/zustand',
19
+ context: 'state/context',
20
+ },
21
+ api: {
22
+ fetch: 'api/fetch',
23
+ axios: 'api/axios',
24
+ trpc: 'api/trpc',
25
+ },
26
+ auth: {
27
+ 'next-auth': 'auth/next-auth',
28
+ clerk: 'auth/clerk',
29
+ },
30
+ forms: {
31
+ 'react-hook-form': 'forms/react-hook-form',
32
+ 'formik': 'forms/formik',
33
+ },
34
+ };
35
+
36
+ const DEPENDENCIES = {
37
+ shadcn: {
38
+ dependencies: ['class-variance-authority', 'clsx', 'tailwind-merge', 'lucide-react', 'react-hook-form', '@hookform/resolvers', 'zod', 'input-otp', 'react-hot-toast', 'react-phone-input-2', '@radix-ui/react-slot', '@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select', '@radix-ui/react-tabs', '@radix-ui/react-toast', '@radix-ui/react-switch', '@radix-ui/react-checkbox', '@radix-ui/react-radio-group', '@radix-ui/react-tooltip', '@radix-ui/react-popover', '@radix-ui/react-scroll-area', '@radix-ui/react-avatar', '@radix-ui/react-progress', '@radix-ui/react-label', '@radix-ui/react-separator'],
39
+ devDependencies: ['tailwindcss', 'postcss', 'autoprefixer', 'tailwindcss-animate'],
40
+ },
41
+ mui: {
42
+ dependencies: ['@mui/material', '@mui/icons-material', '@emotion/react', '@emotion/styled', 'react-hot-toast'],
43
+ },
44
+ antd: {
45
+ dependencies: ['antd', '@ant-design/icons', 'dayjs', 'react-hot-toast'],
46
+ },
47
+ redux: {
48
+ dependencies: ['@reduxjs/toolkit', 'react-redux'],
49
+ },
50
+ zustand: {
51
+ dependencies: ['zustand'],
52
+ },
53
+ context: {},
54
+ fetch: {},
55
+ axios: {
56
+ dependencies: ['axios'],
57
+ },
58
+ trpc: {
59
+ dependencies: ['@trpc/server', '@trpc/client', '@trpc/react-query', '@tanstack/react-query', 'zod', 'superjson'],
60
+ devDependencies: ['@trpc/next'],
61
+ },
62
+ 'next-auth': {
63
+ dependencies: ['next-auth'],
64
+ },
65
+ clerk: {
66
+ dependencies: ['@clerk/nextjs'],
67
+ },
68
+ 'react-hook-form': {
69
+ dependencies: ['react-hook-form', '@hookform/resolvers', 'zod'],
70
+ },
71
+ formik: {
72
+ dependencies: ['formik', 'yup'],
73
+ },
74
+ };
75
+
76
+ export const generateProject = async (answers) => {
77
+ const { projectName, uiLibraries, stateManagement, apiLayer, auth, forms, includeExamples } = answers;
78
+
79
+ const outputDir = path.resolve(process.cwd(), projectName);
80
+
81
+ if (fs.existsSync(outputDir)) {
82
+ const { confirm } = await import('@inquirer/prompts');
83
+ const proceed = await confirm({
84
+ message: `Directory "${projectName}" already exists. Overwrite?`,
85
+ default: false,
86
+ });
87
+ if (!proceed) {
88
+ console.log(chalk.yellow(' Aborted.'));
89
+ process.exit(0);
90
+ }
91
+ fs.removeSync(outputDir);
92
+ }
93
+
94
+ const spinner = ora();
95
+
96
+ spinner.start('Forging base project...');
97
+ await fs.copy(path.join(TEMPLATES_DIR, 'nextjs-base'), outputDir);
98
+ spinner.succeed('Base project created');
99
+
100
+ spinner.start(`Adding UI libraries: ${uiLibraries.join(', ')}...`);
101
+ for (const lib of uiLibraries) {
102
+ const srcPath = path.join(TEMPLATES_DIR, TEMPLATE_MAP.ui[lib]);
103
+ if (fs.existsSync(srcPath)) {
104
+ await fs.copy(srcPath, outputDir);
105
+ }
106
+ }
107
+ spinner.succeed('UI libraries added');
108
+
109
+ spinner.start(`Adding state management: ${stateManagement.join(', ')}...`);
110
+ for (const sm of stateManagement) {
111
+ const srcPath = path.join(TEMPLATES_DIR, TEMPLATE_MAP.state[sm]);
112
+ if (fs.existsSync(srcPath)) {
113
+ await fs.copy(srcPath, outputDir);
114
+ }
115
+ }
116
+ spinner.succeed('State management added');
117
+
118
+ spinner.start(`Adding API layer: ${apiLayer.join(', ')}...`);
119
+ for (const api of apiLayer) {
120
+ const srcPath = path.join(TEMPLATES_DIR, TEMPLATE_MAP.api[api]);
121
+ if (fs.existsSync(srcPath)) {
122
+ await fs.copy(srcPath, outputDir);
123
+ }
124
+ }
125
+ spinner.succeed('API layer added');
126
+
127
+ if (auth !== 'none') {
128
+ spinner.start(`Adding auth: ${auth}...`);
129
+ const srcPath = path.join(TEMPLATES_DIR, TEMPLATE_MAP.auth[auth]);
130
+ if (fs.existsSync(srcPath)) {
131
+ await fs.copy(srcPath, outputDir);
132
+ }
133
+ spinner.succeed('Auth added');
134
+ }
135
+
136
+ if (forms !== 'none') {
137
+ spinner.start(`Adding form handling: ${forms}...`);
138
+ const srcPath = path.join(TEMPLATES_DIR, TEMPLATE_MAP.forms[forms]);
139
+ if (fs.existsSync(srcPath)) {
140
+ await fs.copy(srcPath, outputDir);
141
+ }
142
+ spinner.succeed('Form handling added');
143
+ }
144
+
145
+ spinner.start('Copying shared utilities...');
146
+ const sharedPath = path.join(TEMPLATES_DIR, 'shared');
147
+ if (fs.existsSync(sharedPath)) {
148
+ await fs.copy(sharedPath, outputDir);
149
+ }
150
+ spinner.succeed('Shared utilities added');
151
+
152
+ if (!includeExamples) {
153
+ spinner.start('Removing example files...');
154
+ const examplePaths = [
155
+ path.join(outputDir, 'src/app/page.tsx'),
156
+ path.join(outputDir, 'src/app/examples'),
157
+ ];
158
+ for (const p of examplePaths) {
159
+ if (fs.existsSync(p)) {
160
+ await fs.remove(p);
161
+ }
162
+ }
163
+ spinner.succeed('Examples removed');
164
+ }
165
+
166
+ spinner.start('Generating package.json...');
167
+ await generatePackageJson(outputDir, { projectName, uiLibraries, stateManagement, apiLayer, auth, forms, dualMode: answers.dualMode });
168
+ spinner.succeed('package.json generated');
169
+
170
+ spinner.start('Generating tsconfig.json...');
171
+ await generateTsconfig(outputDir);
172
+ spinner.succeed('tsconfig.json generated');
173
+
174
+ spinner.start('Generating provider composition...');
175
+ await generateProviders(outputDir, answers);
176
+ spinner.succeed('Provider composition generated');
177
+
178
+ spinner.start('Generating project README...');
179
+ await generateReadme(outputDir, answers);
180
+ spinner.succeed('README generated');
181
+
182
+ console.log(chalk.dim(`\n ───────────────────────────────────`));
183
+ console.log(chalk.bold(` Next steps:`));
184
+ console.log(chalk.cyan(` cd ${projectName}`));
185
+ console.log(chalk.cyan(` npm install`));
186
+ console.log(chalk.cyan(` npm run dev`));
187
+ console.log(chalk.dim(` ───────────────────────────────────`));
188
+ };
189
+
190
+ const generatePackageJson = async (outputDir, options) => {
191
+ const { projectName, uiLibraries, stateManagement, apiLayer, auth, forms, dualMode } = options;
192
+
193
+ const allDeps = {};
194
+ const allDevDeps = {
195
+ 'typescript': '^5.4.0',
196
+ '@types/node': '^20.0.0',
197
+ '@types/react': '^18.3.0',
198
+ '@types/react-dom': '^18.3.0',
199
+ 'eslint': '^9.0.0',
200
+ 'eslint-config-next': '^15.0.0',
201
+ '@eslint/eslintrc': '^3.0.0',
202
+ };
203
+
204
+ const mergeDeps = (selected, category) => {
205
+ for (const item of selected) {
206
+ const config = DEPENDENCIES[item];
207
+ if (config) {
208
+ if (config.dependencies) {
209
+ for (const dep of config.dependencies) {
210
+ allDeps[dep] = '*';
211
+ }
212
+ }
213
+ if (config.devDependencies) {
214
+ for (const dep of config.devDependencies) {
215
+ allDevDeps[dep] = '*';
216
+ }
217
+ }
218
+ }
219
+ }
220
+ };
221
+
222
+ mergeDeps(uiLibraries, 'ui');
223
+ mergeDeps(stateManagement, 'state');
224
+ mergeDeps(apiLayer, 'api');
225
+ if (auth !== 'none') mergeDeps([auth], 'auth');
226
+ if (forms !== 'none') mergeDeps([forms], 'forms');
227
+ if (dualMode) allDeps['next-themes'] = '*';
228
+
229
+ const packageJson = {
230
+ name: projectName,
231
+ version: '0.1.0',
232
+ private: true,
233
+ scripts: {
234
+ dev: 'next dev',
235
+ build: 'next build',
236
+ start: 'next start',
237
+ lint: 'next lint',
238
+ typecheck: 'tsc --noEmit',
239
+ },
240
+ dependencies: {
241
+ 'next': '^15.0.0',
242
+ 'react': '^18.3.0',
243
+ 'react-dom': '^18.3.0',
244
+ ...allDeps,
245
+ },
246
+ devDependencies: {
247
+ ...allDevDeps,
248
+ },
249
+ };
250
+
251
+ await fs.writeJson(path.join(outputDir, 'package.json'), packageJson, { spaces: 2 });
252
+ };
253
+
254
+ const generateTsconfig = async (outputDir) => {
255
+ const tsconfig = {
256
+ compilerOptions: {
257
+ target: 'ES2017',
258
+ lib: ['dom', 'dom.iterable', 'esnext'],
259
+ allowJs: true,
260
+ skipLibCheck: true,
261
+ strict: true,
262
+ noEmit: true,
263
+ esModuleInterop: true,
264
+ module: 'esnext',
265
+ moduleResolution: 'bundler',
266
+ resolveJsonModule: true,
267
+ isolatedModules: true,
268
+ jsx: 'preserve',
269
+ incremental: true,
270
+ plugins: [{ name: 'next' }],
271
+ paths: { '@/*': ['./src/*'] },
272
+ },
273
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
274
+ exclude: ['node_modules'],
275
+ };
276
+
277
+ await fs.writeJson(path.join(outputDir, 'tsconfig.json'), tsconfig, { spaces: 2 });
278
+ };
279
+
280
+ const generateProviders = async (outputDir, answers) => {
281
+ const { uiLibraries, stateManagement, apiLayer, auth, dualMode } = answers;
282
+
283
+ const providerImports = [];
284
+ const providerComponents = [];
285
+
286
+ providerImports.push("import { ToastProvider } from '@/providers/toast.provider';");
287
+ providerComponents.push('ToastProvider');
288
+
289
+ if (dualMode) {
290
+ providerImports.push("import { ThemeProvider } from '@/providers/theme.provider';");
291
+ providerComponents.push('ThemeProvider');
292
+ }
293
+
294
+ if (uiLibraries.includes('mui')) {
295
+ providerImports.push("import { MuiProvider } from '@/providers/mui.provider';");
296
+ providerComponents.push('MuiProvider');
297
+ }
298
+ if (uiLibraries.includes('antd')) {
299
+ providerImports.push("import { AntdProvider } from '@/providers/antd.provider';");
300
+ providerComponents.push('AntdProvider');
301
+ }
302
+ if (stateManagement.includes('redux')) {
303
+ providerImports.push("import { ReduxProvider } from '@/providers/redux.provider';");
304
+ providerComponents.push('ReduxProvider');
305
+ }
306
+ if (apiLayer.includes('trpc')) {
307
+ providerImports.push("import { TrpcProvider } from '@/providers/trpc.provider';");
308
+ providerComponents.push('TrpcProvider');
309
+ }
310
+ if (auth === 'next-auth') {
311
+ providerImports.push("import { AuthProvider as NextAuthProvider } from '@/providers/session.provider';");
312
+ providerComponents.push('NextAuthProvider');
313
+ }
314
+ if (auth === 'clerk') {
315
+ providerImports.push("import { AuthProvider as ClerkAuthProvider } from '@/providers/auth.provider';");
316
+ providerComponents.push('ClerkAuthProvider');
317
+ }
318
+
319
+ const nested = providerComponents.reduce(
320
+ (children, provider) => ` <${provider}>${children} </${provider}>`,
321
+ ' {children}\n'
322
+ );
323
+
324
+ const content = `'use client';
325
+ ${providerImports.join('\n')}
326
+
327
+ export function Providers({ children }: { children: React.ReactNode }) {
328
+ return (
329
+ ${nested}
330
+ );
331
+ }
332
+ `;
333
+
334
+ const providersDir = path.join(outputDir, 'src/providers');
335
+ await fs.ensureDir(providersDir);
336
+ await fs.writeFile(path.join(providersDir, 'index.tsx'), content);
337
+
338
+ const layoutPath = path.join(outputDir, 'src/app/layout.tsx');
339
+ if (fs.existsSync(layoutPath)) {
340
+ let layout = await fs.readFile(layoutPath, 'utf-8');
341
+ layout = layout.replace(
342
+ "import './globals.css';",
343
+ "import './globals.css';\nimport { Providers } from '@/providers';"
344
+ );
345
+ layout = layout.replace(
346
+ '<Sidebar>{children}</Sidebar>',
347
+ '<Providers><Sidebar>{children}</Sidebar></Providers>'
348
+ );
349
+ await fs.writeFile(layoutPath, layout);
350
+ }
351
+ };
352
+
353
+ const generateReadme = async (outputDir, answers) => {
354
+ const { projectName, uiLibraries, stateManagement, apiLayer, auth, forms } = answers;
355
+
356
+ const readme = `# ${projectName}
357
+
358
+ Scaffolded with ⚒️ **Nexstruct**
359
+
360
+ ## Stack
361
+
362
+ - **Framework:** Next.js 15 + TypeScript
363
+ - **UI Library:** ${uiLibraries.join(', ')}
364
+ - **State Management:** ${stateManagement.join(', ')}
365
+ - **API Layer:** ${apiLayer.join(', ')}
366
+ - **Auth:** ${auth}
367
+ - **Forms:** ${forms}
368
+
369
+ ## Getting Started
370
+
371
+ \`\`\`bash
372
+ npm install
373
+ npm run dev
374
+ \`\`\`
375
+
376
+ ## Conventions
377
+
378
+ Files follow the \`[name].[category].ts\` naming convention:
379
+
380
+ | Extension | Type |
381
+ |-----------|------|
382
+ | \`.component.tsx\` | UI components |
383
+ | \`.store.ts\` | State management |
384
+ | \`.api.ts\` | API layer |
385
+ | \`.hook.ts\` | React hooks |
386
+ | \`.service.ts\` | Auth/services |
387
+ | \`.util.ts\` | Utilities |
388
+ | \`.type.ts\` | TypeScript types |
389
+ | \`.provider.tsx\` | Context providers |
390
+
391
+ ## Project Structure
392
+
393
+ \`\`\`
394
+ src/
395
+ ├── app/ # Next.js App Router pages
396
+ ├── components/ # Reusable UI components
397
+ │ └── ui/ # Base UI primitives
398
+ ├── hooks/ # Custom React hooks
399
+ ├── lib/ # Library configs
400
+ ├── store/ # State management
401
+ ├── api/ # API layer
402
+ ├── auth/ # Authentication
403
+ ├── forms/ # Form handling
404
+ └── types/ # TypeScript types
405
+ \`\`\`
406
+ `;
407
+
408
+ await fs.writeFile(path.join(outputDir, 'README.md'), readme);
409
+ };
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk';
4
+ import { askPrompts } from './prompts.js';
5
+ import { generateProject } from './generator.js';
6
+
7
+ const main = async () => {
8
+ console.log(chalk.bold.cyan('\n ⚒️ NEXSTRUCT\n'));
9
+ console.log(chalk.dim(' Scaffold your production-ready Next.js project\n'));
10
+
11
+ const answers = await askPrompts();
12
+ await generateProject(answers);
13
+
14
+ console.log(chalk.green.bold('\n ✓ Project scaffolded successfully!\n'));
15
+ };
16
+
17
+ main().catch((err) => {
18
+ console.error(chalk.red(' ✗ Error:'), err.message);
19
+ process.exit(1);
20
+ });
@@ -0,0 +1,108 @@
1
+ import { input, select, checkbox, confirm } from '@inquirer/prompts';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /** Generate a related default project name from selected tech choices */
6
+ const deriveDefaultName = ({ uiLibraries, stateManagement }) => {
7
+ const uiLabel = uiLibraries[0] || 'app';
8
+ const stateLabel = stateManagement[0] || '';
9
+ const parts = [uiLabel, stateLabel, 'app'].filter(Boolean);
10
+ return parts.join('-');
11
+ };
12
+
13
+ /** Check if a directory with the given name already exists in cwd */
14
+ const isDirectoryTaken = (name) => {
15
+ try {
16
+ return fs.statSync(path.resolve(process.cwd(), name)).isDirectory();
17
+ } catch {
18
+ return false;
19
+ }
20
+ };
21
+
22
+ export const askPrompts = async () => {
23
+ const uiLibraries = await checkbox({
24
+ message: 'Select UI libraries:',
25
+ choices: [
26
+ { name: 'shadcn/ui (with Tailwind CSS)', value: 'shadcn', checked: true },
27
+ { name: 'Material UI (MUI)', value: 'mui' },
28
+ { name: 'Ant Design', value: 'antd' },
29
+ ],
30
+ required: true,
31
+ });
32
+
33
+ const stateManagement = await checkbox({
34
+ message: 'Select state management:',
35
+ choices: [
36
+ { name: 'Redux Toolkit (with RTK Query)', value: 'redux' },
37
+ { name: 'Zustand', value: 'zustand', checked: true },
38
+ { name: 'React Context + useReducer', value: 'context' },
39
+ ],
40
+ required: true,
41
+ });
42
+
43
+ const apiLayer = await checkbox({
44
+ message: 'Select API layer:',
45
+ choices: [
46
+ { name: 'Fetch API (built-in)', value: 'fetch', checked: true },
47
+ { name: 'Axios', value: 'axios' },
48
+ { name: 'tRPC', value: 'trpc' },
49
+ ],
50
+ required: true,
51
+ });
52
+
53
+ const auth = await select({
54
+ message: 'Select authentication:',
55
+ choices: [
56
+ { name: 'None', value: 'none' },
57
+ { name: 'NextAuth.js', value: 'next-auth' },
58
+ { name: 'Clerk', value: 'clerk' },
59
+ ],
60
+ });
61
+
62
+ const forms = await select({
63
+ message: 'Select form handling:',
64
+ choices: [
65
+ { name: 'None', value: 'none' },
66
+ { name: 'React Hook Form', value: 'react-hook-form' },
67
+ { name: 'Formik', value: 'formik' },
68
+ ],
69
+ });
70
+
71
+ const dualMode = await confirm({
72
+ message: 'Enable dark/light mode toggle?',
73
+ default: true,
74
+ });
75
+
76
+ const includeExamples = await confirm({
77
+ message: 'Include example components and pages?',
78
+ default: true,
79
+ });
80
+
81
+ // Project name comes last so default can be derived from selections
82
+ const defaultName = deriveDefaultName({ uiLibraries, stateManagement });
83
+
84
+ const projectName = await input({
85
+ message: 'Project name:',
86
+ default: defaultName,
87
+ validate: (v) => {
88
+ if (!/^[a-z0-9-]+$/.test(v)) {
89
+ return 'Use lowercase letters, numbers, and hyphens only';
90
+ }
91
+ if (isDirectoryTaken(v)) {
92
+ return `Directory "${v}" already exists. Choose a unique name`;
93
+ }
94
+ return true;
95
+ },
96
+ });
97
+
98
+ return {
99
+ projectName,
100
+ uiLibraries,
101
+ stateManagement,
102
+ apiLayer,
103
+ auth,
104
+ forms,
105
+ dualMode,
106
+ includeExamples,
107
+ };
108
+ };
@@ -0,0 +1,30 @@
1
+ import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios';
2
+
3
+ const createClient = (config?: AxiosRequestConfig): AxiosInstance => {
4
+ const client = axios.create({
5
+ baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
6
+ headers: { 'Content-Type': 'application/json' },
7
+ ...config,
8
+ });
9
+
10
+ client.interceptors.response.use(
11
+ (response) => response,
12
+ (error) => {
13
+ if (error.response) {
14
+ throw new ApiError(error.response.status, error.response.data?.message || error.message);
15
+ }
16
+ throw error;
17
+ }
18
+ );
19
+
20
+ return client;
21
+ };
22
+
23
+ export class ApiError extends Error {
24
+ constructor(public status: number, message: string) {
25
+ super(message);
26
+ this.name = 'ApiError';
27
+ }
28
+ }
29
+
30
+ export const api = createClient();
@@ -0,0 +1,15 @@
1
+ import { api } from './client.api';
2
+
3
+ export interface User {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ }
8
+
9
+ export const usersApi = {
10
+ list: (params?: { page?: number }) => api.get<User[]>('/users', { params }),
11
+ get: (id: string) => api.get<User>(`/users/${id}`),
12
+ create: (data: Partial<User>) => api.post<User>('/users', data),
13
+ update: (id: string, data: Partial<User>) => api.put<User>(`/users/${id}`, data),
14
+ delete: (id: string) => api.delete<void>(`/users/${id}`),
15
+ };
@@ -0,0 +1,68 @@
1
+ type RequestOptions = Omit<RequestInit, 'body'> & {
2
+ body?: unknown;
3
+ params?: Record<string, string>;
4
+ };
5
+
6
+ class ApiClient {
7
+ private baseUrl: string;
8
+
9
+ constructor(baseUrl?: string) {
10
+ this.baseUrl = baseUrl || process.env.NEXT_PUBLIC_API_URL || '/api';
11
+ }
12
+
13
+ private async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
14
+ const { body, params, ...init } = options;
15
+ let url = `${this.baseUrl}${path}`;
16
+
17
+ if (params) {
18
+ const searchParams = new URLSearchParams(params);
19
+ url += `?${searchParams}`;
20
+ }
21
+
22
+ const headers: HeadersInit = {
23
+ 'Content-Type': 'application/json',
24
+ ...init.headers,
25
+ };
26
+
27
+ const response = await fetch(url, {
28
+ ...init,
29
+ headers,
30
+ body: body ? JSON.stringify(body) : undefined,
31
+ });
32
+
33
+ if (!response.ok) {
34
+ throw new ApiError(response.status, await response.text());
35
+ }
36
+
37
+ return response.json();
38
+ }
39
+
40
+ get<T>(path: string, options?: RequestOptions) {
41
+ return this.request<T>(path, { ...options, method: 'GET' });
42
+ }
43
+
44
+ post<T>(path: string, body?: unknown, options?: RequestOptions) {
45
+ return this.request<T>(path, { ...options, method: 'POST', body });
46
+ }
47
+
48
+ put<T>(path: string, body?: unknown, options?: RequestOptions) {
49
+ return this.request<T>(path, { ...options, method: 'PUT', body });
50
+ }
51
+
52
+ patch<T>(path: string, body?: unknown, options?: RequestOptions) {
53
+ return this.request<T>(path, { ...options, method: 'PATCH', body });
54
+ }
55
+
56
+ delete<T>(path: string, options?: RequestOptions) {
57
+ return this.request<T>(path, { ...options, method: 'DELETE' });
58
+ }
59
+ }
60
+
61
+ export class ApiError extends Error {
62
+ constructor(public status: number, message: string) {
63
+ super(message);
64
+ this.name = 'ApiError';
65
+ }
66
+ }
67
+
68
+ export const api = new ApiClient();
@@ -0,0 +1,15 @@
1
+ import { api } from './client.api';
2
+
3
+ export interface User {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ }
8
+
9
+ export const usersApi = {
10
+ list: (params?: { page?: string }) => api.get<User[]>('/users', { params }),
11
+ get: (id: string) => api.get<User>(`/users/${id}`),
12
+ create: (data: Partial<User>) => api.post<User>('/users', data),
13
+ update: (id: string, data: Partial<User>) => api.put<User>(`/users/${id}`, data),
14
+ delete: (id: string) => api.delete<void>(`/users/${id}`),
15
+ };
@@ -0,0 +1,4 @@
1
+ import { createTRPCReact } from '@trpc/react-query';
2
+ import type { AppRouter } from './router.api';
3
+
4
+ export const trpc = createTRPCReact<AppRouter>();
@@ -0,0 +1,15 @@
1
+ import { initTRPC } from '@trpc/server';
2
+ import superjson from 'superjson';
3
+ import { z } from 'zod';
4
+
5
+ const t = initTRPC.create({ transformer: superjson });
6
+
7
+ export const appRouter = t.router({
8
+ hello: t.procedure
9
+ .input(z.object({ name: z.string() }))
10
+ .query(({ input }) => {
11
+ return { greeting: `Hello, ${input.name}!` };
12
+ }),
13
+ });
14
+
15
+ export type AppRouter = typeof appRouter;
@@ -0,0 +1,4 @@
1
+ import { createTRPCContext } from '@trpc/next';
2
+ import type { AppRouter } from './router.api';
3
+
4
+ export const trpcServer = createTRPCContext<AppRouter>();