create-next-style-guide 0.1.3 → 0.1.5

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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import * as p4 from "@clack/prompts";
5
5
  import color4 from "picocolors";
6
- import path30 from "path";
6
+ import path31 from "path";
7
7
 
8
8
  // src/constants.ts
9
9
  import path from "path";
@@ -78,6 +78,7 @@ var PRESET_SEEDS = {
78
78
  auth: "none",
79
79
  styling: "none",
80
80
  ui: "none",
81
+ shadcnSet: "minimal",
81
82
  data: "rsc",
82
83
  store: "none",
83
84
  icons: "none",
@@ -90,7 +91,7 @@ var PRESET_SEEDS = {
90
91
  eslint: false,
91
92
  prettier: false,
92
93
  husky: false,
93
- docker: false,
94
+ deploy: "none",
94
95
  ci: false,
95
96
  testing: false,
96
97
  playwright: false
@@ -100,6 +101,7 @@ var PRESET_SEEDS = {
100
101
  auth: "jwt",
101
102
  styling: "tailwind",
102
103
  ui: "shadcn",
104
+ shadcnSet: "starter",
103
105
  data: "tanstack-query",
104
106
  store: "zustand",
105
107
  icons: "lucide",
@@ -112,7 +114,7 @@ var PRESET_SEEDS = {
112
114
  eslint: true,
113
115
  prettier: true,
114
116
  husky: true,
115
- docker: false,
117
+ deploy: "none",
116
118
  ci: true,
117
119
  testing: true,
118
120
  playwright: false
@@ -122,6 +124,7 @@ var PRESET_SEEDS = {
122
124
  auth: "none",
123
125
  styling: "tailwind",
124
126
  ui: "shadcn",
127
+ shadcnSet: "minimal",
125
128
  data: "rsc",
126
129
  store: "none",
127
130
  icons: "lucide",
@@ -134,7 +137,7 @@ var PRESET_SEEDS = {
134
137
  eslint: true,
135
138
  prettier: true,
136
139
  husky: true,
137
- docker: false,
140
+ deploy: "vercel",
138
141
  ci: true,
139
142
  testing: true,
140
143
  playwright: false
@@ -144,6 +147,7 @@ var PRESET_SEEDS = {
144
147
  auth: "none",
145
148
  styling: "tailwind",
146
149
  ui: "shadcn",
150
+ shadcnSet: "starter",
147
151
  data: "tanstack-query",
148
152
  store: "zustand",
149
153
  icons: "lucide",
@@ -156,7 +160,7 @@ var PRESET_SEEDS = {
156
160
  eslint: true,
157
161
  prettier: true,
158
162
  husky: true,
159
- docker: false,
163
+ deploy: "none",
160
164
  ci: true,
161
165
  testing: true,
162
166
  playwright: false
@@ -166,19 +170,20 @@ function describePreset(id) {
166
170
  const seed = PRESET_SEEDS[id];
167
171
  return [
168
172
  `styling=${seed.styling}`,
169
- seed.ui === "shadcn" ? "shadcn" : null,
173
+ seed.ui === "shadcn" ? `shadcn:${seed.shadcnSet}` : null,
170
174
  `auth=${seed.auth}`,
171
175
  seed.store !== "none" ? `store=${seed.store}` : null,
172
176
  `validation=${seed.validation}`,
173
177
  seed.endpoints ? "endpoints" : null,
174
178
  seed.data === "tanstack-query" ? "query" : null,
175
179
  seed.darkMode ? "dark" : null,
180
+ seed.deploy !== "none" ? `deploy=${seed.deploy}` : null,
176
181
  seed.seo ? "seo" : null,
177
182
  seed.i18n ? "i18n" : null,
178
183
  seed.testing ? "vitest" : null
179
184
  ].filter(Boolean).join(" \xB7 ");
180
185
  }
181
- function collectOptionWarnings(options) {
186
+ function collectOptionWarnings(options, { verbose = false } = {}) {
182
187
  const warnings = [];
183
188
  if (options.auth === "jwt" && !options.endpoints) {
184
189
  warnings.push(
@@ -199,9 +204,9 @@ function collectOptionWarnings(options) {
199
204
  if (options.playwright && !options.testing) {
200
205
  warnings.push("Playwright was requested without Vitest \u2014 enabling testing.");
201
206
  }
202
- if (options.store !== "none" && options.data === "tanstack-query") {
207
+ if (verbose && options.store !== "none" && options.data === "tanstack-query") {
203
208
  warnings.push(
204
- "Client store + TanStack Query: use Query for server cache; store for UI/session client state only."
209
+ "Tip: Query = server cache; store = UI/session only. See docs/style-guide/overview/architecture.md"
205
210
  );
206
211
  }
207
212
  return warnings;
@@ -217,6 +222,7 @@ var DEFAULT_OPTIONS = {
217
222
  auth: "none",
218
223
  styling: "none",
219
224
  ui: "none",
225
+ shadcnSet: "minimal",
220
226
  data: "rsc",
221
227
  store: "none",
222
228
  icons: "none",
@@ -230,7 +236,7 @@ var DEFAULT_OPTIONS = {
230
236
  eslint: true,
231
237
  prettier: true,
232
238
  husky: true,
233
- docker: false,
239
+ deploy: "none",
234
240
  ci: true,
235
241
  testing: true,
236
242
  playwright: false,
@@ -245,6 +251,9 @@ function normalizeOptions(options) {
245
251
  if (next.styling !== "tailwind" && next.ui === "shadcn") {
246
252
  next = { ...next, ui: "none" };
247
253
  }
254
+ if (next.ui !== "shadcn") {
255
+ next = { ...next, shadcnSet: "minimal" };
256
+ }
248
257
  if (next.styling !== "tailwind" && next.darkMode) {
249
258
  next = { ...next, darkMode: false };
250
259
  }
@@ -283,6 +292,7 @@ async function promptCustomOptions(base) {
283
292
  })
284
293
  );
285
294
  let ui = "none";
295
+ let shadcnSet = "minimal";
286
296
  let darkMode = false;
287
297
  if (styling === "tailwind") {
288
298
  const useShadcn = assertNotCancel(
@@ -292,6 +302,26 @@ async function promptCustomOptions(base) {
292
302
  })
293
303
  );
294
304
  ui = useShadcn ? "shadcn" : "none";
305
+ if (useShadcn) {
306
+ shadcnSet = assertNotCancel(
307
+ await p.select({
308
+ message: "shadcn component set?",
309
+ options: [
310
+ {
311
+ value: "minimal",
312
+ label: "Minimal",
313
+ hint: "Button only"
314
+ },
315
+ {
316
+ value: "starter",
317
+ label: "Starter",
318
+ hint: "Button \xB7 Input \xB7 Card \xB7 Dialog"
319
+ }
320
+ ],
321
+ initialValue: base.shadcnSet
322
+ })
323
+ );
324
+ }
295
325
  darkMode = assertNotCancel(
296
326
  await p.confirm({
297
327
  message: "Add dark mode (next-themes)?",
@@ -439,10 +469,27 @@ async function promptCustomOptions(base) {
439
469
  initialValue: base.husky
440
470
  })
441
471
  );
442
- const docker = assertNotCancel(
443
- await p.confirm({
444
- message: "Add Docker production image?",
445
- initialValue: base.docker
472
+ const deploy = assertNotCancel(
473
+ await p.select({
474
+ message: "Deployment target?",
475
+ options: [
476
+ {
477
+ value: "none",
478
+ label: "None",
479
+ hint: "no deploy files"
480
+ },
481
+ {
482
+ value: "vercel",
483
+ label: "Vercel",
484
+ hint: "vercel.json + docs"
485
+ },
486
+ {
487
+ value: "docker",
488
+ label: "Docker",
489
+ hint: "standalone production image"
490
+ }
491
+ ],
492
+ initialValue: base.deploy
446
493
  })
447
494
  );
448
495
  const ci = assertNotCancel(
@@ -455,6 +502,7 @@ async function promptCustomOptions(base) {
455
502
  ...base,
456
503
  styling,
457
504
  ui,
505
+ shadcnSet,
458
506
  darkMode,
459
507
  icons,
460
508
  validation,
@@ -470,7 +518,7 @@ async function promptCustomOptions(base) {
470
518
  eslint,
471
519
  prettier,
472
520
  husky,
473
- docker,
521
+ deploy,
474
522
  ci
475
523
  };
476
524
  }
@@ -482,9 +530,10 @@ async function runPrompts(initialName) {
482
530
  placeholder: "my-app",
483
531
  initialValue: initialName ?? "my-app",
484
532
  validate(value) {
485
- if (!value.trim()) return "Name is required.";
533
+ const next = value ?? "";
534
+ if (!next.trim()) return "Name is required.";
486
535
  try {
487
- slugify(value);
536
+ slugify(next);
488
537
  } catch (error) {
489
538
  return error instanceof Error ? error.message : "Invalid name.";
490
539
  }
@@ -576,12 +625,36 @@ async function runPrompts(initialName) {
576
625
  }
577
626
 
578
627
  // src/scaffold.ts
579
- import fs27 from "fs-extra";
580
- import path28 from "path";
628
+ import fs28 from "fs-extra";
629
+ import path29 from "path";
630
+ import { execFileSync } from "child_process";
581
631
  import * as p2 from "@clack/prompts";
582
- import { execSync } from "child_process";
583
632
  import color2 from "picocolors";
584
633
 
634
+ // src/helpers/run-package-install.ts
635
+ import { spawn } from "child_process";
636
+ function runPackageInstall(projectDir, pm) {
637
+ return new Promise((resolve, reject) => {
638
+ const child = spawn(pm.id, pm.installArgs, {
639
+ cwd: projectDir,
640
+ stdio: "inherit",
641
+ env: process.env,
642
+ shell: process.platform === "win32"
643
+ });
644
+ child.on("error", reject);
645
+ child.on("close", (code) => {
646
+ if (code === 0) {
647
+ resolve();
648
+ return;
649
+ }
650
+ reject(new Error(`${pm.id} ${pm.installArgs.join(" ")} exited with ${code}`));
651
+ });
652
+ });
653
+ }
654
+ function yieldUi() {
655
+ return new Promise((resolve) => setImmediate(resolve));
656
+ }
657
+
585
658
  // src/helpers/replace-tokens.ts
586
659
  import fs from "fs-extra";
587
660
  import path2 from "path";
@@ -1771,7 +1844,7 @@ CMD ["node", "server.js"]
1771
1844
  }
1772
1845
  var dockerInstaller = {
1773
1846
  id: "docker",
1774
- active: (o) => o.docker,
1847
+ active: (o) => o.deploy === "docker",
1775
1848
  async run({ projectDir, options }) {
1776
1849
  const addon = path11.join(ADDONS_DIR, "docker");
1777
1850
  const pm = getPackageManager(options.packageManager);
@@ -1916,7 +1989,7 @@ var huskyInstaller = {
1916
1989
  },
1917
1990
  "devDependencies"
1918
1991
  );
1919
- await addScripts(projectDir, { prepare: "husky" });
1992
+ await addScripts(projectDir, { prepare: "husky || true" });
1920
1993
  const tsHooks = [];
1921
1994
  if (options.prettier) tsHooks.push("prettier --write");
1922
1995
  if (options.eslint) tsHooks.push("eslint --fix");
@@ -2424,7 +2497,154 @@ export function Button({
2424
2497
  );
2425
2498
  }
2426
2499
  `;
2427
- var RULE10 = `---
2500
+ var INPUT = `import type { ComponentProps } from "react";
2501
+
2502
+ import { cn } from "@/lib/utils";
2503
+
2504
+ export function Input({ className, type, ...props }: ComponentProps<"input">) {
2505
+ return (
2506
+ <input
2507
+ type={type}
2508
+ className={cn(
2509
+ "flex h-9 w-full rounded-md border border-neutral-300 bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 disabled:cursor-not-allowed disabled:opacity-50",
2510
+ className,
2511
+ )}
2512
+ {...props}
2513
+ />
2514
+ );
2515
+ }
2516
+ `;
2517
+ var CARD = `import type { ComponentProps } from "react";
2518
+
2519
+ import { cn } from "@/lib/utils";
2520
+
2521
+ export function Card({ className, ...props }: ComponentProps<"div">) {
2522
+ return (
2523
+ <div
2524
+ className={cn(
2525
+ "rounded-xl border border-neutral-200 bg-white text-neutral-950 shadow-sm",
2526
+ className,
2527
+ )}
2528
+ {...props}
2529
+ />
2530
+ );
2531
+ }
2532
+
2533
+ export function CardHeader({ className, ...props }: ComponentProps<"div">) {
2534
+ return (
2535
+ <div className={cn("flex flex-col gap-1.5 p-6", className)} {...props} />
2536
+ );
2537
+ }
2538
+
2539
+ export function CardTitle({ className, ...props }: ComponentProps<"h3">) {
2540
+ return (
2541
+ <h3
2542
+ className={cn("font-semibold leading-none tracking-tight", className)}
2543
+ {...props}
2544
+ />
2545
+ );
2546
+ }
2547
+
2548
+ export function CardDescription({ className, ...props }: ComponentProps<"p">) {
2549
+ return (
2550
+ <p className={cn("text-sm text-neutral-500", className)} {...props} />
2551
+ );
2552
+ }
2553
+
2554
+ export function CardContent({ className, ...props }: ComponentProps<"div">) {
2555
+ return <div className={cn("p-6 pt-0", className)} {...props} />;
2556
+ }
2557
+
2558
+ export function CardFooter({ className, ...props }: ComponentProps<"div">) {
2559
+ return (
2560
+ <div className={cn("flex items-center p-6 pt-0", className)} {...props} />
2561
+ );
2562
+ }
2563
+ `;
2564
+ var DIALOG = `"use client";
2565
+
2566
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2567
+ import type { ComponentProps } from "react";
2568
+
2569
+ import { cn } from "@/lib/utils";
2570
+
2571
+ export const Dialog = DialogPrimitive.Root;
2572
+ export const DialogTrigger = DialogPrimitive.Trigger;
2573
+ export const DialogClose = DialogPrimitive.Close;
2574
+ export const DialogPortal = DialogPrimitive.Portal;
2575
+
2576
+ export function DialogOverlay({
2577
+ className,
2578
+ ...props
2579
+ }: ComponentProps<typeof DialogPrimitive.Overlay>) {
2580
+ return (
2581
+ <DialogPrimitive.Overlay
2582
+ className={cn(
2583
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out",
2584
+ className,
2585
+ )}
2586
+ {...props}
2587
+ />
2588
+ );
2589
+ }
2590
+
2591
+ export function DialogContent({
2592
+ className,
2593
+ children,
2594
+ ...props
2595
+ }: ComponentProps<typeof DialogPrimitive.Content>) {
2596
+ return (
2597
+ <DialogPortal>
2598
+ <DialogOverlay />
2599
+ <DialogPrimitive.Content
2600
+ className={cn(
2601
+ "fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 sm:rounded-xl",
2602
+ className,
2603
+ )}
2604
+ {...props}
2605
+ >
2606
+ {children}
2607
+ </DialogPrimitive.Content>
2608
+ </DialogPortal>
2609
+ );
2610
+ }
2611
+
2612
+ export function DialogHeader({ className, ...props }: ComponentProps<"div">) {
2613
+ return (
2614
+ <div
2615
+ className={cn("flex flex-col gap-1.5 text-center sm:text-left", className)}
2616
+ {...props}
2617
+ />
2618
+ );
2619
+ }
2620
+
2621
+ export function DialogTitle({
2622
+ className,
2623
+ ...props
2624
+ }: ComponentProps<typeof DialogPrimitive.Title>) {
2625
+ return (
2626
+ <DialogPrimitive.Title
2627
+ className={cn("text-lg font-semibold leading-none", className)}
2628
+ {...props}
2629
+ />
2630
+ );
2631
+ }
2632
+
2633
+ export function DialogDescription({
2634
+ className,
2635
+ ...props
2636
+ }: ComponentProps<typeof DialogPrimitive.Description>) {
2637
+ return (
2638
+ <DialogPrimitive.Description
2639
+ className={cn("text-sm text-neutral-500", className)}
2640
+ {...props}
2641
+ />
2642
+ );
2643
+ }
2644
+ `;
2645
+ function ruleFor(set) {
2646
+ const setLine = set === "starter" ? "- Starter set: `button`, `input`, `card`, `dialog`." : "- Minimal set: `button` only.";
2647
+ return `---
2428
2648
  description: shadcn/ui conventions
2429
2649
  globs: components.json,src/components/ui/**,src/lib/utils.ts
2430
2650
  alwaysApply: false
@@ -2433,41 +2653,59 @@ alwaysApply: false
2433
2653
  # shadcn
2434
2654
 
2435
2655
  - Primitives live flat in \`src/components/ui/*\` (CLI-compatible).
2436
- - Starter \`Button\` is included \u2014 add more with \`npx shadcn@latest add \u2026\`.
2656
+ ${setLine}
2657
+ - Add more with \`npx shadcn@latest add \u2026\`.
2437
2658
  - \`cn()\` from \`@/lib/utils\` for class merging.
2438
2659
  - Config: \`components.json\`. Requires Tailwind.
2439
2660
  `;
2661
+ }
2440
2662
  var shadcnInstaller = {
2441
2663
  id: "shadcn",
2442
2664
  active: (o) => o.ui === "shadcn" && o.styling === "tailwind",
2443
2665
  async run({ projectDir, options }) {
2444
2666
  const iconLibrary = options.icons === "tabler" ? "tabler" : "lucide";
2667
+ const set = options.shadcnSet;
2668
+ const uiDir = path21.join(projectDir, "src", "components", "ui");
2445
2669
  await fs20.writeFile(
2446
2670
  path21.join(projectDir, "components.json"),
2447
2671
  componentsJson(iconLibrary),
2448
2672
  "utf8"
2449
2673
  );
2450
- await fs20.ensureDir(path21.join(projectDir, "src", "components", "ui"));
2451
- await fs20.writeFile(
2452
- path21.join(projectDir, "src", "components", "ui", "button.tsx"),
2453
- BUTTON,
2454
- "utf8"
2455
- );
2456
- await fs20.remove(path21.join(projectDir, "src", "components", "ui", ".gitkeep"));
2674
+ await fs20.ensureDir(uiDir);
2675
+ await fs20.writeFile(path21.join(uiDir, "button.tsx"), BUTTON, "utf8");
2676
+ if (set === "starter") {
2677
+ await fs20.writeFile(path21.join(uiDir, "input.tsx"), INPUT, "utf8");
2678
+ await fs20.writeFile(path21.join(uiDir, "card.tsx"), CARD, "utf8");
2679
+ await fs20.writeFile(path21.join(uiDir, "dialog.tsx"), DIALOG, "utf8");
2680
+ }
2681
+ await fs20.remove(path21.join(uiDir, ".gitkeep"));
2457
2682
  await fs20.remove(path21.join(projectDir, "src", "components", ".gitkeep"));
2458
2683
  await fs20.writeFile(
2459
2684
  path21.join(projectDir, ".cursor", "rules", "shadcn.mdc"),
2460
- RULE10,
2685
+ ruleFor(set),
2461
2686
  "utf8"
2462
2687
  );
2688
+ await fs20.outputFile(
2689
+ path21.join(projectDir, "docs", "style-guide", "ui", "shadcn.md"),
2690
+ `# shadcn/ui
2691
+
2692
+ Set: **${set}**
2693
+ ${set === "starter" ? "- `button` \xB7 `input` \xB7 `card` \xB7 `dialog`\n" : "- `button`\n"}
2694
+ Add more: \`npx shadcn@latest add \u2026\`
2695
+ `
2696
+ );
2463
2697
  if (options.icons === "none") {
2464
2698
  await addDependencies(projectDir, {
2465
2699
  "lucide-react": "^0.469.0"
2466
2700
  });
2467
2701
  }
2468
- await addDependencies(projectDir, {
2702
+ const deps = {
2469
2703
  "class-variance-authority": "^0.7.1"
2470
- });
2704
+ };
2705
+ if (set === "starter") {
2706
+ deps["@radix-ui/react-dialog"] = "^1.1.4";
2707
+ }
2708
+ await addDependencies(projectDir, deps);
2471
2709
  }
2472
2710
  };
2473
2711
 
@@ -2651,9 +2889,10 @@ function stackLines(options) {
2651
2889
  options.eslint ? "Enhanced ESLint" : "Minimal ESLint",
2652
2890
  options.prettier ? "Prettier" : "No Prettier",
2653
2891
  options.husky ? "Husky + lint-staged" : "No git hooks",
2654
- options.docker ? "Docker standalone image" : "No Docker",
2892
+ options.ui === "shadcn" ? `shadcn set: ${options.shadcnSet}` : null,
2893
+ options.deploy === "docker" ? "Deploy: Docker standalone image" : options.deploy === "vercel" ? "Deploy: Vercel" : "No deploy target",
2655
2894
  options.ci ? "GitHub Actions CI" : "No CI workflow"
2656
- ];
2895
+ ].filter((line) => line !== null);
2657
2896
  }
2658
2897
  function welcomeCardSource(options) {
2659
2898
  const items = stackLines(options).map((line) => ` <li>${escapeJsx(line)}</li>`).join("\n");
@@ -2712,6 +2951,7 @@ function docsIndex(options) {
2712
2951
  "Layers: app / components / lib / utils"
2713
2952
  ],
2714
2953
  ["[Project structure](overview/project-structure.md)", "Folder map"],
2954
+ ["[Environment](ops/env.md)", "`.env.example` \xB7 staging \xB7 production"],
2715
2955
  ["[HTTP client](http/client.md)", "`apiFetch` helper"],
2716
2956
  [
2717
2957
  `[Validation](validation/${options.validation}.md)`,
@@ -2755,7 +2995,10 @@ function docsIndex(options) {
2755
2995
  rows.push(["Tailwind", "See `.cursor/rules/tailwind.mdc`"]);
2756
2996
  }
2757
2997
  if (options.ui === "shadcn") {
2758
- rows.push(["shadcn/ui", "See `.cursor/rules/shadcn.mdc` + `components.json`"]);
2998
+ rows.push([
2999
+ "[shadcn/ui](ui/shadcn.md)",
3000
+ `set=${options.shadcnSet}`
3001
+ ]);
2759
3002
  }
2760
3003
  if (options.styling === "mui") {
2761
3004
  rows.push(["MUI", "See `.cursor/rules/mui.mdc`"]);
@@ -2784,9 +3027,12 @@ function docsIndex(options) {
2784
3027
  if (options.husky) {
2785
3028
  rows.push(["[Husky](tooling/husky.md)", "Pre-commit hooks"]);
2786
3029
  }
2787
- if (options.docker) {
3030
+ if (options.deploy === "docker") {
2788
3031
  rows.push(["[Docker](ops/docker.md)", "Standalone production image"]);
2789
3032
  }
3033
+ if (options.deploy === "vercel") {
3034
+ rows.push(["[Vercel](ops/vercel.md)", "vercel.json + env notes"]);
3035
+ }
2790
3036
  if (options.ci) {
2791
3037
  rows.push(["[CI](ops/ci.md)", "GitHub Actions"]);
2792
3038
  }
@@ -2977,7 +3223,7 @@ export function cn(...inputs: ClassValue[]) {
2977
3223
  return twMerge(clsx(inputs));
2978
3224
  }
2979
3225
  `;
2980
- var RULE11 = `---
3226
+ var RULE10 = `---
2981
3227
  description: Tailwind CSS v4 conventions
2982
3228
  globs: src/app/globals.css,src/**/*.{tsx,css},postcss.config.mjs
2983
3229
  alwaysApply: false
@@ -3010,7 +3256,7 @@ var tailwindInstaller = {
3010
3256
  );
3011
3257
  await fs23.writeFile(
3012
3258
  path24.join(projectDir, ".cursor", "rules", "tailwind.mdc"),
3013
- RULE11,
3259
+ RULE10,
3014
3260
  "utf8"
3015
3261
  );
3016
3262
  await addDependencies(projectDir, {
@@ -3058,7 +3304,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
3058
3304
  return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
3059
3305
  }
3060
3306
  `;
3061
- var RULE12 = `---
3307
+ var RULE11 = `---
3062
3308
  description: TanStack Query conventions
3063
3309
  globs: src/providers/**,src/hooks/**,src/endpoints/**,src/**/*query*.{ts,tsx},src/**/use-*.{ts,tsx}
3064
3310
  alwaysApply: false
@@ -3091,7 +3337,7 @@ var tanstackQueryInstaller = {
3091
3337
  await fs24.ensureDir(ruleDir);
3092
3338
  await fs24.writeFile(
3093
3339
  path25.join(ruleDir, "tanstack-query.mdc"),
3094
- RULE12,
3340
+ RULE11,
3095
3341
  "utf8"
3096
3342
  );
3097
3343
  }
@@ -3373,6 +3619,53 @@ Forms: \`@hookform/resolvers/${options.validation}\` when using RHF.
3373
3619
  }
3374
3620
  };
3375
3621
 
3622
+ // src/installers/vercel.ts
3623
+ import fs27 from "fs-extra";
3624
+ import path28 from "path";
3625
+ var VERCEL_JSON = `{
3626
+ "framework": "nextjs",
3627
+ "regions": ["iad1"]
3628
+ }
3629
+ `;
3630
+ var RULE12 = `---
3631
+ description: Vercel deployment conventions
3632
+ globs: vercel.json
3633
+ alwaysApply: false
3634
+ ---
3635
+
3636
+ # Vercel
3637
+
3638
+ - Config: \`vercel.json\` (framework Next.js).
3639
+ - Set \`NEXT_PUBLIC_APP_URL\` / \`NEXT_PUBLIC_API_URL\` (and \`AUTH_SECRET\` if Auth.js) in the Vercel project env.
3640
+ - Prefer Preview env for staging; Production for live.
3641
+ - Do not commit \`.vercel\` or production secrets.
3642
+ `;
3643
+ var vercelInstaller = {
3644
+ id: "vercel",
3645
+ active: (o) => o.deploy === "vercel",
3646
+ async run({ projectDir }) {
3647
+ await fs27.writeFile(
3648
+ path28.join(projectDir, "vercel.json"),
3649
+ VERCEL_JSON,
3650
+ "utf8"
3651
+ );
3652
+ await fs27.writeFile(
3653
+ path28.join(projectDir, ".cursor", "rules", "vercel.mdc"),
3654
+ RULE12,
3655
+ "utf8"
3656
+ );
3657
+ await fs27.outputFile(
3658
+ path28.join(projectDir, "docs", "style-guide", "ops", "vercel.md"),
3659
+ `# Vercel
3660
+
3661
+ - \`vercel.json\` \u2014 Next.js framework hint
3662
+ - Link the repo in the Vercel dashboard (or \`npx vercel\`)
3663
+ - Env: copy from \`.env.production.example\` into Project \u2192 Settings \u2192 Environment Variables
3664
+ `
3665
+ );
3666
+ }
3667
+ };
3668
+
3376
3669
  // src/installers/index.ts
3377
3670
  var installers = [
3378
3671
  packageManagerInstaller,
@@ -3396,14 +3689,23 @@ var installers = [
3396
3689
  prettierInstaller,
3397
3690
  huskyInstaller,
3398
3691
  dockerInstaller,
3692
+ vercelInstaller,
3399
3693
  ciInstaller,
3400
3694
  appProvidersInstaller,
3401
3695
  surfaceInstaller
3402
3696
  ];
3403
- async function runInstallers(ctx) {
3697
+ async function runInstallers(ctx, onProgress) {
3698
+ const active = installers.filter(
3699
+ (installer) => installer.active(ctx.options)
3700
+ );
3404
3701
  const ran = [];
3405
- for (const installer of installers) {
3406
- if (!installer.active(ctx.options)) continue;
3702
+ for (let i = 0; i < active.length; i += 1) {
3703
+ const installer = active[i];
3704
+ await onProgress?.({
3705
+ id: installer.id,
3706
+ index: i + 1,
3707
+ total: active.length
3708
+ });
3407
3709
  await installer.run(ctx);
3408
3710
  ran.push(installer.id);
3409
3711
  }
@@ -3412,10 +3714,10 @@ async function runInstallers(ctx) {
3412
3714
 
3413
3715
  // src/scaffold.ts
3414
3716
  async function scaffold(options) {
3415
- const projectDir = path28.resolve(process.cwd(), options.name);
3717
+ const projectDir = path29.resolve(process.cwd(), options.name);
3416
3718
  const pm = getPackageManager(options.packageManager);
3417
- if (await fs27.pathExists(projectDir)) {
3418
- const entries = await fs27.readdir(projectDir);
3719
+ if (await fs28.pathExists(projectDir)) {
3720
+ const entries = await fs28.readdir(projectDir);
3419
3721
  if (entries.length > 0) {
3420
3722
  throw new Error(
3421
3723
  `Target directory already exists and is not empty: ${projectDir}`
@@ -3424,46 +3726,68 @@ async function scaffold(options) {
3424
3726
  }
3425
3727
  const spin = p2.spinner();
3426
3728
  spin.start("Copying template");
3427
- if (!await fs27.pathExists(TEMPLATE_DIR)) {
3729
+ await yieldUi();
3730
+ if (!await fs28.pathExists(TEMPLATE_DIR)) {
3428
3731
  spin.stop("Template missing");
3429
3732
  throw new Error(`Template directory not found at ${TEMPLATE_DIR}`);
3430
3733
  }
3431
- await fs27.copy(TEMPLATE_DIR, projectDir, {
3734
+ await fs28.copy(TEMPLATE_DIR, projectDir, {
3432
3735
  filter: (src) => {
3433
- const base = path28.basename(src);
3736
+ const base = path29.basename(src);
3434
3737
  return base !== "node_modules" && base !== ".next" && base !== "dist";
3435
3738
  }
3436
3739
  });
3437
- const gitignoreSrc = path28.join(projectDir, "gitignore");
3438
- const gitignoreDest = path28.join(projectDir, ".gitignore");
3439
- if (await fs27.pathExists(gitignoreSrc)) {
3440
- await fs27.move(gitignoreSrc, gitignoreDest, { overwrite: true });
3740
+ const gitignoreSrc = path29.join(projectDir, "gitignore");
3741
+ const gitignoreDest = path29.join(projectDir, ".gitignore");
3742
+ if (await fs28.pathExists(gitignoreSrc)) {
3743
+ await fs28.move(gitignoreSrc, gitignoreDest, { overwrite: true });
3441
3744
  }
3442
3745
  spin.message("Applying project tokens");
3746
+ await yieldUi();
3443
3747
  await replaceTokensInTree(projectDir, {
3444
3748
  __PROJECT_NAME__: options.name,
3445
3749
  __PROJECT_SLUG__: options.slug,
3446
3750
  __PACKAGE_MANAGER__: pm.id
3447
3751
  });
3448
- spin.message("Running installers");
3449
- const ran = await runInstallers({ projectDir, options });
3752
+ const ran = await runInstallers({ projectDir, options }, async (step) => {
3753
+ spin.message(`Installer (${step.index}/${step.total}): ${step.id}`);
3754
+ await yieldUi();
3755
+ });
3756
+ spin.stop(
3757
+ ran.length ? `Scaffold ready \xB7 ${ran.length} installer${ran.length === 1 ? "" : "s"}` : "Scaffold ready \xB7 base template"
3758
+ );
3450
3759
  if (!options.noInstall) {
3451
- spin.message(`Installing dependencies (${pm.id})`);
3760
+ if (options.husky) {
3761
+ try {
3762
+ execFileSync("git", ["init"], {
3763
+ cwd: projectDir,
3764
+ stdio: "ignore"
3765
+ });
3766
+ } catch {
3767
+ }
3768
+ }
3769
+ p2.log.step(
3770
+ `Installing dependencies with ${color2.cyan(pm.id)} \u2014 live output below\u2026`
3771
+ );
3452
3772
  try {
3453
- execSync(`${pm.id} ${pm.installArgs.join(" ")}`, {
3454
- cwd: projectDir,
3455
- stdio: "ignore"
3456
- });
3457
- } catch {
3773
+ await runPackageInstall(projectDir, pm);
3774
+ p2.log.success("Dependencies installed");
3775
+ } catch (error) {
3458
3776
  if (pm.id !== "npm") {
3459
- spin.message(`${pm.id} failed \u2014 trying npm`);
3460
- execSync("npm install", { cwd: projectDir, stdio: "ignore" });
3777
+ p2.log.warn(`${pm.id} failed \u2014 retrying with npm\u2026`);
3778
+ const npm = getPackageManager("npm");
3779
+ await runPackageInstall(projectDir, npm);
3780
+ p2.log.success("Dependencies installed (npm)");
3461
3781
  } else {
3462
- throw new Error("npm install failed");
3782
+ const message = error instanceof Error ? error.message : String(error);
3783
+ throw new Error(`Dependency install failed: ${message}`);
3463
3784
  }
3464
3785
  }
3786
+ } else {
3787
+ p2.log.info(
3788
+ color2.dim(`Skipped install \xB7 run ${pm.id} install in the project folder`)
3789
+ );
3465
3790
  }
3466
- spin.stop("Project ready");
3467
3791
  if (ran.length) {
3468
3792
  p2.log.info(`Installers: ${ran.join(", ")}`);
3469
3793
  } else {
@@ -3473,8 +3797,8 @@ async function scaffold(options) {
3473
3797
  }
3474
3798
 
3475
3799
  // src/upgrade.ts
3476
- import fs28 from "fs-extra";
3477
- import path29 from "path";
3800
+ import fs29 from "fs-extra";
3801
+ import path30 from "path";
3478
3802
  import * as p3 from "@clack/prompts";
3479
3803
  import color3 from "picocolors";
3480
3804
  var TRACKED = [
@@ -3484,17 +3808,17 @@ var TRACKED = [
3484
3808
  ".cursor/rules/forms.mdc"
3485
3809
  ];
3486
3810
  function rel(projectDir, file) {
3487
- return path29.join(projectDir, file);
3811
+ return path30.join(projectDir, file);
3488
3812
  }
3489
3813
  function templatePath(file) {
3490
- return path29.join(TEMPLATE_DIR, file);
3814
+ return path30.join(TEMPLATE_DIR, file);
3491
3815
  }
3492
3816
  async function runUpgrade(options) {
3493
3817
  const { projectDir, apply } = options;
3494
- if (!await fs28.pathExists(path29.join(projectDir, "package.json"))) {
3818
+ if (!await fs29.pathExists(path30.join(projectDir, "package.json"))) {
3495
3819
  throw new Error(`Not an app directory (missing package.json): ${projectDir}`);
3496
3820
  }
3497
- if (!await fs28.pathExists(TEMPLATE_DIR)) {
3821
+ if (!await fs29.pathExists(TEMPLATE_DIR)) {
3498
3822
  throw new Error(`Template missing at ${TEMPLATE_DIR}`);
3499
3823
  }
3500
3824
  p3.intro(color3.bgCyan(color3.black(" next-style-guide upgrade ")));
@@ -3507,14 +3831,14 @@ async function runUpgrade(options) {
3507
3831
  for (const file of TRACKED) {
3508
3832
  const from = templatePath(file);
3509
3833
  const to = rel(projectDir, file);
3510
- if (!await fs28.pathExists(from)) {
3834
+ if (!await fs29.pathExists(from)) {
3511
3835
  p3.log.warn(`Template missing ${file}`);
3512
3836
  continue;
3513
3837
  }
3514
- const next = await fs28.readFile(from, "utf8");
3838
+ const next = await fs29.readFile(from, "utf8");
3515
3839
  let outgoing = next;
3516
- if (file === "AGENTS.md" && await fs28.pathExists(to)) {
3517
- const current2 = await fs28.readFile(to, "utf8");
3840
+ if (file === "AGENTS.md" && await fs29.pathExists(to)) {
3841
+ const current2 = await fs29.readFile(to, "utf8");
3518
3842
  const nameMatch = current2.match(/^# Agents \((.+)\)$/m);
3519
3843
  if (nameMatch?.[1]) {
3520
3844
  outgoing = outgoing.replace("__PROJECT_NAME__", nameMatch[1]);
@@ -3527,18 +3851,18 @@ async function runUpgrade(options) {
3527
3851
  }
3528
3852
  }
3529
3853
  }
3530
- outgoing = outgoing.replaceAll("__PROJECT_NAME__", path29.basename(projectDir)).replaceAll("__PACKAGE_MANAGER__", "pnpm").replaceAll("__PROJECT_SLUG__", path29.basename(projectDir));
3531
- if (!await fs28.pathExists(to)) {
3854
+ outgoing = outgoing.replaceAll("__PROJECT_NAME__", path30.basename(projectDir)).replaceAll("__PACKAGE_MANAGER__", "pnpm").replaceAll("__PROJECT_SLUG__", path30.basename(projectDir));
3855
+ if (!await fs29.pathExists(to)) {
3532
3856
  missing += 1;
3533
3857
  p3.log.warn(`missing ${file}`);
3534
3858
  if (apply) {
3535
- await fs28.ensureDir(path29.dirname(to));
3536
- await fs28.writeFile(to, outgoing, "utf8");
3859
+ await fs29.ensureDir(path30.dirname(to));
3860
+ await fs29.writeFile(to, outgoing, "utf8");
3537
3861
  p3.log.success(`wrote ${file}`);
3538
3862
  }
3539
3863
  continue;
3540
3864
  }
3541
- const current = await fs28.readFile(to, "utf8");
3865
+ const current = await fs29.readFile(to, "utf8");
3542
3866
  if (current === outgoing) {
3543
3867
  same += 1;
3544
3868
  p3.log.step(color3.dim(`same ${file}`));
@@ -3547,7 +3871,7 @@ async function runUpgrade(options) {
3547
3871
  changed += 1;
3548
3872
  p3.log.warn(`differs ${file}`);
3549
3873
  if (apply) {
3550
- await fs28.writeFile(to, outgoing, "utf8");
3874
+ await fs29.writeFile(to, outgoing, "utf8");
3551
3875
  p3.log.success(`updated ${file}`);
3552
3876
  }
3553
3877
  }
@@ -3569,6 +3893,8 @@ var VALIDATION_VALUES = /* @__PURE__ */ new Set(["zod", "yup"]);
3569
3893
  var AUTH_VALUES = /* @__PURE__ */ new Set(["none", "jwt", "authjs"]);
3570
3894
  var STORE_VALUES = /* @__PURE__ */ new Set(["none", "zustand", "redux"]);
3571
3895
  var PM_VALUES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn"]);
3896
+ var SHADCN_SET_VALUES = /* @__PURE__ */ new Set(["minimal", "starter"]);
3897
+ var DEPLOY_VALUES = /* @__PURE__ */ new Set(["none", "vercel", "docker"]);
3572
3898
  var PRESET_VALUES = /* @__PURE__ */ new Set([
3573
3899
  "custom",
3574
3900
  "minimal",
@@ -3604,7 +3930,8 @@ function parseArgs(argv) {
3604
3930
  noInstall: false,
3605
3931
  yes: false,
3606
3932
  upgrade: false,
3607
- apply: false
3933
+ apply: false,
3934
+ verbose: false
3608
3935
  };
3609
3936
  for (let i = 0; i < args.length; i += 1) {
3610
3937
  const arg = args[i];
@@ -3621,6 +3948,10 @@ function parseArgs(argv) {
3621
3948
  out.apply = false;
3622
3949
  continue;
3623
3950
  }
3951
+ if (arg === "--verbose" || arg === "-V") {
3952
+ out.verbose = true;
3953
+ continue;
3954
+ }
3624
3955
  if (arg === "--no-install") {
3625
3956
  out.noInstall = true;
3626
3957
  continue;
@@ -3645,7 +3976,6 @@ function parseArgs(argv) {
3645
3976
  ["eslint", "--eslint", "--no-eslint"],
3646
3977
  ["prettier", "--prettier", "--no-prettier"],
3647
3978
  ["husky", "--husky", "--no-husky"],
3648
- ["docker", "--docker", "--no-docker"],
3649
3979
  ["ci", "--ci", "--no-ci"],
3650
3980
  ["shadcn", "--shadcn", "--no-shadcn"]
3651
3981
  ];
@@ -3713,6 +4043,29 @@ function parseArgs(argv) {
3713
4043
  i = icons.nextIndex;
3714
4044
  continue;
3715
4045
  }
4046
+ const shadcnSet = readChoice(arg, "--shadcn-set", SHADCN_SET_VALUES, args, i);
4047
+ if (shadcnSet) {
4048
+ out.shadcnSet = shadcnSet.value;
4049
+ out.shadcn = true;
4050
+ i = shadcnSet.nextIndex;
4051
+ continue;
4052
+ }
4053
+ const deploy = readChoice(arg, "--deploy", DEPLOY_VALUES, args, i);
4054
+ if (deploy) {
4055
+ out.deploy = deploy.value;
4056
+ i = deploy.nextIndex;
4057
+ continue;
4058
+ }
4059
+ if (arg === "--docker") {
4060
+ out.deploy = "docker";
4061
+ continue;
4062
+ }
4063
+ if (arg === "--no-docker") {
4064
+ if (out.deploy === void 0 || out.deploy === "docker") {
4065
+ out.deploy = "none";
4066
+ }
4067
+ continue;
4068
+ }
3716
4069
  if (!arg.startsWith("-") && !out.name) {
3717
4070
  out.name = arg;
3718
4071
  }
@@ -3736,6 +4089,7 @@ Options:
3736
4089
  --store none|zustand|redux
3737
4090
  --styling none|tailwind|mui
3738
4091
  --shadcn | --no-shadcn
4092
+ --shadcn-set minimal|starter
3739
4093
  --dark-mode | --no-dark-mode
3740
4094
  --validation zod|yup
3741
4095
  --auth none|jwt|authjs
@@ -3750,8 +4104,10 @@ Options:
3750
4104
  --eslint | --no-eslint
3751
4105
  --prettier | --no-prettier
3752
4106
  --husky | --no-husky
3753
- --docker | --no-docker
4107
+ --deploy none|vercel|docker
4108
+ --docker | --no-docker (alias for --deploy docker / none)
3754
4109
  --ci | --no-ci
4110
+ --verbose, -V advisory tips (e.g. Query vs store)
3755
4111
  --help, -h
3756
4112
 
3757
4113
  Upgrade:
@@ -3767,6 +4123,10 @@ function applyFlagOverrides(options, flags) {
3767
4123
  if (flags.icons !== void 0) next.icons = flags.icons;
3768
4124
  if (flags.styling !== void 0) next.styling = flags.styling;
3769
4125
  if (flags.shadcn !== void 0) next.ui = flags.shadcn ? "shadcn" : "none";
4126
+ if (flags.shadcnSet !== void 0) {
4127
+ next.shadcnSet = flags.shadcnSet;
4128
+ next.ui = "shadcn";
4129
+ }
3770
4130
  if (flags.validation !== void 0) next.validation = flags.validation;
3771
4131
  if (flags.auth !== void 0) next.auth = flags.auth;
3772
4132
  if (flags.store !== void 0) next.store = flags.store;
@@ -3783,14 +4143,14 @@ function applyFlagOverrides(options, flags) {
3783
4143
  if (flags.eslint !== void 0) next.eslint = flags.eslint;
3784
4144
  if (flags.prettier !== void 0) next.prettier = flags.prettier;
3785
4145
  if (flags.husky !== void 0) next.husky = flags.husky;
3786
- if (flags.docker !== void 0) next.docker = flags.docker;
4146
+ if (flags.deploy !== void 0) next.deploy = flags.deploy;
3787
4147
  if (flags.ci !== void 0) next.ci = flags.ci;
3788
4148
  return normalizeOptions(next);
3789
4149
  }
3790
4150
  async function main() {
3791
4151
  const flags = parseArgs(process.argv);
3792
4152
  if (flags.upgrade) {
3793
- const projectDir2 = path30.resolve(process.cwd(), flags.name ?? ".");
4153
+ const projectDir2 = path31.resolve(process.cwd(), flags.name ?? ".");
3794
4154
  await runUpgrade({ projectDir: projectDir2, apply: flags.apply });
3795
4155
  return;
3796
4156
  }
@@ -3813,8 +4173,8 @@ async function main() {
3813
4173
  `Non-interactive: ${options.name} (${options.slug})`,
3814
4174
  flags.preset ? `preset=${flags.preset}` : null,
3815
4175
  `pm=${options.packageManager}`,
3816
- `styling=${options.styling} ui=${options.ui} auth=${options.auth} store=${options.store}`,
3817
- `endpoints=${options.endpoints} seo=${options.seo} i18n=${options.i18n} testing=${options.testing}`
4176
+ `styling=${options.styling} ui=${options.ui}${options.ui === "shadcn" ? `:${options.shadcnSet}` : ""} auth=${options.auth} store=${options.store}`,
4177
+ `deploy=${options.deploy} endpoints=${options.endpoints} seo=${options.seo} i18n=${options.i18n} testing=${options.testing}`
3818
4178
  ].filter(Boolean).join(" \xB7 ")
3819
4179
  );
3820
4180
  } else {
@@ -3822,16 +4182,16 @@ async function main() {
3822
4182
  options.noInstall = flags.noInstall;
3823
4183
  options = applyFlagOverrides(options, flags);
3824
4184
  }
3825
- const warnings = collectOptionWarnings(options);
4185
+ const warnings = collectOptionWarnings(options, { verbose: flags.verbose });
3826
4186
  for (const warning of warnings) {
3827
4187
  p4.log.warn(warning);
3828
4188
  }
3829
4189
  const projectDir = await scaffold(options);
3830
4190
  const pm = getPackageManager(options.packageManager);
3831
4191
  const nextSteps = [
3832
- `cd ${path30.relative(process.cwd(), projectDir) || "."}`,
4192
+ `cd ${path31.relative(process.cwd(), projectDir) || "."}`,
3833
4193
  options.noInstall ? `${pm.id} ${pm.installArgs.filter((a) => a !== "--ignore-workspace").join(" ")}`.trim() : null,
3834
- options.ui === "shadcn" ? "npx shadcn@latest add input # optional" : null,
4194
+ options.ui === "shadcn" && options.shadcnSet === "minimal" ? "npx shadcn@latest add input # or re-scaffold with --shadcn-set starter" : null,
3835
4195
  options.endpoints ? `${pm.runScript("endpoints:generate")} # when API schema is up` : null,
3836
4196
  options.testing ? pm.runScript("test") : null,
3837
4197
  pm.runScript("dev")