create-blitzpack 0.1.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 (259) hide show
  1. package/dist/index.js +452 -0
  2. package/package.json +57 -0
  3. package/template/.dockerignore +59 -0
  4. package/template/.github/workflows/ci.yml +157 -0
  5. package/template/.husky/pre-commit +1 -0
  6. package/template/.husky/pre-push +1 -0
  7. package/template/.lintstagedrc.cjs +4 -0
  8. package/template/.nvmrc +1 -0
  9. package/template/.prettierrc +9 -0
  10. package/template/.vscode/settings.json +13 -0
  11. package/template/CLAUDE.md +175 -0
  12. package/template/CONTRIBUTING.md +32 -0
  13. package/template/Dockerfile +90 -0
  14. package/template/GETTING_STARTED.md +35 -0
  15. package/template/LICENSE +21 -0
  16. package/template/README.md +116 -0
  17. package/template/apps/api/.dockerignore +51 -0
  18. package/template/apps/api/.env.local.example +62 -0
  19. package/template/apps/api/emails/account-deleted-email.tsx +69 -0
  20. package/template/apps/api/emails/components/email-layout.tsx +154 -0
  21. package/template/apps/api/emails/config.ts +22 -0
  22. package/template/apps/api/emails/password-changed-email.tsx +88 -0
  23. package/template/apps/api/emails/password-reset-email.tsx +86 -0
  24. package/template/apps/api/emails/verification-email.tsx +85 -0
  25. package/template/apps/api/emails/welcome-email.tsx +70 -0
  26. package/template/apps/api/package.json +84 -0
  27. package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +13 -0
  28. package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +67 -0
  29. package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +5 -0
  30. package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +7 -0
  31. package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +2 -0
  32. package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +26 -0
  33. package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +2 -0
  34. package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +10 -0
  35. package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +29 -0
  36. package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +8 -0
  37. package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +11 -0
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  39. package/template/apps/api/prisma/schema.prisma +116 -0
  40. package/template/apps/api/prisma/seed.ts +159 -0
  41. package/template/apps/api/prisma.config.ts +14 -0
  42. package/template/apps/api/src/app.ts +377 -0
  43. package/template/apps/api/src/common/logger.service.ts +227 -0
  44. package/template/apps/api/src/config/env.ts +60 -0
  45. package/template/apps/api/src/config/rate-limit.ts +29 -0
  46. package/template/apps/api/src/hooks/auth.ts +122 -0
  47. package/template/apps/api/src/plugins/auth.ts +198 -0
  48. package/template/apps/api/src/plugins/database.ts +45 -0
  49. package/template/apps/api/src/plugins/logger.ts +33 -0
  50. package/template/apps/api/src/plugins/multipart.ts +16 -0
  51. package/template/apps/api/src/plugins/scalar.ts +20 -0
  52. package/template/apps/api/src/plugins/schedule.ts +52 -0
  53. package/template/apps/api/src/plugins/services.ts +66 -0
  54. package/template/apps/api/src/plugins/swagger.ts +56 -0
  55. package/template/apps/api/src/routes/accounts.ts +91 -0
  56. package/template/apps/api/src/routes/admin-sessions.ts +92 -0
  57. package/template/apps/api/src/routes/metrics.ts +71 -0
  58. package/template/apps/api/src/routes/password.ts +46 -0
  59. package/template/apps/api/src/routes/sessions.ts +53 -0
  60. package/template/apps/api/src/routes/stats.ts +38 -0
  61. package/template/apps/api/src/routes/uploads-serve.ts +27 -0
  62. package/template/apps/api/src/routes/uploads.ts +154 -0
  63. package/template/apps/api/src/routes/users.ts +114 -0
  64. package/template/apps/api/src/routes/verification.ts +90 -0
  65. package/template/apps/api/src/server.ts +34 -0
  66. package/template/apps/api/src/services/accounts.service.ts +125 -0
  67. package/template/apps/api/src/services/authorization.service.ts +162 -0
  68. package/template/apps/api/src/services/email.service.ts +170 -0
  69. package/template/apps/api/src/services/file-storage.service.ts +267 -0
  70. package/template/apps/api/src/services/metrics.service.ts +175 -0
  71. package/template/apps/api/src/services/password.service.ts +56 -0
  72. package/template/apps/api/src/services/sessions.service.spec.ts +134 -0
  73. package/template/apps/api/src/services/sessions.service.ts +276 -0
  74. package/template/apps/api/src/services/stats.service.ts +273 -0
  75. package/template/apps/api/src/services/uploads.service.ts +163 -0
  76. package/template/apps/api/src/services/users.service.spec.ts +249 -0
  77. package/template/apps/api/src/services/users.service.ts +198 -0
  78. package/template/apps/api/src/utils/file-validation.ts +108 -0
  79. package/template/apps/api/start.sh +33 -0
  80. package/template/apps/api/test/helpers/fastify-app.ts +24 -0
  81. package/template/apps/api/test/helpers/mock-authorization.ts +16 -0
  82. package/template/apps/api/test/helpers/mock-logger.ts +28 -0
  83. package/template/apps/api/test/helpers/mock-prisma.ts +30 -0
  84. package/template/apps/api/test/helpers/test-db.ts +125 -0
  85. package/template/apps/api/test/integration/auth-flow.integration.spec.ts +449 -0
  86. package/template/apps/api/test/integration/password.integration.spec.ts +427 -0
  87. package/template/apps/api/test/integration/rate-limit.integration.spec.ts +51 -0
  88. package/template/apps/api/test/integration/sessions.integration.spec.ts +445 -0
  89. package/template/apps/api/test/integration/users.integration.spec.ts +211 -0
  90. package/template/apps/api/test/setup.ts +31 -0
  91. package/template/apps/api/tsconfig.json +26 -0
  92. package/template/apps/api/vitest.config.ts +35 -0
  93. package/template/apps/web/.env.local.example +11 -0
  94. package/template/apps/web/components.json +24 -0
  95. package/template/apps/web/next.config.ts +22 -0
  96. package/template/apps/web/package.json +56 -0
  97. package/template/apps/web/postcss.config.js +5 -0
  98. package/template/apps/web/public/apple-icon.png +0 -0
  99. package/template/apps/web/public/icon.png +0 -0
  100. package/template/apps/web/public/robots.txt +3 -0
  101. package/template/apps/web/src/app/(admin)/admin/layout.tsx +222 -0
  102. package/template/apps/web/src/app/(admin)/admin/page.tsx +157 -0
  103. package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +18 -0
  104. package/template/apps/web/src/app/(admin)/admin/users/page.tsx +20 -0
  105. package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +177 -0
  106. package/template/apps/web/src/app/(auth)/login/page.tsx +159 -0
  107. package/template/apps/web/src/app/(auth)/reset-password/page.tsx +245 -0
  108. package/template/apps/web/src/app/(auth)/signup/page.tsx +153 -0
  109. package/template/apps/web/src/app/dashboard/change-password/page.tsx +255 -0
  110. package/template/apps/web/src/app/dashboard/page.tsx +296 -0
  111. package/template/apps/web/src/app/error.tsx +32 -0
  112. package/template/apps/web/src/app/examples/file-upload/page.tsx +200 -0
  113. package/template/apps/web/src/app/favicon.ico +0 -0
  114. package/template/apps/web/src/app/global-error.tsx +96 -0
  115. package/template/apps/web/src/app/globals.css +22 -0
  116. package/template/apps/web/src/app/icon.png +0 -0
  117. package/template/apps/web/src/app/layout.tsx +34 -0
  118. package/template/apps/web/src/app/not-found.tsx +28 -0
  119. package/template/apps/web/src/app/page.tsx +192 -0
  120. package/template/apps/web/src/components/admin/activity-feed.tsx +101 -0
  121. package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +114 -0
  122. package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +124 -0
  123. package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +511 -0
  124. package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +102 -0
  125. package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +90 -0
  126. package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +108 -0
  127. package/template/apps/web/src/components/admin/health-indicator.tsx +175 -0
  128. package/template/apps/web/src/components/admin/refresh-control.tsx +90 -0
  129. package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +79 -0
  130. package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +74 -0
  131. package/template/apps/web/src/components/admin/sessions-management-table.tsx +372 -0
  132. package/template/apps/web/src/components/admin/stat-card.tsx +137 -0
  133. package/template/apps/web/src/components/admin/user-create-dialog.tsx +152 -0
  134. package/template/apps/web/src/components/admin/user-delete-dialog.tsx +73 -0
  135. package/template/apps/web/src/components/admin/user-edit-dialog.tsx +170 -0
  136. package/template/apps/web/src/components/admin/users-management-table.tsx +285 -0
  137. package/template/apps/web/src/components/auth/email-verification-banner.tsx +85 -0
  138. package/template/apps/web/src/components/auth/github-button.tsx +40 -0
  139. package/template/apps/web/src/components/auth/google-button.tsx +54 -0
  140. package/template/apps/web/src/components/auth/protected-route.tsx +66 -0
  141. package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +31 -0
  142. package/template/apps/web/src/components/auth/with-auth.tsx +30 -0
  143. package/template/apps/web/src/components/error/error-card.tsx +47 -0
  144. package/template/apps/web/src/components/error/forbidden.tsx +25 -0
  145. package/template/apps/web/src/components/landing/command-block.tsx +64 -0
  146. package/template/apps/web/src/components/landing/feature-card.tsx +60 -0
  147. package/template/apps/web/src/components/landing/included-feature-card.tsx +63 -0
  148. package/template/apps/web/src/components/landing/logo.tsx +41 -0
  149. package/template/apps/web/src/components/landing/tech-badge.tsx +11 -0
  150. package/template/apps/web/src/components/layout/auth-nav.tsx +58 -0
  151. package/template/apps/web/src/components/layout/footer.tsx +3 -0
  152. package/template/apps/web/src/config/landing-data.ts +152 -0
  153. package/template/apps/web/src/config/site.ts +5 -0
  154. package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +181 -0
  155. package/template/apps/web/src/hooks/api/use-admin-sessions.ts +75 -0
  156. package/template/apps/web/src/hooks/api/use-admin-stats.ts +33 -0
  157. package/template/apps/web/src/hooks/api/use-sessions.ts +52 -0
  158. package/template/apps/web/src/hooks/api/use-uploads.ts +156 -0
  159. package/template/apps/web/src/hooks/api/use-users.ts +149 -0
  160. package/template/apps/web/src/hooks/use-mobile.ts +21 -0
  161. package/template/apps/web/src/hooks/use-realtime-metrics.ts +120 -0
  162. package/template/apps/web/src/lib/__tests__/utils.test.ts +29 -0
  163. package/template/apps/web/src/lib/api.ts +151 -0
  164. package/template/apps/web/src/lib/auth.ts +13 -0
  165. package/template/apps/web/src/lib/env.ts +52 -0
  166. package/template/apps/web/src/lib/form-utils.ts +11 -0
  167. package/template/apps/web/src/lib/utils.ts +1 -0
  168. package/template/apps/web/src/providers.tsx +34 -0
  169. package/template/apps/web/src/store/atoms.ts +15 -0
  170. package/template/apps/web/src/test/helpers/test-utils.tsx +44 -0
  171. package/template/apps/web/src/test/setup.ts +8 -0
  172. package/template/apps/web/tailwind.config.ts +5 -0
  173. package/template/apps/web/tsconfig.json +26 -0
  174. package/template/apps/web/vitest.config.ts +32 -0
  175. package/template/assets/logo-512.png +0 -0
  176. package/template/assets/logo.svg +4 -0
  177. package/template/docker-compose.prod.yml +66 -0
  178. package/template/docker-compose.yml +36 -0
  179. package/template/eslint.config.ts +119 -0
  180. package/template/package.json +77 -0
  181. package/template/packages/tailwind-config/package.json +9 -0
  182. package/template/packages/tailwind-config/theme.css +179 -0
  183. package/template/packages/types/package.json +29 -0
  184. package/template/packages/types/src/__tests__/schemas.test.ts +255 -0
  185. package/template/packages/types/src/api-response.ts +53 -0
  186. package/template/packages/types/src/health-check.ts +11 -0
  187. package/template/packages/types/src/pagination.ts +41 -0
  188. package/template/packages/types/src/role.ts +5 -0
  189. package/template/packages/types/src/session.ts +48 -0
  190. package/template/packages/types/src/stats.ts +113 -0
  191. package/template/packages/types/src/upload.ts +51 -0
  192. package/template/packages/types/src/user.ts +36 -0
  193. package/template/packages/types/tsconfig.json +5 -0
  194. package/template/packages/types/vitest.config.ts +21 -0
  195. package/template/packages/ui/components.json +21 -0
  196. package/template/packages/ui/package.json +108 -0
  197. package/template/packages/ui/src/__tests__/button.test.tsx +70 -0
  198. package/template/packages/ui/src/alert-dialog.tsx +141 -0
  199. package/template/packages/ui/src/alert.tsx +66 -0
  200. package/template/packages/ui/src/animated-theme-toggler.tsx +167 -0
  201. package/template/packages/ui/src/avatar.tsx +53 -0
  202. package/template/packages/ui/src/badge.tsx +36 -0
  203. package/template/packages/ui/src/button.tsx +84 -0
  204. package/template/packages/ui/src/card.tsx +92 -0
  205. package/template/packages/ui/src/checkbox.tsx +32 -0
  206. package/template/packages/ui/src/data-table/data-table-column-header.tsx +68 -0
  207. package/template/packages/ui/src/data-table/data-table-pagination.tsx +99 -0
  208. package/template/packages/ui/src/data-table/data-table-toolbar.tsx +55 -0
  209. package/template/packages/ui/src/data-table/data-table-view-options.tsx +63 -0
  210. package/template/packages/ui/src/data-table/data-table.tsx +167 -0
  211. package/template/packages/ui/src/dialog.tsx +143 -0
  212. package/template/packages/ui/src/dropdown-menu.tsx +257 -0
  213. package/template/packages/ui/src/empty-state.tsx +52 -0
  214. package/template/packages/ui/src/file-upload-input.tsx +202 -0
  215. package/template/packages/ui/src/form.tsx +168 -0
  216. package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
  217. package/template/packages/ui/src/icons/brand-icons.tsx +16 -0
  218. package/template/packages/ui/src/input.tsx +21 -0
  219. package/template/packages/ui/src/label.tsx +24 -0
  220. package/template/packages/ui/src/lib/utils.ts +6 -0
  221. package/template/packages/ui/src/password-input.tsx +102 -0
  222. package/template/packages/ui/src/popover.tsx +48 -0
  223. package/template/packages/ui/src/radio-group.tsx +45 -0
  224. package/template/packages/ui/src/scroll-area.tsx +58 -0
  225. package/template/packages/ui/src/select.tsx +187 -0
  226. package/template/packages/ui/src/separator.tsx +28 -0
  227. package/template/packages/ui/src/sheet.tsx +139 -0
  228. package/template/packages/ui/src/sidebar.tsx +726 -0
  229. package/template/packages/ui/src/skeleton-variants.tsx +87 -0
  230. package/template/packages/ui/src/skeleton.tsx +13 -0
  231. package/template/packages/ui/src/slider.tsx +63 -0
  232. package/template/packages/ui/src/sonner.tsx +25 -0
  233. package/template/packages/ui/src/spinner.tsx +16 -0
  234. package/template/packages/ui/src/switch.tsx +31 -0
  235. package/template/packages/ui/src/table.tsx +116 -0
  236. package/template/packages/ui/src/tabs.tsx +66 -0
  237. package/template/packages/ui/src/textarea.tsx +18 -0
  238. package/template/packages/ui/src/tooltip.tsx +61 -0
  239. package/template/packages/ui/src/user-avatar.tsx +97 -0
  240. package/template/packages/ui/test-config.js +3 -0
  241. package/template/packages/ui/tsconfig.json +12 -0
  242. package/template/packages/ui/turbo.json +18 -0
  243. package/template/packages/ui/vitest.config.ts +17 -0
  244. package/template/packages/ui/vitest.setup.ts +1 -0
  245. package/template/packages/utils/package.json +23 -0
  246. package/template/packages/utils/src/__tests__/utils.test.ts +223 -0
  247. package/template/packages/utils/src/array.ts +18 -0
  248. package/template/packages/utils/src/async.ts +3 -0
  249. package/template/packages/utils/src/date.ts +77 -0
  250. package/template/packages/utils/src/errors.ts +73 -0
  251. package/template/packages/utils/src/number.ts +11 -0
  252. package/template/packages/utils/src/string.ts +13 -0
  253. package/template/packages/utils/tsconfig.json +5 -0
  254. package/template/packages/utils/vitest.config.ts +21 -0
  255. package/template/pnpm-workspace.yaml +4 -0
  256. package/template/tsconfig.base.json +32 -0
  257. package/template/turbo.json +133 -0
  258. package/template/vitest.shared.ts +26 -0
  259. package/template/vitest.workspace.ts +9 -0
