create-tigra 1.0.7 → 2.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 (237) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -87
  3. package/bin/create-tigra.js +242 -309
  4. package/package.json +49 -41
  5. package/template/_claude/QUICK_REFERENCE.md +193 -0
  6. package/template/_claude/README.md +53 -0
  7. package/template/_claude/commands/create-client.md +881 -0
  8. package/template/_claude/commands/create-server.md +383 -0
  9. package/template/_claude/rules/client/01-project-structure.md +133 -0
  10. package/template/_claude/rules/client/02-components-and-types.md +146 -0
  11. package/template/_claude/rules/client/03-data-and-state.md +156 -0
  12. package/template/_claude/rules/client/04-design-system.md +185 -0
  13. package/template/_claude/rules/client/05-security.md +55 -0
  14. package/template/_claude/rules/client/06-ux-checklist.md +81 -0
  15. package/template/_claude/rules/client/core.md +42 -0
  16. package/template/_claude/rules/global/core.md +77 -0
  17. package/template/_claude/rules/server/core.md +50 -0
  18. package/template/_claude/rules/server/database.md +124 -0
  19. package/template/_claude/rules/server/project-conventions.md +150 -0
  20. package/template/_claude/rules/server/response-handling.md +144 -0
  21. package/template/client/.env.example +5 -0
  22. package/template/client/README.md +36 -0
  23. package/template/client/components.json +23 -0
  24. package/template/client/eslint.config.mjs +18 -0
  25. package/template/client/next.config.ts +34 -0
  26. package/template/client/package.json +44 -0
  27. package/template/client/postcss.config.mjs +7 -0
  28. package/template/client/src/app/(auth)/layout.tsx +18 -0
  29. package/template/client/src/app/(auth)/login/page.tsx +13 -0
  30. package/template/client/src/app/(auth)/register/page.tsx +13 -0
  31. package/template/client/src/app/(main)/dashboard/page.tsx +22 -0
  32. package/template/client/src/app/(main)/layout.tsx +11 -0
  33. package/template/client/src/app/error.tsx +27 -0
  34. package/template/client/src/app/favicon.ico +0 -0
  35. package/template/client/src/app/globals.css +145 -0
  36. package/template/client/src/app/layout.tsx +36 -0
  37. package/template/client/src/app/loading.tsx +11 -0
  38. package/template/client/src/app/not-found.tsx +23 -0
  39. package/template/client/src/app/page.tsx +45 -0
  40. package/template/client/src/app/providers.tsx +43 -0
  41. package/template/client/src/components/common/ConfirmDialog.tsx +56 -0
  42. package/template/client/src/components/common/EmptyState.tsx +31 -0
  43. package/template/client/src/components/common/LoadingSpinner.tsx +30 -0
  44. package/template/client/src/components/common/Pagination.tsx +55 -0
  45. package/template/client/src/components/layout/Footer.tsx +17 -0
  46. package/template/client/src/components/layout/Header.tsx +173 -0
  47. package/template/client/src/components/layout/MainLayout.tsx +18 -0
  48. package/template/client/src/components/ui/alert-dialog.tsx +196 -0
  49. package/template/client/src/components/ui/badge.tsx +48 -0
  50. package/template/client/src/components/ui/button.tsx +64 -0
  51. package/template/client/src/components/ui/card.tsx +92 -0
  52. package/template/client/src/components/ui/input.tsx +21 -0
  53. package/template/client/src/components/ui/label.tsx +24 -0
  54. package/template/client/src/components/ui/select.tsx +190 -0
  55. package/template/client/src/components/ui/skeleton.tsx +13 -0
  56. package/template/client/src/components/ui/table.tsx +116 -0
  57. package/template/client/src/features/auth/components/AuthInitializer.tsx +55 -0
  58. package/template/client/src/features/auth/components/LoginForm.tsx +107 -0
  59. package/template/client/src/features/auth/components/RegisterForm.tsx +178 -0
  60. package/template/client/src/features/auth/hooks/useAuth.ts +84 -0
  61. package/template/client/src/features/auth/services/auth.service.ts +52 -0
  62. package/template/client/src/features/auth/store/authSlice.ts +38 -0
  63. package/template/client/src/features/auth/types/auth.types.ts +32 -0
  64. package/template/client/src/hooks/useDebounce.ts +14 -0
  65. package/template/client/src/hooks/useLocalStorage.ts +55 -0
  66. package/template/client/src/hooks/useMediaQuery.ts +27 -0
  67. package/template/client/src/lib/api/api.types.ts +34 -0
  68. package/template/client/src/lib/api/axios.config.ts +98 -0
  69. package/template/client/src/lib/constants/api-endpoints.ts +18 -0
  70. package/template/client/src/lib/constants/app.constants.ts +12 -0
  71. package/template/client/src/lib/constants/routes.ts +9 -0
  72. package/template/client/src/lib/utils/error.ts +32 -0
  73. package/template/client/src/lib/utils/format.ts +37 -0
  74. package/template/client/src/lib/utils/security.ts +34 -0
  75. package/template/client/src/lib/utils.ts +6 -0
  76. package/template/client/src/middleware.ts +57 -0
  77. package/template/client/src/store/hooks.ts +7 -0
  78. package/template/client/src/store/index.ts +12 -0
  79. package/template/client/src/types/index.ts +3 -0
  80. package/template/client/tsconfig.json +34 -0
  81. package/template/gitignore +34 -0
  82. package/template/server/.dockerignore +66 -0
  83. package/template/server/.env.example +96 -69
  84. package/template/server/.env.production.example +90 -0
  85. package/template/server/Dockerfile +94 -0
  86. package/template/server/docker-compose.yml +80 -111
  87. package/template/server/docs/logging.md +62 -0
  88. package/template/server/eslint.config.mjs +17 -0
  89. package/template/server/package.json +68 -81
  90. package/template/server/phpmyadmin-config.php +26 -0
  91. package/template/server/postman_collection.json +666 -0
  92. package/template/server/prisma/schema.prisma +77 -93
  93. package/template/server/prisma/seed.ts +46 -142
  94. package/template/server/scripts/flush-redis.ts +41 -0
  95. package/template/server/src/app.ts +243 -71
  96. package/template/server/src/config/env.ts +67 -94
  97. package/template/server/src/libs/auth.ts +88 -0
  98. package/template/server/src/libs/cleanup.ts +35 -0
  99. package/template/server/src/libs/cookies.ts +46 -0
  100. package/template/server/src/libs/logger.ts +33 -60
  101. package/template/server/src/libs/monitoring.ts +205 -0
  102. package/template/server/src/libs/password.ts +38 -0
  103. package/template/server/src/libs/prisma.ts +68 -0
  104. package/template/server/src/libs/redis.ts +60 -79
  105. package/template/server/src/libs/requestLogger.ts +66 -0
  106. package/template/server/src/libs/storage/file-storage.service.ts +211 -0
  107. package/template/server/src/libs/storage/file-validator.ts +97 -0
  108. package/template/server/src/libs/storage/filename-sanitizer.ts +71 -0
  109. package/template/server/src/libs/storage/image-optimizer.service.ts +144 -0
  110. package/template/server/src/modules/auth/__tests__/auth.service.test.ts +365 -0
  111. package/template/server/src/modules/auth/auth.controller.ts +90 -141
  112. package/template/server/src/modules/auth/auth.repo.ts +120 -218
  113. package/template/server/src/modules/auth/auth.routes.ts +96 -83
  114. package/template/server/src/modules/auth/auth.schemas.ts +35 -137
  115. package/template/server/src/modules/auth/auth.service.ts +286 -329
  116. package/template/server/src/modules/auth/session.repo.ts +110 -0
  117. package/template/server/src/modules/users/users.controller.ts +120 -0
  118. package/template/server/src/modules/users/users.repo.ts +77 -0
  119. package/template/server/src/modules/users/users.routes.ts +89 -0
  120. package/template/server/src/modules/users/users.schemas.ts +21 -0
  121. package/template/server/src/modules/users/users.service.ts +169 -0
  122. package/template/server/src/server.ts +58 -139
  123. package/template/server/src/shared/errors/AppError.ts +21 -0
  124. package/template/server/src/shared/errors/errors.ts +43 -0
  125. package/template/server/src/shared/responses/paginatedResponse.ts +38 -0
  126. package/template/server/src/shared/responses/successResponse.ts +17 -0
  127. package/template/server/src/shared/schemas/pagination.schema.ts +12 -0
  128. package/template/server/src/shared/types/index.ts +26 -0
  129. package/template/server/src/test/setup.ts +74 -38
  130. package/template/server/tsconfig.json +27 -89
  131. package/template/server/uploads/avatars/.gitkeep +1 -0
  132. package/template/server/vitest.config.ts +43 -98
  133. package/template/.agent/rules/client/01-project-structure.md +0 -326
  134. package/template/.agent/rules/client/02-component-patterns.md +0 -249
  135. package/template/.agent/rules/client/03-typescript-rules.md +0 -226
  136. package/template/.agent/rules/client/04-state-management.md +0 -474
  137. package/template/.agent/rules/client/05-api-integration.md +0 -129
  138. package/template/.agent/rules/client/06-forms-validation.md +0 -129
  139. package/template/.agent/rules/client/07-common-patterns.md +0 -150
  140. package/template/.agent/rules/client/08-color-system.md +0 -93
  141. package/template/.agent/rules/client/09-security-rules.md +0 -97
  142. package/template/.agent/rules/client/10-testing-strategy.md +0 -370
  143. package/template/.agent/rules/global/ai-edit-safety.md +0 -38
  144. package/template/.agent/rules/server/01-db-and-migrations.md +0 -242
  145. package/template/.agent/rules/server/02-general-rules.md +0 -111
  146. package/template/.agent/rules/server/03-migrations.md +0 -20
  147. package/template/.agent/rules/server/04-pagination.md +0 -130
  148. package/template/.agent/rules/server/05-project-conventions.md +0 -71
  149. package/template/.agent/rules/server/06-response-handling.md +0 -173
  150. package/template/.agent/rules/server/07-testing-strategy.md +0 -506
  151. package/template/.agent/rules/server/08-observability.md +0 -180
  152. package/template/.agent/rules/server/10-background-jobs-v2.md +0 -185
  153. package/template/.agent/rules/server/11-rate-limiting-v2.md +0 -210
  154. package/template/.agent/rules/server/12-performance-optimization.md +0 -567
  155. package/template/.claude/rules/client-01-project-structure.md +0 -327
  156. package/template/.claude/rules/client-02-component-patterns.md +0 -250
  157. package/template/.claude/rules/client-03-typescript-rules.md +0 -227
  158. package/template/.claude/rules/client-04-state-management.md +0 -475
  159. package/template/.claude/rules/client-05-api-integration.md +0 -130
  160. package/template/.claude/rules/client-06-forms-validation.md +0 -130
  161. package/template/.claude/rules/client-07-common-patterns.md +0 -151
  162. package/template/.claude/rules/client-08-color-system.md +0 -94
  163. package/template/.claude/rules/client-09-security-rules.md +0 -98
  164. package/template/.claude/rules/client-10-testing-strategy.md +0 -371
  165. package/template/.claude/rules/global-ai-edit-safety.md +0 -39
  166. package/template/.claude/rules/server-01-db-and-migrations.md +0 -243
  167. package/template/.claude/rules/server-02-general-rules.md +0 -112
  168. package/template/.claude/rules/server-03-migrations.md +0 -21
  169. package/template/.claude/rules/server-04-pagination.md +0 -131
  170. package/template/.claude/rules/server-05-project-conventions.md +0 -72
  171. package/template/.claude/rules/server-06-response-handling.md +0 -174
  172. package/template/.claude/rules/server-07-testing-strategy.md +0 -507
  173. package/template/.claude/rules/server-08-observability.md +0 -181
  174. package/template/.claude/rules/server-10-background-jobs-v2.md +0 -186
  175. package/template/.claude/rules/server-11-rate-limiting-v2.md +0 -211
  176. package/template/.claude/rules/server-12-performance-optimization.md +0 -568
  177. package/template/.cursor/rules/client-01-project-structure.mdc +0 -327
  178. package/template/.cursor/rules/client-02-component-patterns.mdc +0 -250
  179. package/template/.cursor/rules/client-03-typescript-rules.mdc +0 -227
  180. package/template/.cursor/rules/client-04-state-management.mdc +0 -475
  181. package/template/.cursor/rules/client-05-api-integration.mdc +0 -130
  182. package/template/.cursor/rules/client-06-forms-validation.mdc +0 -130
  183. package/template/.cursor/rules/client-07-common-patterns.mdc +0 -151
  184. package/template/.cursor/rules/client-08-color-system.mdc +0 -94
  185. package/template/.cursor/rules/client-09-security-rules.mdc +0 -98
  186. package/template/.cursor/rules/client-10-testing-strategy.mdc +0 -371
  187. package/template/.cursor/rules/global-ai-edit-safety.mdc +0 -39
  188. package/template/.cursor/rules/server-01-db-and-migrations.mdc +0 -243
  189. package/template/.cursor/rules/server-02-general-rules.mdc +0 -112
  190. package/template/.cursor/rules/server-03-migrations.mdc +0 -21
  191. package/template/.cursor/rules/server-04-pagination.mdc +0 -131
  192. package/template/.cursor/rules/server-05-project-conventions.mdc +0 -72
  193. package/template/.cursor/rules/server-06-response-handling.mdc +0 -174
  194. package/template/.cursor/rules/server-07-testing-strategy.mdc +0 -507
  195. package/template/.cursor/rules/server-08-observability.mdc +0 -181
  196. package/template/.cursor/rules/server-09-api-documentation-v2.mdc +0 -169
  197. package/template/.cursor/rules/server-10-background-jobs-v2.mdc +0 -186
  198. package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +0 -211
  199. package/template/.cursor/rules/server-12-performance-optimization.mdc +0 -568
  200. package/template/CLAUDE.md +0 -207
  201. package/template/server/.tsc-aliasrc.json +0 -12
  202. package/template/server/README.md +0 -183
  203. package/template/server/SECURITY.md +0 -190
  204. package/template/server/Tigra-API.postman_collection.json +0 -733
  205. package/template/server/biome.json +0 -42
  206. package/template/server/scripts/setup-env.js +0 -50
  207. package/template/server/scripts/wait-for-db.js +0 -60
  208. package/template/server/src/hooks/request-timing.hook.ts +0 -26
  209. package/template/server/src/libs/auth/authenticate.middleware.ts +0 -22
  210. package/template/server/src/libs/auth/rbac.middleware.test.ts +0 -134
  211. package/template/server/src/libs/auth/rbac.middleware.ts +0 -147
  212. package/template/server/src/libs/db.ts +0 -76
  213. package/template/server/src/libs/error-handler.ts +0 -89
  214. package/template/server/src/libs/queue.ts +0 -79
  215. package/template/server/src/modules/admin/admin.controller.ts +0 -122
  216. package/template/server/src/modules/admin/admin.routes.ts +0 -62
  217. package/template/server/src/modules/admin/admin.schemas.ts +0 -35
  218. package/template/server/src/modules/admin/admin.service.ts +0 -167
  219. package/template/server/src/modules/auth/auth.integration.test.ts +0 -150
  220. package/template/server/src/modules/auth/auth.service.test.ts +0 -119
  221. package/template/server/src/modules/auth/auth.types.ts +0 -97
  222. package/template/server/src/modules/resources/resources.controller.ts +0 -218
  223. package/template/server/src/modules/resources/resources.repo.ts +0 -253
  224. package/template/server/src/modules/resources/resources.routes.ts +0 -116
  225. package/template/server/src/modules/resources/resources.schemas.ts +0 -146
  226. package/template/server/src/modules/resources/resources.service.ts +0 -218
  227. package/template/server/src/modules/resources/resources.types.ts +0 -73
  228. package/template/server/src/plugins/rate-limit.plugin.ts +0 -21
  229. package/template/server/src/plugins/security.plugin.ts +0 -21
  230. package/template/server/src/routes/health.routes.ts +0 -31
  231. package/template/server/src/types/fastify.d.ts +0 -36
  232. package/template/server/src/utils/errors.ts +0 -108
  233. package/template/server/src/utils/pagination.ts +0 -120
  234. package/template/server/src/utils/response.ts +0 -110
  235. package/template/server/src/workers/file.worker.ts +0 -106
  236. package/template/server/tsconfig.build.json +0 -30
  237. package/template/server/tsconfig.test.json +0 -22
