create-rudder 1.4.0 → 1.5.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 (243) hide show
  1. package/README.md +23 -5
  2. package/dist/agent-detect.d.ts +21 -0
  3. package/dist/agent-detect.d.ts.map +1 -0
  4. package/dist/agent-detect.js +36 -0
  5. package/dist/agent-detect.js.map +1 -0
  6. package/dist/cli-flags.d.ts +60 -0
  7. package/dist/cli-flags.d.ts.map +1 -0
  8. package/dist/cli-flags.js +251 -0
  9. package/dist/cli-flags.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +642 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/logo.d.ts +6 -0
  15. package/dist/logo.d.ts.map +1 -0
  16. package/dist/logo.js +52 -0
  17. package/dist/logo.js.map +1 -0
  18. package/dist/templates/app/auth-controller.d.ts +2 -0
  19. package/dist/templates/app/auth-controller.d.ts.map +1 -0
  20. package/dist/templates/app/auth-controller.js +53 -0
  21. package/dist/templates/app/auth-controller.js.map +1 -0
  22. package/dist/templates/app/mcp-echo-server.d.ts +2 -0
  23. package/dist/templates/app/mcp-echo-server.d.ts.map +1 -0
  24. package/dist/templates/app/mcp-echo-server.js +13 -0
  25. package/dist/templates/app/mcp-echo-server.js.map +1 -0
  26. package/dist/templates/app/mcp-echo-tool.d.ts +2 -0
  27. package/dist/templates/app/mcp-echo-tool.d.ts.map +1 -0
  28. package/dist/templates/app/mcp-echo-tool.js +20 -0
  29. package/dist/templates/app/mcp-echo-tool.js.map +1 -0
  30. package/dist/templates/app/service-provider.d.ts +3 -0
  31. package/dist/templates/app/service-provider.d.ts.map +1 -0
  32. package/dist/templates/app/service-provider.js +33 -0
  33. package/dist/templates/app/service-provider.js.map +1 -0
  34. package/dist/templates/app/terminal-dashboard.d.ts +2 -0
  35. package/dist/templates/app/terminal-dashboard.d.ts.map +1 -0
  36. package/dist/templates/app/terminal-dashboard.js +22 -0
  37. package/dist/templates/app/terminal-dashboard.js.map +1 -0
  38. package/dist/templates/app/user-model.d.ts +2 -0
  39. package/dist/templates/app/user-model.d.ts.map +1 -0
  40. package/dist/templates/app/user-model.js +19 -0
  41. package/dist/templates/app/user-model.js.map +1 -0
  42. package/dist/templates/bootstrap/app.d.ts +3 -0
  43. package/dist/templates/bootstrap/app.d.ts.map +1 -0
  44. package/dist/templates/bootstrap/app.js +41 -0
  45. package/dist/templates/bootstrap/app.js.map +1 -0
  46. package/dist/templates/bootstrap/providers.d.ts +3 -0
  47. package/dist/templates/bootstrap/providers.d.ts.map +1 -0
  48. package/dist/templates/bootstrap/providers.js +27 -0
  49. package/dist/templates/bootstrap/providers.js.map +1 -0
  50. package/dist/templates/components/site-header.d.ts +4 -0
  51. package/dist/templates/components/site-header.d.ts.map +1 -0
  52. package/dist/templates/components/site-header.js +233 -0
  53. package/dist/templates/components/site-header.js.map +1 -0
  54. package/dist/templates/configs/ai.d.ts +2 -0
  55. package/dist/templates/configs/ai.d.ts.map +1 -0
  56. package/dist/templates/configs/ai.js +32 -0
  57. package/dist/templates/configs/ai.js.map +1 -0
  58. package/dist/templates/configs/app.d.ts +2 -0
  59. package/dist/templates/configs/app.d.ts.map +1 -0
  60. package/dist/templates/configs/app.js +12 -0
  61. package/dist/templates/configs/app.js.map +1 -0
  62. package/dist/templates/configs/auth.d.ts +3 -0
  63. package/dist/templates/configs/auth.d.ts.map +1 -0
  64. package/dist/templates/configs/auth.js +16 -0
  65. package/dist/templates/configs/auth.js.map +1 -0
  66. package/dist/templates/configs/cache.d.ts +2 -0
  67. package/dist/templates/configs/cache.d.ts.map +1 -0
  68. package/dist/templates/configs/cache.js +28 -0
  69. package/dist/templates/configs/cache.js.map +1 -0
  70. package/dist/templates/configs/crypt.d.ts +2 -0
  71. package/dist/templates/configs/crypt.d.ts.map +1 -0
  72. package/dist/templates/configs/crypt.js +16 -0
  73. package/dist/templates/configs/crypt.js.map +1 -0
  74. package/dist/templates/configs/database.d.ts +3 -0
  75. package/dist/templates/configs/database.d.ts.map +1 -0
  76. package/dist/templates/configs/database.js +28 -0
  77. package/dist/templates/configs/database.js.map +1 -0
  78. package/dist/templates/configs/hash.d.ts +2 -0
  79. package/dist/templates/configs/hash.d.ts.map +1 -0
  80. package/dist/templates/configs/hash.js +12 -0
  81. package/dist/templates/configs/hash.js.map +1 -0
  82. package/dist/templates/configs/horizon.d.ts +2 -0
  83. package/dist/templates/configs/horizon.d.ts.map +1 -0
  84. package/dist/templates/configs/horizon.js +30 -0
  85. package/dist/templates/configs/horizon.js.map +1 -0
  86. package/dist/templates/configs/index.d.ts +3 -0
  87. package/dist/templates/configs/index.d.ts.map +1 -0
  88. package/dist/templates/configs/index.js +92 -0
  89. package/dist/templates/configs/index.js.map +1 -0
  90. package/dist/templates/configs/localization.d.ts +2 -0
  91. package/dist/templates/configs/localization.d.ts.map +1 -0
  92. package/dist/templates/configs/localization.js +13 -0
  93. package/dist/templates/configs/localization.js.map +1 -0
  94. package/dist/templates/configs/log.d.ts +2 -0
  95. package/dist/templates/configs/log.d.ts.map +1 -0
  96. package/dist/templates/configs/log.js +40 -0
  97. package/dist/templates/configs/log.js.map +1 -0
  98. package/dist/templates/configs/mail.d.ts +2 -0
  99. package/dist/templates/configs/mail.d.ts.map +1 -0
  100. package/dist/templates/configs/mail.js +33 -0
  101. package/dist/templates/configs/mail.js.map +1 -0
  102. package/dist/templates/configs/passport.d.ts +2 -0
  103. package/dist/templates/configs/passport.d.ts.map +1 -0
  104. package/dist/templates/configs/passport.js +22 -0
  105. package/dist/templates/configs/passport.js.map +1 -0
  106. package/dist/templates/configs/pennant.d.ts +2 -0
  107. package/dist/templates/configs/pennant.d.ts.map +1 -0
  108. package/dist/templates/configs/pennant.js +16 -0
  109. package/dist/templates/configs/pennant.js.map +1 -0
  110. package/dist/templates/configs/pulse.d.ts +2 -0
  111. package/dist/templates/configs/pulse.d.ts.map +1 -0
  112. package/dist/templates/configs/pulse.js +21 -0
  113. package/dist/templates/configs/pulse.js.map +1 -0
  114. package/dist/templates/configs/queue.d.ts +2 -0
  115. package/dist/templates/configs/queue.d.ts.map +1 -0
  116. package/dist/templates/configs/queue.js +28 -0
  117. package/dist/templates/configs/queue.js.map +1 -0
  118. package/dist/templates/configs/sanctum.d.ts +2 -0
  119. package/dist/templates/configs/sanctum.d.ts.map +1 -0
  120. package/dist/templates/configs/sanctum.js +19 -0
  121. package/dist/templates/configs/sanctum.js.map +1 -0
  122. package/dist/templates/configs/server.d.ts +2 -0
  123. package/dist/templates/configs/server.d.ts.map +1 -0
  124. package/dist/templates/configs/server.js +15 -0
  125. package/dist/templates/configs/server.js.map +1 -0
  126. package/dist/templates/configs/session.d.ts +2 -0
  127. package/dist/templates/configs/session.d.ts.map +1 -0
  128. package/dist/templates/configs/session.js +26 -0
  129. package/dist/templates/configs/session.js.map +1 -0
  130. package/dist/templates/configs/socialite.d.ts +2 -0
  131. package/dist/templates/configs/socialite.d.ts.map +1 -0
  132. package/dist/templates/configs/socialite.js +27 -0
  133. package/dist/templates/configs/socialite.js.map +1 -0
  134. package/dist/templates/configs/storage.d.ts +2 -0
  135. package/dist/templates/configs/storage.d.ts.map +1 -0
  136. package/dist/templates/configs/storage.js +35 -0
  137. package/dist/templates/configs/storage.js.map +1 -0
  138. package/dist/templates/configs/sync.d.ts +3 -0
  139. package/dist/templates/configs/sync.d.ts.map +1 -0
  140. package/dist/templates/configs/sync.js +17 -0
  141. package/dist/templates/configs/sync.js.map +1 -0
  142. package/dist/templates/configs/telescope.d.ts +2 -0
  143. package/dist/templates/configs/telescope.d.ts.map +1 -0
  144. package/dist/templates/configs/telescope.js +25 -0
  145. package/dist/templates/configs/telescope.js.map +1 -0
  146. package/dist/templates/css/index.d.ts +3 -0
  147. package/dist/templates/css/index.d.ts.map +1 -0
  148. package/dist/templates/css/index.js +140 -0
  149. package/dist/templates/css/index.js.map +1 -0
  150. package/dist/templates/css/plain.d.ts +2 -0
  151. package/dist/templates/css/plain.d.ts.map +1 -0
  152. package/dist/templates/css/plain.js +373 -0
  153. package/dist/templates/css/plain.js.map +1 -0
  154. package/dist/templates/css/tailwind.d.ts +2 -0
  155. package/dist/templates/css/tailwind.d.ts.map +1 -0
  156. package/dist/templates/css/tailwind.js +176 -0
  157. package/dist/templates/css/tailwind.js.map +1 -0
  158. package/dist/templates/env.d.ts +7 -0
  159. package/dist/templates/env.d.ts.map +1 -0
  160. package/dist/templates/env.js +113 -0
  161. package/dist/templates/env.js.map +1 -0
  162. package/dist/templates/package-json.d.ts +3 -0
  163. package/dist/templates/package-json.d.ts.map +1 -0
  164. package/dist/templates/package-json.js +204 -0
  165. package/dist/templates/package-json.js.map +1 -0
  166. package/dist/templates/package-managers.d.ts +14 -0
  167. package/dist/templates/package-managers.d.ts.map +1 -0
  168. package/dist/templates/package-managers.js +49 -0
  169. package/dist/templates/package-managers.js.map +1 -0
  170. package/dist/templates/pages/ai-chat.d.ts +7 -0
  171. package/dist/templates/pages/ai-chat.d.ts.map +1 -0
  172. package/dist/templates/pages/ai-chat.js +285 -0
  173. package/dist/templates/pages/ai-chat.js.map +1 -0
  174. package/dist/templates/pages/demo.d.ts +4 -0
  175. package/dist/templates/pages/demo.d.ts.map +1 -0
  176. package/dist/templates/pages/demo.js +71 -0
  177. package/dist/templates/pages/demo.js.map +1 -0
  178. package/dist/templates/pages/error.d.ts +7 -0
  179. package/dist/templates/pages/error.d.ts.map +1 -0
  180. package/dist/templates/pages/error.js +148 -0
  181. package/dist/templates/pages/error.js.map +1 -0
  182. package/dist/templates/pages/index.d.ts +22 -0
  183. package/dist/templates/pages/index.d.ts.map +1 -0
  184. package/dist/templates/pages/index.js +374 -0
  185. package/dist/templates/pages/index.js.map +1 -0
  186. package/dist/templates/prisma/auth.d.ts +2 -0
  187. package/dist/templates/prisma/auth.d.ts.map +1 -0
  188. package/dist/templates/prisma/auth.js +22 -0
  189. package/dist/templates/prisma/auth.js.map +1 -0
  190. package/dist/templates/prisma/base.d.ts +3 -0
  191. package/dist/templates/prisma/base.d.ts.map +1 -0
  192. package/dist/templates/prisma/base.js +14 -0
  193. package/dist/templates/prisma/base.js.map +1 -0
  194. package/dist/templates/prisma/config.d.ts +3 -0
  195. package/dist/templates/prisma/config.d.ts.map +1 -0
  196. package/dist/templates/prisma/config.js +15 -0
  197. package/dist/templates/prisma/config.js.map +1 -0
  198. package/dist/templates/prisma/notification.d.ts +2 -0
  199. package/dist/templates/prisma/notification.d.ts.map +1 -0
  200. package/dist/templates/prisma/notification.js +16 -0
  201. package/dist/templates/prisma/notification.js.map +1 -0
  202. package/dist/templates/prisma/passport.d.ts +2 -0
  203. package/dist/templates/prisma/passport.d.ts.map +1 -0
  204. package/dist/templates/prisma/passport.js +69 -0
  205. package/dist/templates/prisma/passport.js.map +1 -0
  206. package/dist/templates/routes/api.d.ts +3 -0
  207. package/dist/templates/routes/api.d.ts.map +1 -0
  208. package/dist/templates/routes/api.js +76 -0
  209. package/dist/templates/routes/api.js.map +1 -0
  210. package/dist/templates/routes/console.d.ts +3 -0
  211. package/dist/templates/routes/console.d.ts.map +1 -0
  212. package/dist/templates/routes/console.js +29 -0
  213. package/dist/templates/routes/console.js.map +1 -0
  214. package/dist/templates/routes/web.d.ts +4 -0
  215. package/dist/templates/routes/web.d.ts.map +1 -0
  216. package/dist/templates/routes/web.js +77 -0
  217. package/dist/templates/routes/web.js.map +1 -0
  218. package/dist/templates/routes-manifest.d.ts +28 -0
  219. package/dist/templates/routes-manifest.d.ts.map +1 -0
  220. package/dist/templates/routes-manifest.js +48 -0
  221. package/dist/templates/routes-manifest.js.map +1 -0
  222. package/dist/templates/server.d.ts +2 -0
  223. package/dist/templates/server.d.ts.map +1 -0
  224. package/dist/templates/server.js +10 -0
  225. package/dist/templates/server.js.map +1 -0
  226. package/dist/templates/tsconfig.d.ts +3 -0
  227. package/dist/templates/tsconfig.d.ts.map +1 -0
  228. package/dist/templates/tsconfig.js +33 -0
  229. package/dist/templates/tsconfig.js.map +1 -0
  230. package/dist/templates/views/welcome.d.ts +19 -0
  231. package/dist/templates/views/welcome.d.ts.map +1 -0
  232. package/dist/templates/views/welcome.js +338 -0
  233. package/dist/templates/views/welcome.js.map +1 -0
  234. package/dist/templates/vite.d.ts +3 -0
  235. package/dist/templates/vite.d.ts.map +1 -0
  236. package/dist/templates/vite.js +66 -0
  237. package/dist/templates/vite.js.map +1 -0
  238. package/dist/templates.d.ts +45 -0
  239. package/dist/templates.d.ts.map +1 -0
  240. package/dist/templates.js +202 -0
  241. package/dist/templates.js.map +1 -0
  242. package/package.json +27 -7
  243. package/bin/index.mjs +0 -18
