servcraft 0.1.0 → 0.1.3

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 (217) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -1,21 +1,49 @@
1
1
  import { Command } from 'commander';
2
2
  import path from 'path';
3
- import fs from 'fs/promises';
4
3
  import ora from 'ora';
5
4
  import chalk from 'chalk';
6
- import { ensureDir, writeFile, fileExists, success, error, info, warn, getModulesDir, getSourceDir } from '../utils/helpers.js';
5
+ import * as fs from 'fs/promises';
6
+ import {
7
+ ensureDir,
8
+ writeFile,
9
+ fileExists,
10
+ success,
11
+ error,
12
+ info,
13
+ warn,
14
+ getModulesDir,
15
+ } from '../utils/helpers.js';
16
+ import { EnvManager } from '../utils/env-manager.js';
17
+ import { TemplateManager } from '../utils/template-manager.js';
18
+ import { InteractivePrompt } from '../utils/interactive-prompt.js';
7
19
 
8
20
  // Pre-built modules that can be added
9
21
  const AVAILABLE_MODULES = {
10
22
  auth: {
11
23
  name: 'Authentication',
12
24
  description: 'JWT authentication with access/refresh tokens',
13
- files: ['auth.service', 'auth.controller', 'auth.routes', 'auth.middleware', 'auth.schemas', 'auth.types', 'index'],
25
+ files: [
26
+ 'auth.service',
27
+ 'auth.controller',
28
+ 'auth.routes',
29
+ 'auth.middleware',
30
+ 'auth.schemas',
31
+ 'auth.types',
32
+ 'index',
33
+ ],
14
34
  },
15
35
  users: {
16
36
  name: 'User Management',
17
37
  description: 'User CRUD with RBAC (roles & permissions)',
18
- files: ['user.service', 'user.controller', 'user.repository', 'user.routes', 'user.schemas', 'user.types', 'index'],
38
+ files: [
39
+ 'user.service',
40
+ 'user.controller',
41
+ 'user.repository',
42
+ 'user.routes',
43
+ 'user.schemas',
44
+ 'user.types',
45
+ 'index',
46
+ ],
19
47
  },
20
48
  email: {
21
49
  name: 'Email Service',
@@ -27,109 +55,280 @@ const AVAILABLE_MODULES = {
27
55
  description: 'Activity logging and audit trail',
28
56
  files: ['audit.service', 'audit.types', 'index'],
29
57
  },
30
- upload: {
31
- name: 'File Upload',
32
- description: 'File upload with local/S3 storage',
33
- files: ['upload.service', 'upload.controller', 'upload.routes', 'upload.types', 'index'],
34
- },
35
58
  cache: {
36
59
  name: 'Redis Cache',
37
- description: 'Redis caching service',
60
+ description: 'Redis caching with TTL & invalidation',
38
61
  files: ['cache.service', 'cache.types', 'index'],
39
62
  },
40
- notifications: {
63
+ upload: {
64
+ name: 'File Upload',
65
+ description: 'File upload with local/S3/Cloudinary storage',
66
+ files: ['upload.service', 'upload.routes', 'upload.types', 'index'],
67
+ },
68
+ mfa: {
69
+ name: 'MFA/TOTP',
70
+ description: 'Two-factor authentication with QR codes',
71
+ files: ['mfa.service', 'mfa.routes', 'totp.ts', 'types.ts', 'index'],
72
+ },
73
+ oauth: {
74
+ name: 'OAuth',
75
+ description: 'Social login (Google, GitHub, Facebook, Twitter, Apple)',
76
+ files: ['oauth.service', 'oauth.routes', 'providers', 'types.ts', 'index'],
77
+ },
78
+ payment: {
79
+ name: 'Payments',
80
+ description: 'Payment processing (Stripe, PayPal, Mobile Money)',
81
+ files: ['payment.service', 'payment.routes', 'providers', 'types.ts', 'index'],
82
+ },
83
+ notification: {
41
84
  name: 'Notifications',
42
- description: 'In-app and push notifications',
43
- files: ['notification.service', 'notification.types', 'index'],
85
+ description: 'Email, SMS, Push notifications',
86
+ files: ['notification.service', 'types.ts', 'index'],
87
+ },
88
+ 'rate-limit': {
89
+ name: 'Rate Limiting',
90
+ description: 'Advanced rate limiting with multiple algorithms',
91
+ files: [
92
+ 'rate-limit.service',
93
+ 'rate-limit.middleware',
94
+ 'rate-limit.routes',
95
+ 'stores',
96
+ 'types.ts',
97
+ 'index',
98
+ ],
44
99
  },
45
- settings: {
46
- name: 'Settings',
47
- description: 'Application settings management',
48
- files: ['settings.service', 'settings.controller', 'settings.routes', 'settings.types', 'index'],
100
+ webhook: {
101
+ name: 'Webhooks',
102
+ description: 'Outgoing webhooks with HMAC signatures & retry',
103
+ files: ['webhook.service', 'webhook.routes', 'signature.ts', 'retry.ts', 'types.ts', 'index'],
104
+ },
105
+ queue: {
106
+ name: 'Queue/Jobs',
107
+ description: 'Background jobs with Bull/BullMQ & cron scheduling',
108
+ files: ['queue.service', 'cron.ts', 'workers.ts', 'routes.ts', 'types.ts', 'index'],
109
+ },
110
+ websocket: {
111
+ name: 'WebSockets',
112
+ description: 'Real-time communication with Socket.io',
113
+ files: ['websocket.service', 'features.ts', 'middlewares.ts', 'types.ts', 'index'],
114
+ },
115
+ search: {
116
+ name: 'Search',
117
+ description: 'Full-text search with Elasticsearch/Meilisearch',
118
+ files: ['search.service', 'adapters', 'types.ts', 'index'],
119
+ },
120
+ i18n: {
121
+ name: 'i18n/Localization',
122
+ description: 'Multi-language support with 7+ locales',
123
+ files: ['i18n.service', 'i18n.middleware', 'i18n.routes', 'types.ts', 'index'],
124
+ },
125
+ 'feature-flag': {
126
+ name: 'Feature Flags',
127
+ description: 'A/B testing & progressive rollout',
128
+ files: ['feature-flag.service', 'feature-flag.routes', 'types.ts', 'index'],
129
+ },
130
+ analytics: {
131
+ name: 'Analytics/Metrics',
132
+ description: 'Prometheus metrics & event tracking',
133
+ files: ['analytics.service', 'analytics.routes', 'types.ts', 'index'],
134
+ },
135
+ 'media-processing': {
136
+ name: 'Media Processing',
137
+ description: 'Image/video processing with FFmpeg',
138
+ files: ['media-processing.service', 'media-processing.routes', 'types.ts', 'index'],
139
+ },
140
+ 'api-versioning': {
141
+ name: 'API Versioning',
142
+ description: 'Multiple API versions support',
143
+ files: [
144
+ 'versioning.service',
145
+ 'versioning.middleware',
146
+ 'versioning.routes',
147
+ 'types.ts',
148
+ 'index',
149
+ ],
49
150
  },
50
151
  };
51
152
 
52
153
  export const addModuleCommand = new Command('add')
53
154
  .description('Add a pre-built module to your project')
54
- .argument('[module]', 'Module to add (auth, users, email, audit, upload, cache, notifications, settings)')
155
+ .argument(
156
+ '[module]',
157
+ 'Module to add (auth, users, email, audit, upload, cache, notifications, settings)'
158
+ )
55
159
  .option('-l, --list', 'List available modules')
56
- .action(async (moduleName?: string, options?: { list?: boolean }) => {
57
- if (options?.list || !moduleName) {
58
- console.log(chalk.bold('\n📦 Available Modules:\n'));
59
-
60
- for (const [key, mod] of Object.entries(AVAILABLE_MODULES)) {
61
- console.log(` ${chalk.cyan(key.padEnd(15))} ${mod.name}`);
62
- console.log(` ${' '.repeat(15)} ${chalk.gray(mod.description)}\n`);
160
+ .option('-f, --force', 'Force overwrite existing module')
161
+ .option('-u, --update', 'Update existing module (smart merge)')
162
+ .option('--skip-existing', 'Skip if module already exists')
163
+ .action(
164
+ async (
165
+ moduleName?: string,
166
+ options?: { list?: boolean; force?: boolean; update?: boolean; skipExisting?: boolean }
167
+ ) => {
168
+ if (options?.list || !moduleName) {
169
+ console.log(chalk.bold('\n📦 Available Modules:\n'));
170
+
171
+ for (const [key, mod] of Object.entries(AVAILABLE_MODULES)) {
172
+ console.log(` ${chalk.cyan(key.padEnd(15))} ${mod.name}`);
173
+ console.log(` ${' '.repeat(15)} ${chalk.gray(mod.description)}\n`);
174
+ }
175
+
176
+ console.log(chalk.bold('Usage:'));
177
+ console.log(` ${chalk.yellow('servcraft add auth')} Add authentication module`);
178
+ console.log(` ${chalk.yellow('servcraft add users')} Add user management module`);
179
+ console.log(` ${chalk.yellow('servcraft add email')} Add email service module\n`);
180
+ return;
63
181
  }
64
182
 
65
- console.log(chalk.bold('Usage:'));
66
- console.log(` ${chalk.yellow('servcraft add auth')} Add authentication module`);
67
- console.log(` ${chalk.yellow('servcraft add users')} Add user management module`);
68
- console.log(` ${chalk.yellow('servcraft add email')} Add email service module\n`);
69
- return;
70
- }
71
-
72
- const module = AVAILABLE_MODULES[moduleName as keyof typeof AVAILABLE_MODULES];
73
-
74
- if (!module) {
75
- error(`Unknown module: ${moduleName}`);
76
- info('Run "servcraft add --list" to see available modules');
77
- return;
78
- }
183
+ const module = AVAILABLE_MODULES[moduleName as keyof typeof AVAILABLE_MODULES];
79
184
 
80
- const spinner = ora(`Adding ${module.name} module...`).start();
81
-
82
- try {
83
- const moduleDir = path.join(getModulesDir(), moduleName);
84
-
85
- // Check if module already exists
86
- if (await fileExists(moduleDir)) {
87
- spinner.stop();
88
- warn(`Module "${moduleName}" already exists`);
185
+ if (!module) {
186
+ error(`Unknown module: ${moduleName}`);
187
+ info('Run "servcraft add --list" to see available modules');
89
188
  return;
90
189
  }
91
190
 
92
- await ensureDir(moduleDir);
93
-
94
- // Generate module files based on type
95
- switch (moduleName) {
96
- case 'auth':
97
- await generateAuthModule(moduleDir);
98
- break;
99
- case 'users':
100
- await generateUsersModule(moduleDir);
101
- break;
102
- case 'email':
103
- await generateEmailModule(moduleDir);
104
- break;
105
- case 'audit':
106
- await generateAuditModule(moduleDir);
107
- break;
108
- case 'upload':
109
- await generateUploadModule(moduleDir);
110
- break;
111
- case 'cache':
112
- await generateCacheModule(moduleDir);
113
- break;
114
- default:
115
- await generateGenericModule(moduleDir, moduleName);
191
+ const spinner = ora(`Adding ${module.name} module...`).start();
192
+
193
+ try {
194
+ const moduleDir = path.join(getModulesDir(), moduleName);
195
+ const templateManager = new TemplateManager(process.cwd());
196
+ const moduleExists = await fileExists(moduleDir);
197
+
198
+ // Handle existing module
199
+ if (moduleExists) {
200
+ spinner.stop();
201
+
202
+ // Check flags
203
+ if (options?.skipExisting) {
204
+ info(`Module "${moduleName}" already exists, skipping...`);
205
+ return;
206
+ }
207
+
208
+ // Check for modifications
209
+ const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
210
+ const hasModifications = modifiedFiles.some((f) => f.isModified);
211
+
212
+ let action: string;
213
+
214
+ if (options?.force) {
215
+ action = 'overwrite';
216
+ } else if (options?.update) {
217
+ action = 'update';
218
+ } else {
219
+ // Interactive prompt
220
+ const choice = await InteractivePrompt.askModuleExists(moduleName, hasModifications);
221
+ action = choice.action;
222
+ }
223
+
224
+ // Handle action
225
+ if (action === 'skip') {
226
+ info('Keeping existing module');
227
+ return;
228
+ }
229
+
230
+ if (action === 'diff') {
231
+ // Show diff and ask again
232
+ await showDiffForModule(templateManager, moduleName, moduleDir);
233
+ return;
234
+ }
235
+
236
+ if (action === 'backup-overwrite' || action === 'overwrite') {
237
+ if (action === 'backup-overwrite') {
238
+ const backupPath = await templateManager.createBackup(moduleName, moduleDir);
239
+ InteractivePrompt.showBackupCreated(backupPath);
240
+ }
241
+
242
+ // Remove existing module
243
+ await fs.rm(moduleDir, { recursive: true, force: true });
244
+ await ensureDir(moduleDir);
245
+
246
+ // Generate fresh module
247
+ await generateModuleFiles(moduleName, moduleDir);
248
+
249
+ // Save templates and manifest
250
+ const files = await getModuleFiles(moduleName, moduleDir);
251
+ await templateManager.saveTemplate(moduleName, files);
252
+ await templateManager.saveManifest(moduleName, files);
253
+
254
+ spinner.succeed(
255
+ `${module.name} module ${action === 'backup-overwrite' ? 'backed up and ' : ''}overwritten!`
256
+ );
257
+ } else if (action === 'update') {
258
+ // Smart merge
259
+ await performSmartMerge(templateManager, moduleName, moduleDir, module.name);
260
+ }
261
+ } else {
262
+ // Fresh installation
263
+ await ensureDir(moduleDir);
264
+
265
+ // Generate module files
266
+ await generateModuleFiles(moduleName, moduleDir);
267
+
268
+ // Save templates and manifest for future updates
269
+ const files = await getModuleFiles(moduleName, moduleDir);
270
+ await templateManager.saveTemplate(moduleName, files);
271
+ await templateManager.saveManifest(moduleName, files);
272
+
273
+ spinner.succeed(`${module.name} module added successfully!`);
274
+ }
275
+
276
+ if (!moduleExists) {
277
+ console.log('\n📁 Files created:');
278
+ module.files.forEach((f) => success(` src/modules/${moduleName}/${f}.ts`));
279
+ }
280
+
281
+ // Update .env file with module-specific variables
282
+ const envManager = new EnvManager(process.cwd());
283
+ const envSections = EnvManager.getModuleEnvVariables(moduleName);
284
+
285
+ if (envSections.length > 0) {
286
+ const envSpinner = ora('Updating environment variables...').start();
287
+ try {
288
+ const result = await envManager.addVariables(envSections);
289
+
290
+ envSpinner.succeed('Environment variables updated!');
291
+
292
+ if (result.created) {
293
+ info('\n📝 Created new .env file');
294
+ }
295
+
296
+ if (result.added.length > 0) {
297
+ console.log(chalk.bold('\n✅ Added to .env:'));
298
+ result.added.forEach((key) => success(` ${key}`));
299
+ }
300
+
301
+ if (result.skipped.length > 0) {
302
+ console.log(chalk.bold('\n⏭️ Already in .env (skipped):'));
303
+ result.skipped.forEach((key) => info(` ${key}`));
304
+ }
305
+
306
+ // Show which variables need configuration
307
+ const requiredVars = envSections
308
+ .flatMap((section) => section.variables)
309
+ .filter((v) => v.required && !v.value)
310
+ .map((v) => v.key);
311
+
312
+ if (requiredVars.length > 0) {
313
+ console.log(chalk.bold('\n⚠️ Required configuration:'));
314
+ requiredVars.forEach((key) => warn(` ${key} - Please configure this variable`));
315
+ }
316
+ } catch (err) {
317
+ envSpinner.fail('Failed to update environment variables');
318
+ error(err instanceof Error ? err.message : String(err));
319
+ }
320
+ }
321
+
322
+ console.log('\n📌 Next steps:');
323
+ info(' 1. Configure environment variables in .env (if needed)');
324
+ info(' 2. Register the module in your main app file');
325
+ info(' 3. Run database migrations if needed');
326
+ } catch (err) {
327
+ spinner.fail('Failed to add module');
328
+ error(err instanceof Error ? err.message : String(err));
116
329
  }
117
-
118
- spinner.succeed(`${module.name} module added successfully!`);
119
-
120
- console.log('\n📁 Files created:');
121
- module.files.forEach((f) => success(` src/modules/${moduleName}/${f}.ts`));
122
-
123
- console.log('\n📌 Next steps:');
124
- info(' 1. Register the module in your main app file');
125
- info(' 2. Configure any required environment variables');
126
- info(' 3. Run database migrations if needed');
127
-
128
- } catch (err) {
129
- spinner.fail('Failed to add module');
130
- error(err instanceof Error ? err.message : String(err));
131
330
  }
132
- });
331
+ );
133
332
 
134
333
  async function generateAuthModule(dir: string): Promise<void> {
135
334
  // This would copy from templates or generate inline
@@ -420,3 +619,237 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
420
619
  await writeFile(path.join(dir, fileName), content);
421
620
  }
422
621
  }
622
+
623
+ /**
624
+ * Helper: Generate module files - copies from existing src/modules if available
625
+ */
626
+ async function generateModuleFiles(moduleName: string, moduleDir: string): Promise<void> {
627
+ // Check if module exists in src/modules (our new modules)
628
+ const sourceModuleDir = path.join(process.cwd(), 'src', 'modules', moduleName);
629
+
630
+ if (await fileExists(sourceModuleDir)) {
631
+ // Copy from existing module
632
+ await copyModuleFromSource(sourceModuleDir, moduleDir);
633
+ return;
634
+ }
635
+
636
+ // Fallback to old generation methods for basic modules
637
+ switch (moduleName) {
638
+ case 'auth':
639
+ await generateAuthModule(moduleDir);
640
+ break;
641
+ case 'users':
642
+ await generateUsersModule(moduleDir);
643
+ break;
644
+ case 'email':
645
+ await generateEmailModule(moduleDir);
646
+ break;
647
+ case 'audit':
648
+ await generateAuditModule(moduleDir);
649
+ break;
650
+ case 'upload':
651
+ await generateUploadModule(moduleDir);
652
+ break;
653
+ case 'cache':
654
+ await generateCacheModule(moduleDir);
655
+ break;
656
+ default:
657
+ await generateGenericModule(moduleDir, moduleName);
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Helper: Copy module from source directory
663
+ */
664
+ async function copyModuleFromSource(sourceDir: string, targetDir: string): Promise<void> {
665
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
666
+
667
+ for (const entry of entries) {
668
+ const sourcePath = path.join(sourceDir, entry.name);
669
+ const targetPath = path.join(targetDir, entry.name);
670
+
671
+ if (entry.isDirectory()) {
672
+ await fs.mkdir(targetPath, { recursive: true });
673
+ await copyModuleFromSource(sourcePath, targetPath);
674
+ } else {
675
+ await fs.copyFile(sourcePath, targetPath);
676
+ }
677
+ }
678
+ }
679
+
680
+ /**
681
+ * Helper: Get all module files as Record<filename, content>
682
+ */
683
+ async function getModuleFiles(
684
+ moduleName: string,
685
+ moduleDir: string
686
+ ): Promise<Record<string, string>> {
687
+ const files: Record<string, string> = {};
688
+ const entries = await fs.readdir(moduleDir);
689
+
690
+ for (const entry of entries) {
691
+ const filePath = path.join(moduleDir, entry);
692
+ const stat = await fs.stat(filePath);
693
+
694
+ if (stat.isFile() && entry.endsWith('.ts')) {
695
+ const content = await fs.readFile(filePath, 'utf-8');
696
+ files[entry] = content;
697
+ }
698
+ }
699
+
700
+ return files;
701
+ }
702
+
703
+ /**
704
+ * Helper: Show diff for entire module
705
+ */
706
+ async function showDiffForModule(
707
+ templateManager: TemplateManager,
708
+ moduleName: string,
709
+ moduleDir: string
710
+ ): Promise<void> {
711
+ const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
712
+
713
+ console.log(chalk.cyan(`\n📊 Changes in module "${moduleName}":\n`));
714
+
715
+ for (const file of modifiedFiles) {
716
+ if (file.isModified) {
717
+ console.log(chalk.yellow(`\n📄 ${file.fileName}:`));
718
+
719
+ const currentPath = path.join(moduleDir, file.fileName);
720
+ const currentContent = await fs.readFile(currentPath, 'utf-8');
721
+ const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
722
+
723
+ if (originalContent) {
724
+ const diff = templateManager.generateDiff(originalContent, currentContent);
725
+ console.log(diff);
726
+ }
727
+ }
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Helper: Perform smart merge
733
+ */
734
+ async function performSmartMerge(
735
+ templateManager: TemplateManager,
736
+ moduleName: string,
737
+ moduleDir: string,
738
+ _displayName: string
739
+ ): Promise<void> {
740
+ const spinner = ora('Analyzing files for merge...').start();
741
+
742
+ // Get new template files
743
+ const newFiles: Record<string, string> = {};
744
+ const templateDir = path.join(templateManager['templatesDir'], moduleName);
745
+
746
+ try {
747
+ const entries = await fs.readdir(templateDir);
748
+ for (const entry of entries) {
749
+ const content = await fs.readFile(path.join(templateDir, entry), 'utf-8');
750
+ newFiles[entry] = content;
751
+ }
752
+ } catch {
753
+ spinner.fail('Could not find template files');
754
+ return;
755
+ }
756
+
757
+ const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
758
+ spinner.stop();
759
+
760
+ // Ask for batch action or individual
761
+ const batchAction = await InteractivePrompt.askBatchAction();
762
+
763
+ const stats = {
764
+ merged: 0,
765
+ kept: 0,
766
+ overwritten: 0,
767
+ conflicts: 0,
768
+ };
769
+
770
+ for (const fileInfo of modifiedFiles) {
771
+ const fileName = fileInfo.fileName;
772
+ const filePath = path.join(moduleDir, fileName);
773
+ const newContent = newFiles[fileName];
774
+
775
+ if (!newContent) {
776
+ // File doesn't exist in new template, keep existing
777
+ continue;
778
+ }
779
+
780
+ let fileAction: string;
781
+
782
+ if (batchAction === 'merge-all') {
783
+ fileAction = 'merge';
784
+ } else if (batchAction === 'keep-all') {
785
+ fileAction = 'keep';
786
+ } else if (batchAction === 'overwrite-all') {
787
+ fileAction = 'overwrite';
788
+ } else {
789
+ // Ask for each file
790
+ const currentContent = await fs.readFile(filePath, 'utf-8');
791
+ const yourLines = currentContent.split('\n').length;
792
+ const newLines = newContent.split('\n').length;
793
+
794
+ const choice = await InteractivePrompt.askFileAction(
795
+ fileName,
796
+ fileInfo.isModified,
797
+ yourLines,
798
+ newLines
799
+ );
800
+ fileAction = choice.action;
801
+
802
+ if (fileAction === 'diff') {
803
+ const originalContent = await templateManager.getTemplate(moduleName, fileName);
804
+ if (originalContent) {
805
+ const diff = templateManager.generateDiff(originalContent, currentContent);
806
+ const proceed = await InteractivePrompt.showDiffAndAsk(diff);
807
+ fileAction = proceed ? 'merge' : 'keep';
808
+ }
809
+ }
810
+ }
811
+
812
+ // Perform action
813
+ if (fileAction === 'keep' || fileAction === 'skip') {
814
+ stats.kept++;
815
+ continue;
816
+ }
817
+
818
+ if (fileAction === 'overwrite') {
819
+ await fs.writeFile(filePath, newContent, 'utf-8');
820
+ stats.overwritten++;
821
+ continue;
822
+ }
823
+
824
+ if (fileAction === 'merge') {
825
+ const originalContent = await templateManager.getTemplate(moduleName, fileName);
826
+ const currentContent = await fs.readFile(filePath, 'utf-8');
827
+
828
+ if (originalContent) {
829
+ const mergeResult = await templateManager.mergeFiles(
830
+ originalContent,
831
+ currentContent,
832
+ newContent
833
+ );
834
+
835
+ await fs.writeFile(filePath, mergeResult.merged, 'utf-8');
836
+
837
+ if (mergeResult.hasConflicts) {
838
+ stats.conflicts++;
839
+ InteractivePrompt.displayConflicts(mergeResult.conflicts);
840
+ } else {
841
+ stats.merged++;
842
+ }
843
+ } else {
844
+ await fs.writeFile(filePath, newContent, 'utf-8');
845
+ stats.overwritten++;
846
+ }
847
+ }
848
+ }
849
+
850
+ // Update manifest
851
+ const files = await getModuleFiles(moduleName, moduleDir);
852
+ await templateManager.updateManifest(moduleName, files);
853
+
854
+ InteractivePrompt.showMergeSummary(stats);
855
+ }
@@ -2,10 +2,9 @@ import { Command } from 'commander';
2
2
  import { execSync, spawn } from 'child_process';
3
3
  import ora from 'ora';
4
4
  import chalk from 'chalk';
5
- import { success, error, info } from '../utils/helpers.js';
5
+ import { error, info } from '../utils/helpers.js';
6
6
 
7
- export const dbCommand = new Command('db')
8
- .description('Database management commands');
7
+ export const dbCommand = new Command('db').description('Database management commands');
9
8
 
10
9
  dbCommand
11
10
  .command('migrate')
@@ -131,7 +130,7 @@ dbCommand
131
130
  .action(async () => {
132
131
  try {
133
132
  execSync('npx prisma migrate status', { stdio: 'inherit' });
134
- } catch (err) {
133
+ } catch {
135
134
  error('Failed to get migration status');
136
135
  }
137
136
  });