package/dist/index.js ADDED
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/create.ts
7
+ import chalk2 from "chalk";
8
+ import { spawn } from "child_process";
9
+ import fs3 from "fs-extra";
10
+ import ora from "ora";
11
+ import path3 from "path";
12
+ import { fileURLToPath } from "url";
13
+
14
+ // src/git.ts
15
+ import { execSync } from "child_process";
16
+ function isGitInstalled() {
17
+ try {
18
+ execSync("git --version", { stdio: "ignore" });
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+ function initGit(targetDir) {
25
+ try {
26
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
27
+ execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
28
+ execSync('git commit -m "Initial commit from create-blitzpack"', {
29
+ cwd: targetDir,
30
+ stdio: "ignore"
31
+ });
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ // src/prompts.ts
39
+ import prompts from "prompts";
40
+
41
+ // src/constants.ts
42
+ var TEMPLATE_EXCLUDES = [
43
+ "node_modules",
44
+ ".next",
45
+ ".turbo",
46
+ "dist",
47
+ "build",
48
+ "coverage",
49
+ ".git",
50
+ "pnpm-lock.yaml",
51
+ "*.tsbuildinfo",
52
+ "tsconfig.tsbuildinfo",
53
+ ".env.local",
54
+ ".env.*.local",
55
+ ".DS_Store",
56
+ ".temp",
57
+ ".claude",
58
+ ".cursor",
59
+ "create-blitzpack",
60
+ "apps/api/src/generated",
61
+ "apps/api/public/uploads",
62
+ "scripts/setup.js"
63
+ ];
64
+ var REPLACEABLE_FILES = [
65
+ "package.json",
66
+ "apps/web/src/config/site.ts",
67
+ "apps/web/src/app/layout.tsx",
68
+ "apps/api/src/plugins/swagger.ts",
69
+ "README.md"
70
+ ];
71
+ var DEFAULT_DESCRIPTION = "A full-stack TypeScript monorepo built with Blitzpack";
72
+
73
+ // src/utils.ts
74
+ import chalk from "chalk";
75
+ import validatePackageName from "validate-npm-package-name";
76
+ function toSlug(name) {
77
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
78
+ }
79
+ function validateProjectName(name) {
80
+ const result = validatePackageName(name);
81
+ if (result.validForNewPackages) {
82
+ return { valid: true };
83
+ }
84
+ const problems = [...result.errors || [], ...result.warnings || []];
85
+ return { valid: false, problems };
86
+ }
87
+ function printHeader() {
88
+ console.log();
89
+ console.log(chalk.bold.cyan(" Create Blitzpack"));
90
+ console.log(chalk.dim(" Full-stack TypeScript monorepo"));
91
+ console.log();
92
+ }
93
+ function printSuccess(projectName, targetDir) {
94
+ console.log();
95
+ console.log(chalk.green("\u2714"), chalk.bold(`Created ${projectName}`));
96
+ console.log();
97
+ console.log(chalk.bold(" Next steps:"));
98
+ console.log();
99
+ console.log(chalk.cyan(" 1."), `cd ${targetDir}`);
100
+ console.log(
101
+ chalk.cyan(" 2."),
102
+ "docker compose up -d",
103
+ chalk.dim(" # Start PostgreSQL")
104
+ );
105
+ console.log(
106
+ chalk.cyan(" 3."),
107
+ "pnpm db:migrate",
108
+ chalk.dim(" # Run database migrations")
109
+ );
110
+ console.log(
111
+ chalk.cyan(" 4."),
112
+ "pnpm dev",
113
+ chalk.dim(" # Start dev servers")
114
+ );
115
+ console.log();
116
+ console.log(
117
+ chalk.dim(" Optional: pnpm db:seed to add test data like admin accounts")
118
+ );
119
+ console.log();
120
+ console.log(
121
+ chalk.dim(" Web: http://localhost:3000 | API: http://localhost:8080")
122
+ );
123
+ console.log();
124
+ }
125
+ function printError(message) {
126
+ console.log();
127
+ console.log(chalk.red("\u2716"), message);
128
+ console.log();
129
+ }
130
+
131
+ // src/prompts.ts
132
+ async function getProjectOptions(providedName, flags = {}) {
133
+ const questions = [];
134
+ if (!providedName) {
135
+ questions.push({
136
+ type: "text",
137
+ name: "projectName",
138
+ message: "Project name:",
139
+ initial: "my-app",
140
+ validate: (value) => {
141
+ const result = validateProjectName(value);
142
+ if (!result.valid) {
143
+ return result.problems?.[0] || "Invalid project name";
144
+ }
145
+ return true;
146
+ }
147
+ });
148
+ }
149
+ questions.push({
150
+ type: "text",
151
+ name: "projectDescription",
152
+ message: "Project description:",
153
+ initial: DEFAULT_DESCRIPTION
154
+ });
155
+ let cancelled = false;
156
+ const response = await prompts(questions, {
157
+ onCancel: () => {
158
+ cancelled = true;
159
+ }
160
+ });
161
+ if (cancelled) {
162
+ return null;
163
+ }
164
+ const projectName = providedName || response.projectName;
165
+ const validation = validateProjectName(projectName);
166
+ if (!validation.valid) {
167
+ console.log(`Invalid project name: ${validation.problems?.[0]}`);
168
+ return null;
169
+ }
170
+ return {
171
+ projectName,
172
+ projectSlug: toSlug(projectName),
173
+ projectDescription: response.projectDescription || DEFAULT_DESCRIPTION,
174
+ skipGit: flags.skipGit || false,
175
+ skipInstall: flags.skipInstall || false
176
+ };
177
+ }
178
+
179
+ // src/template.ts
180
+ import fs from "fs-extra";
181
+ import path from "path";
182
+ function shouldExclude(relativePath) {
183
+ const normalizedPath = relativePath.replace(/\\/g, "/");
184
+ for (const pattern of TEMPLATE_EXCLUDES) {
185
+ if (pattern.startsWith("*")) {
186
+ const ext = pattern.slice(1);
187
+ if (normalizedPath.endsWith(ext)) return true;
188
+ } else if (normalizedPath === pattern || normalizedPath.startsWith(pattern + "/")) {
189
+ return true;
190
+ } else {
191
+ const parts = normalizedPath.split("/");
192
+ if (parts.includes(pattern)) return true;
193
+ }
194
+ }
195
+ return false;
196
+ }
197
+ async function copyDir(src, dest, baseDir) {
198
+ await fs.ensureDir(dest);
199
+ const entries = await fs.readdir(src, { withFileTypes: true });
200
+ for (const entry of entries) {
201
+ const srcPath = path.join(src, entry.name);
202
+ const destPath = path.join(dest, entry.name);
203
+ const relativePath = path.relative(baseDir, srcPath);
204
+ if (shouldExclude(relativePath)) {
205
+ continue;
206
+ }
207
+ if (entry.isDirectory()) {
208
+ await copyDir(srcPath, destPath, baseDir);
209
+ } else {
210
+ await fs.copy(srcPath, destPath);
211
+ }
212
+ }
213
+ }
214
+ async function copyTemplate(templateDir, targetDir) {
215
+ if (await fs.pathExists(targetDir)) {
216
+ const files = await fs.readdir(targetDir);
217
+ if (files.length > 0) {
218
+ throw new Error(`Directory ${targetDir} is not empty`);
219
+ }
220
+ }
221
+ await copyDir(templateDir, targetDir, templateDir);
222
+ }
223
+
224
+ // src/transform.ts
225
+ import fs2 from "fs-extra";
226
+ import path2 from "path";
227
+ function transformPackageJson(content, vars, filePath) {
228
+ const pkg = JSON.parse(content);
229
+ if (filePath === "package.json") {
230
+ pkg.name = vars.projectSlug;
231
+ pkg.description = vars.projectDescription;
232
+ delete pkg.repository;
233
+ delete pkg.homepage;
234
+ delete pkg.scripts?.["init:project"];
235
+ pkg.version = "0.1.0";
236
+ }
237
+ return JSON.stringify(pkg, null, 2) + "\n";
238
+ }
239
+ function transformSiteConfig(content, vars) {
240
+ return content.replace(/name: ['"].*['"]/, `name: '${vars.projectName}'`).replace(/description: ['"].*['"]/, `description: '${vars.projectDescription}'`).replace(/github: ['"].*['"]/, `github: ''`);
241
+ }
242
+ function transformLayout(content, vars) {
243
+ return content.replace(/title: ['"].*['"]/, `title: '${vars.projectName}'`).replace(/description: ['"].*['"]/, `description: '${vars.projectDescription}'`);
244
+ }
245
+ function transformSwagger(content, vars) {
246
+ return content.replace(/title: ['"].*['"]/, `title: '${vars.projectName} API'`).replace(
247
+ /description: ['"]Production-ready TypeScript API built with Fastify['"]/,
248
+ `description: '${vars.projectDescription}'`
249
+ );
250
+ }
251
+ function generateReadme(vars) {
252
+ return `# ${vars.projectName}
253
+
254
+ ${vars.projectDescription}
255
+
256
+ ## Quick Start
257
+
258
+ \`\`\`bash
259
+ pnpm install
260
+ pnpm init:project
261
+ pnpm dev
262
+ \`\`\`
263
+
264
+ ## What's Running
265
+
266
+ - **Web:** http://localhost:3000
267
+ - **API:** http://localhost:8080
268
+ - **API Docs:** http://localhost:8080/docs
269
+
270
+ ## Project Structure
271
+
272
+ \`\`\`
273
+ ${vars.projectSlug}/
274
+ \u251C\u2500\u2500 apps/
275
+ \u2502 \u251C\u2500\u2500 web/ # Next.js frontend
276
+ \u2502 \u2514\u2500\u2500 api/ # Fastify backend
277
+ \u2514\u2500\u2500 packages/
278
+ \u251C\u2500\u2500 types/ # Shared Zod schemas
279
+ \u251C\u2500\u2500 utils/ # Shared utilities
280
+ \u2514\u2500\u2500 ui/ # Shared UI components
281
+ \`\`\`
282
+
283
+ ## Database
284
+
285
+ \`\`\`bash
286
+ pnpm db:studio # Open Prisma Studio
287
+ pnpm db:migrate # Run migrations
288
+ pnpm db:seed # Seed database
289
+ \`\`\`
290
+
291
+ ---
292
+
293
+ Built with [Blitzpack](https://github.com/CarboxyDev/blitzpack)
294
+ `;
295
+ }
296
+ async function transformFiles(targetDir, vars) {
297
+ for (const relativePath of REPLACEABLE_FILES) {
298
+ const filePath = path2.join(targetDir, relativePath);
299
+ if (!await fs2.pathExists(filePath)) {
300
+ continue;
301
+ }
302
+ const content = await fs2.readFile(filePath, "utf-8");
303
+ let transformed;
304
+ if (relativePath === "README.md") {
305
+ transformed = generateReadme(vars);
306
+ } else if (relativePath.endsWith("package.json")) {
307
+ transformed = transformPackageJson(content, vars, relativePath);
308
+ } else if (relativePath.includes("site.ts")) {
309
+ transformed = transformSiteConfig(content, vars);
310
+ } else if (relativePath.includes("layout.tsx")) {
311
+ transformed = transformLayout(content, vars);
312
+ } else if (relativePath.includes("swagger.ts")) {
313
+ transformed = transformSwagger(content, vars);
314
+ } else {
315
+ transformed = content;
316
+ }
317
+ await fs2.writeFile(filePath, transformed, "utf-8");
318
+ }
319
+ }
320
+
321
+ // src/commands/create.ts
322
+ var __dirname = path3.dirname(fileURLToPath(import.meta.url));
323
+ var ENV_FILES = [
324
+ { from: "apps/web/.env.local.example", to: "apps/web/.env.local" },
325
+ { from: "apps/api/.env.local.example", to: "apps/api/.env.local" }
326
+ ];
327
+ async function copyEnvFiles(targetDir) {
328
+ for (const { from, to } of ENV_FILES) {
329
+ const source = path3.join(targetDir, from);
330
+ const dest = path3.join(targetDir, to);
331
+ if (await fs3.pathExists(source)) {
332
+ await fs3.copy(source, dest);
333
+ }
334
+ }
335
+ }
336
+ function runInstall(cwd) {
337
+ return new Promise((resolve) => {
338
+ const isWindows = process.platform === "win32";
339
+ const child = spawn(isWindows ? "pnpm.cmd" : "pnpm", ["install"], {
340
+ cwd,
341
+ stdio: "ignore"
342
+ });
343
+ child.on("close", (code) => resolve(code === 0));
344
+ child.on("error", () => resolve(false));
345
+ });
346
+ }
347
+ function printDryRun(options) {
348
+ console.log(chalk2.yellow(" Dry run mode - no changes will be made"));
349
+ console.log();
350
+ console.log(chalk2.bold(" Would create:"));
351
+ console.log();
352
+ console.log(` ${chalk2.cyan("Directory:")} ${options.targetDir}`);
353
+ console.log(` ${chalk2.cyan("Name:")} ${options.projectName}`);
354
+ console.log(` ${chalk2.cyan("Slug:")} ${options.projectSlug}`);
355
+ console.log(
356
+ ` ${chalk2.cyan("Description:")} ${options.projectDescription}`
357
+ );
358
+ console.log();
359
+ console.log(chalk2.bold(" Would run:"));
360
+ console.log();
361
+ console.log(` ${chalk2.dim("\u2022")} Copy template files`);
362
+ console.log(` ${chalk2.dim("\u2022")} Transform package.json files`);
363
+ console.log(` ${chalk2.dim("\u2022")} Create .env.local files`);
364
+ if (!options.skipGit) {
365
+ console.log(` ${chalk2.dim("\u2022")} Initialize git repository`);
366
+ }
367
+ if (!options.skipInstall) {
368
+ console.log(` ${chalk2.dim("\u2022")} Install dependencies (pnpm install)`);
369
+ }
370
+ console.log();
371
+ }
372
+ async function create(projectName, flags) {
373
+ printHeader();
374
+ const options = await getProjectOptions(projectName, flags);
375
+ if (!options) {
376
+ return;
377
+ }
378
+ const targetDir = path3.resolve(process.cwd(), options.projectName);
379
+ if (flags.dryRun) {
380
+ printDryRun({
381
+ projectName: options.projectName,
382
+ projectSlug: options.projectSlug,
383
+ projectDescription: options.projectDescription,
384
+ targetDir,
385
+ skipGit: options.skipGit,
386
+ skipInstall: options.skipInstall
387
+ });
388
+ return;
389
+ }
390
+ if (await fs3.pathExists(targetDir)) {
391
+ const files = await fs3.readdir(targetDir);
392
+ if (files.length > 0) {
393
+ printError(`Directory "${options.projectName}" is not empty`);
394
+ return;
395
+ }
396
+ }
397
+ const templateDir = path3.resolve(__dirname, "..", "template");
398
+ if (!await fs3.pathExists(templateDir)) {
399
+ printError(
400
+ "Template directory not found. This is a bug in create-blitzpack."
401
+ );
402
+ return;
403
+ }
404
+ const spinner = ora();
405
+ try {
406
+ spinner.start("Creating project structure...");
407
+ await copyTemplate(templateDir, targetDir);
408
+ spinner.succeed("Created project structure");
409
+ spinner.start("Configuring project...");
410
+ await transformFiles(targetDir, {
411
+ projectName: options.projectName,
412
+ projectSlug: options.projectSlug,
413
+ projectDescription: options.projectDescription
414
+ });
415
+ await copyEnvFiles(targetDir);
416
+ spinner.succeed("Configured project");
417
+ if (!options.skipGit && isGitInstalled()) {
418
+ spinner.start("Initializing git repository...");
419
+ const gitSuccess = initGit(targetDir);
420
+ if (gitSuccess) {
421
+ spinner.succeed("Initialized git repository");
422
+ } else {
423
+ spinner.warn("Failed to initialize git repository");
424
+ }
425
+ }
426
+ if (!options.skipInstall) {
427
+ spinner.start("Installing dependencies...");
428
+ const success = await runInstall(targetDir);
429
+ if (success) {
430
+ spinner.succeed("Installed dependencies");
431
+ } else {
432
+ spinner.warn(
433
+ 'Failed to install dependencies. Run "pnpm install" manually.'
434
+ );
435
+ }
436
+ }
437
+ printSuccess(options.projectName, options.projectName);
438
+ } catch (error) {
439
+ spinner.fail();
440
+ printError(
441
+ error instanceof Error ? error.message : "Unknown error occurred"
442
+ );
443
+ process.exit(1);
444
+ }
445
+ }
446
+
447
+ // src/index.ts
448
+ var program = new Command();
449
+ program.name("create-blitzpack").description("Create a new Blitzpack project").version("0.1.0").argument("[project-name]", "Name of the project").option("--skip-git", "Skip git initialization").option("--skip-install", "Skip dependency installation").option("--dry-run", "Show what would be done without making changes").action(async (projectName, options) => {
450
+ await create(projectName, options);
451
+ });
452
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "create-blitzpack",
3
+ "version": "0.1.0",
4
+ "description": "Create a new Blitzpack project - full-stack TypeScript monorepo with Next.js and Fastify",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-blitzpack": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "template"
12
+ ],
13
+ "scripts": {
14
+ "dev": "tsup --watch",
15
+ "build": "pnpm run build:template && tsup",
16
+ "build:template": "node scripts/copy-template.js",
17
+ "typecheck": "tsc --noEmit",
18
+ "clean": "rm -rf dist template"
19
+ },
20
+ "dependencies": {
21
+ "chalk": "^5.4.1",
22
+ "commander": "^13.1.0",
23
+ "fs-extra": "^11.3.0",
24
+ "ora": "^8.2.0",
25
+ "prompts": "^2.4.2",
26
+ "validate-npm-package-name": "^6.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/fs-extra": "^11.0.4",
30
+ "@types/node": "^22.10.5",
31
+ "@types/prompts": "^2.4.9",
32
+ "@types/validate-npm-package-name": "^4.0.2",
33
+ "tsup": "^8.5.0",
34
+ "typescript": "^5.7.2"
35
+ },
36
+ "keywords": [
37
+ "create",
38
+ "template",
39
+ "monorepo",
40
+ "nextjs",
41
+ "fastify",
42
+ "typescript",
43
+ "full-stack",
44
+ "prisma",
45
+ "turborepo"
46
+ ],
47
+ "author": "CarboxyDev",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/CarboxyDev/blitzpack"
52
+ },
53
+ "homepage": "https://github.com/CarboxyDev/blitzpack",
54
+ "engines": {
55
+ "node": ">=20.0.0"
56
+ }
57
+ }
@@ -0,0 +1,59 @@
1
+ # Dependencies
2
+ node_modules
3
+ npm-debug.log
4
+ pnpm-debug.log
5
+ yarn-error.log
6
+
7
+ # Build outputs (but keep package dist/ folders for copying built packages)
8
+ # We ignore these initially but packages/*/dist will be built during Docker build
9
+ .next
10
+ .turbo
11
+ *.tsbuildinfo
12
+ apps/*/dist
13
+
14
+ # Testing
15
+ coverage
16
+ .vitest
17
+
18
+ # Environment files (never include in image)
19
+ .env
20
+ .env.local
21
+ .env.*.local
22
+ .env.development
23
+ .env.production
24
+
25
+ # Git
26
+ .git
27
+ .gitignore
28
+ .github
29
+
30
+ # IDE
31
+ .vscode
32
+ .idea
33
+ *.swp
34
+ *.swo
35
+ *~
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Logs
42
+ logs
43
+ *.log
44
+
45
+ # Documentation (not needed in production)
46
+ README.md
47
+ DEPLOYMENT.md
48
+ CLAUDE.md
49
+ *.md
50
+
51
+ # Docker
52
+ Dockerfile
53
+ .dockerignore
54
+ docker-compose.yml
55
+
56
+ # Misc
57
+ .husky
58
+ scripts
59
+ # Keep config/ for package builds