package/dist/index.js ADDED
@@ -0,0 +1,642 @@
1
+ #!/usr/bin/env node
2
+ import { intro, outro, text, select, groupMultiselect, confirm, spinner, log, isCancel, cancel, } from '@clack/prompts';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { randomBytes } from 'node:crypto';
8
+ import { createRequire } from 'node:module';
9
+ import { getTemplates, detectPackageManager, pmRun, pmInstall } from './templates.js';
10
+ import { detectAgent } from './agent-detect.js';
11
+ import { printLogo } from './logo.js';
12
+ import { parseFlags, validateJsonMode, resolveJsonAnswers, packagesFromList, FlagError, DB_GATED, RECIPES, } from './cli-flags.js';
13
+ // ──────────────────────────────────────────────────────────────
14
+ // Interactive prompt flow — only prompts for what's missing
15
+ // ──────────────────────────────────────────────────────────────
16
+ async function gatherInteractive(name, p) {
17
+ let resolvedName;
18
+ if (p.name)
19
+ resolvedName = p.name;
20
+ else if (name) {
21
+ resolvedName = name;
22
+ console.log(` Project name: ${name}`);
23
+ }
24
+ else {
25
+ const answer = await text({
26
+ message: 'Project name',
27
+ placeholder: 'my-app',
28
+ validate: (v) => (v ?? '').trim().length === 0 ? 'Project name is required' : undefined,
29
+ });
30
+ if (isCancel(answer)) {
31
+ cancel('Cancelled.');
32
+ process.exit(0);
33
+ }
34
+ resolvedName = answer.trim();
35
+ }
36
+ // ── Recipe (the one-question replacement for the 25-option multiselect) ──
37
+ let recipe;
38
+ if (p.recipe)
39
+ recipe = p.recipe;
40
+ else {
41
+ const recipeAnswer = await select({
42
+ message: 'What are you building?',
43
+ options: [
44
+ { value: 'web-app', label: 'Web app', hint: 'auth + ORM + frontend' },
45
+ { value: 'saas', label: 'SaaS', hint: '+ queue + mail + notifications' },
46
+ { value: 'api-service', label: 'API service', hint: 'ORM + auth + http, no frontend' },
47
+ { value: 'realtime', label: 'Realtime', hint: '+ broadcast + sync (WebSocket)' },
48
+ { value: 'minimal', label: 'Minimal', hint: 'just the framework — no extras' },
49
+ { value: 'custom', label: 'Custom', hint: 'pick packages yourself' },
50
+ ],
51
+ initialValue: 'web-app',
52
+ });
53
+ if (isCancel(recipeAnswer)) {
54
+ cancel('Cancelled.');
55
+ process.exit(0);
56
+ }
57
+ recipe = recipeAnswer;
58
+ }
59
+ const preset = recipe === 'custom' ? null : RECIPES[recipe];
60
+ // ── Database ORM + driver ──
61
+ let orm;
62
+ if (p.orm !== undefined)
63
+ orm = p.orm;
64
+ else if (preset && !preset.needsOrm)
65
+ orm = false;
66
+ else {
67
+ const ormAnswer = await select({
68
+ message: 'Database',
69
+ options: recipe === 'minimal' || recipe === 'custom'
70
+ ? [
71
+ { value: 'prisma', label: 'Prisma' },
72
+ { value: 'drizzle', label: 'Drizzle' },
73
+ { value: 'none', label: 'None', hint: 'no database' },
74
+ ]
75
+ : [
76
+ { value: 'prisma', label: 'Prisma' },
77
+ { value: 'drizzle', label: 'Drizzle' },
78
+ ],
79
+ });
80
+ if (isCancel(ormAnswer)) {
81
+ cancel('Cancelled.');
82
+ process.exit(0);
83
+ }
84
+ orm = ormAnswer === 'none' ? false : ormAnswer;
85
+ }
86
+ let db = p.db ?? 'sqlite';
87
+ if (orm && p.db === undefined) {
88
+ const dbAnswer = await select({
89
+ message: 'Database driver',
90
+ options: [
91
+ { value: 'sqlite', label: 'SQLite', hint: 'recommended — no setup' },
92
+ { value: 'postgresql', label: 'PostgreSQL' },
93
+ { value: 'mysql', label: 'MySQL / MariaDB' },
94
+ ],
95
+ initialValue: 'sqlite',
96
+ });
97
+ if (isCancel(dbAnswer)) {
98
+ cancel('Cancelled.');
99
+ process.exit(0);
100
+ }
101
+ db = dbAnswer;
102
+ }
103
+ // ── Packages: recipe preset OR explicit Custom multiselect ──
104
+ let packages;
105
+ if (p.packages !== undefined)
106
+ packages = p.packages;
107
+ else if (recipe !== 'custom') {
108
+ packages = packagesFromList([...(preset?.packages ?? [])], orm);
109
+ }
110
+ else {
111
+ packages = await promptCustomPackages(orm);
112
+ }
113
+ if (packages.passport && (!packages.auth || orm !== 'prisma')) {
114
+ cancel('Passport requires Auth + Prisma. Re-run and select both, or drop Passport.');
115
+ process.exit(1);
116
+ }
117
+ // ── Frontend framework + styling (skipped for API-service / Minimal) ──
118
+ const wantsFrontend = preset
119
+ ? preset.needsFrontend
120
+ : (recipe === 'minimal' ? false : true);
121
+ let frameworks;
122
+ let primary = 'react';
123
+ let tailwind;
124
+ let shadcn;
125
+ if (!wantsFrontend && p.frameworks === undefined) {
126
+ frameworks = [];
127
+ tailwind = false;
128
+ shadcn = false;
129
+ }
130
+ else if (p.frameworks?.length) {
131
+ // legacy multi-framework flag path
132
+ frameworks = p.frameworks;
133
+ primary = p.primary ?? frameworks[0];
134
+ tailwind = p.tailwind ?? true;
135
+ shadcn = p.shadcn ?? (frameworks.includes('react') && tailwind);
136
+ }
137
+ else {
138
+ const frameworkAnswer = await select({
139
+ message: 'Frontend framework',
140
+ options: [
141
+ { value: 'react', label: 'React', hint: 'recommended' },
142
+ { value: 'vue', label: 'Vue' },
143
+ { value: 'solid', label: 'Solid' },
144
+ { value: 'none', label: 'None', hint: 'server-rendered HTML only' },
145
+ ],
146
+ initialValue: 'react',
147
+ });
148
+ if (isCancel(frameworkAnswer)) {
149
+ cancel('Cancelled.');
150
+ process.exit(0);
151
+ }
152
+ const fw = frameworkAnswer;
153
+ frameworks = fw === 'none' ? [] : [fw];
154
+ primary = fw === 'none' ? 'react' : fw;
155
+ if (frameworks.length === 0) {
156
+ tailwind = p.tailwind ?? false;
157
+ shadcn = false;
158
+ }
159
+ else if (p.tailwind !== undefined) {
160
+ tailwind = p.tailwind;
161
+ shadcn = p.shadcn ?? (fw === 'react' && tailwind);
162
+ }
163
+ else {
164
+ const stylingAnswer = await select({
165
+ message: 'Styling',
166
+ options: fw === 'react'
167
+ ? [
168
+ { value: 'tailwind+shadcn', label: 'Tailwind + shadcn/ui', hint: 'recommended' },
169
+ { value: 'tailwind', label: 'Tailwind only' },
170
+ { value: 'plain', label: 'Plain CSS', hint: 'no framework' },
171
+ ]
172
+ : [
173
+ { value: 'tailwind', label: 'Tailwind', hint: 'recommended' },
174
+ { value: 'plain', label: 'Plain CSS', hint: 'no framework' },
175
+ ],
176
+ initialValue: fw === 'react' ? 'tailwind+shadcn' : 'tailwind',
177
+ });
178
+ if (isCancel(stylingAnswer)) {
179
+ cancel('Cancelled.');
180
+ process.exit(0);
181
+ }
182
+ tailwind = stylingAnswer !== 'plain';
183
+ shadcn = stylingAnswer === 'tailwind+shadcn';
184
+ }
185
+ }
186
+ // ── Smart DB-push: for non-SQLite ask once whether the DB is reachable ──
187
+ let dbReady;
188
+ if (p.dbReady !== undefined)
189
+ dbReady = p.dbReady;
190
+ else if (orm === false)
191
+ dbReady = false;
192
+ else if (db === 'sqlite')
193
+ dbReady = true;
194
+ else {
195
+ const dbReadyAnswer = await confirm({
196
+ message: `Is your ${db === 'postgresql' ? 'Postgres' : 'MySQL'} running now? (we'll push the schema for you)`,
197
+ initialValue: true,
198
+ });
199
+ if (isCancel(dbReadyAnswer)) {
200
+ cancel('Cancelled.');
201
+ process.exit(0);
202
+ }
203
+ dbReady = dbReadyAnswer;
204
+ }
205
+ let install;
206
+ if (p.install !== undefined)
207
+ install = p.install;
208
+ else {
209
+ const installAnswer = await confirm({ message: 'Install and run setup?', initialValue: true });
210
+ if (isCancel(installAnswer)) {
211
+ cancel('Cancelled.');
212
+ process.exit(0);
213
+ }
214
+ install = installAnswer;
215
+ }
216
+ const git = p.git ?? install;
217
+ return {
218
+ name: resolvedName, recipe, orm, db, packages, frameworks, primary, tailwind, shadcn,
219
+ git, dbReady, install,
220
+ };
221
+ }
222
+ // ──────────────────────────────────────────────────────────────
223
+ // Custom-recipe package picker — only shown for `recipe = 'custom'`
224
+ // ──────────────────────────────────────────────────────────────
225
+ async function promptCustomPackages(orm) {
226
+ const PACKAGE_GROUPS = {
227
+ 'Auth & Security': [
228
+ { value: 'auth', label: 'Authentication', hint: 'login, register, sessions' },
229
+ { value: 'sanctum', label: 'Sanctum', hint: 'API tokens (SHA-256 + abilities)' },
230
+ { value: 'passport', label: 'Passport', hint: 'OAuth2 server — requires Auth + Prisma' },
231
+ { value: 'socialite', label: 'Socialite', hint: 'social login: GitHub, Google, Facebook, Apple' },
232
+ { value: 'crypt', label: 'Crypt', hint: 'AES-256-CBC + HMAC encryption' },
233
+ ],
234
+ 'Infrastructure': [
235
+ { value: 'queue', label: 'Queue', hint: 'background jobs' },
236
+ { value: 'storage', label: 'Storage', hint: 'file uploads (local + S3)' },
237
+ { value: 'scheduler', label: 'Scheduler', hint: 'cron-like task scheduling' },
238
+ ],
239
+ 'Communication': [
240
+ { value: 'mail', label: 'Mail', hint: 'SMTP + log driver' },
241
+ { value: 'notifications', label: 'Notifications', hint: 'multi-channel notifications' },
242
+ { value: 'broadcast', label: 'WebSocket / Broadcast', hint: 'real-time channels' },
243
+ { value: 'sync', label: 'Sync (Yjs CRDT)', hint: 'collaborative documents' },
244
+ ],
245
+ 'Internationalization': [
246
+ { value: 'localization', label: 'Localization', hint: 'i18n — trans(), setLocale()' },
247
+ ],
248
+ 'Developer Experience': [
249
+ { value: 'pennant', label: 'Pennant', hint: 'feature flags' },
250
+ { value: 'http', label: 'HTTP', hint: 'fluent fetch client — retries, timeouts, pools' },
251
+ { value: 'process', label: 'Process', hint: 'shell execution — run, pool, pipe' },
252
+ { value: 'concurrency', label: 'Concurrency', hint: 'parallel execution via worker threads' },
253
+ { value: 'terminal', label: 'Terminal', hint: 'rich terminal UIs from CLI commands (Ink)' },
254
+ ],
255
+ 'Media': [
256
+ { value: 'image', label: 'Image', hint: 'resize, crop, convert (sharp wrapper)' },
257
+ ],
258
+ 'Observability': [
259
+ { value: 'telescope', label: 'Telescope', hint: 'debug dashboard — requests, queries, jobs, exceptions' },
260
+ { value: 'pulse', label: 'Pulse', hint: 'metrics dashboard — throughput, latency, hit rates' },
261
+ { value: 'horizon', label: 'Horizon', hint: 'queue monitoring — lifecycle, workers, retry/delete' },
262
+ ],
263
+ 'AI & Tooling': [
264
+ { value: 'ai', label: 'AI', hint: 'LLM providers (Anthropic, OpenAI, Google, Ollama)' },
265
+ { value: 'mcp', label: 'MCP', hint: 'Model Context Protocol — expose tools/resources to LLMs' },
266
+ { value: 'boost', label: 'Boost', hint: 'AI coding DX (Claude Code/Cursor/Copilot)' },
267
+ ],
268
+ };
269
+ if (orm === false)
270
+ log.info('Database not selected — auth, sanctum, and passport options are hidden.');
271
+ const groupedOptions = {};
272
+ for (const [group, pkgs] of Object.entries(PACKAGE_GROUPS)) {
273
+ const visible = orm === false ? pkgs.filter(p => !DB_GATED.has(p.value)) : pkgs;
274
+ if (visible.length > 0)
275
+ groupedOptions[group] = visible;
276
+ }
277
+ const packageAnswer = await groupMultiselect({
278
+ message: 'Select packages',
279
+ options: groupedOptions,
280
+ initialValues: orm === false ? [] : ['auth'],
281
+ required: false,
282
+ selectableGroups: false,
283
+ });
284
+ if (isCancel(packageAnswer)) {
285
+ cancel('Cancelled.');
286
+ process.exit(0);
287
+ }
288
+ return packagesFromList(packageAnswer, orm);
289
+ }
290
+ async function scaffold(answers, opts) {
291
+ const { pm, quiet, logFile } = opts;
292
+ const target = path.resolve(process.cwd(), answers.name);
293
+ const authSecret = randomBytes(32).toString('hex');
294
+ const appKey = randomBytes(32).toString('base64');
295
+ // Make sure target directory doesn't exist
296
+ try {
297
+ await fs.access(target);
298
+ throw new ScaffoldError(`Directory "${answers.name}" already exists.`);
299
+ }
300
+ catch (e) {
301
+ if (e instanceof ScaffoldError)
302
+ throw e;
303
+ // ENOENT — good, directory doesn't exist
304
+ }
305
+ const s = quiet ? null : spinner();
306
+ s?.start('Scaffolding project files...');
307
+ const templates = getTemplates({
308
+ name: answers.name, db: answers.db, orm: answers.orm,
309
+ authSecret, appKey,
310
+ frameworks: answers.frameworks, primary: answers.primary,
311
+ tailwind: answers.tailwind, shadcn: answers.shadcn,
312
+ pm, packages: answers.packages,
313
+ });
314
+ for (const [filePath, content] of Object.entries(templates)) {
315
+ const abs = path.join(target, filePath);
316
+ await fs.mkdir(path.dirname(abs), { recursive: true });
317
+ await fs.writeFile(abs, content, 'utf8');
318
+ }
319
+ let authViewsCopied = true;
320
+ if (answers.packages.auth) {
321
+ try {
322
+ const require = createRequire(import.meta.url);
323
+ const authPkgPath = require.resolve('@rudderjs/auth/package.json');
324
+ const authViewsDir = path.join(path.dirname(authPkgPath), 'views', answers.primary);
325
+ await fs.cp(authViewsDir, path.join(target, 'app', 'Views', 'Auth'), { recursive: true });
326
+ }
327
+ catch {
328
+ authViewsCopied = false;
329
+ }
330
+ }
331
+ s?.stop(`${Object.keys(templates).length} files written`);
332
+ let installAttempted = false, installOk = false, discoverOk = false;
333
+ let dbGenerateOk = null;
334
+ let dbPushOk = null;
335
+ let vendorPublishOk = null;
336
+ let passportKeysOk = null;
337
+ let gitInitOk = null;
338
+ if (answers.install) {
339
+ installAttempted = true;
340
+ const s2 = quiet ? null : spinner();
341
+ s2?.start(`Installing dependencies with ${pm}...`);
342
+ const [cmd, ...args] = pmInstall(pm).split(' ');
343
+ installOk = await runChild(cmd, args, target, logFile);
344
+ s2?.stop(installOk ? 'Dependencies installed' : `${pmInstall(pm)} failed — run it manually`);
345
+ if (installOk) {
346
+ const s3 = quiet ? null : spinner();
347
+ s3?.start('Discovering framework providers...');
348
+ discoverOk = await runRudder(pm, 'providers:discover', target, logFile);
349
+ s3?.stop(discoverOk
350
+ ? 'Provider manifest generated'
351
+ : `providers:discover failed — run \`${pmRun(pm, 'rudder')} providers:discover\` manually`);
352
+ // ── Auto-cascade — only when install + providers:discover both succeed ──
353
+ if (discoverOk && answers.orm) {
354
+ const s4 = quiet ? null : spinner();
355
+ s4?.start('Generating database client...');
356
+ dbGenerateOk = await runRudder(pm, 'db:generate', target, logFile);
357
+ s4?.stop(dbGenerateOk
358
+ ? (answers.orm === 'prisma' ? 'Prisma client generated' : 'Client step skipped (Drizzle)')
359
+ : `db:generate failed — run \`${pmRun(pm, 'rudder')} db:generate\` manually`);
360
+ }
361
+ if (discoverOk && answers.orm && answers.dbReady) {
362
+ const s5 = quiet ? null : spinner();
363
+ s5?.start('Pushing schema to database...');
364
+ dbPushOk = await runRudder(pm, 'db:push', target, logFile);
365
+ const niceDb = answers.db === 'sqlite' ? 'dev.db ready' : 'schema pushed';
366
+ s5?.stop(dbPushOk
367
+ ? niceDb
368
+ : `db:push failed — start your database and run \`${pmRun(pm, 'rudder')} db:push\``);
369
+ }
370
+ if (discoverOk && answers.packages.auth && !authViewsCopied) {
371
+ const s6 = quiet ? null : spinner();
372
+ s6?.start('Publishing auth views...');
373
+ vendorPublishOk = await runRudder(pm, `vendor:publish --tag=auth-views-${answers.primary}`, target, logFile);
374
+ s6?.stop(vendorPublishOk
375
+ ? 'Auth views published'
376
+ : `vendor:publish failed — run \`${pmRun(pm, 'rudder')} vendor:publish --tag=auth-views-${answers.primary}\``);
377
+ }
378
+ if (discoverOk && answers.packages.passport) {
379
+ const s7 = quiet ? null : spinner();
380
+ s7?.start('Generating Passport keys...');
381
+ passportKeysOk = await runRudder(pm, 'passport:keys', target, logFile);
382
+ s7?.stop(passportKeysOk
383
+ ? 'Passport keys generated'
384
+ : `passport:keys failed — run \`${pmRun(pm, 'rudder')} passport:keys\` manually`);
385
+ }
386
+ }
387
+ }
388
+ // ── git init — independent of install; runs whenever user opted in ──
389
+ if (answers.git) {
390
+ const s8 = quiet ? null : spinner();
391
+ s8?.start('Initializing git repository...');
392
+ gitInitOk = await runGitInit(target, logFile);
393
+ s8?.stop(gitInitOk
394
+ ? 'Git initialized'
395
+ : 'git init skipped — git not available or already a repo');
396
+ }
397
+ return {
398
+ target,
399
+ filesWritten: Object.keys(templates).length,
400
+ authViewsCopied,
401
+ installAttempted, installOk, discoverOk,
402
+ dbGenerateOk, dbPushOk, vendorPublishOk, passportKeysOk, gitInitOk,
403
+ };
404
+ }
405
+ /** Run a `rudder <subcommand>` in the target dir. Subcommand may include flags. */
406
+ async function runRudder(pm, subcommand, cwd, logFile) {
407
+ const [cmd, ...args] = `${pmRun(pm, 'rudder')} ${subcommand}`.split(' ').filter(Boolean);
408
+ return runChild(cmd, args, cwd, logFile);
409
+ }
410
+ /** `git init` + first commit. Returns false if git isn't on $PATH or the dir is already a repo. */
411
+ async function runGitInit(cwd, logFile) {
412
+ // Probe `git --version` first so we don't write a half-init repo on systems without git.
413
+ const hasGit = await runChild('git', ['--version'], cwd, logFile);
414
+ if (!hasGit)
415
+ return false;
416
+ // If `.git/` exists (rare in scaffolded apps but possible) bail rather than commit blindly.
417
+ try {
418
+ await fs.access(path.join(cwd, '.git'));
419
+ return false;
420
+ }
421
+ catch { /* good — no existing repo */ }
422
+ if (!await runChild('git', ['init', '-q'], cwd, logFile))
423
+ return false;
424
+ if (!await runChild('git', ['add', '.'], cwd, logFile))
425
+ return false;
426
+ // -q silences the commit summary; --no-gpg-sign avoids pgp prompts on fresh machines.
427
+ if (!await runChild('git', ['commit', '-q', '-m', 'Initial commit (create-rudder)', '--no-gpg-sign'], cwd, logFile))
428
+ return false;
429
+ return true;
430
+ }
431
+ class ScaffoldError extends Error {
432
+ }
433
+ function runChild(cmd, args, cwd, logFile) {
434
+ return new Promise((resolve) => {
435
+ const child = spawn(cmd, args, { cwd, stdio: 'pipe' });
436
+ if (logFile) {
437
+ child.stdout?.on('data', (b) => { void fs.appendFile(logFile, b); });
438
+ child.stderr?.on('data', (b) => { void fs.appendFile(logFile, b); });
439
+ }
440
+ child.on('close', (code) => resolve(code === 0));
441
+ child.on('error', () => resolve(false));
442
+ });
443
+ }
444
+ async function readLogTail(logFile, lines = 40) {
445
+ try {
446
+ const text = await fs.readFile(logFile, 'utf8');
447
+ return text.split('\n').slice(-lines).join('\n');
448
+ }
449
+ catch {
450
+ return '';
451
+ }
452
+ }
453
+ // ──────────────────────────────────────────────────────────────
454
+ // Main
455
+ // ──────────────────────────────────────────────────────────────
456
+ async function main() {
457
+ const argv = process.argv.slice(2);
458
+ const pm = detectPackageManager();
459
+ let parsed;
460
+ try {
461
+ parsed = parseFlags(argv);
462
+ }
463
+ catch (err) {
464
+ if (err instanceof FlagError) {
465
+ // Always emit JSON for flag errors when an agent is detected; otherwise
466
+ // print a friendly message and exit 1.
467
+ const agent = detectAgent();
468
+ if (agent.detected) {
469
+ process.stdout.write(JSON.stringify({
470
+ success: false,
471
+ error: err.message,
472
+ ...(agent.name !== undefined ? { agent: agent.name } : {}),
473
+ }) + '\n');
474
+ process.exit(1);
475
+ }
476
+ console.error(`\n ${err.message}\n`);
477
+ process.exit(1);
478
+ }
479
+ throw err;
480
+ }
481
+ const agent = detectAgent();
482
+ const jsonMode = !parsed.forceInteractive && (parsed.jsonRequested || agent.detected);
483
+ if (jsonMode) {
484
+ const missing = validateJsonMode(parsed.name, parsed.partial);
485
+ if (missing.length > 0) {
486
+ process.stdout.write(JSON.stringify({
487
+ success: false,
488
+ error: `Missing required flags for non-interactive mode: ${missing.join(', ')}`,
489
+ requiredFlags: missing,
490
+ ...(agent.name !== undefined ? { agent: agent.name } : {}),
491
+ }) + '\n');
492
+ process.exit(1);
493
+ }
494
+ const answers = resolveJsonAnswers(parsed.name, parsed.partial);
495
+ if (answers.packages.passport && (!answers.packages.auth || answers.orm !== 'prisma')) {
496
+ process.stdout.write(JSON.stringify({
497
+ success: false,
498
+ error: 'Passport requires --packages to include auth and --orm=prisma.',
499
+ ...(agent.name !== undefined ? { agent: agent.name } : {}),
500
+ }) + '\n');
501
+ process.exit(1);
502
+ }
503
+ const logFile = path.join(os.tmpdir(), `create-rudder-${Date.now()}.log`);
504
+ await fs.writeFile(logFile, '');
505
+ try {
506
+ const result = await scaffold(answers, { pm, quiet: true, logFile });
507
+ const payload = {
508
+ success: true,
509
+ name: answers.name,
510
+ directory: result.target,
511
+ files: result.filesWritten,
512
+ };
513
+ if (agent.name)
514
+ payload['agent'] = agent.name;
515
+ if (result.installAttempted) {
516
+ payload['installed'] = result.installOk;
517
+ payload['providersDiscovered'] = result.discoverOk;
518
+ if (result.dbGenerateOk !== null)
519
+ payload['dbGenerated'] = result.dbGenerateOk;
520
+ if (result.dbPushOk !== null)
521
+ payload['dbPushed'] = result.dbPushOk;
522
+ if (result.vendorPublishOk !== null)
523
+ payload['authViewsPublished'] = result.vendorPublishOk;
524
+ if (result.passportKeysOk !== null)
525
+ payload['passportKeysGenerated'] = result.passportKeysOk;
526
+ }
527
+ if (result.gitInitOk !== null)
528
+ payload['gitInitialized'] = result.gitInitOk;
529
+ if (answers.packages.auth && !result.authViewsCopied && result.vendorPublishOk !== true) {
530
+ payload['warning'] = `Auth views could not be vendored. Run: ${pmRun(pm, 'rudder')} vendor:publish --tag=auth-views-${answers.primary}`;
531
+ }
532
+ process.stdout.write(JSON.stringify(payload) + '\n');
533
+ try {
534
+ await fs.unlink(logFile);
535
+ }
536
+ catch { /* ignore */ }
537
+ process.exit(0);
538
+ }
539
+ catch (err) {
540
+ const message = err instanceof Error ? err.message : String(err);
541
+ const tail = await readLogTail(logFile);
542
+ process.stdout.write(JSON.stringify({
543
+ success: false,
544
+ error: message,
545
+ logFile,
546
+ logTail: tail,
547
+ ...(agent.name !== undefined ? { agent: agent.name } : {}),
548
+ }) + '\n');
549
+ process.exit(1);
550
+ }
551
+ }
552
+ // ── Interactive flow ────────────────────────────────────
553
+ console.log();
554
+ printLogo();
555
+ console.log();
556
+ // Soft deprecation nudge — only when the user invoked the legacy bin.
557
+ // The `create-rudder-app` stub sets RUDDER_INVOKED_AS=create-rudder-app
558
+ // so we can detect users who are still on the old install command.
559
+ if (process.env['RUDDER_INVOKED_AS'] === 'create-rudder-app') {
560
+ log.info('This scaffolder now ships as `create-rudder` — use `npm create rudder@latest` next time.');
561
+ }
562
+ intro(' create-rudder ');
563
+ const answers = await gatherInteractive(parsed.name, parsed.partial);
564
+ let result;
565
+ try {
566
+ result = await scaffold(answers, { pm, quiet: false });
567
+ }
568
+ catch (err) {
569
+ if (err instanceof ScaffoldError) {
570
+ cancel(err.message);
571
+ process.exit(1);
572
+ }
573
+ throw err;
574
+ }
575
+ // ── Build the "manual steps" list ─ only includes things the auto-cascade
576
+ // either didn't run or couldn't finish. The goal: when everything succeeded,
577
+ // the panel has exactly one line (`cd app && pnpm dev`).
578
+ const manual = [];
579
+ if (!answers.install) {
580
+ manual.push(` ${pmInstall(pm)}`);
581
+ manual.push(` ${pmRun(pm, 'rudder')} providers:discover`);
582
+ }
583
+ if (answers.orm && (result.dbGenerateOk === false || (result.dbGenerateOk === null && !answers.install))) {
584
+ manual.push(` ${pmRun(pm, 'rudder')} db:generate`);
585
+ }
586
+ if (answers.orm && result.dbPushOk !== true) {
587
+ // dbPushOk is null when we deliberately skipped (e.g. user said DB not running)
588
+ if (result.dbPushOk === null && !answers.dbReady) {
589
+ manual.push(` ${pmRun(pm, 'rudder')} db:push ${result.installOk ? '# once your database is running' : ''}`);
590
+ }
591
+ else if (result.dbPushOk === false) {
592
+ manual.push(` ${pmRun(pm, 'rudder')} db:push # retry after starting your database`);
593
+ }
594
+ else if (result.dbPushOk === null && !answers.install) {
595
+ manual.push(` ${pmRun(pm, 'rudder')} db:push`);
596
+ }
597
+ }
598
+ if (answers.packages.auth && result.vendorPublishOk !== true && !result.authViewsCopied) {
599
+ manual.push(` ${pmRun(pm, 'rudder')} vendor:publish --tag=auth-views-${answers.primary}`);
600
+ }
601
+ if (answers.packages.passport && result.passportKeysOk !== true) {
602
+ manual.push(` ${pmRun(pm, 'rudder')} passport:keys`);
603
+ }
604
+ const hints = [];
605
+ if (answers.packages.ai)
606
+ hints.push(' AI chat: /ai-chat (set ANTHROPIC_API_KEY in .env)');
607
+ if (answers.packages.mcp)
608
+ hints.push(' MCP echo: POST /mcp/echo (see app/Mcp/EchoServer.ts)');
609
+ if (answers.packages.passport)
610
+ hints.push(' OAuth2: /oauth/authorize, /oauth/token (run `rudder passport:client <name>` first)');
611
+ if (answers.packages.telescope)
612
+ hints.push(' Telescope: /telescope (debug dashboard — requests, queries, jobs, AI, mail)');
613
+ if (answers.packages.boost)
614
+ hints.push(` Boost: ${pmRun(pm, 'rudder')} boost:install (wire your AI coding assistant)`);
615
+ if (answers.packages.terminal)
616
+ hints.push(` Terminal: ${pmRun(pm, 'rudder')} make:terminal <Name> (scaffold a terminal view)`);
617
+ const hintsStr = hints.length > 0 ? '\n\n' + hints.join('\n') : '';
618
+ // GitHub's tree view of the framework playground (15 working demo views,
619
+ // every package wired up) is the actual examples gallery today. The
620
+ // rudderjs.com/examples URL was vaporware in the original scaffolder copy.
621
+ const exampleLink = '\n\n Examples: https://github.com/rudderjs/rudder/tree/main/playground';
622
+ // The happy-path output — auto-cascade succeeded, no manual remediation.
623
+ if (manual.length === 0) {
624
+ outro(`Done!\n\n` +
625
+ ` cd ${answers.name} && ${pmRun(pm, 'dev')}` +
626
+ hintsStr +
627
+ exampleLink);
628
+ return;
629
+ }
630
+ // Something needs the user's attention — print remediation steps explicitly.
631
+ outro(`Done! A few things still need your attention:\n\n` +
632
+ ` cd ${answers.name}\n` +
633
+ manual.join('\n') + '\n' +
634
+ ` ${pmRun(pm, 'dev')}` +
635
+ hintsStr +
636
+ exampleLink);
637
+ }
638
+ main().catch((err) => {
639
+ console.error(err);
640
+ process.exit(1);
641
+ });
642
+ //# sourceMappingURL=index.js.map