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
@@ -5,12 +5,13 @@ import ora from 'ora';
5
5
  import inquirer from 'inquirer';
6
6
  import chalk from 'chalk';
7
7
  import { execSync } from 'child_process';
8
- import { ensureDir, writeFile, success, error, info, warn } from '../utils/helpers.js';
8
+ import { ensureDir, writeFile, error, warn } from '../utils/helpers.js';
9
9
 
10
10
  interface InitOptions {
11
11
  name: string;
12
12
  language: 'typescript' | 'javascript';
13
13
  database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
14
+ orm: 'prisma' | 'mongoose' | 'none';
14
15
  validator: 'zod' | 'joi' | 'yup';
15
16
  features: string[];
16
17
  }
@@ -23,195 +24,223 @@ export const initCommand = new Command('init')
23
24
  .option('--ts, --typescript', 'Use TypeScript (default)')
24
25
  .option('--js, --javascript', 'Use JavaScript')
25
26
  .option('--db <database>', 'Database type (postgresql, mysql, sqlite, mongodb, none)')
26
- .action(async (name?: string, cmdOptions?: { yes?: boolean; typescript?: boolean; javascript?: boolean; db?: string }) => {
27
- console.log(chalk.blue(`
27
+ .action(
28
+ async (
29
+ name?: string,
30
+ cmdOptions?: { yes?: boolean; typescript?: boolean; javascript?: boolean; db?: string }
31
+ ) => {
32
+ console.log(
33
+ chalk.blue(`
28
34
  ╔═══════════════════════════════════════════╗
29
35
  ║ ║
30
36
  ║ ${chalk.bold('🚀 Servcraft Project Generator')} ║
31
37
  ║ ║
32
38
  ╚═══════════════════════════════════════════╝
33
- `));
34
-
35
- let options: InitOptions;
36
-
37
- if (cmdOptions?.yes) {
38
- options = {
39
- name: name || 'my-servcraft-app',
40
- language: cmdOptions.javascript ? 'javascript' : 'typescript',
41
- database: (cmdOptions.db as InitOptions['database']) || 'postgresql',
42
- validator: 'zod',
43
- features: ['auth', 'users', 'email'],
44
- };
45
- } else {
46
- const answers = await inquirer.prompt([
47
- {
48
- type: 'input',
49
- name: 'name',
50
- message: 'Project name:',
51
- default: name || 'my-servcraft-app',
52
- validate: (input: string) => {
53
- if (!/^[a-z0-9-_]+$/i.test(input)) {
54
- return 'Project name can only contain letters, numbers, hyphens, and underscores';
55
- }
56
- return true;
57
- },
58
- },
59
- {
60
- type: 'list',
61
- name: 'language',
62
- message: 'Select language:',
63
- choices: [
64
- { name: 'TypeScript (Recommended)', value: 'typescript' },
65
- { name: 'JavaScript', value: 'javascript' },
66
- ],
67
- default: 'typescript',
68
- },
69
- {
70
- type: 'list',
71
- name: 'database',
72
- message: 'Select database:',
73
- choices: [
74
- { name: 'PostgreSQL (Recommended)', value: 'postgresql' },
75
- { name: 'MySQL', value: 'mysql' },
76
- { name: 'SQLite (Development)', value: 'sqlite' },
77
- { name: 'MongoDB', value: 'mongodb' },
78
- { name: 'None (Add later)', value: 'none' },
79
- ],
80
- default: 'postgresql',
81
- },
82
- {
83
- type: 'list',
84
- name: 'validator',
85
- message: 'Select validation library:',
86
- choices: [
87
- { name: 'Zod (Recommended - TypeScript-first)', value: 'zod' },
88
- { name: 'Joi (Battle-tested, feature-rich)', value: 'joi' },
89
- { name: 'Yup (Inspired by Joi, lighter)', value: 'yup' },
90
- ],
91
- default: 'zod',
92
- },
93
- {
94
- type: 'checkbox',
95
- name: 'features',
96
- message: 'Select features to include:',
97
- choices: [
98
- { name: 'Authentication (JWT)', value: 'auth', checked: true },
99
- { name: 'User Management', value: 'users', checked: true },
100
- { name: 'Email Service', value: 'email', checked: true },
101
- { name: 'Audit Logs', value: 'audit', checked: false },
102
- { name: 'File Upload', value: 'upload', checked: false },
103
- { name: 'Redis Cache', value: 'redis', checked: false },
104
- ],
105
- },
106
- ]);
107
-
108
- options = answers as InitOptions;
109
- }
110
-
111
- const projectDir = path.resolve(process.cwd(), options.name);
112
- const spinner = ora('Creating project...').start();
113
-
114
- try {
115
- // Check if directory exists
116
- try {
117
- await fs.access(projectDir);
118
- spinner.stop();
119
- error(`Directory "${options.name}" already exists`);
120
- return;
121
- } catch {
122
- // Directory doesn't exist, continue
123
- }
124
-
125
- // Create project directory
126
- await ensureDir(projectDir);
127
-
128
- spinner.text = 'Generating project files...';
129
-
130
- // Generate package.json
131
- const packageJson = generatePackageJson(options);
132
- await writeFile(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
39
+ `)
40
+ );
133
41
 
134
- // Generate tsconfig or jsconfig
135
- if (options.language === 'typescript') {
136
- await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
137
- await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig());
42
+ let options: InitOptions;
43
+
44
+ if (cmdOptions?.yes) {
45
+ const db = (cmdOptions.db as InitOptions['database']) || 'postgresql';
46
+ options = {
47
+ name: name || 'my-servcraft-app',
48
+ language: cmdOptions.javascript ? 'javascript' : 'typescript',
49
+ database: db,
50
+ orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
51
+ validator: 'zod',
52
+ features: ['auth', 'users', 'email'],
53
+ };
138
54
  } else {
139
- await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig());
55
+ const answers = await inquirer.prompt([
56
+ {
57
+ type: 'input',
58
+ name: 'name',
59
+ message: 'Project name:',
60
+ default: name || 'my-servcraft-app',
61
+ validate: (input: string) => {
62
+ if (!/^[a-z0-9-_]+$/i.test(input)) {
63
+ return 'Project name can only contain letters, numbers, hyphens, and underscores';
64
+ }
65
+ return true;
66
+ },
67
+ },
68
+ {
69
+ type: 'list',
70
+ name: 'language',
71
+ message: 'Select language:',
72
+ choices: [
73
+ { name: 'TypeScript (Recommended)', value: 'typescript' },
74
+ { name: 'JavaScript', value: 'javascript' },
75
+ ],
76
+ default: 'typescript',
77
+ },
78
+ {
79
+ type: 'list',
80
+ name: 'database',
81
+ message: 'Select database:',
82
+ choices: [
83
+ { name: 'PostgreSQL (Recommended for SQL)', value: 'postgresql' },
84
+ { name: 'MySQL', value: 'mysql' },
85
+ { name: 'SQLite (Development)', value: 'sqlite' },
86
+ { name: 'MongoDB (NoSQL)', value: 'mongodb' },
87
+ { name: 'None (Add later)', value: 'none' },
88
+ ],
89
+ default: 'postgresql',
90
+ },
91
+ {
92
+ type: 'list',
93
+ name: 'validator',
94
+ message: 'Select validation library:',
95
+ choices: [
96
+ { name: 'Zod (Recommended - TypeScript-first)', value: 'zod' },
97
+ { name: 'Joi (Battle-tested, feature-rich)', value: 'joi' },
98
+ { name: 'Yup (Inspired by Joi, lighter)', value: 'yup' },
99
+ ],
100
+ default: 'zod',
101
+ },
102
+ {
103
+ type: 'checkbox',
104
+ name: 'features',
105
+ message: 'Select features to include:',
106
+ choices: [
107
+ { name: 'Authentication (JWT)', value: 'auth', checked: true },
108
+ { name: 'User Management', value: 'users', checked: true },
109
+ { name: 'Email Service', value: 'email', checked: true },
110
+ { name: 'Audit Logs', value: 'audit', checked: false },
111
+ { name: 'File Upload', value: 'upload', checked: false },
112
+ { name: 'Redis Cache', value: 'redis', checked: false },
113
+ ],
114
+ },
115
+ ]);
116
+
117
+ // Auto-determine ORM based on database choice
118
+ const db = answers.database as InitOptions['database'];
119
+ options = {
120
+ ...answers,
121
+ orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
122
+ } as InitOptions;
140
123
  }
141
124
 
142
- // Generate .env files
143
- await writeFile(path.join(projectDir, '.env.example'), generateEnvExample(options));
144
- await writeFile(path.join(projectDir, '.env'), generateEnvExample(options));
145
-
146
- // Generate .gitignore
147
- await writeFile(path.join(projectDir, '.gitignore'), generateGitignore());
148
-
149
- // Generate Docker files
150
- await writeFile(path.join(projectDir, 'Dockerfile'), generateDockerfile(options));
151
- await writeFile(path.join(projectDir, 'docker-compose.yml'), generateDockerCompose(options));
152
-
153
- // Create directory structure
154
- const ext = options.language === 'typescript' ? 'ts' : 'js';
155
- const dirs = [
156
- 'src/core',
157
- 'src/config',
158
- 'src/modules',
159
- 'src/middleware',
160
- 'src/utils',
161
- 'src/types',
162
- 'tests/unit',
163
- 'tests/integration',
164
- ];
165
-
166
- if (options.database !== 'none' && options.database !== 'mongodb') {
167
- dirs.push('prisma');
168
- }
125
+ const projectDir = path.resolve(process.cwd(), options.name);
126
+ const spinner = ora('Creating project...').start();
169
127
 
170
- for (const dir of dirs) {
171
- await ensureDir(path.join(projectDir, dir));
172
- }
128
+ try {
129
+ // Check if directory exists
130
+ try {
131
+ await fs.access(projectDir);
132
+ spinner.stop();
133
+ error(`Directory "${options.name}" already exists`);
134
+ return;
135
+ } catch {
136
+ // Directory doesn't exist, continue
137
+ }
138
+
139
+ // Create project directory
140
+ await ensureDir(projectDir);
141
+
142
+ spinner.text = 'Generating project files...';
143
+
144
+ // Generate package.json
145
+ const packageJson = generatePackageJson(options);
146
+ await writeFile(
147
+ path.join(projectDir, 'package.json'),
148
+ JSON.stringify(packageJson, null, 2)
149
+ );
173
150
 
174
- // Generate main entry file
175
- await writeFile(
176
- path.join(projectDir, `src/index.${ext}`),
177
- generateEntryFile(options)
178
- );
151
+ // Generate tsconfig or jsconfig
152
+ if (options.language === 'typescript') {
153
+ await writeFile(path.join(projectDir, 'tsconfig.json'), generateTsConfig());
154
+ await writeFile(path.join(projectDir, 'tsup.config.ts'), generateTsupConfig());
155
+ } else {
156
+ await writeFile(path.join(projectDir, 'jsconfig.json'), generateJsConfig());
157
+ }
179
158
 
180
- // Generate core files
181
- await writeFile(
182
- path.join(projectDir, `src/core/server.${ext}`),
183
- generateServerFile(options)
184
- );
185
- await writeFile(
186
- path.join(projectDir, `src/core/logger.${ext}`),
187
- generateLoggerFile(options)
188
- );
159
+ // Generate .env files
160
+ await writeFile(path.join(projectDir, '.env.example'), generateEnvExample(options));
161
+ await writeFile(path.join(projectDir, '.env'), generateEnvExample(options));
162
+
163
+ // Generate .gitignore
164
+ await writeFile(path.join(projectDir, '.gitignore'), generateGitignore());
189
165
 
190
- // Generate Prisma schema if database is selected
191
- if (options.database !== 'none' && options.database !== 'mongodb') {
166
+ // Generate Docker files
167
+ await writeFile(path.join(projectDir, 'Dockerfile'), generateDockerfile(options));
192
168
  await writeFile(
193
- path.join(projectDir, 'prisma/schema.prisma'),
194
- generatePrismaSchema(options)
169
+ path.join(projectDir, 'docker-compose.yml'),
170
+ generateDockerCompose(options)
195
171
  );
196
- }
197
-
198
- spinner.succeed('Project files generated!');
199
172
 
200
- // Install dependencies
201
- const installSpinner = ora('Installing dependencies...').start();
202
-
203
- try {
204
- execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
205
- installSpinner.succeed('Dependencies installed!');
206
- } catch {
207
- installSpinner.warn('Failed to install dependencies automatically');
208
- warn(' Run "npm install" manually in the project directory');
209
- }
173
+ // Create directory structure
174
+ const ext = options.language === 'typescript' ? 'ts' : 'js';
175
+ const dirs = [
176
+ 'src/core',
177
+ 'src/config',
178
+ 'src/modules',
179
+ 'src/middleware',
180
+ 'src/utils',
181
+ 'src/types',
182
+ 'tests/unit',
183
+ 'tests/integration',
184
+ ];
185
+
186
+ if (options.orm === 'prisma') {
187
+ dirs.push('prisma');
188
+ }
189
+ if (options.orm === 'mongoose') {
190
+ dirs.push('src/database/models');
191
+ }
192
+
193
+ for (const dir of dirs) {
194
+ await ensureDir(path.join(projectDir, dir));
195
+ }
196
+
197
+ // Generate main entry file
198
+ await writeFile(path.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
199
+
200
+ // Generate core files
201
+ await writeFile(
202
+ path.join(projectDir, `src/core/server.${ext}`),
203
+ generateServerFile(options)
204
+ );
205
+ await writeFile(
206
+ path.join(projectDir, `src/core/logger.${ext}`),
207
+ generateLoggerFile(options)
208
+ );
210
209
 
211
- // Print success message
212
- console.log('\n' + chalk.green('✨ Project created successfully!'));
213
- console.log('\n' + chalk.bold('📁 Project structure:'));
214
- console.log(`
210
+ // Generate database files based on ORM choice
211
+ if (options.orm === 'prisma') {
212
+ await writeFile(
213
+ path.join(projectDir, 'prisma/schema.prisma'),
214
+ generatePrismaSchema(options)
215
+ );
216
+ } else if (options.orm === 'mongoose') {
217
+ await writeFile(
218
+ path.join(projectDir, `src/database/connection.${ext}`),
219
+ generateMongooseConnection(options)
220
+ );
221
+ await writeFile(
222
+ path.join(projectDir, `src/database/models/user.model.${ext}`),
223
+ generateMongooseUserModel(options)
224
+ );
225
+ }
226
+
227
+ spinner.succeed('Project files generated!');
228
+
229
+ // Install dependencies
230
+ const installSpinner = ora('Installing dependencies...').start();
231
+
232
+ try {
233
+ execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
234
+ installSpinner.succeed('Dependencies installed!');
235
+ } catch {
236
+ installSpinner.warn('Failed to install dependencies automatically');
237
+ warn(' Run "npm install" manually in the project directory');
238
+ }
239
+
240
+ // Print success message
241
+ console.log('\n' + chalk.green('✨ Project created successfully!'));
242
+ console.log('\n' + chalk.bold('📁 Project structure:'));
243
+ console.log(`
215
244
  ${options.name}/
216
245
  ├── src/
217
246
  │ ├── core/ # Core server, logger
@@ -226,26 +255,26 @@ export const initCommand = new Command('init')
226
255
  └── package.json
227
256
  `);
228
257
 
229
- console.log(chalk.bold('🚀 Get started:'));
230
- console.log(`
258
+ console.log(chalk.bold('🚀 Get started:'));
259
+ console.log(`
231
260
  ${chalk.cyan(`cd ${options.name}`)}
232
261
  ${options.database !== 'none' ? chalk.cyan('npm run db:push # Setup database') : ''}
233
262
  ${chalk.cyan('npm run dev # Start development server')}
234
263
  `);
235
264
 
236
- console.log(chalk.bold('📚 Available commands:'));
237
- console.log(`
265
+ console.log(chalk.bold('📚 Available commands:'));
266
+ console.log(`
238
267
  ${chalk.yellow('servcraft generate module <name>')} Generate a new module
239
268
  ${chalk.yellow('servcraft generate controller <name>')} Generate a controller
240
269
  ${chalk.yellow('servcraft generate service <name>')} Generate a service
241
270
  ${chalk.yellow('servcraft add auth')} Add authentication module
242
271
  `);
243
-
244
- } catch (err) {
245
- spinner.fail('Failed to create project');
246
- error(err instanceof Error ? err.message : String(err));
272
+ } catch (err) {
273
+ spinner.fail('Failed to create project');
274
+ error(err instanceof Error ? err.message : String(err));
275
+ }
247
276
  }
248
- });
277
+ );
249
278
 
250
279
  function generatePackageJson(options: InitOptions): Record<string, unknown> {
251
280
  const isTS = options.language === 'typescript';
@@ -301,7 +330,7 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
301
330
  (pkg.devDependencies as Record<string, string>)['@types/bcryptjs'] = '^2.4.6';
302
331
  }
303
332
 
304
- if (options.database !== 'none' && options.database !== 'mongodb') {
333
+ if (options.orm === 'prisma') {
305
334
  (pkg.dependencies as Record<string, string>)['@prisma/client'] = '^5.22.0';
306
335
  (pkg.devDependencies as Record<string, string>).prisma = '^5.22.0';
307
336
  (pkg.scripts as Record<string, string>)['db:generate'] = 'prisma generate';
@@ -310,8 +339,11 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
310
339
  (pkg.scripts as Record<string, string>)['db:studio'] = 'prisma studio';
311
340
  }
312
341
 
313
- if (options.database === 'mongodb') {
342
+ if (options.orm === 'mongoose') {
314
343
  (pkg.dependencies as Record<string, string>).mongoose = '^8.8.4';
344
+ if (isTS) {
345
+ (pkg.devDependencies as Record<string, string>)['@types/mongoose'] = '^5.11.97';
346
+ }
315
347
  }
316
348
 
317
349
  if (options.features.includes('email')) {
@@ -330,38 +362,46 @@ function generatePackageJson(options: InitOptions): Record<string, unknown> {
330
362
  }
331
363
 
332
364
  function generateTsConfig(): string {
333
- return JSON.stringify({
334
- compilerOptions: {
335
- target: 'ES2022',
336
- module: 'NodeNext',
337
- moduleResolution: 'NodeNext',
338
- lib: ['ES2022'],
339
- outDir: './dist',
340
- rootDir: './src',
341
- strict: true,
342
- esModuleInterop: true,
343
- skipLibCheck: true,
344
- forceConsistentCasingInFileNames: true,
345
- resolveJsonModule: true,
346
- declaration: true,
347
- sourceMap: true,
365
+ return JSON.stringify(
366
+ {
367
+ compilerOptions: {
368
+ target: 'ES2022',
369
+ module: 'NodeNext',
370
+ moduleResolution: 'NodeNext',
371
+ lib: ['ES2022'],
372
+ outDir: './dist',
373
+ rootDir: './src',
374
+ strict: true,
375
+ esModuleInterop: true,
376
+ skipLibCheck: true,
377
+ forceConsistentCasingInFileNames: true,
378
+ resolveJsonModule: true,
379
+ declaration: true,
380
+ sourceMap: true,
381
+ },
382
+ include: ['src/**/*'],
383
+ exclude: ['node_modules', 'dist'],
348
384
  },
349
- include: ['src/**/*'],
350
- exclude: ['node_modules', 'dist'],
351
- }, null, 2);
385
+ null,
386
+ 2
387
+ );
352
388
  }
353
389
 
354
390
  function generateJsConfig(): string {
355
- return JSON.stringify({
356
- compilerOptions: {
357
- module: 'NodeNext',
358
- moduleResolution: 'NodeNext',
359
- target: 'ES2022',
360
- checkJs: true,
391
+ return JSON.stringify(
392
+ {
393
+ compilerOptions: {
394
+ module: 'NodeNext',
395
+ moduleResolution: 'NodeNext',
396
+ target: 'ES2022',
397
+ checkJs: true,
398
+ },
399
+ include: ['src/**/*'],
400
+ exclude: ['node_modules'],
361
401
  },
362
- include: ['src/**/*'],
363
- exclude: ['node_modules'],
364
- }, null, 2);
402
+ null,
403
+ 2
404
+ );
365
405
  }
366
406
 
367
407
  function generateTsupConfig(): string {
@@ -517,6 +557,23 @@ volumes:
517
557
 
518
558
  volumes:
519
559
  mysql-data:
560
+ `;
561
+ } else if (options.database === 'mongodb') {
562
+ compose += ` - MONGODB_URI=mongodb://mongodb:27017/mydb
563
+ depends_on:
564
+ - mongodb
565
+
566
+ mongodb:
567
+ image: mongo:7
568
+ environment:
569
+ MONGO_INITDB_DATABASE: mydb
570
+ ports:
571
+ - "27017:27017"
572
+ volumes:
573
+ - mongodb-data:/data/db
574
+
575
+ volumes:
576
+ mongodb-data:
520
577
  `;
521
578
  }
522
579
 
@@ -583,10 +640,14 @@ main();
583
640
  function generateServerFile(options: InitOptions): string {
584
641
  const isTS = options.language === 'typescript';
585
642
 
586
- return `${isTS ? `import Fastify from 'fastify';
643
+ return `${
644
+ isTS
645
+ ? `import Fastify from 'fastify';
587
646
  import type { FastifyInstance } from 'fastify';
588
- import { logger } from './logger.js';` : `const Fastify = require('fastify');
589
- const { logger } = require('./logger.js');`}
647
+ import { logger } from './logger.js';`
648
+ : `const Fastify = require('fastify');
649
+ const { logger } = require('./logger.js');`
650
+ }
590
651
 
591
652
  ${isTS ? 'export function createServer(): { instance: FastifyInstance; start: () => Promise<void> }' : 'function createServer()'} {
592
653
  const app = Fastify({ logger });
@@ -638,3 +699,112 @@ ${isTS ? 'export const logger: Logger' : 'const logger'} = pino({
638
699
  ${isTS ? '' : 'module.exports = { logger };'}
639
700
  `;
640
701
  }
702
+
703
+ function generateMongooseConnection(options: InitOptions): string {
704
+ const isTS = options.language === 'typescript';
705
+
706
+ return `${isTS ? "import mongoose from 'mongoose';\nimport { logger } from '../core/logger.js';" : "const mongoose = require('mongoose');\nconst { logger } = require('../core/logger.js');"}
707
+
708
+ const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mydb';
709
+
710
+ ${isTS ? 'export async function connectDatabase(): Promise<typeof mongoose>' : 'async function connectDatabase()'} {
711
+ try {
712
+ const conn = await mongoose.connect(MONGODB_URI);
713
+ logger.info(\`MongoDB connected: \${conn.connection.host}\`);
714
+ return conn;
715
+ } catch (error) {
716
+ logger.error({ err: error }, 'MongoDB connection failed');
717
+ process.exit(1);
718
+ }
719
+ }
720
+
721
+ ${isTS ? 'export async function disconnectDatabase(): Promise<void>' : 'async function disconnectDatabase()'} {
722
+ try {
723
+ await mongoose.disconnect();
724
+ logger.info('MongoDB disconnected');
725
+ } catch (error) {
726
+ logger.error({ err: error }, 'MongoDB disconnect failed');
727
+ }
728
+ }
729
+
730
+ ${isTS ? 'export { mongoose };' : 'module.exports = { connectDatabase, disconnectDatabase, mongoose };'}
731
+ `;
732
+ }
733
+
734
+ function generateMongooseUserModel(options: InitOptions): string {
735
+ const isTS = options.language === 'typescript';
736
+
737
+ return `${isTS ? "import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';" : "const mongoose = require('mongoose');\nconst bcrypt = require('bcryptjs');\nconst { Schema } = mongoose;"}
738
+
739
+ ${
740
+ isTS
741
+ ? `export interface IUser extends Document {
742
+ email: string;
743
+ password: string;
744
+ name?: string;
745
+ role: 'user' | 'admin';
746
+ status: 'active' | 'inactive' | 'suspended';
747
+ emailVerified: boolean;
748
+ createdAt: Date;
749
+ updatedAt: Date;
750
+ comparePassword(candidatePassword: string): Promise<boolean>;
751
+ }`
752
+ : ''
753
+ }
754
+
755
+ const userSchema = new Schema${isTS ? '<IUser>' : ''}({
756
+ email: {
757
+ type: String,
758
+ required: true,
759
+ unique: true,
760
+ lowercase: true,
761
+ trim: true,
762
+ },
763
+ password: {
764
+ type: String,
765
+ required: true,
766
+ minlength: 8,
767
+ },
768
+ name: {
769
+ type: String,
770
+ trim: true,
771
+ },
772
+ role: {
773
+ type: String,
774
+ enum: ['user', 'admin'],
775
+ default: 'user',
776
+ },
777
+ status: {
778
+ type: String,
779
+ enum: ['active', 'inactive', 'suspended'],
780
+ default: 'active',
781
+ },
782
+ emailVerified: {
783
+ type: Boolean,
784
+ default: false,
785
+ },
786
+ }, {
787
+ timestamps: true,
788
+ });
789
+
790
+ // Hash password before saving
791
+ userSchema.pre('save', async function(next) {
792
+ if (!this.isModified('password')) return next();
793
+
794
+ try {
795
+ const salt = await bcrypt.genSalt(10);
796
+ this.password = await bcrypt.hash(this.password, salt);
797
+ next();
798
+ } catch (error${isTS ? ': any' : ''}) {
799
+ next(error);
800
+ }
801
+ });
802
+
803
+ // Compare password method
804
+ userSchema.methods.comparePassword = async function(candidatePassword${isTS ? ': string' : ''})${isTS ? ': Promise<boolean>' : ''} {
805
+ return bcrypt.compare(candidatePassword, this.password);
806
+ };
807
+
808
+ ${isTS ? "export const User = mongoose.model<IUser>('User', userSchema);" : "const User = mongoose.model('User', userSchema);\nmodule.exports = { User };"}
809
+ `;
810
+ }