create-kofi-stack 2.0.23 → 2.1.1

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 (2) hide show
  1. package/dist/index.js +200 -52
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -85,76 +85,185 @@ async function runPrompts(projectNameArg, options) {
85
85
  if (p.isCancel(structure)) throw new Error("cancelled");
86
86
  let marketingSite = "none";
87
87
  if (structure === "monorepo") {
88
+ const marketingOptions = [
89
+ { value: "none", label: "None", hint: "Skip marketing site" },
90
+ { value: "nextjs", label: "Next.js", hint: "Simple marketing pages" },
91
+ { value: "payload", label: "Payload CMS", hint: "Full CMS for content" }
92
+ ];
88
93
  marketingSite = await p.select({
89
94
  message: "Add a marketing site?",
90
- options: [
91
- { value: "none", label: "None", hint: "Skip marketing site" },
92
- { value: "nextjs", label: "Next.js", hint: "Simple marketing pages" },
93
- { value: "payload", label: "Payload CMS", hint: "Full CMS for content" }
94
- ],
95
+ options: marketingOptions,
95
96
  initialValue: "none"
96
97
  });
97
98
  if (p.isCancel(marketingSite)) throw new Error("cancelled");
98
99
  }
100
+ const componentLibraryOptions = [
101
+ { value: "base", label: "Base UI", hint: "Recommended - Headless, unstyled primitives" },
102
+ { value: "radix", label: "Radix UI", hint: "Full-featured accessible primitives" }
103
+ ];
104
+ const componentLibrary = await p.select({
105
+ message: "Select a component library",
106
+ options: componentLibraryOptions,
107
+ initialValue: "base"
108
+ });
109
+ if (p.isCancel(componentLibrary)) throw new Error("cancelled");
110
+ const styleOptions = [
111
+ { value: "nova", label: "Nova", hint: "Modern, clean design" },
112
+ { value: "vega", label: "Vega", hint: "Bold, vibrant" },
113
+ { value: "maia", label: "Maia", hint: "Soft, organic" },
114
+ { value: "lyra", label: "Lyra", hint: "Minimal, refined" },
115
+ { value: "mira", label: "Mira", hint: "Elegant, classic" }
116
+ ];
117
+ const style = await p.select({
118
+ message: "What style do you want?",
119
+ options: styleOptions,
120
+ initialValue: "nova"
121
+ });
122
+ if (p.isCancel(style)) throw new Error("cancelled");
123
+ const baseColorOptions = [
124
+ { value: "neutral", label: "Neutral" },
125
+ { value: "stone", label: "Stone" },
126
+ { value: "zinc", label: "Zinc" },
127
+ { value: "gray", label: "Gray" }
128
+ ];
99
129
  const baseColor = await p.select({
100
- message: "Base color for theme?",
101
- options: [
102
- { value: "neutral", label: "Neutral" },
103
- { value: "slate", label: "Slate" },
104
- { value: "zinc", label: "Zinc" },
105
- { value: "stone", label: "Stone" },
106
- { value: "gray", label: "Gray" }
107
- ],
130
+ message: "Choose your base color",
131
+ options: baseColorOptions,
108
132
  initialValue: "neutral"
109
133
  });
110
134
  if (p.isCancel(baseColor)) throw new Error("cancelled");
135
+ const themeColorOptions = [
136
+ { value: "neutral", label: "Neutral" },
137
+ { value: "blue", label: "Blue" },
138
+ { value: "green", label: "Green" },
139
+ { value: "purple", label: "Purple" },
140
+ { value: "red", label: "Red" },
141
+ { value: "orange", label: "Orange" },
142
+ { value: "amber", label: "Amber" },
143
+ { value: "cyan", label: "Cyan" },
144
+ { value: "emerald", label: "Emerald" },
145
+ { value: "fuchsia", label: "Fuchsia" },
146
+ { value: "indigo", label: "Indigo" },
147
+ { value: "lime", label: "Lime" },
148
+ { value: "pink", label: "Pink" },
149
+ { value: "rose", label: "Rose" },
150
+ { value: "sky", label: "Sky" },
151
+ { value: "teal", label: "Teal" },
152
+ { value: "violet", label: "Violet" },
153
+ { value: "yellow", label: "Yellow" }
154
+ ];
155
+ const themeColor = await p.select({
156
+ message: "Choose your theme color",
157
+ options: themeColorOptions,
158
+ initialValue: "neutral"
159
+ });
160
+ if (p.isCancel(themeColor)) throw new Error("cancelled");
161
+ const iconLibraryOptions = [
162
+ { value: "hugeicons", label: "HugeIcons", hint: "Recommended - 4000+ icons" },
163
+ { value: "lucide", label: "Lucide", hint: "Popular, lightweight" },
164
+ { value: "tabler", label: "Tabler Icons", hint: "Free, open source" },
165
+ { value: "phosphor", label: "Phosphor Icons", hint: "Flexible, consistent" }
166
+ ];
167
+ const iconLibrary = await p.select({
168
+ message: "Choose your icon library",
169
+ options: iconLibraryOptions,
170
+ initialValue: "hugeicons"
171
+ });
172
+ if (p.isCancel(iconLibrary)) throw new Error("cancelled");
173
+ const fontOptions = [
174
+ { value: "inter", label: "Inter", hint: "Recommended - Clean, modern" },
175
+ { value: "dm-sans", label: "DM Sans", hint: "Geometric, friendly" },
176
+ { value: "figtree", label: "Figtree", hint: "Playful, readable" },
177
+ { value: "outfit", label: "Outfit", hint: "Modern, versatile" },
178
+ { value: "public-sans", label: "Public Sans", hint: "Neutral, accessible" },
179
+ { value: "roboto", label: "Roboto", hint: "Classic, reliable" },
180
+ { value: "raleway", label: "Raleway", hint: "Elegant, stylish" },
181
+ { value: "noto-sans", label: "Noto Sans", hint: "Global, inclusive" },
182
+ { value: "nunito-sans", label: "Nunito Sans", hint: "Rounded, friendly" },
183
+ { value: "jetbrains-mono", label: "JetBrains Mono", hint: "Code-focused" }
184
+ ];
185
+ const font = await p.select({
186
+ message: "Select your font",
187
+ options: fontOptions,
188
+ initialValue: "inter"
189
+ });
190
+ if (p.isCancel(font)) throw new Error("cancelled");
191
+ const menuAccentOptions = [
192
+ { value: "subtle", label: "Subtle", hint: "Muted accent colors" },
193
+ { value: "bold", label: "Bold", hint: "Strong accent colors" }
194
+ ];
195
+ const menuAccent = await p.select({
196
+ message: "Menu accent",
197
+ options: menuAccentOptions,
198
+ initialValue: "subtle"
199
+ });
200
+ if (p.isCancel(menuAccent)) throw new Error("cancelled");
201
+ const menuColorOptions = [
202
+ { value: "default", label: "Default", hint: "Standard colors" },
203
+ { value: "inverted", label: "Inverted", hint: "Dark sidebar" }
204
+ ];
205
+ const menuColor = await p.select({
206
+ message: "Menu color",
207
+ options: menuColorOptions,
208
+ initialValue: "default"
209
+ });
210
+ if (p.isCancel(menuColor)) throw new Error("cancelled");
211
+ const radiusOptions = [
212
+ { value: "default", label: "Default" },
213
+ { value: "none", label: "None", hint: "Sharp corners" },
214
+ { value: "small", label: "Small" },
215
+ { value: "medium", label: "Medium" },
216
+ { value: "large", label: "Large", hint: "Rounded corners" }
217
+ ];
218
+ const radius = await p.select({
219
+ message: "Border radius",
220
+ options: radiusOptions,
221
+ initialValue: "default"
222
+ });
223
+ if (p.isCancel(radius)) throw new Error("cancelled");
224
+ const analyticsOptions = [
225
+ { value: "none", label: "None", hint: "Skip analytics" },
226
+ { value: "posthog", label: "PostHog", hint: "Product analytics" },
227
+ { value: "vercel", label: "Vercel Analytics", hint: "Simple analytics" }
228
+ ];
111
229
  const analytics = await p.select({
112
230
  message: "Analytics provider?",
113
- options: [
114
- { value: "none", label: "None", hint: "Skip analytics" },
115
- { value: "posthog", label: "PostHog", hint: "Product analytics" },
116
- { value: "vercel", label: "Vercel Analytics", hint: "Simple analytics" }
117
- ],
231
+ options: analyticsOptions,
118
232
  initialValue: "none"
119
233
  });
120
234
  if (p.isCancel(analytics)) throw new Error("cancelled");
235
+ const uploadsOptions = [
236
+ { value: "convex-fs", label: "Convex FS", hint: "Built-in Convex storage (Recommended)" },
237
+ { value: "r2", label: "Cloudflare R2", hint: "S3-compatible edge storage" },
238
+ { value: "uploadthing", label: "UploadThing", hint: "Easy file uploads" },
239
+ { value: "s3", label: "AWS S3", hint: "S3-compatible storage" },
240
+ { value: "vercel-blob", label: "Vercel Blob", hint: "Vercel storage" },
241
+ { value: "none", label: "None", hint: "Skip file storage" }
242
+ ];
121
243
  const uploads = await p.select({
122
244
  message: "File storage provider?",
123
- options: [
124
- { value: "convex-fs", label: "Convex FS", hint: "Built-in Convex storage (Recommended)" },
125
- { value: "r2", label: "Cloudflare R2", hint: "S3-compatible edge storage" },
126
- { value: "uploadthing", label: "UploadThing", hint: "Easy file uploads" },
127
- { value: "s3", label: "AWS S3", hint: "S3-compatible storage" },
128
- { value: "vercel-blob", label: "Vercel Blob", hint: "Vercel storage" },
129
- { value: "none", label: "None", hint: "Skip file storage" }
130
- ],
245
+ options: uploadsOptions,
131
246
  initialValue: "convex-fs"
132
247
  });
133
248
  if (p.isCancel(uploads)) throw new Error("cancelled");
249
+ const paymentsOptions = [
250
+ { value: "none", label: "None", hint: "Skip payments" },
251
+ { value: "stripe", label: "Stripe", hint: "Full payment platform" },
252
+ { value: "polar", label: "Polar", hint: "Open source monetization" }
253
+ ];
134
254
  const payments = await p.select({
135
255
  message: "Payment provider?",
136
- options: [
137
- { value: "none", label: "None", hint: "Skip payments" },
138
- { value: "stripe", label: "Stripe", hint: "Full payment platform" },
139
- { value: "polar", label: "Polar", hint: "Open source monetization" }
140
- ],
256
+ options: paymentsOptions,
141
257
  initialValue: "none"
142
258
  });
143
259
  if (p.isCancel(payments)) throw new Error("cancelled");
260
+ const addonsOptions = [
261
+ { value: "rate-limiting", label: "Rate Limiting", hint: "Convex Rate Limiter (Recommended)" },
262
+ { value: "monitoring", label: "Error Monitoring", hint: "Sentry integration" }
263
+ ];
144
264
  const addonsSelected = await p.multiselect({
145
265
  message: "Additional features?",
146
- options: [
147
- {
148
- value: "rate-limiting",
149
- label: "Rate Limiting",
150
- hint: "Convex Rate Limiter (Recommended)"
151
- },
152
- {
153
- value: "monitoring",
154
- label: "Error Monitoring",
155
- hint: "Sentry integration"
156
- }
157
- ],
266
+ options: addonsOptions,
158
267
  required: false
159
268
  });
160
269
  if (p.isCancel(addonsSelected)) throw new Error("cancelled");
@@ -164,7 +273,17 @@ async function runPrompts(projectNameArg, options) {
164
273
  `${pc.cyan("Project:")} ${name}`,
165
274
  `${pc.cyan("Structure:")} ${structure}`,
166
275
  structure === "monorepo" ? `${pc.cyan("Marketing:")} ${marketingSite}` : null,
276
+ "",
277
+ pc.dim("\u2500\u2500\u2500 Design System \u2500\u2500\u2500"),
278
+ `${pc.cyan("Component Library:")} ${componentLibrary}`,
279
+ `${pc.cyan("Style:")} ${style}`,
167
280
  `${pc.cyan("Base Color:")} ${baseColor}`,
281
+ `${pc.cyan("Theme Color:")} ${themeColor}`,
282
+ `${pc.cyan("Icon Library:")} ${iconLibrary}`,
283
+ `${pc.cyan("Font:")} ${font}`,
284
+ `${pc.cyan("Radius:")} ${radius}`,
285
+ "",
286
+ pc.dim("\u2500\u2500\u2500 Integrations \u2500\u2500\u2500"),
168
287
  `${pc.cyan("Analytics:")} ${analytics}`,
169
288
  `${pc.cyan("Storage:")} ${uploads}`,
170
289
  `${pc.cyan("Payments:")} ${payments}`,
@@ -187,14 +306,15 @@ async function runPrompts(projectNameArg, options) {
187
306
  organizations: false
188
307
  },
189
308
  shadcn: {
309
+ componentLibrary,
310
+ style,
190
311
  baseColor,
191
- themeColor: "zinc",
192
- menuAccent: "muted",
193
- iconLibrary: "lucide",
194
- headingFont: "geist",
195
- bodyFont: "geist",
196
- borderRadius: "0.5",
197
- spacingScale: "default"
312
+ themeColor,
313
+ iconLibrary,
314
+ font,
315
+ menuAccent,
316
+ menuColor,
317
+ radius
198
318
  },
199
319
  integrations: {
200
320
  analytics,
@@ -251,6 +371,22 @@ function generateSecret() {
251
371
  return crypto.randomBytes(32).toString("base64");
252
372
  }
253
373
  }
374
+ function buildShadcnPresetUrl(config) {
375
+ const { shadcn } = config;
376
+ const params = new URLSearchParams({
377
+ base: shadcn.componentLibrary,
378
+ style: shadcn.style,
379
+ baseColor: shadcn.baseColor,
380
+ theme: shadcn.themeColor,
381
+ iconLibrary: shadcn.iconLibrary,
382
+ font: shadcn.font,
383
+ menuAccent: shadcn.menuAccent,
384
+ menuColor: shadcn.menuColor,
385
+ radius: shadcn.radius,
386
+ template: "next"
387
+ });
388
+ return `https://ui.shadcn.com/init?${params.toString()}`;
389
+ }
254
390
  async function updateEnvWithSecrets(envPath, secrets) {
255
391
  if (!await fs.pathExists(envPath)) {
256
392
  return;
@@ -314,15 +450,27 @@ async function generateProject(config, options = {}) {
314
450
  spinner.warn("Failed to install dependencies. Run pnpm install manually.");
315
451
  }
316
452
  const shadcnDir = config.structure === "monorepo" ? path2.join(config.targetDir, "apps/web") : config.targetDir;
453
+ const presetUrl = buildShadcnPresetUrl(config);
454
+ spinner.start("Initializing shadcn/ui...");
455
+ try {
456
+ await execa("pnpm", ["dlx", "shadcn@latest", "init", "--yes", "--preset", presetUrl], {
457
+ cwd: shadcnDir,
458
+ stdio: "pipe"
459
+ });
460
+ spinner.succeed("shadcn/ui initialized");
461
+ } catch {
462
+ spinner.warn(`Failed to initialize shadcn. Run manually:
463
+ pnpm dlx shadcn@latest init --preset "${presetUrl}"`);
464
+ }
317
465
  spinner.start("Installing shadcn/ui components...");
318
466
  try {
319
- await execa("pnpm", ["dlx", "shadcn@latest", "add", "--all", "-y"], {
467
+ await execa("pnpm", ["dlx", "shadcn@latest", "add", "--all", "--yes"], {
320
468
  cwd: shadcnDir,
321
469
  stdio: "pipe"
322
470
  });
323
471
  spinner.succeed("shadcn/ui components installed");
324
472
  } catch {
325
- spinner.warn("Failed to install shadcn/ui components. Run pnpm dlx shadcn@latest add --all manually.");
473
+ spinner.warn("Failed to install shadcn/ui components. Run: pnpm dlx shadcn@latest add --all");
326
474
  }
327
475
  if (!skipPrompts) {
328
476
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kofi-stack",
3
- "version": "2.0.23",
3
+ "version": "2.1.1",
4
4
  "description": "Scaffold opinionated full-stack projects with Next.js, Convex, Better-Auth, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,8 +17,8 @@
17
17
  "gradient-string": "^2.0.2",
18
18
  "ora": "^8.0.0",
19
19
  "picocolors": "^1.0.0",
20
- "kofi-stack-types": "^2.0.23",
21
- "kofi-stack-template-generator": "^2.0.23"
20
+ "kofi-stack-template-generator": "^2.1.0",
21
+ "kofi-stack-types": "^2.1.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/fs-extra": "^11.0.4",