@@ -1,309 +1,242 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import prompts from 'prompts';
5
- import chalk from 'chalk';
6
- import ora from 'ora';
7
- import fs from 'fs-extra';
8
- import path from 'path';
9
- import { fileURLToPath } from 'url';
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
-
14
- // ==============================================
15
- // Case Transformation Utilities
16
- // ==============================================
17
-
18
- function toKebabCase(str) {
19
- return str
20
- .toLowerCase()
21
- .replace(/[^a-z0-9]+/g, '-')
22
- .replace(/^-|-$/g, '');
23
- }
24
-
25
- function toSnakeCase(str) {
26
- return str
27
- .toLowerCase()
28
- .replace(/[^a-z0-9]+/g, '_')
29
- .replace(/^_|_$/g, '');
30
- }
31
-
32
- function toTitleCase(str) {
33
- return str
34
- .replace(/[^a-z0-9]+/gi, ' ')
35
- .split(' ')
36
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
37
- .join(' ');
38
- }
39
-
40
- // ==============================================
41
- // Template Variable Replacement
42
- // ==============================================
43
-
44
- function generateSecureSecret(length = 32) {
45
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
46
- let result = '';
47
- for (let i = 0; i < length; i++) {
48
- result += chars.charAt(Math.floor(Math.random() * chars.length));
49
- }
50
- return result;
51
- }
52
-
53
- function replaceTemplateVariables(content, config) {
54
- const replacements = {
55
- '{{PROJECT_NAME}}': config.projectName,
56
- '{{PROJECT_NAME_SNAKE}}': config.projectNameSnake,
57
- '{{PROJECT_DISPLAY_NAME}}': config.projectDisplayName,
58
- '{{PROJECT_DESCRIPTION}}': config.projectDescription,
59
- '{{DATABASE_NAME}}': config.databaseName,
60
- '{{DATABASE_USER}}': config.databaseUser,
61
- '{{DATABASE_PASSWORD}}': config.databasePassword,
62
- '{{AUTHOR_NAME}}': config.authorName,
63
- '{{JWT_SECRET}}': config.jwtSecret,
64
- };
65
-
66
- let result = content;
67
- for (const [key, value] of Object.entries(replacements)) {
68
- result = result.split(key).join(value);
69
- }
70
-
71
- return result;
72
- }
73
-
74
- // ==============================================
75
- // Files to Process (relative to project root)
76
- // ==============================================
77
-
78
- const filesToProcess = [
79
- // Root files
80
- 'CLAUDE.md',
81
- // Server files
82
- 'server/package.json',
83
- 'server/.env.example',
84
- 'server/docker-compose.yml',
85
- 'server/README.md',
86
- 'server/src/config/env.ts',
87
- 'server/prisma/schema.prisma',
88
- 'server/Tigra-API.postman_collection.json',
89
- ];
90
-
91
- // Files/folders to exclude from template
92
- const excludePatterns = [
93
- 'node_modules',
94
- 'dist',
95
- '.git',
96
- 'package-lock.json',
97
- 'pnpm-lock.yaml',
98
- 'yarn.lock',
99
- 'coverage',
100
- 'build-errors.txt',
101
- '.env.local',
102
- '.env',
103
- '*.tsbuildinfo',
104
- 'tsconfig.build.tsbuildinfo',
105
- '.DS_Store',
106
- 'Thumbs.db',
107
- ];
108
-
109
- // ==============================================
110
- // Main CLI
111
- // ==============================================
112
-
113
- const program = new Command();
114
-
115
- program
116
- .name('create-tigra')
117
- .description('Create a production-ready Fastify + TypeScript + Prisma API server')
118
- .version('1.0.0')
119
- .argument('[project-name]', 'Name of the project')
120
- .option('-y, --yes', 'Skip prompts and use defaults')
121
- .action(async (projectNameArg, options) => {
122
- console.log();
123
- console.log(chalk.bold.cyan(' Create Tigra'));
124
- console.log(chalk.gray(' Production-ready Fastify + TypeScript + Prisma'));
125
- console.log();
126
-
127
- let projectName = projectNameArg;
128
-
129
- // If no project name provided, prompt for it
130
- if (!projectName) {
131
- const response = await prompts({
132
- type: 'text',
133
- name: 'projectName',
134
- message: 'Project name:',
135
- initial: 'my-project',
136
- validate: (value) =>
137
- /^[a-z][a-z0-9-]*$/.test(value) ||
138
- 'Must start with letter, lowercase letters, numbers, and hyphens only',
139
- });
140
-
141
- if (!response.projectName) {
142
- console.log(chalk.red('Cancelled.'));
143
- process.exit(1);
144
- }
145
- projectName = response.projectName;
146
- }
147
-
148
- // Validate project name
149
- projectName = toKebabCase(projectName);
150
-
151
- // Check if directory exists
152
- const targetDir = path.join(process.cwd(), projectName);
153
- if (fs.existsSync(targetDir)) {
154
- console.log(chalk.red(`Error: Directory "${projectName}" already exists.`));
155
- process.exit(1);
156
- }
157
-
158
- // Gather additional info (or use defaults)
159
- let config;
160
- if (options.yes) {
161
- config = {
162
- projectName,
163
- projectNameSnake: toSnakeCase(projectName),
164
- projectDisplayName: toTitleCase(projectName),
165
- projectDescription: 'A production-ready REST API server',
166
- databaseName: toSnakeCase(projectName) + '_db',
167
- databaseUser: 'root',
168
- databasePassword: 'password',
169
- authorName: '',
170
- jwtSecret: generateSecureSecret(48),
171
- };
172
- } else {
173
- const answers = await prompts([
174
- {
175
- type: 'text',
176
- name: 'projectDisplayName',
177
- message: 'Display name:',
178
- initial: toTitleCase(projectName),
179
- },
180
- {
181
- type: 'text',
182
- name: 'projectDescription',
183
- message: 'Description (optional):',
184
- initial: '',
185
- },
186
- {
187
- type: 'text',
188
- name: 'authorName',
189
- message: 'Author name (optional):',
190
- initial: '',
191
- },
192
- ]);
193
-
194
- if (!answers.projectDisplayName) {
195
- console.log(chalk.red('Cancelled.'));
196
- process.exit(1);
197
- }
198
-
199
- config = {
200
- projectName,
201
- projectNameSnake: toSnakeCase(projectName),
202
- projectDisplayName: answers.projectDisplayName,
203
- projectDescription: answers.projectDescription || '',
204
- databaseName: toSnakeCase(projectName) + '_db',
205
- databaseUser: 'root',
206
- databasePassword: 'password',
207
- authorName: answers.authorName || '',
208
- jwtSecret: generateSecureSecret(48),
209
- };
210
- }
211
-
212
- // Copy template
213
- const spinner = ora('Creating project...').start();
214
-
215
- try {
216
- const templateDir = path.join(__dirname, '..', 'template');
217
-
218
- // Filter function to exclude unwanted files/folders
219
- const filterFunc = (src) => {
220
- const relativePath = path.relative(templateDir, src);
221
- const basename = path.basename(src);
222
-
223
- // Always include the root
224
- if (!relativePath) return true;
225
-
226
- // Special case: ALWAYS include .env.example
227
- if (basename === '.env.example') {
228
- return true;
229
- }
230
-
231
- // Special case: exclude .env (but not .env.example which was already handled)
232
- if (basename === '.env') {
233
- return false;
234
- }
235
-
236
- // Check against exclude patterns
237
- return !excludePatterns.some(
238
- (pattern) =>
239
- relativePath === pattern ||
240
- relativePath.includes(`${path.sep}${pattern}`) ||
241
- relativePath.includes(`${pattern}${path.sep}`) ||
242
- relativePath.startsWith(`${pattern}${path.sep}`)
243
- );
244
- };
245
-
246
- // Copy entire template directory to target
247
- await fs.copy(templateDir, targetDir, { filter: filterFunc });
248
-
249
- spinner.text = 'Replacing template variables...';
250
-
251
- // Process files with template variables
252
- for (const file of filesToProcess) {
253
- const filePath = path.join(targetDir, file);
254
- if (await fs.pathExists(filePath)) {
255
- const content = await fs.readFile(filePath, 'utf8');
256
- const replaced = replaceTemplateVariables(content, config);
257
- await fs.writeFile(filePath, replaced, 'utf8');
258
- }
259
- }
260
-
261
- spinner.succeed('Project created successfully!');
262
-
263
- // Print next steps
264
- console.log();
265
- console.log(chalk.green(` Done! Created ${chalk.bold(projectName)}`));
266
- console.log();
267
- console.log(' Project structure:');
268
- console.log(chalk.gray(` ${projectName}/`));
269
- console.log(chalk.gray(' ├── server/ # Backend API'));
270
- console.log(chalk.gray(' ├── .agent/ # AI agent rules'));
271
- console.log(chalk.gray(' ├── .claude/ # Claude Code rules'));
272
- console.log(chalk.gray(' ├── .cursor/ # Cursor IDE rules'));
273
- console.log(chalk.gray(' └── CLAUDE.md # Project rules'));
274
- console.log();
275
- console.log(' Next steps:');
276
- console.log();
277
- console.log(chalk.cyan(` cd ${projectName}/server`));
278
- console.log(chalk.cyan(' npm install'));
279
- console.log();
280
- console.log(chalk.yellow(' Start Docker services (Docker must be running):'));
281
- console.log(chalk.cyan(' docker-compose up -d'));
282
- console.log();
283
- console.log(chalk.yellow(' Initialize database (waits for MySQL to be ready):'));
284
- console.log(chalk.cyan(' npm run db:init # Waits for DB + runs migration'));
285
- console.log(chalk.cyan(' npm run prisma:seed # Seed database with admin + sample data'));
286
- console.log();
287
- console.log(chalk.yellow(' Build and start:'));
288
- console.log(chalk.cyan(' npm run build # Build the project'));
289
- console.log(chalk.cyan(' npm run dev # Start development server'));
290
- console.log();
291
- console.log(chalk.yellow(' Note on Docker:'));
292
- console.log(chalk.gray(` Containers will be named: ${projectName}-mysql, ${projectName}-redis, etc.`));
293
- console.log(chalk.gray(' Default ports: 3306 (MySQL), 6379 (Redis), 8080 (phpMyAdmin), 8081 (Redis UI)'));
294
- console.log(chalk.gray(' If ports conflict, edit docker-compose.yml or stop other containers first.'));
295
- console.log();
296
- console.log(chalk.gray(' URLs after starting:'));
297
- console.log(chalk.gray(' API Server: http://localhost:3000'));
298
- console.log(chalk.gray(' Health Check: http://localhost:3000/health'));
299
- console.log(chalk.gray(' phpMyAdmin: http://localhost:8080 (root / password)'));
300
- console.log(chalk.gray(' Redis UI: http://localhost:8081'));
301
- console.log();
302
- } catch (error) {
303
- spinner.fail('Failed to create project');
304
- console.error(chalk.red(error.message));
305
- process.exit(1);
306
- }
307
- });
308
-
309
- program.parse();
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import prompts from 'prompts';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import crypto from 'crypto';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
16
+
17
+ // Files that contain template variables and need replacement
18
+ const FILES_TO_REPLACE = [
19
+ 'server/package.json',
20
+ 'server/.env.example',
21
+ 'server/docker-compose.yml',
22
+ 'client/package.json',
23
+ 'client/.env.example',
24
+ ];
25
+
26
+ // Directories/files to skip when copying
27
+ const SKIP_PATTERNS = [
28
+ 'node_modules',
29
+ '.next',
30
+ 'dist',
31
+ 'out',
32
+ '.env',
33
+ '.env.local',
34
+ '.env.*.local',
35
+ 'package-lock.json',
36
+ 'pnpm-lock.yaml',
37
+ 'yarn.lock',
38
+ ];
39
+
40
+ function toKebabCase(str) {
41
+ return str
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9-]/g, '-')
44
+ .replace(/-+/g, '-')
45
+ .replace(/^-|-$/g, '');
46
+ }
47
+
48
+ function toSnakeCase(str) {
49
+ return str.replace(/-/g, '_');
50
+ }
51
+
52
+ function toTitleCase(str) {
53
+ return str
54
+ .split('-')
55
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
56
+ .join(' ');
57
+ }
58
+
59
+ function validateProjectName(name) {
60
+ if (!name || name.trim().length === 0) {
61
+ return 'Project name cannot be empty';
62
+ }
63
+ const kebab = toKebabCase(name);
64
+ if (kebab.length === 0) {
65
+ return 'Project name must contain at least one alphanumeric character';
66
+ }
67
+ if (kebab.length > 214) {
68
+ return 'Project name is too long (max 214 characters)';
69
+ }
70
+ return true;
71
+ }
72
+
73
+ function shouldSkip(filePath) {
74
+ const parts = filePath.split(path.sep);
75
+ return parts.some((part) =>
76
+ SKIP_PATTERNS.some((pattern) => {
77
+ if (pattern.includes('*')) {
78
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
79
+ return regex.test(part);
80
+ }
81
+ return part === pattern;
82
+ })
83
+ );
84
+ }
85
+
86
+ async function copyTemplate(templateDir, targetDir) {
87
+ const entries = await fs.readdir(templateDir, { withFileTypes: true });
88
+
89
+ for (const entry of entries) {
90
+ const srcPath = path.join(templateDir, entry.name);
91
+ const relativePath = path.relative(TEMPLATE_DIR, srcPath);
92
+
93
+ if (shouldSkip(relativePath)) {
94
+ continue;
95
+ }
96
+
97
+ // Handle dotfile renaming: gitignore -> .gitignore, _claude -> .claude
98
+ let destName = entry.name;
99
+ if (entry.name === 'gitignore') destName = '.gitignore';
100
+ if (entry.name === '_claude') destName = '.claude';
101
+
102
+ const destPath = path.join(targetDir, destName);
103
+
104
+ if (entry.isDirectory()) {
105
+ await fs.ensureDir(destPath);
106
+ await copyTemplate(srcPath, destPath);
107
+ } else {
108
+ await fs.copy(srcPath, destPath);
109
+ }
110
+ }
111
+ }
112
+
113
+ function replaceVariables(content, variables) {
114
+ let result = content;
115
+ for (const [key, value] of Object.entries(variables)) {
116
+ result = result.replaceAll(`{{${key}}}`, value);
117
+ }
118
+ return result;
119
+ }
120
+
121
+ async function main() {
122
+ const program = new Command();
123
+
124
+ program
125
+ .name('create-tigra')
126
+ .description('Create a production-ready full-stack app with Next.js + Fastify + Prisma + Redis')
127
+ .version('2.0.0')
128
+ .argument('[project-name]', 'Name for your new project')
129
+ .action(async (projectNameArg) => {
130
+ console.log();
131
+ console.log(chalk.bold(' Create Tigra') + chalk.dim(' v2.0.0'));
132
+ console.log();
133
+
134
+ let projectName = projectNameArg;
135
+
136
+ if (!projectName) {
137
+ const response = await prompts(
138
+ {
139
+ type: 'text',
140
+ name: 'projectName',
141
+ message: 'What is your project name?',
142
+ validate: validateProjectName,
143
+ },
144
+ {
145
+ onCancel: () => {
146
+ console.log(chalk.red('\n Cancelled.\n'));
147
+ process.exit(1);
148
+ },
149
+ }
150
+ );
151
+ projectName = response.projectName;
152
+ }
153
+
154
+ // Validate and normalize
155
+ const validation = validateProjectName(projectName);
156
+ if (validation !== true) {
157
+ console.error(chalk.red(`\n ${validation}\n`));
158
+ process.exit(1);
159
+ }
160
+
161
+ projectName = toKebabCase(projectName);
162
+
163
+ const targetDir = path.resolve(process.cwd(), projectName);
164
+
165
+ // Check if directory exists and is non-empty
166
+ if (await fs.pathExists(targetDir)) {
167
+ const files = await fs.readdir(targetDir);
168
+ if (files.length > 0) {
169
+ console.error(chalk.red(`\n Directory "${projectName}" already exists and is not empty.\n`));
170
+ process.exit(1);
171
+ }
172
+ }
173
+
174
+ // Derive all variables
175
+ const variables = {
176
+ PROJECT_NAME: projectName,
177
+ PROJECT_NAME_SNAKE: toSnakeCase(projectName),
178
+ PROJECT_DISPLAY_NAME: toTitleCase(projectName),
179
+ DATABASE_NAME: `${toSnakeCase(projectName)}_db`,
180
+ JWT_SECRET: crypto.randomBytes(48).toString('hex'),
181
+ };
182
+
183
+ // Copy template
184
+ const spinner = ora('Scaffolding project...').start();
185
+
186
+ try {
187
+ await fs.ensureDir(targetDir);
188
+ await copyTemplate(TEMPLATE_DIR, targetDir);
189
+
190
+ // Replace template variables in specific files
191
+ for (const filePath of FILES_TO_REPLACE) {
192
+ const fullPath = path.join(targetDir, filePath);
193
+ if (await fs.pathExists(fullPath)) {
194
+ const content = await fs.readFile(fullPath, 'utf-8');
195
+ const replaced = replaceVariables(content, variables);
196
+ await fs.writeFile(fullPath, replaced, 'utf-8');
197
+ }
198
+ }
199
+
200
+ spinner.succeed('Project scaffolded successfully!');
201
+ } catch (error) {
202
+ spinner.fail('Failed to scaffold project');
203
+ console.error(chalk.red(`\n ${error.message}\n`));
204
+ process.exit(1);
205
+ }
206
+
207
+ // Print next steps
208
+ console.log();
209
+ console.log(chalk.green.bold(` Success!`) + ` Created ${chalk.cyan(projectName)} at ${chalk.dim(targetDir)}`);
210
+ console.log();
211
+ console.log(chalk.bold(' Next steps:'));
212
+ console.log();
213
+ console.log(chalk.cyan(' 1.') + ' Start infrastructure:');
214
+ console.log(chalk.dim(` cd ${projectName}/server`));
215
+ console.log(chalk.dim(' docker compose up -d'));
216
+ console.log();
217
+ console.log(chalk.cyan(' 2.') + ' Install server dependencies & set up database:');
218
+ console.log(chalk.dim(' npm install'));
219
+ console.log(chalk.dim(' cp .env.example .env'));
220
+ console.log(chalk.dim(' npm run prisma:generate'));
221
+ console.log(chalk.dim(' npm run prisma:migrate:dev -- --name init'));
222
+ console.log();
223
+ console.log(chalk.cyan(' 3.') + ' Start the server:');
224
+ console.log(chalk.dim(' npm run dev'));
225
+ console.log();
226
+ console.log(chalk.cyan(' 4.') + ` In a ${chalk.bold('new terminal')}, set up the client:`);
227
+ console.log(chalk.dim(` cd ${projectName}/client`));
228
+ console.log(chalk.dim(' npm install'));
229
+ console.log(chalk.dim(' cp .env.example .env'));
230
+ console.log(chalk.dim(' npm run dev'));
231
+ console.log();
232
+ console.log(chalk.dim(' Server running at: ') + chalk.cyan('http://localhost:8000'));
233
+ console.log(chalk.dim(' Client running at: ') + chalk.cyan('http://localhost:3000'));
234
+ console.log();
235
+ console.log(chalk.dim(' Happy coding!'));
236
+ console.log();
237
+ });
238
+
239
+ program.parse();
240
+ }
241
+
242
+ main();
package/package.json CHANGED
@@ -1,41 +1,49 @@
1
- {
2
- "name": "create-tigra",
3
- "version": "1.0.7",
4
- "type": "module",
5
- "description": "Create a production-ready Fastify + TypeScript + Prisma API server",
6
- "keywords": [
7
- "create",
8
- "generator",
9
- "scaffold",
10
- "fastify",
11
- "typescript",
12
- "prisma",
13
- "api",
14
- "rest",
15
- "boilerplate",
16
- "starter"
17
- ],
18
- "author": "",
19
- "license": "MIT",
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/Makinloot/create-tigra.git"
23
- },
24
- "bin": {
25
- "create-tigra": "bin/create-tigra.js"
26
- },
27
- "files": [
28
- "bin",
29
- "template"
30
- ],
31
- "engines": {
32
- "node": ">=18.0.0"
33
- },
34
- "dependencies": {
35
- "chalk": "^5.3.0",
36
- "commander": "^12.1.0",
37
- "fs-extra": "^11.2.0",
38
- "ora": "^8.0.1",
39
- "prompts": "^2.4.2"
40
- }
41
- }
1
+ {
2
+ "name": "create-tigra",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "description": "Create a production-ready full-stack app with Next.js 16 + Fastify 5 + Prisma + Redis",
6
+ "bin": {
7
+ "create-tigra": "bin/create-tigra.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "keywords": [
17
+ "create",
18
+ "generator",
19
+ "scaffold",
20
+ "fullstack",
21
+ "nextjs",
22
+ "fastify",
23
+ "typescript",
24
+ "prisma",
25
+ "redis",
26
+ "boilerplate",
27
+ "starter",
28
+ "react",
29
+ "tailwind",
30
+ "shadcn"
31
+ ],
32
+ "author": "seed977",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/blessandsoul/create-tigra.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/blessandsoul/create-tigra/issues"
40
+ },
41
+ "homepage": "https://github.com/blessandsoul/create-tigra#readme",
42
+ "dependencies": {
43
+ "chalk": "^5.4.1",
44
+ "commander": "^13.1.0",
45
+ "fs-extra": "^11.3.0",
46
+ "ora": "^8.2.0",
47
+ "prompts": "^2.4.2"
48
+ }
49
+ }