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 +277 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/package.json +5 -5
- package/templates/ui/accordion.tsx +215 -0
- package/templates/ui/alert.tsx +134 -0
- package/templates/ui/avatar.tsx +89 -0
- package/templates/ui/button.tsx +133 -0
- package/templates/ui/card.tsx +124 -0
- package/templates/ui/input.tsx +87 -0
- package/templates/ui/label.tsx +49 -0
- package/templates/ui/utils.ts +6 -0
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("--
|
|
97
|
-
${pc.yellow("--
|
|
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.
|
|
139
|
-
else if (a === "--no-shadcn")
|
|
140
|
-
flags.
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
...(
|
|
512
|
-
|
|
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
|
|
623
|
+
const uiTokens = useUi
|
|
595
624
|
? `
|
|
596
625
|
|
|
597
|
-
/*
|
|
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
|
-
${
|
|
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: ${
|
|
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
|
-
${
|
|
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 =
|
|
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.${
|
|
750
|
-
const
|
|
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.${
|
|
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
|
-
|
|
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.${
|
|
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.${
|
|
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.${
|
|
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.${
|
|
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.${
|
|
896
|
-
: `\`\`\`\napp/\n layout.${
|
|
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 (
|
|
939
|
-
await scaffoldShadcn({ target, uiDir, libDir, componentExt:
|
|
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
|
-
|
|
1156
|
-
await writeFile(join(
|
|
1157
|
-
await writeFile(join(uiDir, `
|
|
1158
|
-
await writeFile(join(uiDir, `
|
|
1159
|
-
await writeFile(join(uiDir, `
|
|
1160
|
-
await writeFile(join(uiDir, `
|
|
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("•")}
|
|
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.
|
|
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
|
}
|