create-swift-rust 0.8.2 → 0.9.2

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
@@ -93,8 +93,9 @@ ${pc.bold("Options")}
93
93
  ${pc.yellow("--js, --javascript")} Use JavaScript
94
94
  ${pc.yellow("--tailwind")} Install Tailwind CSS
95
95
  ${pc.yellow("--no-tailwind")} Skip Tailwind CSS
96
- ${pc.yellow("--shadcn")} Install shadcn-style UI components (default)
97
- ${pc.yellow("--no-shadcn")} Skip shadcn UI components
96
+ ${pc.yellow("--swift-rust-ui")} Install swift-rust ui components (default)
97
+ ${pc.yellow("--shadcn")} Install canonical shadcn/ui components
98
+ ${pc.yellow("--no-ui")} Skip UI components
98
99
  ${pc.yellow("--src-dir")} Use a src/ directory (src/app) (default)
99
100
  ${pc.yellow("--no-src-dir")} Use top-level app/
100
101
  ${pc.yellow("--biome")} Use Biome for linting (default)
@@ -134,10 +135,12 @@ function parseFlags(args) {
134
135
  flags.tailwind = true;
135
136
  else if (a === "--no-tailwind")
136
137
  flags.tailwind = false;
138
+ else if (a === "--swift-rust-ui" || a === "--sr-ui")
139
+ flags.ui = "swift-rust-ui";
137
140
  else if (a === "--shadcn")
138
- flags.useShadcn = true;
139
- else if (a === "--no-shadcn")
140
- flags.useShadcn = false;
141
+ flags.ui = "shadcn";
142
+ else if (a === "--no-ui" || a === "--no-shadcn")
143
+ flags.ui = "none";
141
144
  else if (a === "--src-dir")
142
145
  flags.srcDir = true;
143
146
  else if (a === "--no-src-dir")
@@ -266,7 +269,7 @@ async function askQuestions(flags, positional) {
266
269
  tailwind: true,
267
270
  srcDir: true,
268
271
  importAlias: "@/*",
269
- useShadcn: false,
272
+ ui: "none",
270
273
  install,
271
274
  };
272
275
  }
@@ -344,19 +347,38 @@ async function askQuestions(flags, positional) {
344
347
  return null;
345
348
  }
346
349
  const tailwind = tailwindAnswer === true;
347
- const useShadcnAnswer = typeof flags.useShadcn === "boolean"
348
- ? flags.useShadcn
349
- : yesMode
350
- ? true
351
- : ((await p.confirm({
352
- message: "Would you like to use shadcn-style UI components?",
353
- initialValue: true,
354
- })) ?? false);
355
- if (p.isCancel(useShadcnAnswer)) {
356
- p.cancel("Aborted.");
357
- return null;
350
+ // UI kit: swift-rust ui (our registry, default) · shadcn/ui · none.
351
+ // Only meaningful with Tailwind; without it, skip straight to "none".
352
+ let ui;
353
+ if (typeof flags.ui === "string") {
354
+ ui = flags.ui;
355
+ }
356
+ else if (!tailwind) {
357
+ ui = "none";
358
+ }
359
+ else if (yesMode) {
360
+ ui = "swift-rust-ui";
361
+ }
362
+ else {
363
+ const uiAnswer = (await p.select({
364
+ message: "Which component library would you like to set up?",
365
+ options: [
366
+ {
367
+ value: "swift-rust-ui",
368
+ label: "swift-rust ui",
369
+ hint: "our shadcn-style registry — variant × size × design (3d, glass, neo…), Tailwind v4",
370
+ },
371
+ { value: "shadcn", label: "shadcn/ui", hint: "the canonical shadcn registry" },
372
+ { value: "none", label: "None", hint: "no components — add them later" },
373
+ ],
374
+ initialValue: "swift-rust-ui",
375
+ }));
376
+ if (p.isCancel(uiAnswer)) {
377
+ p.cancel("Aborted.");
378
+ return null;
379
+ }
380
+ ui = uiAnswer;
358
381
  }
359
- const useShadcn = useShadcnAnswer === true;
360
382
  const srcDirAnswer = typeof flags.srcDir === "boolean"
361
383
  ? flags.srcDir
362
384
  : yesMode
@@ -424,19 +446,21 @@ async function askQuestions(flags, positional) {
424
446
  language,
425
447
  renderer,
426
448
  linter,
427
- tailwind,
449
+ // A UI kit (chosen via flag) requires Tailwind, so it implies --tailwind even
450
+ // if Tailwind wasn't requested. The prompt path already gates ui on tailwind.
451
+ tailwind: tailwind || ui !== "none",
428
452
  srcDir,
429
453
  importAlias,
430
- useShadcn,
454
+ ui,
431
455
  install,
432
456
  };
433
457
  }
