create-next-style-guide 0.1.3 → 0.1.4

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
  }
@@ -576,8 +624,8 @@ async function runPrompts(initialName) {
576
624
  }
577
625
 
578
626
  // src/scaffold.ts
579
- import fs27 from "fs-extra";
580
- import path28 from "path";
627
+ import fs28 from "fs-extra";
628
+ import path29 from "path";
581
629
  import * as p2 from "@clack/prompts";
582
630
  import { execSync } from "child_process";
583
631
  import color2 from "picocolors";
@@ -1771,7 +1819,7 @@ CMD ["node", "server.js"]
1771
1819
  }
1772
1820
  var dockerInstaller = {
1773
1821
  id: "docker",
1774
- active: (o) => o.docker,
1822
+ active: (o) => o.deploy === "docker",
1775
1823
  async run({ projectDir, options }) {
1776
1824
  const addon = path11.join(ADDONS_DIR, "docker");
1777
1825
  const pm = getPackageManager(options.packageManager);
@@ -2424,7 +2472,154 @@ export function Button({
2424
2472
  );
2425
2473
  }
2426
2474
  `;
2427
- var RULE10 = `---
2475
+ var INPUT = `import type { ComponentProps } from "react";
2476
+
2477
+ import { cn } from "@/lib/utils";
2478
+
2479
+ export function Input({ className, type, ...props }: ComponentProps<"input">) {
2480
+ return (
2481
+ <input
2482
+ type={type}
2483
+ className={cn(
2484
+ "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",
2485
+ className,
2486
+ )}
2487
+ {...props}
2488
+ />
2489
+ );
2490
+ }
2491
+ `;
2492
+ var CARD = `import type { ComponentProps } from "react";
2493
+
2494
+ import { cn } from "@/lib/utils";
2495
+
2496
+ export function Card({ className, ...props }: ComponentProps<"div">) {
2497
+ return (
2498
+ <div
2499
+ className={cn(
2500
+ "rounded-xl border border-neutral-200 bg-white text-neutral-950 shadow-sm",
2501
+ className,
2502
+ )}
2503
+ {...props}
2504
+ />
2505
+ );
2506
+ }
2507
+
2508
+ export function CardHeader({ className, ...props }: ComponentProps<"div">) {
2509
+ return (
2510
+ <div className={cn("flex flex-col gap-1.5 p-6", className)} {...props} />
2511
+ );
2512
+ }
2513
+
2514
+ export function CardTitle({ className, ...props }: ComponentProps<"h3">) {
2515
+ return (
2516
+ <h3
2517
+ className={cn("font-semibold leading-none tracking-tight", className)}
2518
+ {...props}
2519
+ />
2520
+ );
2521
+ }
2522
+
2523
+ export function CardDescription({ className, ...props }: ComponentProps<"p">) {
2524
+ return (
2525
+ <p className={cn("text-sm text-neutral-500", className)} {...props} />
2526
+ );
2527
+ }
2528
+
2529
+ export function CardContent({ className, ...props }: ComponentProps<"div">) {
2530
+ return <div className={cn("p-6 pt-0", className)} {...props} />;
2531
+ }
2532
+
2533
+ export function CardFooter({ className, ...props }: ComponentProps<"div">) {
2534
+ return (
2535
+ <div className={cn("flex items-center p-6 pt-0", className)} {...props} />
2536
+ );
2537
+ }
2538
+ `;
2539
+ var DIALOG = `"use client";
2540
+
2541
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2542
+ import type { ComponentProps } from "react";
2543
+
2544
+ import { cn } from "@/lib/utils";
2545
+
2546
+ export const Dialog = DialogPrimitive.Root;
2547
+ export const DialogTrigger = DialogPrimitive.Trigger;
2548
+ export const DialogClose = DialogPrimitive.Close;
2549
+ export const DialogPortal = DialogPrimitive.Portal;
2550
+
2551
+ export function DialogOverlay({
2552
+ className,
2553
+ ...props
2554
+ }: ComponentProps<typeof DialogPrimitive.Overlay>) {
2555
+ return (
2556
+ <DialogPrimitive.Overlay
2557
+ className={cn(
2558
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out",
2559
+ className,
2560
+ )}
2561
+ {...props}
2562
+ />
2563
+ );
2564
+ }
2565
+
2566
+ export function DialogContent({
2567
+ className,
2568
+ children,
2569
+ ...props
2570
+ }: ComponentProps<typeof DialogPrimitive.Content>) {
2571
+ return (
2572
+ <DialogPortal>
2573
+ <DialogOverlay />
2574
+ <DialogPrimitive.Content
2575
+ className={cn(
2576
+ "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",
2577
+ className,
2578
+ )}
2579
+ {...props}
2580
+ >
2581
+ {children}
2582
+ </DialogPrimitive.Content>
2583
+ </DialogPortal>
2584
+ );
2585
+ }
2586
+
2587
+ export function DialogHeader({ className, ...props }: ComponentProps<"div">) {
2588
+ return (
2589
+ <div
2590
+ className={cn("flex flex-col gap-1.5 text-center sm:text-left", className)}
2591
+ {...props}
2592
+ />
2593
+ );
2594
+ }
2595
+
2596
+ export function DialogTitle({
2597
+ className,
2598
+ ...props
2599
+ }: ComponentProps<typeof DialogPrimitive.Title>) {
2600
+ return (
2601
+ <DialogPrimitive.Title
2602
+ className={cn("text-lg font-semibold leading-none", className)}
2603
+ {...props}
2604
+ />
2605
+ );
2606
+ }
2607
+
2608
+ export function DialogDescription({
2609
+ className,
2610
+ ...props
2611
+ }: ComponentProps<typeof DialogPrimitive.Description>) {
2612
+ return (
2613
+ <DialogPrimitive.Description
2614
+ className={cn("text-sm text-neutral-500", className)}
2615
+ {...props}
2616
+ />
2617
+ );
2618
+ }
2619
+ `;
2620
+ function ruleFor(set) {
2621
+ const setLine = set === "starter" ? "- Starter set: `button`, `input`, `card`, `dialog`." : "- Minimal set: `button` only.";
2622
+ return `---
2428
2623
  description: shadcn/ui conventions
2429
2624
  globs: components.json,src/components/ui/**,src/lib/utils.ts
2430
2625
  alwaysApply: false
@@ -2433,41 +2628,59 @@ alwaysApply: false
2433
2628
  # shadcn
2434
2629
 
2435
2630
  - Primitives live flat in \`src/components/ui/*\` (CLI-compatible).
2436
- - Starter \`Button\` is included \u2014 add more with \`npx shadcn@latest add \u2026\`.
2631
+ ${setLine}
2632
+ - Add more with \`npx shadcn@latest add \u2026\`.
2437
2633
  - \`cn()\` from \`@/lib/utils\` for class merging.
2438
2634
  - Config: \`components.json\`. Requires Tailwind.
2439
2635
  `;
2636
+ }
2440
2637
  var shadcnInstaller = {
2441
2638
  id: "shadcn",
2442
2639
  active: (o) => o.ui === "shadcn" && o.styling === "tailwind",
2443
2640
  async run({ projectDir, options }) {
2444
2641
  const iconLibrary = options.icons === "tabler" ? "tabler" : "lucide";
2642
+ const set = options.shadcnSet;
2643
+ const uiDir = path21.join(projectDir, "src", "components", "ui");
2445
2644
  await fs20.writeFile(
2446
2645
  path21.join(projectDir, "components.json"),
2447
2646
  componentsJson(iconLibrary),
2448
2647
  "utf8"
2449
2648
  );
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"));
2649
+ await fs20.ensureDir(uiDir);
2650
+ await fs20.writeFile(path21.join(uiDir, "button.tsx"), BUTTON, "utf8");
2651
+ if (set === "starter") {
2652
+ await fs20.writeFile(path21.join(uiDir, "input.tsx"), INPUT, "utf8");
2653
+ await fs20.writeFile(path21.join(uiDir, "card.tsx"), CARD, "utf8");
2654
+ await fs20.writeFile(path21.join(uiDir, "dialog.tsx"), DIALOG, "utf8");
2655
+ }
2656
+ await fs20.remove(path21.join(uiDir, ".gitkeep"));
2457
2657
  await fs20.remove(path21.join(projectDir, "src", "components", ".gitkeep"));
2458
2658
  await fs20.writeFile(
2459
2659
  path21.join(projectDir, ".cursor", "rules", "shadcn.mdc"),
2460
- RULE10,
2660
+ ruleFor(set),
2461
2661
  "utf8"
2462
2662
  );
2663
+ await fs20.outputFile(
2664
+ path21.join(projectDir, "docs", "style-guide", "ui", "shadcn.md"),
2665
+ `# shadcn/ui
2666
+
2667
+ Set: **${set}**
2668
+ ${set === "starter" ? "- `button` \xB7 `input` \xB7 `card` \xB7 `dialog`\n" : "- `button`\n"}
2669
+ Add more: \`npx shadcn@latest add \u2026\`
2670
+ `
2671
+ );
2463
2672
  if (options.icons === "none") {
2464
2673
  await addDependencies(projectDir, {
2465
2674
  "lucide-react": "^0.469.0"
2466
2675
  });
2467
2676
  }
2468
- await addDependencies(projectDir, {
2677
+ const deps = {
2469
2678
  "class-variance-authority": "^0.7.1"
2470
- });
2679
+ };
2680
+ if (set === "starter") {
2681
+ deps["@radix-ui/react-dialog"] = "^1.1.4";
2682
+ }
2683
+ await addDependencies(projectDir, deps);
2471
2684
  }
2472
2685
  };
2473
2686
 
@@ -2651,9 +2864,10 @@ function stackLines(options) {
2651
2864
  options.eslint ? "Enhanced ESLint" : "Minimal ESLint",
2652
2865
  options.prettier ? "Prettier" : "No Prettier",
2653
2866
  options.husky ? "Husky + lint-staged" : "No git hooks",
2654
- options.docker ? "Docker standalone image" : "No Docker",
2867
+ options.ui === "shadcn" ? `shadcn set: ${options.shadcnSet}` : null,
2868
+ options.deploy === "docker" ? "Deploy: Docker standalone image" : options.deploy === "vercel" ? "Deploy: Vercel" : "No deploy target",
2655
2869
  options.ci ? "GitHub Actions CI" : "No CI workflow"
2656
- ];
2870
+ ].filter((line) => line !== null);
2657
2871
  }