434
458
  // Fallback pin used when the npm registry can't be reached at scaffold time.
435
459
  // Kept in sync with the current swift-rust release so a generated project never
436
460
  // advertises an ancient version. `^` still lets it float forward within the major.
437
- const SWIFT_RUST_FALLBACK = "^1.5.1";
461
+ const SWIFT_RUST_FALLBACK = "^1.10.4";
438
462
  // Resolve the newest published swift-rust version so the scaffolded package.json
439
- // reflects reality (e.g. ^1.5.1) instead of a stale literal like ^1.0.0. A caret
463
+ // reflects reality (e.g. ^1.10.4) instead of a stale literal like ^1.0.0. A caret
440
464
  // range already installs the latest matching release, but showing the real number
441
465
  // avoids the "it installed an old version" confusion. Falls back offline.
442
466
  async function resolveSwiftRustVersion() {
@@ -456,7 +480,8 @@ async function resolveSwiftRustVersion() {
456
480
  return SWIFT_RUST_FALLBACK;
457
481
  }
458
482
  async function writeProjectFiles(target, answers) {
459
- const { projectName: rawName, language, renderer, linter, tailwind, srcDir, importAlias, useShadcn, } = answers;
483
+ const { projectName: rawName, language, renderer, linter, tailwind, srcDir, importAlias, ui, } = answers;
484
+ const useUi = ui !== "none";
460
485
  // When scaffolding into the current directory ("."), name the package after
461
486
  // the directory itself (like create-next-app), since "." is not a valid name.
462
487
  const projectName = rawName === "." ? basename(resolve(target)) : rawName;
@@ -469,11 +494,12 @@ async function writeProjectFiles(target, answers) {
469
494
  await mkdir(componentsDir, { recursive: true });
470
495
  await mkdir(libDir, { recursive: true });
471
496
  await mkdir(join(target, "public"), { recursive: true });
472
- if (useShadcn) {
497
+ if (useUi) {
473
498
  await mkdir(uiDir, { recursive: true });
474
499
  }
475
500
  const fileExt = (lang) => (lang === "ts" ? "ts" : "js");
476
501
  const componentExt = (lang) => (lang === "ts" ? "tsx" : "jsx");
502
+ const jsxExt = componentExt(language);
477
503
  const swiftRustVersion = await resolveSwiftRustVersion();
478
504
  const pkg = {
479
505
  name: projectName,
@@ -493,14 +519,19 @@ async function writeProjectFiles(target, answers) {
493
519
  "swift-rust": swiftRustVersion,
494
520
  react: "^19.0.0",
495
521
  "react-dom": "^19.0.0",
496
- ...(useShadcn
497
- ? {
498
- clsx: "^2.1.1",
499
- "tailwind-merge": "^2.5.5",
500
- "class-variance-authority": "^0.7.1",
501
- "lucide-react": "^0.460.0",
502
- "tw-animate-css": "^1.0.0",
503
- }
522
+ // Tailwind lives in dependencies (not devDependencies): the swift-rust
523
+ // build compiles CSS by loading @tailwindcss/postcss at build time, so it
524
+ // must be installed in production. As a devDependency it gets pruned on
525
+ // some hosts → the deployed site ships unstyled.
526
+ ...(tailwind
527
+ ? { tailwindcss: "^4.0.0", "@tailwindcss/postcss": "^4.0.0", postcss: "^8.4.0" }
528
+ : {}),
529
+ // Both kits need the cn() helper (clsx + tailwind-merge). shadcn also
530
+ // pulls in class-variance-authority + tw-animate-css; swift-rust ui needs
531
+ // neither (its components use plain class maps and v4-native animations).
532
+ ...(useUi ? { clsx: "^2.1.1", "tailwind-merge": "^3.6.0", "lucide-react": "^0.460.0" } : {}),
533
+ ...(ui === "shadcn"
534
+ ? { "class-variance-authority": "^0.7.1", "tw-animate-css": "^1.0.0" }
504
535
  : {}),
505
536
  },
506
537
  devDependencies: {
@@ -508,10 +539,8 @@ async function writeProjectFiles(target, answers) {
508
539
  ? { typescript: "^6.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" }
509
540
  : {}),
510
541
  ...(linter === "biome" ? { "@biomejs/biome": "^2.4.16" } : { eslint: "^9.0.0" }),
511
- ...(tailwind
512
- ? { tailwindcss: "^4.0.0", "@tailwindcss/postcss": "^4.0.0", postcss: "^8.4.0" }
513
- : {}),
514
- ...(useShadcn ? { shadcn: "^4.0.0" } : {}),
542
+ ...(ui === "shadcn" ? { shadcn: "^4.0.0" } : {}),
543
+ ...(ui === "swift-rust-ui" ? { "@swift-rust/ui": "latest" } : {}),
515
544
  },
516
545
  };
517
546
  await writeFile(join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
@@ -591,10 +620,13 @@ async function writeProjectFiles(target, answers) {
591
620
  export default config;
592
621
  `;
593
622
  await writeFile(join(target, "postcss.config.mjs"), postcss);
594
- const shadcnVars = useShadcn
623
+ const uiTokens = useUi
595
624
  ? `
596
625
 
597
- /* shadcn design tokens (https://ui.shadcn.com/docs/theming) */
626
+ /* UI design tokens — shared by swift-rust ui and shadcn/ui.
627
+ Components reference these via the @theme inline mapping below
628
+ (bg-primary, text-muted-foreground, border-border, …).
629
+ Retheme by editing the --ui-* values (light + .dark). */
598
630
  :root {
599
631
  --ui-bg: #ffffff;
600
632
  --ui-fg: #09090b;
@@ -689,12 +721,12 @@ export default config;
689
721
  `
690
722
  : "";
691
723
  const css = `@import "tailwindcss";
692
- ${useShadcn ? '@import "tw-animate-css";' : ""}
724
+ ${ui === "shadcn" ? '@import "tw-animate-css";' : ""}
693
725
 
694
726
  @theme {
695
727
  --color-bg: #ffffff;
696
728
  --color-fg: #09090b;
697
- --color-accent: ${useShadcn ? "#f97316" : "#0070f3"};
729
+ --color-accent: ${useUi ? "#f97316" : "#0070f3"};
698
730
  --font-sans: var(--font-geist-sans), system-ui, sans-serif;
699
731
  --font-mono: var(--font-geist-mono), ui-monospace, monospace;
700
732
  }
@@ -713,12 +745,11 @@ body {
713
745
  color: var(--color-fg);
714
746
  font-family: var(--font-sans);
715
747
  }
716
- ${shadcnVars}`;
748
+ ${uiTokens}`;
717
749
  await writeFile(join(appDir, `globals.${fileExt(language)}`), language === "ts" ? `import "./globals.css";\n` : `import "./globals.css";\n`);
718
750
  await writeFile(join(appDir, "globals.css"), css);
719
751
  }
720
- const layoutImports = `import type { ReactNode } from "react";
721
- import { Geist, GeistMono } from "swift-rust/font/google";
752
+ const layoutImports = `${language === "ts" ? 'import type { ReactNode } from "react";\n' : ""}import { Geist, GeistMono } from "swift-rust/font/google";
722
753
  ${tailwind ? `import "./globals.css";\n` : ""}
723
754
  const geistSans = Geist({ subsets: ["latin"], display: "swap", variable: true });
724
755
  const geistMono = GeistMono({ subsets: ["latin"], display: "swap", variable: true });
@@ -731,7 +762,7 @@ export const metadata = {
731
762
  description: "Built with swift-rust — the React framework powered with Rust + Bun. 10x faster than Next.js.",
732
763
  };
733
764
 
734
- export default function RootLayout({ children }: { children: ReactNode }) {
765
+ export default function RootLayout(${language === "ts" ? "{ children }: { children: ReactNode }" : "{ children }"}) {
735
766
  return (
736
767
  <html
737
768
  lang="en"
@@ -746,10 +777,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
746
777
  );
747
778
  }
748
779
  `;
749
- await writeFile(join(appDir, `layout.${componentExt(language)}`), layoutImports);
750
- const homePage = tailwind
751
- ? useShadcn
752
- ? `import { buttonVariants } from "@/components/ui/button";
780
+ await writeFile(join(appDir, `layout.${jsxExt}`), layoutImports);
781
+ const shadcnHome = `import { buttonVariants } from "@/components/ui/button";
753
782
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
754
783
  import { Badge } from "@/components/ui/badge";
755
784
 
@@ -761,7 +790,7 @@ export default function Home() {
761
790
  <Badge variant="secondary" className="w-fit mb-2">Welcome</Badge>
762
791
  <CardTitle className="text-3xl sm:text-5xl">${projectName}</CardTitle>
763
792
  <CardDescription>
764
- Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${componentExt(language)}</code>.
793
+ Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${jsxExt}</code>.
765
794
  </CardDescription>
766
795
  </CardHeader>
767
796
  <CardContent className="flex gap-2">
@@ -782,8 +811,77 @@ export default function Home() {
782
811
  </main>
783
812
  );
784
813
  }
785
- `
786
- : `export default function Home() {
814
+ `;
815
+ const swiftRustUiHome = `import { Button } from "@/components/ui/button";
816
+
817
+ export default function Home() {
818
+ return (
819
+ <main className="min-h-screen bg-bg text-fg">
820
+ <section className="mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-8">
821
+ <header className="flex items-center justify-between border-b border-border/70 pb-5">
822
+ <div>
823
+ <p className="text-sm font-medium text-fg/55">Swift Rust starter</p>
824
+ <h1 className="mt-1 text-xl font-semibold tracking-tight">${projectName}</h1>
825
+ </div>
826
+ <Button asChild size="sm" variant="outline">
827
+ <a href="https://swift-rust.dev/docs">Docs</a>
828
+ </Button>
829
+ </header>
830
+
831
+ <div className="grid flex-1 items-center gap-10 py-16 lg:grid-cols-[1.05fr_0.95fr]">
832
+ <div>
833
+ <div className="mb-6 inline-flex rounded-full border border-border bg-bg px-3 py-1 text-sm text-fg/65">
834
+ Production-ready React on Rust + Bun
835
+ </div>
836
+ <h2 className="max-w-3xl text-5xl font-semibold leading-[0.98] tracking-tight sm:text-6xl">
837
+ Build a fast app with a quiet, professional baseline.
838
+ </h2>
839
+ <p className="mt-6 max-w-2xl text-lg leading-8 text-fg/65">
840
+ Routing, rendering, styling, and deployment defaults are already wired so you can focus on the product surface.
841
+ </p>
842
+ <div className="mt-8 flex flex-wrap gap-3">
843
+ <Button asChild>
844
+ <a href="https://swift-rust.dev/docs">Read the docs</a>
845
+ </Button>
846
+ <Button asChild variant="outline">
847
+ <a href="https://github.com/swift-rust/swift-rust">GitHub</a>
848
+ </Button>
849
+ </div>
850
+ </div>
851
+
852
+ <div className="rounded-lg border border-border bg-bg p-5 shadow-sm">
853
+ <div className="flex items-center justify-between border-b border-border pb-4">
854
+ <p className="text-sm font-medium">Project health</p>
855
+ <span className="rounded-full bg-accent/10 px-2.5 py-1 text-xs font-medium text-accent">
856
+ Ready
857
+ </span>
858
+ </div>
859
+ <dl className="mt-5 grid gap-4">
860
+ {[
861
+ ["Framework", "swift-rust"],
862
+ ["Renderer", "${renderer}"],
863
+ ["Styling", "Tailwind CSS"],
864
+ ["Components", "swift-rust ui"],
865
+ ].map(([label, value]) => (
866
+ <div key={label} className="flex items-center justify-between rounded-md border border-border/70 px-4 py-3">
867
+ <dt className="text-sm text-fg/55">{label}</dt>
868
+ <dd className="text-sm font-medium">{value}</dd>
869
+ </div>
870
+ ))}
871
+ </dl>
872
+ </div>
873
+ </div>
874
+ </section>
875
+ </main>
876
+ );
877
+ }
878
+ `;
879
+ const homePage = tailwind
880
+ ? ui === "shadcn"
881
+ ? shadcnHome
882
+ : ui === "swift-rust-ui"
883
+ ? swiftRustUiHome
884
+ : `export default function Home() {
787
885
  return (
788
886
  <main className="min-h-screen flex items-center justify-center px-6">
789
887
  <div className="max-w-2xl text-center">
@@ -794,7 +892,7 @@ export default function Home() {
794
892
  ${projectName}
795
893
  </h1>
796
894
  <p className="text-lg text-fg-secondary mb-8">
797
- Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${componentExt(language)}</code>.
895
+ Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${jsxExt}</code>.
798
896
  </p>
799
897
  <a
800
898
  href="https://swift-rust.dev/docs"
@@ -812,14 +910,14 @@ export default function Home() {
812
910
  <main style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: "2rem" }}>
813
911
  <div style={{ maxWidth: "32rem", textAlign: "center" }}>
814
912
  <h1>${projectName}</h1>
815
- <p>Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${componentExt(language)}</code>.</p>
913
+ <p>Get started by editing <code>${srcDir ? "src/app/" : "app/"}page.${jsxExt}</code>.</p>
816
914
  <a href="https://swift-rust.dev/docs">Read the docs →</a>
817
915
  </div>
818
916
  </main>
819
917
  );
820
918
  }
821
919
  `;
822
- await writeFile(join(appDir, `page.${componentExt(language)}`), homePage);
920
+ await writeFile(join(appDir, `page.${jsxExt}`), homePage);
823
921
  const notFoundPage = tailwind
824
922
  ? `export const metadata = { title: "Not found" };
825
923
 
@@ -845,7 +943,7 @@ export default function NotFound() {
845
943
  );
846
944
  }
847
945
  `;
848
- await writeFile(join(appDir, `not-found.${componentExt(language)}`), notFoundPage);
946
+ await writeFile(join(appDir, `not-found.${jsxExt}`), notFoundPage);
849
947
  if (linter === "biome") {
850
948
  const biome = {
851
949
  $schema: "https://biomejs.dev/schemas/2.4.16/schema.json",
@@ -892,8 +990,8 @@ Built with [swift-rust](https://swift-rust.dev) — the React framework powered
892
990
  ## Project structure
893
991
 
894
992
  ${srcDir
895
- ? `\`\`\`\nsrc/\n app/\n layout.${componentExt(language)}\n page.${componentExt(language)}\n not-found.${componentExt(language)}\n components/\n lib/\n\`\`\``
896
- : `\`\`\`\napp/\n layout.${componentExt(language)}\n page.${componentExt(language)}\n not-found.${componentExt(language)}\ncomponents/\nlib/\n\`\`\``}
993
+ ? `\`\`\`\nsrc/\n app/\n layout.${jsxExt}\n page.${jsxExt}\n not-found.${jsxExt}\n components/\n lib/\n\`\`\``
994
+ : `\`\`\`\napp/\n layout.${jsxExt}\n page.${jsxExt}\n not-found.${jsxExt}\ncomponents/\nlib/\n\`\`\``}
897
995
 
898
996
  ## Learn more
899
997
 
@@ -935,13 +1033,16 @@ For custom domains and ISR / serverless functions, see the [deploy guide](https:
935
1033
  // request /favicon.ico get a real icon instead of a 404.
936
1034
  const faviconIco = "AAABAAEAICAAAAEAIAAZBQAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAAZiS0dEAP8A/wD/oL2nkwAABM5JREFUWIXFl19sU2UYxn/f6cbsoTsFoqywPw5BMzodpGNxhUIINMREgssEg5ELI7tUs/HnYiExRI1wwRKWXRBiMGEmphqykKl3JYOOBLMy3U7nthDI6LqRzii6ullit75eYJtNtnWWoU/yJc15z/s8z/f2y/ueTzEDNpttdTKZfB94FXgeWM7SYBK4BXyraVrLxMTET6mASv2wWq0HgAtKqfwlEp0TIvI78E48Hr+UNmC1Wg8opb6caegJQ0TkjXg8fknZbLbV09PTt7PZeU1NDQCXL1/OxkRM07QNStf1j4ET2TCMjIwAUFRUlE06wEc5wN5ss3Nzc7NNTWGvBqx/XJZsISIbNMA2V1DTNPr6+vD7/RiGMSdBR0cHHR0dc8YMw8Dv99PX14emaXO+o5TKnzsCJJNJotEobreb9vb2WSaUUpSXl9Pa2srFixdxOp2PiLe3t+N2u4lGoySTyflkQNd1mW85HA7p6uoSEZGWlhax2+1y7NgxiUQiMhPJ+2EJh8Ny5MgRsdvt0tLSIiIiXV1d4nA45uXXdV2Urusyv72Hu2lsbOTKlSscP34cj8dDMpmku7ubUCjEyulfeHlljDWvf4hSis7OTs6cOcPu3bs5deoUsVhsIfqFK5BadrtdOjs7RUSkp6dHqqurRdd1KXo6X26frJL3dj0rbrdbTNMUEZFAICCGYWTk1XVd5j0DM1FXV4fH46G3t5c9e/Zgmia5FsXnh9bz7Ko8+qNxent78Xq9hEIhtm/fzuHDhxdDTca/QCnFwMAAhYWFbNu2DdM0AWh6rZg69zMAlJzsZfzBNACbN2/m+vXrRCIRnE4nIgvSk7ECGzdupLi4mJs3b6bF33StSouP/PZnWhygp6eH7u5uSkpKKCsry0Q/20BNTQ2RSITR0VFGR0fx+XyUlpYCpMWrSpbTXFuSzilasYzx0y7GT7u4deIl1hq56XdLS0vx+Xxpvkgkkp4fi65AqoSaplGQn0vroefIy3k0LTEtvP3FEPdiiXTjyVR+WMQZcDqdBINBgsEgO3funBXrb3yRQvsyABouD/PZdz8DEAgEqKyspLKyksHBwQUNZKzAwMAAw8PDbNmyhU2bNqWfG09ZWGs8FPf9cD8t7nK5cLlchMPhjOKLMiAiNDc3o5Ti/Pnz2GwPR0e5w4pSYN77g/q2YQBsNhvnzp1DKcXZs2czigOLb0SBQEBEREzTlK1bt8q7u0rk3ifV8sLaFaLrung8HgmFQiIicvXq1UU3on/Vio8ePcqOHTsQEcbaPuD7X62MWdZQUVGBy+VCKcW1a9doamrC6/U+fiv+5zAyDEMaGhokHA5Lcmxw1kC6e/eu1NfXi2EYSzeM/H4/brebYDDIvn37Zu2mrKyMdevWATA0NDTrwKXGcVVVFTdu3MDr9c5bgHkNaJqGaZpEo1Fqa2vnLKXP5wPg4MGDj8QMw6CtrQ2Hw0FFRcW83wTKarXGsr0LjI2NAVBQUJBNOkBMU0rdyTZ7CXBHA77JNjuRSJBIJB7HwNf/58Vk3GKxbEhdzfYrpb7iv72a7Y/H420WgKmpqf6cnJwflVKvAHlPWHxcRN6Kx+NtAJbU06mpqf68vLwLIvIAyP97LVsi0QmgD/jUYrEcmpyc7E4F/gIrAoFO2P2bCQAAAABJRU5ErkJggg==";
937
1035
  await writeFile(join(appDir, "favicon.ico"), Buffer.from(faviconIco, "base64"));
938
- if (useShadcn) {
939
- await scaffoldShadcn({ target, uiDir, libDir, componentExt: componentExt(language), srcDir });
1036
+ if (ui === "shadcn") {
1037
+ await scaffoldShadcn({ target, uiDir, libDir, componentExt: jsxExt, srcDir, language });
1038
+ }
1039
+ else if (ui === "swift-rust-ui") {
1040
+ await scaffoldSwiftRustUi({ uiDir, libDir, componentExt: jsxExt, language });
940
1041
  }
941
1042
  await writeVscodeConfig(target);
942
1043
  }
943
1044
  async function scaffoldShadcn(options) {
944
- const { target, uiDir, libDir, componentExt, srcDir } = options;
1045
+ const { target, uiDir, libDir, componentExt, srcDir, language } = options;
945
1046
  const utilsContent = `import { clsx, type ClassValue } from "clsx";
946
1047
  import { twMerge } from "tailwind-merge";
947
1048
 
@@ -1152,14 +1253,96 @@ export { badgeVariants };
1152
1253
  "iconLibrary": "lucide"
1153
1254
  }
1154
1255
  `;
1155
- await writeFile(join(libDir, "utils.ts"), utilsContent);
1156
- await writeFile(join(uiDir, `button.${componentExt}`), buttonContent);
1157
- await writeFile(join(uiDir, `card.${componentExt}`), cardContent);
1158
- await writeFile(join(uiDir, `input.${componentExt}`), inputContent);
1159
- await writeFile(join(uiDir, `label.${componentExt}`), labelContent);
1160
- await writeFile(join(uiDir, `badge.${componentExt}`), badgeContent);
1256
+ const source = (content) => language === "js" ? stripTypeScriptForJavaScript(content) : content;
1257
+ await writeFile(join(libDir, `utils.${language === "js" ? "js" : "ts"}`), source(utilsContent));
1258
+ await writeFile(join(uiDir, `button.${componentExt}`), source(buttonContent));
1259
+ await writeFile(join(uiDir, `card.${componentExt}`), source(cardContent));
1260
+ await writeFile(join(uiDir, `input.${componentExt}`), source(inputContent));
1261
+ await writeFile(join(uiDir, `label.${componentExt}`), source(labelContent));
1262
+ await writeFile(join(uiDir, `badge.${componentExt}`), source(badgeContent));
1161
1263
  await writeFile(join(target, "components.json"), `${componentsJson}\n`);
1162
1264
  }
1265
+ function stripTypeScriptForJavaScript(source) {
1266
+ return source
1267
+ .replace(/import\s+type\s+[^;]+;\n/g, "")
1268
+ .replace(/,\s*type\s+\w+/g, "")
1269
+ .replace(/type\s+\w+\s*,\s*/g, "")
1270
+ .replace(/export\s+type\s+\w+\s*=\s*[\s\S]*?;\n/g, "")
1271
+ .replace(/export\s+interface\s+\w+[\s\S]*?\n}\n/g, "")
1272
+ .replace(/interface\s+\w+[\s\S]*?\n}\n/g, "")
1273
+ .replace(/React\.forwardRef<[\s\S]*?>\(/g, "React.forwardRef(")
1274
+ .replace(/React\.createContext<[^>]+>\(/g, "React.createContext(")
1275
+ .replace(/React\.useState<[^)]*>\(/g, "React.useState(")
1276
+ .replace(/const\s+(\w+):\s*Record<[^=]+>\s*=/g, "const $1 =")
1277
+ .replace(/}\s*:\s*[A-Za-z0-9_<>, |&?{}:[\]]+\s*=\s*{}\)/g, "} = {})")
1278
+ .replace(/}\s*:\s*{[\s\S]*?}\)/g, "})")
1279
+ .replace(/function\s+(\w+)\(([^)]*)\):\s*[\w.<>[\] |&]+/g, "function $1($2)")
1280
+ .replace(/\(([^)]*)\):\s*[\w.<>[\] |&]+\s*=>/g, "($1) =>")
1281
+ .replace(/(\.\.\.\w+):\s*[^,)]+/g, "$1")
1282
+ .replace(/(\w+):\s*(string|number|boolean)\b/g, "$1")
1283
+ .replace(/:\s*React\.[A-Za-z0-9_.<>[\], |&]+/g, "")
1284
+ .replace(/:\s*[A-Z][A-Za-z0-9_<>, |&?]+(?=\s*[=,)])/g, "")
1285
+ .replace(/:\s*[A-Z][A-Za-z0-9_<>, |&?{}:[\]]+(?=\s*=\s*{})/g, "")
1286
+ .replace(/\s+as\s+React\.[A-Za-z0-9_.<>[\], |&?{}:]+/g, "")
1287
+ .replace(/\s+as\s+Set<[^>]+>/g, "")
1288
+ .replace(/\s+as\s+const/g, "")
1289
+ .replace(/\s+as\s+string/g, "");
1290
+ }
1291
+ // The default component set for swift-rust ui — the components the user asked us
1292
+ // to ship first, all carrying the variant × size × design dimensions.
1293
+ const SWIFT_RUST_UI_DEFAULTS = [
1294
+ "accordion",
1295
+ "alert",
1296
+ "avatar",
1297
+ "button",
1298
+ "card",
1299
+ "input",
1300
+ "label",
1301
+ ];
1302
+ /** Locate the @swift-rust/ui registry: the synced copy bundled into this
1303
+ * package (templates/ui), or the monorepo source when running from a checkout. */
1304
+ function swiftRustUiRegistry() {
1305
+ const here = dirname(fileURLToPath(import.meta.url));
1306
+ const candidates = [
1307
+ // Published: scripts/sync-template.mjs flattens the registry into templates/ui.
1308
+ {
1309
+ componentsDir: resolve(here, "..", "templates", "ui"),
1310
+ utilsFile: resolve(here, "..", "templates", "ui", "utils.ts"),
1311
+ },
1312
+ {
1313
+ componentsDir: resolve(here, "templates", "ui"),
1314
+ utilsFile: resolve(here, "templates", "ui", "utils.ts"),
1315
+ },
1316
+ // Monorepo source: packages/create-swift-rust/dist → packages/ui/registry.
1317
+ {
1318
+ componentsDir: resolve(here, "..", "..", "ui", "registry", "components"),
1319
+ utilsFile: resolve(here, "..", "..", "ui", "registry", "lib", "utils.ts"),
1320
+ },
1321
+ ];
1322
+ for (const c of candidates) {
1323
+ if (existsSync(c.componentsDir) && existsSync(c.utilsFile))
1324
+ return c;
1325
+ }
1326
+ return null;
1327
+ }
1328
+ // Offline/pre-install fallback: copy the bundled registry components straight
1329
+ // in, so the project isn't empty if `@swift-rust/ui add` (the real, CLI-driven
1330
+ // path, run after install) can't be fetched. Mirrors how the shadcn path writes
1331
+ // bundled components before trying `shadcn add`.
1332
+ async function scaffoldSwiftRustUi(options) {
1333
+ const { uiDir, libDir, componentExt, language } = options;
1334
+ const registry = swiftRustUiRegistry();
1335
+ if (!registry) {
1336
+ throw new Error("swift-rust ui registry not found. Run `node scripts/sync-template.mjs` before packaging.");
1337
+ }
1338
+ // The cn() helper the components import from "@/lib/utils".
1339
+ const source = (content) => language === "js" ? stripTypeScriptForJavaScript(content) : content;
1340
+ await writeFile(join(libDir, `utils.${language === "js" ? "js" : "ts"}`), source(await readFile(registry.utilsFile, "utf8")));
1341
+ for (const name of SWIFT_RUST_UI_DEFAULTS) {
1342
+ const src = await readFile(join(registry.componentsDir, `${name}.tsx`), "utf8");
1343
+ await writeFile(join(uiDir, `${name}.${componentExt}`), source(src));
1344
+ }
1345
+ }
1163
1346
  async function runInstall(target) {
1164
1347
  const { spawn } = await import("node:child_process");
1165
1348
  return new Promise((resolve, reject) => {
@@ -1195,6 +1378,18 @@ async function runShadcnAdd(target, components) {
1195
1378
  proc.on("error", () => resolve(false));
1196
1379
  });
1197
1380
  }
1381
+ // Drive the @swift-rust/ui CLI (the same one users run as `bunx @swift-rust/ui
1382
+ // add <component>`) to install the starter components. The CLI auto-detects the
1383
+ // src/ layout, runs init (cn helper + deps), and writes into components/ui.
1384
+ async function runSwiftRustUiAdd(target, components) {
1385
+ const { spawn } = await import("node:child_process");
1386
+ return new Promise((resolve) => {
1387
+ const args = ["@swift-rust/ui@latest", "add", ...components, "--yes", "--overwrite"];
1388
+ const proc = spawn("bunx", args, { cwd: target, stdio: "pipe" });
1389
+ proc.on("exit", (code) => resolve(code === 0));
1390
+ proc.on("error", () => resolve(false));
1391
+ });
1392
+ }
1198
1393
  async function main() {
1199
1394
  const args = process.argv.slice(2);
1200
1395
  if (args.includes("--help") || args.includes("-h")) {
@@ -1260,7 +1455,7 @@ async function main() {
1260
1455
  `${pc.cyan("•")} Renderer: ${answers.renderer}`,
1261
1456
  `${pc.cyan("•")} Linter: ${linter === "biome" ? "Biome" : "ESLint"}`,
1262
1457
  `${pc.cyan("•")} Tailwind: ${tailwind ? "Yes" : "No"}`,
1263
- `${pc.cyan("•")} shadcn UI: ${answers.useShadcn ? "Yes" : "No"}`,
1458
+ `${pc.cyan("•")} Components: ${answers.ui === "swift-rust-ui" ? "swift-rust ui" : answers.ui === "shadcn" ? "shadcn/ui" : "None"}`,
1264
1459
  `${pc.cyan("•")} src/ dir: ${srcDir ? "Yes (src/app/)" : "No"}`,
1265
1460
  `${pc.cyan("•")} Import as: ${answers.importAlias}`,
1266
1461
  `${pc.cyan("•")} Install: ${install ? "Yes" : "No"}`,
@@ -1300,7 +1495,7 @@ async function main() {
1300
1495
  console.error("Failed to install dependencies. Run `bun install` manually.");
1301
1496
  }
1302
1497
  }
1303
- if (answers.useShadcn && installSucceeded) {
1498
+ if (answers.ui === "shadcn" && installSucceeded) {
1304
1499
  console.log("Fetching canonical shadcn components…");
1305
1500
  const ok = await runShadcnAdd(target, SHADCN_DEFAULT_COMPONENTS);
1306
1501
  if (ok) {
@@ -1311,6 +1506,20 @@ async function main() {
1311
1506
  console.log(`${pc.yellow("!")} Could not fetch shadcn components from the registry. Using bundled shadcn-style components as a fallback. You can retry later with: ${pc.cyan(`bunx --bun shadcn@latest add ${SHADCN_DEFAULT_COMPONENTS.join(" ")} --cwd ${cwdArg}`)}`);
1312
1507
  }
1313
1508
  }
1509
+ else if (answers.ui === "swift-rust-ui" && installSucceeded) {
1510
+ console.log("Adding swift-rust ui components via the CLI…");
1511
+ const ok = await runSwiftRustUiAdd(target, SWIFT_RUST_UI_DEFAULTS);
1512
+ if (ok) {
1513
+ console.log(`✓ Added ${SWIFT_RUST_UI_DEFAULTS.length} swift-rust ui components via ${pc.cyan("@swift-rust/ui")}`);
1514
+ }
1515
+ else {
1516
+ console.log(`${pc.yellow("!")} Could not run the ${pc.cyan("@swift-rust/ui")} CLI. Using the bundled components as a fallback. Add more later with: ${pc.cyan("bunx @swift-rust/ui add <component>")}`);
1517
+ }
1518
+ }
1519
+ else if (answers.ui === "swift-rust-ui") {
1520
+ // install was skipped → the bundled components are already in place.
1521
+ console.log(`✓ Added ${SWIFT_RUST_UI_DEFAULTS.length} swift-rust ui components (${SWIFT_RUST_UI_DEFAULTS.join(", ")}). Add more with ${pc.cyan("bunx @swift-rust/ui add <component>")}.`);
1522
+ }
1314
1523
  const next = projectName === "." ? "bun run dev" : `cd ${projectName}\nbun run dev`;
1315
1524
  console.log(`\n${pc.green("Done!")} Next steps:\n${pc.cyan(next)}`);
1316
1525
  }