2658
2872
  function welcomeCardSource(options) {
2659
2873
  const items = stackLines(options).map((line) => ` <li>${escapeJsx(line)}</li>`).join("\n");
@@ -2712,6 +2926,7 @@ function docsIndex(options) {
2712
2926
  "Layers: app / components / lib / utils"
2713
2927
  ],
2714
2928
  ["[Project structure](overview/project-structure.md)", "Folder map"],
2929
+ ["[Environment](ops/env.md)", "`.env.example` \xB7 staging \xB7 production"],
2715
2930
  ["[HTTP client](http/client.md)", "`apiFetch` helper"],
2716
2931
  [
2717
2932
  `[Validation](validation/${options.validation}.md)`,
@@ -2755,7 +2970,10 @@ function docsIndex(options) {
2755
2970
  rows.push(["Tailwind", "See `.cursor/rules/tailwind.mdc`"]);
2756
2971
  }
2757
2972
  if (options.ui === "shadcn") {
2758
- rows.push(["shadcn/ui", "See `.cursor/rules/shadcn.mdc` + `components.json`"]);
2973
+ rows.push([
2974
+ "[shadcn/ui](ui/shadcn.md)",
2975
+ `set=${options.shadcnSet}`
2976
+ ]);
2759
2977
  }
2760
2978
  if (options.styling === "mui") {
2761
2979
  rows.push(["MUI", "See `.cursor/rules/mui.mdc`"]);
@@ -2784,9 +3002,12 @@ function docsIndex(options) {
2784
3002
  if (options.husky) {
2785
3003
  rows.push(["[Husky](tooling/husky.md)", "Pre-commit hooks"]);
2786
3004
  }
2787
- if (options.docker) {
3005
+ if (options.deploy === "docker") {
2788
3006
  rows.push(["[Docker](ops/docker.md)", "Standalone production image"]);
2789
3007
  }
3008
+ if (options.deploy === "vercel") {
3009
+ rows.push(["[Vercel](ops/vercel.md)", "vercel.json + env notes"]);
3010
+ }
2790
3011
  if (options.ci) {
2791
3012
  rows.push(["[CI](ops/ci.md)", "GitHub Actions"]);
2792
3013
  }
@@ -2977,7 +3198,7 @@ export function cn(...inputs: ClassValue[]) {
2977
3198
  return twMerge(clsx(inputs));
2978
3199
  }
2979
3200
  `;
2980
- var RULE11 = `---
3201
+ var RULE10 = `---
2981
3202
  description: Tailwind CSS v4 conventions
2982
3203
  globs: src/app/globals.css,src/**/*.{tsx,css},postcss.config.mjs
2983
3204
  alwaysApply: false
@@ -3010,7 +3231,7 @@ var tailwindInstaller = {
3010
3231
  );
3011
3232
  await fs23.writeFile(
3012
3233
  path24.join(projectDir, ".cursor", "rules", "tailwind.mdc"),
3013
- RULE11,
3234
+ RULE10,
3014
3235
  "utf8"
3015
3236
  );
3016
3237
  await addDependencies(projectDir, {
@@ -3058,7 +3279,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
3058
3279
  return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
3059
3280
  }
3060
3281
  `;
3061
- var RULE12 = `---
3282
+ var RULE11 = `---
3062
3283
  description: TanStack Query conventions
3063
3284
  globs: src/providers/**,src/hooks/**,src/endpoints/**,src/**/*query*.{ts,tsx},src/**/use-*.{ts,tsx}
3064
3285
  alwaysApply: false
@@ -3091,7 +3312,7 @@ var tanstackQueryInstaller = {
3091
3312
  await fs24.ensureDir(ruleDir);
3092
3313
  await fs24.writeFile(
3093
3314
  path25.join(ruleDir, "tanstack-query.mdc"),
3094
- RULE12,
3315
+ RULE11,
3095
3316
  "utf8"
3096
3317
  );
3097
3318
  }
@@ -3373,6 +3594,53 @@ Forms: \`@hookform/resolvers/${options.validation}\` when using RHF.
3373
3594
  }
3374
3595
  };
3375
3596
 
3597
+ // src/installers/vercel.ts
3598
+ import fs27 from "fs-extra";
3599
+ import path28 from "path";
3600
+ var VERCEL_JSON = `{
3601
+ "framework": "nextjs",
3602
+ "regions": ["iad1"]
3603
+ }
3604
+ `;
3605
+ var RULE12 = `---
3606
+ description: Vercel deployment conventions
3607
+ globs: vercel.json
3608
+ alwaysApply: false
3609
+ ---
3610
+
3611
+ # Vercel
3612
+
3613
+ - Config: \`vercel.json\` (framework Next.js).
3614
+ - Set \`NEXT_PUBLIC_APP_URL\` / \`NEXT_PUBLIC_API_URL\` (and \`AUTH_SECRET\` if Auth.js) in the Vercel project env.
3615
+ - Prefer Preview env for staging; Production for live.
3616
+ - Do not commit \`.vercel\` or production secrets.
3617
+ `;
3618
+ var vercelInstaller = {
3619
+ id: "vercel",
3620
+ active: (o) => o.deploy === "vercel",
3621
+ async run({ projectDir }) {
3622
+ await fs27.writeFile(
3623
+ path28.join(projectDir, "vercel.json"),
3624
+ VERCEL_JSON,
3625
+ "utf8"
3626
+ );
3627
+ await fs27.writeFile(
3628
+ path28.join(projectDir, ".cursor", "rules", "vercel.mdc"),
3629
+ RULE12,
3630
+ "utf8"
3631
+ );
3632
+ await fs27.outputFile(
3633
+ path28.join(projectDir, "docs", "style-guide", "ops", "vercel.md"),
3634
+ `# Vercel
3635
+
3636
+ - \`vercel.json\` \u2014 Next.js framework hint
3637
+ - Link the repo in the Vercel dashboard (or \`npx vercel\`)
3638
+ - Env: copy from \`.env.production.example\` into Project \u2192 Settings \u2192 Environment Variables
3639
+ `
3640
+ );
3641
+ }
3642
+ };
3643
+
3376
3644
  // src/installers/index.ts
3377
3645
  var installers = [
3378
3646
  packageManagerInstaller,
@@ -3396,6 +3664,7 @@ var installers = [
3396
3664
  prettierInstaller,
3397
3665
  huskyInstaller,
3398
3666
  dockerInstaller,
3667
+ vercelInstaller,
3399
3668
  ciInstaller,
3400
3669
  appProvidersInstaller,
3401
3670
  surfaceInstaller
@@ -3412,10 +3681,10 @@ async function runInstallers(ctx) {
3412
3681
 
3413
3682
  // src/scaffold.ts
3414
3683
  async function scaffold(options) {
3415
- const projectDir = path28.resolve(process.cwd(), options.name);
3684
+ const projectDir = path29.resolve(process.cwd(), options.name);
3416
3685
  const pm = getPackageManager(options.packageManager);
3417
- if (await fs27.pathExists(projectDir)) {
3418
- const entries = await fs27.readdir(projectDir);
3686
+ if (await fs28.pathExists(projectDir)) {
3687
+ const entries = await fs28.readdir(projectDir);
3419
3688
  if (entries.length > 0) {
3420
3689
  throw new Error(
3421
3690
  `Target directory already exists and is not empty: ${projectDir}`
@@ -3424,20 +3693,20 @@ async function scaffold(options) {
3424
3693
  }
3425
3694
  const spin = p2.spinner();
3426
3695
  spin.start("Copying template");
3427
- if (!await fs27.pathExists(TEMPLATE_DIR)) {
3696
+ if (!await fs28.pathExists(TEMPLATE_DIR)) {
3428
3697
  spin.stop("Template missing");
3429
3698
  throw new Error(`Template directory not found at ${TEMPLATE_DIR}`);
3430
3699
  }
3431
- await fs27.copy(TEMPLATE_DIR, projectDir, {
3700
+ await fs28.copy(TEMPLATE_DIR, projectDir, {
3432
3701
  filter: (src) => {
3433
- const base = path28.basename(src);
3702
+ const base = path29.basename(src);
3434
3703
  return base !== "node_modules" && base !== ".next" && base !== "dist";
3435
3704
  }
3436
3705
  });
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 });
3706
+ const gitignoreSrc = path29.join(projectDir, "gitignore");
3707
+ const gitignoreDest = path29.join(projectDir, ".gitignore");
3708
+ if (await fs28.pathExists(gitignoreSrc)) {
3709
+ await fs28.move(gitignoreSrc, gitignoreDest, { overwrite: true });
3441
3710
  }
3442
3711
  spin.message("Applying project tokens");
3443
3712
  await replaceTokensInTree(projectDir, {
@@ -3473,8 +3742,8 @@ async function scaffold(options) {
3473
3742
  }
3474
3743
 
3475
3744
  // src/upgrade.ts
3476
- import fs28 from "fs-extra";
3477
- import path29 from "path";
3745
+ import fs29 from "fs-extra";
3746
+ import path30 from "path";
3478
3747
  import * as p3 from "@clack/prompts";
3479
3748
  import color3 from "picocolors";
3480
3749
  var TRACKED = [
@@ -3484,17 +3753,17 @@ var TRACKED = [
3484
3753
  ".cursor/rules/forms.mdc"
3485
3754
  ];
3486
3755
  function rel(projectDir, file) {
3487
- return path29.join(projectDir, file);
3756
+ return path30.join(projectDir, file);
3488
3757
  }
3489
3758
  function templatePath(file) {
3490
- return path29.join(TEMPLATE_DIR, file);
3759
+ return path30.join(TEMPLATE_DIR, file);
3491
3760
  }
3492
3761
  async function runUpgrade(options) {
3493
3762
  const { projectDir, apply } = options;
3494
- if (!await fs28.pathExists(path29.join(projectDir, "package.json"))) {
3763
+ if (!await fs29.pathExists(path30.join(projectDir, "package.json"))) {
3495
3764
  throw new Error(`Not an app directory (missing package.json): ${projectDir}`);
3496
3765
  }
3497
- if (!await fs28.pathExists(TEMPLATE_DIR)) {
3766
+ if (!await fs29.pathExists(TEMPLATE_DIR)) {
3498
3767
  throw new Error(`Template missing at ${TEMPLATE_DIR}`);
3499
3768
  }
3500
3769
  p3.intro(color3.bgCyan(color3.black(" next-style-guide upgrade ")));
@@ -3507,14 +3776,14 @@ async function runUpgrade(options) {
3507
3776
  for (const file of TRACKED) {
3508
3777
  const from = templatePath(file);
3509
3778
  const to = rel(projectDir, file);
3510
- if (!await fs28.pathExists(from)) {
3779
+ if (!await fs29.pathExists(from)) {
3511
3780
  p3.log.warn(`Template missing ${file}`);
3512
3781
  continue;
3513
3782
  }
3514
- const next = await fs28.readFile(from, "utf8");
3783
+ const next = await fs29.readFile(from, "utf8");
3515
3784
  let outgoing = next;
3516
- if (file === "AGENTS.md" && await fs28.pathExists(to)) {
3517
- const current2 = await fs28.readFile(to, "utf8");
3785
+ if (file === "AGENTS.md" && await fs29.pathExists(to)) {
3786
+ const current2 = await fs29.readFile(to, "utf8");
3518
3787
  const nameMatch = current2.match(/^# Agents \((.+)\)$/m);
3519
3788
  if (nameMatch?.[1]) {
3520
3789
  outgoing = outgoing.replace("__PROJECT_NAME__", nameMatch[1]);
@@ -3527,18 +3796,18 @@ async function runUpgrade(options) {
3527
3796
  }
3528
3797
  }
3529
3798
  }
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)) {
3799
+ outgoing = outgoing.replaceAll("__PROJECT_NAME__", path30.basename(projectDir)).replaceAll("__PACKAGE_MANAGER__", "pnpm").replaceAll("__PROJECT_SLUG__", path30.basename(projectDir));
3800
+ if (!await fs29.pathExists(to)) {
3532
3801
  missing += 1;
3533
3802
  p3.log.warn(`missing ${file}`);
3534
3803
  if (apply) {
3535
- await fs28.ensureDir(path29.dirname(to));
3536
- await fs28.writeFile(to, outgoing, "utf8");
3804
+ await fs29.ensureDir(path30.dirname(to));
3805
+ await fs29.writeFile(to, outgoing, "utf8");
3537
3806
  p3.log.success(`wrote ${file}`);
3538
3807
  }
3539
3808
  continue;
3540
3809
  }
3541
- const current = await fs28.readFile(to, "utf8");
3810
+ const current = await fs29.readFile(to, "utf8");
3542
3811
  if (current === outgoing) {
3543
3812
  same += 1;
3544
3813
  p3.log.step(color3.dim(`same ${file}`));
@@ -3547,7 +3816,7 @@ async function runUpgrade(options) {
3547
3816
  changed += 1;
3548
3817
  p3.log.warn(`differs ${file}`);
3549
3818
  if (apply) {
3550
- await fs28.writeFile(to, outgoing, "utf8");
3819
+ await fs29.writeFile(to, outgoing, "utf8");
3551
3820
  p3.log.success(`updated ${file}`);
3552
3821
  }
3553
3822
  }
@@ -3569,6 +3838,8 @@ var VALIDATION_VALUES = /* @__PURE__ */ new Set(["zod", "yup"]);
3569
3838
  var AUTH_VALUES = /* @__PURE__ */ new Set(["none", "jwt", "authjs"]);
3570
3839
  var STORE_VALUES = /* @__PURE__ */ new Set(["none", "zustand", "redux"]);
3571
3840
  var PM_VALUES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn"]);
3841
+ var SHADCN_SET_VALUES = /* @__PURE__ */ new Set(["minimal", "starter"]);
3842
+ var DEPLOY_VALUES = /* @__PURE__ */ new Set(["none", "vercel", "docker"]);
3572
3843
  var PRESET_VALUES = /* @__PURE__ */ new Set([
3573
3844
  "custom",
3574
3845
  "minimal",
@@ -3604,7 +3875,8 @@ function parseArgs(argv) {
3604
3875
  noInstall: false,
3605
3876
  yes: false,
3606
3877
  upgrade: false,
3607
- apply: false
3878
+ apply: false,
3879
+ verbose: false
3608
3880
  };
3609
3881
  for (let i = 0; i < args.length; i += 1) {
3610
3882
  const arg = args[i];
@@ -3621,6 +3893,10 @@ function parseArgs(argv) {
3621
3893
  out.apply = false;
3622
3894
  continue;
3623
3895
  }
3896
+ if (arg === "--verbose" || arg === "-V") {
3897
+ out.verbose = true;
3898
+ continue;
3899
+ }
3624
3900
  if (arg === "--no-install") {
3625
3901
  out.noInstall = true;
3626
3902
  continue;
@@ -3645,7 +3921,6 @@ function parseArgs(argv) {
3645
3921
  ["eslint", "--eslint", "--no-eslint"],
3646
3922
  ["prettier", "--prettier", "--no-prettier"],
3647
3923
  ["husky", "--husky", "--no-husky"],
3648
- ["docker", "--docker", "--no-docker"],
3649
3924
  ["ci", "--ci", "--no-ci"],
3650
3925
  ["shadcn", "--shadcn", "--no-shadcn"]
3651
3926
  ];
@@ -3713,6 +3988,29 @@ function parseArgs(argv) {
3713
3988
  i = icons.nextIndex;
3714
3989
  continue;
3715
3990
  }
3991
+ const shadcnSet = readChoice(arg, "--shadcn-set", SHADCN_SET_VALUES, args, i);
3992
+ if (shadcnSet) {
3993
+ out.shadcnSet = shadcnSet.value;
3994
+ out.shadcn = true;
3995
+ i = shadcnSet.nextIndex;
3996
+ continue;
3997
+ }
3998
+ const deploy = readChoice(arg, "--deploy", DEPLOY_VALUES, args, i);
3999
+ if (deploy) {
4000
+ out.deploy = deploy.value;
4001
+ i = deploy.nextIndex;
4002
+ continue;
4003
+ }
4004
+ if (arg === "--docker") {
4005
+ out.deploy = "docker";
4006
+ continue;
4007
+ }
4008
+ if (arg === "--no-docker") {
4009
+ if (out.deploy === void 0 || out.deploy === "docker") {
4010
+ out.deploy = "none";
4011
+ }
4012
+ continue;
4013
+ }
3716
4014
  if (!arg.startsWith("-") && !out.name) {
3717
4015
  out.name = arg;
3718
4016
  }
@@ -3736,6 +4034,7 @@ Options:
3736
4034
  --store none|zustand|redux
3737
4035
  --styling none|tailwind|mui
3738
4036
  --shadcn | --no-shadcn
4037
+ --shadcn-set minimal|starter
3739
4038
  --dark-mode | --no-dark-mode
3740
4039
  --validation zod|yup
3741
4040
  --auth none|jwt|authjs
@@ -3750,8 +4049,10 @@ Options:
3750
4049
  --eslint | --no-eslint
3751
4050
  --prettier | --no-prettier
3752
4051
  --husky | --no-husky
3753
- --docker | --no-docker
4052
+ --deploy none|vercel|docker
4053
+ --docker | --no-docker (alias for --deploy docker / none)
3754
4054
  --ci | --no-ci
4055
+ --verbose, -V advisory tips (e.g. Query vs store)
3755
4056
  --help, -h
3756
4057
 
3757
4058
  Upgrade:
@@ -3767,6 +4068,10 @@ function applyFlagOverrides(options, flags) {
3767
4068
  if (flags.icons !== void 0) next.icons = flags.icons;
3768
4069
  if (flags.styling !== void 0) next.styling = flags.styling;
3769
4070
  if (flags.shadcn !== void 0) next.ui = flags.shadcn ? "shadcn" : "none";
4071
+ if (flags.shadcnSet !== void 0) {
4072
+ next.shadcnSet = flags.shadcnSet;
4073
+ next.ui = "shadcn";
4074
+ }
3770
4075
  if (flags.validation !== void 0) next.validation = flags.validation;
3771
4076
  if (flags.auth !== void 0) next.auth = flags.auth;
3772
4077
  if (flags.store !== void 0) next.store = flags.store;
@@ -3783,14 +4088,14 @@ function applyFlagOverrides(options, flags) {
3783
4088
  if (flags.eslint !== void 0) next.eslint = flags.eslint;
3784
4089
  if (flags.prettier !== void 0) next.prettier = flags.prettier;
3785
4090
  if (flags.husky !== void 0) next.husky = flags.husky;
3786
- if (flags.docker !== void 0) next.docker = flags.docker;
4091
+ if (flags.deploy !== void 0) next.deploy = flags.deploy;
3787
4092
  if (flags.ci !== void 0) next.ci = flags.ci;
3788
4093
  return normalizeOptions(next);
3789
4094
  }
3790
4095
  async function main() {
3791
4096
  const flags = parseArgs(process.argv);
3792
4097
  if (flags.upgrade) {
3793
- const projectDir2 = path30.resolve(process.cwd(), flags.name ?? ".");
4098
+ const projectDir2 = path31.resolve(process.cwd(), flags.name ?? ".");
3794
4099
  await runUpgrade({ projectDir: projectDir2, apply: flags.apply });
3795
4100
  return;
3796
4101
  }
@@ -3813,8 +4118,8 @@ async function main() {
3813
4118
  `Non-interactive: ${options.name} (${options.slug})`,
3814
4119
  flags.preset ? `preset=${flags.preset}` : null,
3815
4120
  `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}`
4121
+ `styling=${options.styling} ui=${options.ui}${options.ui === "shadcn" ? `:${options.shadcnSet}` : ""} auth=${options.auth} store=${options.store}`,
4122
+ `deploy=${options.deploy} endpoints=${options.endpoints} seo=${options.seo} i18n=${options.i18n} testing=${options.testing}`
3818
4123
  ].filter(Boolean).join(" \xB7 ")
3819
4124
  );
3820
4125
  } else {
@@ -3822,16 +4127,16 @@ async function main() {
3822
4127
  options.noInstall = flags.noInstall;
3823
4128
  options = applyFlagOverrides(options, flags);
3824
4129
  }
3825
- const warnings = collectOptionWarnings(options);
4130
+ const warnings = collectOptionWarnings(options, { verbose: flags.verbose });
3826
4131
  for (const warning of warnings) {
3827
4132
  p4.log.warn(warning);
3828
4133
  }
3829
4134
  const projectDir = await scaffold(options);
3830
4135
  const pm = getPackageManager(options.packageManager);
3831
4136
  const nextSteps = [
3832
- `cd ${path30.relative(process.cwd(), projectDir) || "."}`,
4137
+ `cd ${path31.relative(process.cwd(), projectDir) || "."}`,
3833
4138
  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,
4139
+ options.ui === "shadcn" && options.shadcnSet === "minimal" ? "npx shadcn@latest add input # or re-scaffold with --shadcn-set starter" : null,
3835
4140
  options.endpoints ? `${pm.runScript("endpoints:generate")} # when API schema is up` : null,
3836
4141
  options.testing ? pm.runScript("test") : null,
3837
4142
  pm.runScript("dev")