create-swift-rust 0.3.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/index.js +87 -881
  2. package/dist/index.js.map +1 -1
  3. package/package.json +8 -6
  4. package/templates/default/README.md +29 -0
  5. package/templates/default/_env.example +5 -0
  6. package/templates/default/_env.local +5 -0
  7. package/templates/default/_gitignore +14 -0
  8. package/templates/default/biome.json +12 -0
  9. package/templates/default/globals.d.ts +19 -0
  10. package/templates/default/package.json +30 -0
  11. package/templates/default/postcss.config.mjs +7 -0
  12. package/templates/default/public/blog/introducing-swift-rust.svg +12 -0
  13. package/templates/default/public/og-image.svg +25 -0
  14. package/templates/default/public/samples/axelborg-towers-modern-architecture.jpg +0 -0
  15. package/templates/default/public/samples/brown-high-skyscrapers.jpg +0 -0
  16. package/templates/default/public/samples/close-up-portrait-gorgeous-young-woman.jpg +0 -0
  17. package/templates/default/public/samples/full-shot-woman-taking-selfie.jpg +0 -0
  18. package/templates/default/public/samples/happy-black-parents-with-kids-making-video-call-smart-phone-home.jpg +0 -0
  19. package/templates/default/public/samples/metaverse-concept-collage-design.jpg +0 -0
  20. package/templates/default/src/app/about/layout.tsx +5 -0
  21. package/templates/default/src/app/about/page.tsx +74 -0
  22. package/templates/default/src/app/about/team/page.tsx +88 -0
  23. package/templates/default/src/app/api/contact/route.ts +15 -0
  24. package/templates/default/src/app/api/health/route.ts +7 -0
  25. package/templates/default/src/app/api/posts/[id]/route.ts +33 -0
  26. package/templates/default/src/app/api/posts/route.ts +29 -0
  27. package/templates/default/src/app/api/search/route.ts +15 -0
  28. package/templates/default/src/app/api/stats/route.ts +16 -0
  29. package/templates/default/src/app/blog/[slug]/page.tsx +116 -0
  30. package/templates/default/src/app/blog/page.tsx +101 -0
  31. package/templates/default/src/app/blog/tag/[tag]/page.tsx +82 -0
  32. package/templates/default/src/app/contact/page.tsx +131 -0
  33. package/templates/default/src/app/dashboard/analytics/page.tsx +103 -0
  34. package/templates/default/src/app/dashboard/layout.tsx +11 -0
  35. package/templates/default/src/app/dashboard/loading.tsx +18 -0
  36. package/templates/default/src/app/dashboard/page.tsx +181 -0
  37. package/templates/default/src/app/dashboard/posts/[id]/not-found.tsx +23 -0
  38. package/templates/default/src/app/dashboard/posts/[id]/page.tsx +87 -0
  39. package/templates/default/src/app/dashboard/posts/new/page.tsx +103 -0
  40. package/templates/default/src/app/dashboard/posts/page.tsx +81 -0
  41. package/templates/default/src/app/dashboard/settings/page.tsx +119 -0
  42. package/templates/default/src/app/error.tsx +42 -0
  43. package/templates/default/src/app/favicon.ico +0 -0
  44. package/templates/default/src/app/favicon.svg +8 -0
  45. package/templates/default/src/app/fonts/page.tsx +610 -0
  46. package/templates/default/src/app/globals.css +238 -0
  47. package/templates/default/src/app/images/page.tsx +56 -0
  48. package/templates/default/src/app/layout.tsx +44 -0
  49. package/templates/default/src/app/loading.tsx +13 -0
  50. package/templates/default/src/app/not-found.tsx +25 -0
  51. package/templates/default/src/app/page.tsx +410 -0
  52. package/templates/default/src/app/videos/page.tsx +182 -0
  53. package/templates/default/src/components/font-studio.tsx +378 -0
  54. package/templates/default/src/components/footer.tsx +75 -0
  55. package/templates/default/src/components/logo.tsx +26 -0
  56. package/templates/default/src/components/nav.tsx +102 -0
  57. package/templates/default/src/components/sidebar.tsx +109 -0
  58. package/templates/default/src/lib/blur.ts +5 -0
  59. package/templates/default/src/lib/format.ts +31 -0
  60. package/templates/default/src/lib/posts.ts +132 -0
  61. package/templates/default/src/lib/site.config.ts +39 -0
  62. package/templates/default/src/lib/stats.ts +25 -0
  63. package/templates/default/swift-rust.config.json +8 -0
  64. package/templates/default/tsconfig.json +29 -0
  65. package/templates/default/vercel.json +23 -0
package/dist/index.js CHANGED
@@ -1,8 +1,54 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, stat, writeFile } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { cp, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
4
6
  import * as p from "@clack/prompts";
5
7
  import pc from "picocolors";
8
+ /** Locate the bundled template dir, both from src/ (dev) and dist/ (published). */
9
+ function templateDir() {
10
+ const here = dirname(fileURLToPath(import.meta.url));
11
+ for (const candidate of [
12
+ resolve(here, "..", "templates", "default"),
13
+ resolve(here, "templates", "default"),
14
+ ]) {
15
+ if (existsSync(candidate))
16
+ return candidate;
17
+ }
18
+ return resolve(here, "..", "templates", "default");
19
+ }
20
+ /**
21
+ * Copy the full-demo template into `target`, restoring underscore-prefixed
22
+ * dotfiles and substituting the project name placeholder.
23
+ */
24
+ async function copyTemplate(target, projectName) {
25
+ const src = templateDir();
26
+ if (!existsSync(src)) {
27
+ throw new Error(`Template not found at ${src}. Run \`node scripts/sync-template.mjs\` before publishing.`);
28
+ }
29
+ await mkdir(target, { recursive: true });
30
+ await cp(src, target, { recursive: true });
31
+ // Restore real dotfile names (npm mangles dotfiles inside packages).
32
+ const dotfiles = [
33
+ ["_gitignore", ".gitignore"],
34
+ ["_env.example", ".env.example"],
35
+ ["_env.local", ".env.local"],
36
+ ];
37
+ for (const [from, to] of dotfiles) {
38
+ const fromPath = join(target, from);
39
+ if (existsSync(fromPath))
40
+ await rename(fromPath, join(target, to));
41
+ }
42
+ // Substitute the project name in package.json + README.
43
+ const displayName = projectName === "." ? basename(resolve(target)) : projectName;
44
+ for (const file of ["package.json", "README.md"]) {
45
+ const path = join(target, file);
46
+ if (!existsSync(path))
47
+ continue;
48
+ const contents = await readFile(path, "utf8");
49
+ await writeFile(path, contents.split("__PROJECT_NAME__").join(displayName));
50
+ }
51
+ }
6
52
  async function pathExists(path) {
7
53
  try {
8
54
  await stat(path);
@@ -15,8 +61,17 @@ async function pathExists(path) {
15
61
  function isValidName(name) {
16
62
  return /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
17
63
  }
18
- function isValidAlias(alias) {
19
- return /^[a-zA-Z@][a-zA-Z0-9_/*-]*$/.test(alias);
64
+ /** ".", "./", "" all mean "scaffold into the current directory". */
65
+ function isCurrentDir(name) {
66
+ const t = name.trim();
67
+ return t === "." || t === "./" || t === "";
68
+ }
69
+ /** Normalize a project-name input: current-dir variants → ".", strip trailing slashes. */
70
+ function normalizeProjectName(name) {
71
+ const t = name.trim();
72
+ if (isCurrentDir(t))
73
+ return ".";
74
+ return t.replace(/\/+$/, "");
20
75
  }
21
76
  const HELP = `
22
77
  ${pc.bold("create-swift-rust")} - scaffold a new swift-rust project
@@ -24,22 +79,10 @@ ${pc.bold("create-swift-rust")} - scaffold a new swift-rust project
24
79
  ${pc.bold("Usage")}
25
80
  ${pc.cyan("bun create swift-rust@latest")} [project-name] [options]
26
81
 
82
+ Scaffolds the full-demo starter: blog, dashboard, API routes, and the
83
+ Image / PDF / Video / Font components — TypeScript, Tailwind, Biome, src/app.
84
+
27
85
  ${pc.bold("Options")}
28
- ${pc.yellow("--ts, --typescript")} Use TypeScript (default)
29
- ${pc.yellow("--js, --javascript")} Use JavaScript
30
- ${pc.yellow("--tailwind")} Install Tailwind CSS
31
- ${pc.yellow("--no-tailwind")} Skip Tailwind CSS
32
- ${pc.yellow("--shadcn")} Install shadcn-style UI components (default)
33
- ${pc.yellow("--no-shadcn")} Skip shadcn UI components
34
- ${pc.yellow("--src-dir")} Use src/ under app/ (default)
35
- ${pc.yellow("--no-src-dir")} Use top-level app/
36
- ${pc.yellow("--biome")} Use Biome for linting (default)
37
- ${pc.yellow("--eslint")} Use ESLint for linting
38
- ${pc.yellow("--import-alias <prefix>")} Set import alias (default "@/*")
39
- ${pc.yellow("--ssr")} Server-side rendering
40
- ${pc.yellow("--ssr-wasm")} SSR with WASM hydration (default)
41
- ${pc.yellow("--ssr-htmx")} SSR with HTMX
42
- ${pc.yellow("--wasm")} Full WASM SPA
43
86
  ${pc.yellow("--install")} Run bun install after scaffolding
44
87
  ${pc.yellow("--no-install")} Skip bun install (default)
45
88
  ${pc.yellow("--yes, -y")} Skip prompts, use defaults
@@ -47,8 +90,8 @@ ${pc.bold("Options")}
47
90
 
48
91
  ${pc.bold("Examples")}
49
92
  ${pc.cyan("bun create swift-rust@latest")}
50
- ${pc.cyan("bun create swift-rust@latest my-app --ts --tailwind --src-dir")}
51
- ${pc.cyan("bun create swift-rust@latest ./ --tailwind --eslint")}
93
+ ${pc.cyan("bun create swift-rust@latest my-app --install")}
94
+ ${pc.cyan("bun create swift-rust@latest ./")}
52
95
  `;
53
96
  function parseFlags(args) {
54
97
  const flags = {};
@@ -101,24 +144,6 @@ function parseFlags(args) {
101
144
  }
102
145
  return flags;
103
146
  }
104
- const RENDERER_OPTIONS = [
105
- {
106
- value: "ssr-wasm",
107
- label: "SSR + WASM hydration",
108
- hint: "Default. Server-rendered HTML, WASM-hydrated interactive islands.",
109
- },
110
- { value: "ssr", label: "SSR only", hint: "Server-rendered HTML, no client JavaScript at all." },
111
- {
112
- value: "ssr-htmx",
113
- label: "SSR + HTMX",
114
- hint: "Progressive enhancement with HTMX-style interactions.",
115
- },
116
- {
117
- value: "wasm",
118
- label: "Full WASM SPA",
119
- hint: "Single-page app compiled entirely to WebAssembly.",
120
- },
121
- ];
122
147
  async function askQuestions(flags, positional) {
123
148
  const yesMode = flags.yes === true;
124
149
  const projectName = positional ??
@@ -131,8 +156,8 @@ async function askQuestions(flags, positional) {
131
156
  validate: (v) => {
132
157
  if (!v)
133
158
  return "Project name is required";
134
- if (!isValidName(v))
135
- return "Invalid name. Use letters, numbers, dashes, and underscores.";
159
+ if (!isCurrentDir(v) && !isValidName(normalizeProjectName(v)))
160
+ return 'Invalid name. Use letters, numbers, dashes, and underscores — or "." for the current directory.';
136
161
  return undefined;
137
162
  },
138
163
  }));
@@ -140,130 +165,8 @@ async function askQuestions(flags, positional) {
140
165
  p.cancel("Aborted.");
141
166
  return null;
142
167
  }
143
- let languageAnswer;
144
- if (flags.language) {
145
- languageAnswer = flags.language;
146
- }
147
- else if (yesMode) {
148
- languageAnswer = "ts";
149
- }
150
- else {
151
- const languageOptions = [
152
- { value: "ts", label: "TypeScript", hint: "Type-safe, recommended" },
153
- { value: "js", label: "JavaScript", hint: "No type checking" },
154
- ];
155
- const result = await p.select({
156
- message: "Which language would you like to use?",
157
- options: languageOptions,
158
- initialValue: "ts",
159
- });
160
- languageAnswer = result;
161
- }
162
- if (p.isCancel(languageAnswer)) {
163
- p.cancel("Aborted.");
164
- return null;
165
- }
166
- const language = languageAnswer;
167
- let renderer = flags.renderer ?? "ssr-wasm";
168
- if (!flags.renderer && !yesMode) {
169
- const r = (await p.select({
170
- message: "Which rendering mode do you want?",
171
- options: RENDERER_OPTIONS,
172
- initialValue: "ssr-wasm",
173
- }));
174
- if (p.isCancel(r)) {
175
- p.cancel("Aborted.");
176
- return null;
177
- }
178
- renderer = r;
179
- }
180
- let linterAnswer;
181
- if (flags.linter) {
182
- linterAnswer = flags.linter;
183
- }
184
- else if (yesMode) {
185
- linterAnswer = "biome";
186
- }
187
- else {
188
- const linterOptions = [
189
- { value: "biome", label: "Biome", hint: "Fast Rust-based all-in-one tool (recommended)" },
190
- { value: "eslint", label: "ESLint", hint: "The classic, more configurable" },
191
- ];
192
- const result = await p.select({
193
- message: "Which linter would you like to use?",
194
- options: linterOptions,
195
- initialValue: "biome",
196
- });
197
- linterAnswer = result;
198
- }
199
- if (p.isCancel(linterAnswer)) {
200
- p.cancel("Aborted.");
201
- return null;
202
- }
203
- const linter = linterAnswer;
204
- const tailwindAnswer = typeof flags.tailwind === "boolean"
205
- ? flags.tailwind
206
- : yesMode
207
- ? false
208
- : ((await p.confirm({
209
- message: "Would you like to use Tailwind CSS?",
210
- initialValue: true,
211
- })) ?? false);
212
- if (p.isCancel(tailwindAnswer)) {
213
- p.cancel("Aborted.");
214
- return null;
215
- }
216
- const tailwind = tailwindAnswer === true;
217
- const useShadcnAnswer = typeof flags.useShadcn === "boolean"
218
- ? flags.useShadcn
219
- : yesMode
220
- ? true
221
- : ((await p.confirm({
222
- message: "Would you like to use shadcn-style UI components?",
223
- initialValue: true,
224
- })) ?? false);
225
- if (p.isCancel(useShadcnAnswer)) {
226
- p.cancel("Aborted.");
227
- return null;
228
- }
229
- const useShadcn = useShadcnAnswer === true;
230
- const srcDirAnswer = typeof flags.srcDir === "boolean"
231
- ? flags.srcDir
232
- : yesMode
233
- ? true
234
- : ((await p.confirm({
235
- message: "Would you like to use a `src/` directory for your app code?",
236
- initialValue: true,
237
- })) ?? true);
238
- if (p.isCancel(srcDirAnswer)) {
239
- p.cancel("Aborted.");
240
- return null;
241
- }
242
- const srcDir = srcDirAnswer === true;
243
- const importAliasAnswer = flags.importAlias ??
244
- (yesMode
245
- ? "@/*"
246
- : (await p.text({
247
- message: "Would you like to customize the import alias?",
248
- placeholder: "@/*",
249
- defaultValue: "@/*",
250
- validate: (v) => {
251
- if (!v)
252
- return "Import alias is required";
253
- if (!isValidAlias(v))
254
- return "Invalid alias. Use letters, numbers, @, /, *, -, _.";
255
- return undefined;
256
- },
257
- })));
258
- if (p.isCancel(importAliasAnswer)) {
259
- p.cancel("Aborted.");
260
- return null;
261
- }
262
- const importAlias = importAliasAnswer;
263
- if (!importAlias) {
264
- p.cancel("Aborted.");
265
- return null;
266
- }
168
+ // The scaffold is the full-demo template (TypeScript + src/ + Tailwind +
169
+ // Biome). The only remaining choice is whether to install dependencies.
267
170
  const installAnswer = typeof flags.install === "boolean"
268
171
  ? flags.install
269
172
  : yesMode
@@ -276,705 +179,24 @@ async function askQuestions(flags, positional) {
276
179
  p.cancel("Aborted.");
277
180
  return null;
278
181
  }
279
- const install = installAnswer === true;
280
182
  return {
281
- projectName,
282
- language,
283
- renderer,
284
- linter,
285
- tailwind,
286
- srcDir,
287
- importAlias,
288
- useShadcn,
289
- install,
290
- };
291
- }
292
- async function writeProjectFiles(target, answers) {
293
- const { projectName, language, renderer, linter, tailwind, srcDir, importAlias, useShadcn } = answers;
294
- const appDir = join(target, srcDir ? "app/src" : "app");
295
- const componentsDir = join(target, "components");
296
- const libDir = join(target, "lib");
297
- const uiDir = join(componentsDir, "ui");
298
- await mkdir(target, { recursive: true });
299
- await mkdir(appDir, { recursive: true });
300
- await mkdir(componentsDir, { recursive: true });
301
- await mkdir(libDir, { recursive: true });
302
- await mkdir(join(target, "public"), { recursive: true });
303
- if (useShadcn) {
304
- await mkdir(uiDir, { recursive: true });
305
- }
306
- const fileExt = (lang) => (lang === "ts" ? "ts" : "js");
307
- const componentExt = (lang) => (lang === "ts" ? "tsx" : "jsx");
308
- const pkg = {
309
- name: projectName,
310
- version: "0.1.0",
311
- private: true,
312
- type: "module",
313
- scripts: {
314
- dev: "swift-rust dev",
315
- build: "swift-rust build",
316
- start: "swift-rust start",
317
- lint: linter === "biome" ? "biome check ." : "eslint .",
318
- test: "bun test",
319
- ...(language === "ts" ? { typecheck: "tsc --noEmit" } : {}),
320
- format: linter === "biome" ? "biome format --write ." : "prettier --write .",
321
- },
322
- dependencies: {
323
- "swift-rust": "0.1.0",
324
- react: "^19.0.0",
325
- "react-dom": "^19.0.0",
326
- ...(useShadcn
327
- ? {
328
- clsx: "^2.1.1",
329
- "tailwind-merge": "^2.5.5",
330
- "class-variance-authority": "^0.7.1",
331
- "lucide-react": "^0.460.0",
332
- "tw-animate-css": "^1.0.0",
333
- }
334
- : {}),
335
- },
336
- devDependencies: {
337
- ...(language === "ts"
338
- ? { typescript: "^5.6.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" }
339
- : {}),
340
- ...(linter === "biome" ? { "@biomejs/biome": "^1.9.0" } : { eslint: "^9.0.0" }),
341
- ...(tailwind
342
- ? { tailwindcss: "^4.0.0", "@tailwindcss/postcss": "^4.0.0", postcss: "^8.4.0" }
343
- : {}),
344
- ...(useShadcn ? { shadcn: "^2.0.0" } : {}),
345
- },
183
+ projectName: normalizeProjectName(String(projectName)),
184
+ language: "ts",
185
+ renderer: "ssr-wasm",
186
+ linter: "biome",
187
+ tailwind: true,
188
+ srcDir: true,
189
+ importAlias: "@/*",
190
+ useShadcn: false,
191
+ install: installAnswer === true,
346
192
  };
347
- await writeFile(join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
348
- const config = {
349
- rendering: renderer,
350
- image: { domains: [], formats: ["image/avif", "image/webp"] },
351
- font: { subsets: ["latin"], display: "swap" },
352
- pdf: { defaultPageSize: "A4" },
353
- };
354
- await writeFile(join(target, "swift-rust.config.json"), `${JSON.stringify(config, null, 2)}\n`);
355
- if (language === "ts") {
356
- const tsconfig = {
357
- extends: "swift-rust/tsconfig.base.json",
358
- compilerOptions: {
359
- jsx: "preserve",
360
- baseUrl: ".",
361
- paths: { [importAlias]: ["./*"] },
362
- },
363
- include: [
364
- srcDir ? "app/src/**/*" : "app/**/*",
365
- "components/**/*",
366
- "lib/**/*",
367
- "globals.d.ts",
368
- ".swift-rust/types/**/*",
369
- ],
370
- exclude: ["node_modules", "dist", ".swift-rust", ".turbo"],
371
- };
372
- await writeFile(join(target, "tsconfig.json"), `${JSON.stringify(tsconfig, null, 2)}\n`);
373
- }
374
- else {
375
- const jsconfig = {
376
- compilerOptions: {
377
- baseUrl: ".",
378
- paths: { [importAlias]: ["./*"] },
379
- },
380
- include: [srcDir ? "app/src/**/*" : "app/**/*", "components/**/*", "lib/**/*"],
381
- };
382
- await writeFile(join(target, "jsconfig.json"), `${JSON.stringify(jsconfig, null, 2)}\n`);
383
- }
384
- const gitignore = [
385
- "node_modules/",
386
- ".swift-rust/",
387
- "dist/",
388
- ".turbo/",
389
- ".env*.local",
390
- "target/",
391
- "*.log",
392
- ".DS_Store",
393
- ].join("\n");
394
- await writeFile(join(target, ".gitignore"), `${gitignore}\n`);
395
- if (tailwind) {
396
- const postcss = `const config = {
397
- plugins: {
398
- "@tailwindcss/postcss": {},
399
- },
400
- };
401
-
402
- export default config;
403
- `;
404
- await writeFile(join(target, "postcss.config.mjs"), postcss);
405
- const shadcnVars = useShadcn
406
- ? `
407
-
408
- /* shadcn design tokens (https://ui.shadcn.com/docs/theming) */
409
- :root {
410
- --ui-bg: #ffffff;
411
- --ui-fg: #09090b;
412
- --ui-card: #ffffff;
413
- --ui-card-fg: #09090b;
414
- --ui-popover: #ffffff;
415
- --ui-popover-fg: #09090b;
416
- --ui-primary: #18181b;
417
- --ui-primary-fg: #fafafa;
418
- --ui-secondary: #f4f4f5;
419
- --ui-secondary-fg: #18181b;
420
- --ui-muted: #f4f4f5;
421
- --ui-muted-fg: #71717a;
422
- --ui-accent: #f4f4f5;
423
- --ui-accent-fg: #18181b;
424
- --ui-destructive: #ef4444;
425
- --ui-destructive-fg: #fafafa;
426
- --ui-border: #e4e4e7;
427
- --ui-input: #e4e4e7;
428
- --ui-ring: #18181b;
429
- --ui-radius: 0.5rem;
430
- }
431
-
432
- .dark {
433
- --ui-bg: #09090b;
434
- --ui-fg: #fafafa;
435
- --ui-card: #09090b;
436
- --ui-card-fg: #fafafa;
437
- --ui-popover: #09090b;
438
- --ui-popover-fg: #fafafa;
439
- --ui-primary: #fafafa;
440
- --ui-primary-fg: #18181b;
441
- --ui-secondary: #27272a;
442
- --ui-secondary-fg: #fafafa;
443
- --ui-muted: #27272a;
444
- --ui-muted-fg: #a1a1aa;
445
- --ui-accent: #27272a;
446
- --ui-accent-fg: #fafafa;
447
- --ui-destructive: #7f1d1d;
448
- --ui-destructive-fg: #fafafa;
449
- --ui-border: #27272a;
450
- --ui-input: #27272a;
451
- --ui-ring: #d4d4d8;
452
- }
453
-
454
- @media (prefers-color-scheme: dark) {
455
- :root:not(.light) {
456
- --ui-bg: #09090b;
457
- --ui-fg: #fafafa;
458
- --ui-card: #09090b;
459
- --ui-card-fg: #fafafa;
460
- --ui-popover: #09090b;
461
- --ui-popover-fg: #fafafa;
462
- --ui-primary: #fafafa;
463
- --ui-primary-fg: #18181b;
464
- --ui-secondary: #27272a;
465
- --ui-secondary-fg: #fafafa;
466
- --ui-muted: #27272a;
467
- --ui-muted-fg: #a1a1aa;
468
- --ui-accent: #27272a;
469
- --ui-accent-fg: #fafafa;
470
- --ui-destructive: #7f1d1d;
471
- --ui-destructive-fg: #fafafa;
472
- --ui-border: #27272a;
473
- --ui-input: #27272a;
474
- --ui-ring: #d4d4d8;
475
- }
476
- }
477
-
478
- @theme inline {
479
- --color-background: var(--ui-bg);
480
- --color-foreground: var(--ui-fg);
481
- --color-card: var(--ui-card);
482
- --color-card-foreground: var(--ui-card-fg);
483
- --color-popover: var(--ui-popover);
484
- --color-popover-foreground: var(--ui-popover-fg);
485
- --color-primary: var(--ui-primary);
486
- --color-primary-foreground: var(--ui-primary-fg);
487
- --color-secondary: var(--ui-secondary);
488
- --color-secondary-foreground: var(--ui-secondary-fg);
489
- --color-muted: var(--ui-muted);
490
- --color-muted-foreground: var(--ui-muted-fg);
491
- --color-accent: var(--ui-accent);
492
- --color-accent-foreground: var(--ui-accent-fg);
493
- --color-destructive: var(--ui-destructive);
494
- --color-destructive-foreground: var(--ui-destructive-fg);
495
- --color-border: var(--ui-border);
496
- --color-input: var(--ui-input);
497
- --color-ring: var(--ui-ring);
498
- --radius: var(--ui-radius);
499
- }
500
- `
501
- : "";
502
- const css = `@import "tailwindcss";
503
- ${useShadcn ? '@import "tw-animate-css";' : ""}
504
-
505
- @theme {
506
- --color-bg: #ffffff;
507
- --color-fg: #09090b;
508
- --color-accent: ${useShadcn ? "#f97316" : "#0070f3"};
509
- --font-sans: var(--font-geist-sans), system-ui, sans-serif;
510
- --font-mono: var(--font-geist-mono), ui-monospace, monospace;
511
- }
512
-
513
- @variant dark (&:where(.dark, .dark *));
514
-
515
- @media (prefers-color-scheme: dark) {
516
- :root:not(.light) {
517
- --color-bg: #09090b;
518
- --color-fg: #fafafa;
519
- }
520
- }
521
-
522
- body {
523
- background: var(--color-bg);
524
- color: var(--color-fg);
525
- font-family: var(--font-sans);
526
- }
527
- ${shadcnVars}`;
528
- await writeFile(join(appDir, `globals.${fileExt(language)}`), language === "ts" ? `import "./globals.css";\n` : `import "./globals.css";\n`);
529
- await writeFile(join(appDir, "globals.css"), css);
530
- }
531
- const layoutImports = `import type { ReactNode } from "react";
532
- import { Geist, Geist_Mono } from "swift-rust/font/google";
533
- ${tailwind ? `import "./globals.css";\n` : ""}
534
- const geistSans = Geist({ subsets: ["latin"], display: "swap", variable: true });
535
- const geistMono = Geist_Mono({ subsets: ["latin"], display: "swap", variable: true });
536
-
537
- export const metadata = {
538
- title: {
539
- template: "%s | ${projectName}",
540
- default: "${projectName}",
541
- },
542
- description: "Built with swift-rust — the React framework powered with Rust + Bun. 10x faster than Next.js.",
543
- };
544
-
545
- export default function RootLayout({ children }: { children: ReactNode }) {
546
- return (
547
- <html
548
- lang="en"
549
- className={\`\${geistSans.variable} \${geistMono.variable} h-full antialiased\`}
550
- >
551
- <head>
552
- <link rel="preconnect" href="https://fonts.googleapis.com" />
553
- <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
554
- </head>
555
- <body className="${tailwind ? "min-h-full flex flex-col" : ""}">${tailwind ? "{children}" : "\\n {children}\\n "}</body>
556
- </html>
557
- );
558
- }
559
- `;
560
- await writeFile(join(appDir, `layout.${componentExt(language)}`), layoutImports);
561
- const homePage = tailwind
562
- ? useShadcn
563
- ? `import { buttonVariants } from "@/components/ui/button";
564
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
565
- import { Badge } from "@/components/ui/badge";
566
-
567
- export default function Home() {
568
- return (
569
- <main className="min-h-screen flex items-center justify-center px-6 py-12">
570
- <Card className="max-w-2xl w-full">
571
- <CardHeader>
572
- <Badge variant="secondary" className="w-fit mb-2">Welcome</Badge>
573
- <CardTitle className="text-3xl sm:text-5xl">${projectName}</CardTitle>
574
- <CardDescription>
575
- Get started by editing <code>${srcDir ? "app/src/" : "app/"}page.${componentExt(language)}</code>.
576
- </CardDescription>
577
- </CardHeader>
578
- <CardContent className="flex gap-2">
579
- <a
580
- href="https://swift-rust.dev/docs"
581
- className={buttonVariants({ variant: "default" })}
582
- >
583
- Read the docs →
584
- </a>
585
- <a
586
- href="https://github.com/swift-rust/swift-rust"
587
- className={buttonVariants({ variant: "outline" })}
588
- >
589
- GitHub
590
- </a>
591
- </CardContent>
592
- </Card>
593
- </main>
594
- );
595
- }
596
- `
597
- : `export default function Home() {
598
- return (
599
- <main className="min-h-screen flex items-center justify-center px-6">
600
- <div className="max-w-2xl text-center">
601
- <div className="inline-block mb-4 px-3 py-1 text-xs font-medium rounded-full bg-accent/10 text-accent">
602
- Welcome
603
- </div>
604
- <h1 className="text-5xl sm:text-7xl font-bold tracking-tight mb-6">
605
- ${projectName}
606
- </h1>
607
- <p className="text-lg text-fg-secondary mb-8">
608
- Get started by editing <code>${srcDir ? "app/src/" : "app/"}page.${componentExt(language)}</code>.
609
- </p>
610
- <a
611
- href="https://swift-rust.dev/docs"
612
- className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-fg text-bg font-medium text-sm hover:opacity-90 transition-opacity"
613
- >
614
- Read the docs →
615
- </a>
616
- </div>
617
- </main>
618
- );
619
- }
620
- `
621
- : `export default function Home() {
622
- return (
623
- <main style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: "2rem" }}>
624
- <div style={{ maxWidth: "32rem", textAlign: "center" }}>
625
- <h1>${projectName}</h1>
626
- <p>Get started by editing <code>${srcDir ? "app/src/" : "app/"}page.${componentExt(language)}</code>.</p>
627
- <a href="https://swift-rust.dev/docs">Read the docs →</a>
628
- </div>
629
- </main>
630
- );
631
- }
632
- `;
633
- await writeFile(join(appDir, `page.${componentExt(language)}`), homePage);
634
- const notFoundPage = tailwind
635
- ? `export const metadata = { title: "Not found" };
636
-
637
- export default function NotFound() {
638
- return (
639
- <main className="min-h-screen flex items-center justify-center px-6">
640
- <div className="text-center">
641
- <h1 className="text-6xl font-bold mb-4">404</h1>
642
- <p className="text-fg-secondary">Page not found</p>
643
- <a href="/" className="text-accent mt-6 inline-block">← Go home</a>
644
- </div>
645
- </main>
646
- );
647
- }
648
- `
649
- : `export default function NotFound() {
650
- return (
651
- <main>
652
- <h1>404</h1>
653
- <p>Page not found</p>
654
- <a href="/">← Go home</a>
655
- </main>
656
- );
657
- }
658
- `;
659
- await writeFile(join(appDir, `not-found.${componentExt(language)}`), notFoundPage);
660
- if (linter === "biome") {
661
- const biome = {
662
- $schema: "https://biomejs.dev/schemas/1.9.0/schema.json",
663
- vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
664
- files: { ignore: [".swift-rust", "node_modules", "dist", ".turbo"] },
665
- formatter: { enabled: true, indentStyle: "space", indentWidth: 2 },
666
- organizeImports: { enabled: true },
667
- linter: {
668
- enabled: true,
669
- rules: { recommended: true, a11y: { noSvgWithoutTitle: "off" } },
670
- },
671
- javascript: { formatter: { quoteStyle: "double", semicolons: "always" } },
672
- };
673
- await writeFile(join(target, "biome.json"), `${JSON.stringify(biome, null, 2)}\n`);
674
- }
675
- else {
676
- const eslint = `import swiftRust from "eslint-config-swift-rust";
677
-
678
- export default [
679
- ...swiftRust,
680
- {
681
- ignores: [".swift-rust/**", "node_modules/**", "dist/**", ".turbo/**"],
682
- },
683
- ];
684
- `;
685
- await writeFile(join(target, "eslint.config.mjs"), eslint);
686
- }
687
- const readme = `# ${projectName}
688
-
689
- Built with [swift-rust](https://swift-rust.dev) — the React framework powered with Rust + Bun. 10x faster than Next.js.
690
-
691
- ## Scripts
692
-
693
- - \`bun run dev\` — start the dev server
694
- - \`bun run build\` — build for production
695
- - \`bun run start\` — start the production server
696
- - \`bun run lint\` — run the linter
697
- - \`bun run typecheck\` — run TypeScript checks (if TS)
698
- - \`bun run test\` — run tests
699
- - \`bun run format\` — format the code
700
-
701
- ## Project structure
702
-
703
- ${srcDir
704
- ? `\`\`\`\napp/\n src/\n layout.${componentExt(language)}\n page.${componentExt(language)}\n not-found.${componentExt(language)}\ncomponents/\nlib/\n\`\`\``
705
- : `\`\`\`\napp/\n layout.${componentExt(language)}\n page.${componentExt(language)}\n not-found.${componentExt(language)}\ncomponents/\nlib/\n\`\`\``}
706
-
707
- ## Learn more
708
-
709
- - [Documentation](https://swift-rust.dev/docs)
710
- - [Examples](https://github.com/swift-rust/swift-rust/tree/main/examples)
711
- - [Discord](https://discord.gg/swift-rust)
712
- `;
713
- await writeFile(join(target, "README.md"), readme);
714
- const envExample = `# Rename to .env.local to use
715
- # SWIFT_RUST_RUNTIME=/path/to/bun
716
- `;
717
- await writeFile(join(target, ".env.example"), envExample);
718
- const favicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#0070f3"/><path d="M8 8L16 24L24 8" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="16" cy="16" r="2.5" fill="white"/></svg>
719
- `;
720
- await writeFile(join(target, "public", "favicon.svg"), favicon);
721
- if (useShadcn) {
722
- await scaffoldShadcn({ target, uiDir, libDir, componentExt: componentExt(language), srcDir });
723
- }
724
- }
725
- async function scaffoldShadcn(options) {
726
- const { target, uiDir, libDir, componentExt, srcDir } = options;
727
- const utilsContent = `import { clsx, type ClassValue } from "clsx";
728
- import { twMerge } from "tailwind-merge";
729
-
730
- export function cn(...inputs: ClassValue[]) {
731
- return twMerge(clsx(inputs));
732
- }
733
- `;
734
- const buttonContent = `import * as React from "react";
735
- import { cva, type VariantProps } from "class-variance-authority";
736
- import { cn } from "@/lib/utils";
737
-
738
- const buttonVariants = cva(
739
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
740
- {
741
- variants: {
742
- variant: {
743
- default: "bg-[var(--ui-primary)] text-[var(--ui-primary-fg)] hover:opacity-90",
744
- destructive: "bg-[var(--ui-destructive)] text-white hover:opacity-90",
745
- outline:
746
- "border border-[var(--ui-border)] bg-[var(--ui-bg)] hover:bg-[var(--ui-accent)] hover:text-[var(--ui-accent-fg)]",
747
- secondary: "bg-[var(--ui-secondary)] text-[var(--ui-secondary-fg)] hover:opacity-90",
748
- ghost: "hover:bg-[var(--ui-accent)] hover:text-[var(--ui-accent-fg)]",
749
- link: "text-[var(--ui-primary)] underline-offset-4 hover:underline",
750
- },
751
- size: {
752
- default: "h-9 px-4 py-2",
753
- sm: "h-8 rounded-md px-3 text-xs",
754
- lg: "h-10 rounded-md px-8",
755
- icon: "h-9 w-9",
756
- },
757
- },
758
- defaultVariants: {
759
- variant: "default",
760
- size: "default",
761
- },
762
- },
763
- );
764
-
765
- export interface ButtonProps
766
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
767
- VariantProps<typeof buttonVariants> {}
768
-
769
- export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
770
- ({ className, variant, size, ...props }, ref) => (
771
- <button ref={ref} className={cn(buttonVariants({ variant, size, className }))} {...props} />
772
- ),
773
- );
774
- Button.displayName = "Button";
775
-
776
- export { buttonVariants };
777
- `;
778
- const cardContent = `import * as React from "react";
779
- import { cn } from "@/lib/utils";
780
-
781
- export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
782
- ({ className, ...props }, ref) => (
783
- <div
784
- ref={ref}
785
- className={cn(
786
- "rounded-xl border border-[var(--ui-border)] bg-[var(--ui-card)] text-[var(--ui-card-fg)] shadow-sm",
787
- className,
788
- )}
789
- {...props}
790
- />
791
- ),
792
- );
793
- Card.displayName = "Card";
794
-
795
- export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
796
- ({ className, ...props }, ref) => (
797
- <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
798
- ),
799
- );
800
- CardHeader.displayName = "CardHeader";
801
-
802
- export const CardTitle = React.forwardRef<
803
- HTMLDivElement,
804
- React.HTMLAttributes<HTMLDivElement>
805
- >(({ className, ...props }, ref) => (
806
- <div
807
- ref={ref}
808
- className={cn("font-semibold leading-none tracking-tight", className)}
809
- {...props}
810
- />
811
- ));
812
- CardTitle.displayName = "CardTitle";
813
-
814
- export const CardDescription = React.forwardRef<
815
- HTMLDivElement,
816
- React.HTMLAttributes<HTMLDivElement>
817
- >(({ className, ...props }, ref) => (
818
- <div
819
- ref={ref}
820
- className={cn("text-sm text-[var(--ui-muted-fg)]", className)}
821
- {...props}
822
- />
823
- ));
824
- CardDescription.displayName = "CardDescription";
825
-
826
- export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
827
- ({ className, ...props }, ref) => (
828
- <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
829
- ),
830
- );
831
- CardContent.displayName = "CardContent";
832
-
833
- export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
834
- ({ className, ...props }, ref) => (
835
- <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
836
- ),
837
- );
838
- CardFooter.displayName = "CardFooter";
839
- `;
840
- const inputContent = `import * as React from "react";
841
- import { cn } from "@/lib/utils";
842
-
843
- export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
844
-
845
- export const Input = React.forwardRef<HTMLInputElement, InputProps>(
846
- ({ className, type, ...props }, ref) => (
847
- <input
848
- type={type}
849
- ref={ref}
850
- className={cn(
851
- "flex h-9 w-full rounded-md border border-[var(--ui-border)] bg-transparent px-3 py-1 text-sm shadow-sm transition-colors",
852
- "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-[var(--ui-fg)]",
853
- "placeholder:text-[var(--ui-muted-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)] focus-visible:ring-offset-1",
854
- "disabled:cursor-not-allowed disabled:opacity-50",
855
- className,
856
- )}
857
- {...props}
858
- />
859
- ),
860
- );
861
- Input.displayName = "Input";
862
- `;
863
- const labelContent = `import * as React from "react";
864
- import { cn } from "@/lib/utils";
865
-
866
- export const Label = React.forwardRef<
867
- HTMLLabelElement,
868
- React.LabelHTMLAttributes<HTMLLabelElement>
869
- >(({ className, ...props }, ref) => (
870
- <label
871
- ref={ref}
872
- className={cn(
873
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
874
- className,
875
- )}
876
- {...props}
877
- />
878
- ));
879
- Label.displayName = "Label";
880
- `;
881
- const badgeContent = `import * as React from "react";
882
- import { cva, type VariantProps } from "class-variance-authority";
883
- import { cn } from "@/lib/utils";
884
-
885
- const badgeVariants = cva(
886
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--ui-ring)] focus:ring-offset-2",
887
- {
888
- variants: {
889
- variant: {
890
- default:
891
- "border-transparent bg-[var(--ui-primary)] text-[var(--ui-primary-fg)] hover:opacity-90",
892
- secondary:
893
- "border-transparent bg-[var(--ui-secondary)] text-[var(--ui-secondary-fg)] hover:opacity-90",
894
- destructive:
895
- "border-transparent bg-[var(--ui-destructive)] text-white hover:opacity-90",
896
- outline: "text-[var(--ui-fg)] border-[var(--ui-border)]",
897
- },
898
- },
899
- defaultVariants: {
900
- variant: "default",
901
- },
902
- },
903
- );
904
-
905
- export interface BadgeProps
906
- extends React.HTMLAttributes<HTMLDivElement>,
907
- VariantProps<typeof badgeVariants> {}
908
-
909
- export function Badge({ className, variant, ...props }: BadgeProps) {
910
- return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
911
- }
912
-
913
- export { badgeVariants };
914
- `;
915
- const componentsJson = `{
916
- "$schema": "https://ui.shadcn.com/schema.json",
917
- "style": "new-york",
918
- "rsc": true,
919
- "tsx": ${componentExt === "tsx"},
920
- "tailwind": {
921
- "config": "",
922
- "css": "${srcDir ? "app/src/globals.css" : "app/globals.css"}",
923
- "baseColor": "zinc",
924
- "cssVariables": true,
925
- "prefix": ""
926
- },
927
- "aliases": {
928
- "components": "@/components",
929
- "utils": "@/lib/utils",
930
- "ui": "@/components/ui",
931
- "lib": "@/lib",
932
- "hooks": "@/hooks"
933
- },
934
- "iconLibrary": "lucide"
935
- }
936
- `;
937
- await writeFile(join(libDir, "utils.ts"), utilsContent);
938
- await writeFile(join(uiDir, `button.${componentExt}`), buttonContent);
939
- await writeFile(join(uiDir, `card.${componentExt}`), cardContent);
940
- await writeFile(join(uiDir, `input.${componentExt}`), inputContent);
941
- await writeFile(join(uiDir, `label.${componentExt}`), labelContent);
942
- await writeFile(join(uiDir, `badge.${componentExt}`), badgeContent);
943
- await writeFile(join(target, "components.json"), `${componentsJson}\n`);
944
193
  }
945
194
  async function runInstall(target) {
946
195
  const { spawn } = await import("node:child_process");
947
- return new Promise((resolve, reject) => {
948
- const proc = spawn("bun", ["install"], { cwd: target, stdio: "inherit" });
949
- proc.on("exit", (code) => {
950
- if (code === 0)
951
- resolve();
952
- else
953
- reject(new Error(`bun install exited with code ${code}`));
954
- });
955
- proc.on("error", reject);
956
- });
957
- }
958
- const SHADCN_DEFAULT_COMPONENTS = ["button", "card", "input", "label", "badge"];
959
- async function runShadcnAdd(target, components) {
960
- const { spawn } = await import("node:child_process");
961
- return new Promise((resolve) => {
962
- const args = [
963
- "--bun",
964
- "shadcn@latest",
965
- "add",
966
- ...components,
967
- "--yes",
968
- "--overwrite",
969
- "--cwd",
970
- target,
971
- "--silent",
972
- ];
973
- const proc = spawn("bunx", args, { cwd: target, stdio: "pipe" });
974
- proc.on("exit", (code) => {
975
- resolve(code === 0);
976
- });
977
- proc.on("error", () => resolve(false));
196
+ await new Promise((resolvePromise, reject) => {
197
+ const child = spawn("bun", ["install"], { cwd: target, stdio: "inherit" });
198
+ child.on("error", reject);
199
+ child.on("close", (code) => code === 0 ? resolvePromise() : reject(new Error(`bun install exited with code ${code}`)));
978
200
  });
979
201
  }
980
202
  async function main() {
@@ -996,7 +218,7 @@ async function main() {
996
218
  const answers = await askQuestions(flags, positional);
997
219
  if (!answers)
998
220
  process.exit(1);
999
- const { projectName, tailwind, linter, srcDir, install } = answers;
221
+ const { projectName, install } = answers;
1000
222
  const target = resolve(process.cwd(), projectName);
1001
223
  if (projectName === ".") {
1002
224
  if ((await listDir(target)).length > 0) {
@@ -1028,13 +250,10 @@ async function main() {
1028
250
  }
1029
251
  const summary = [
1030
252
  `${pc.cyan("•")} Project: ${pc.bold(projectName === "." ? resolve(target) : projectName)}`,
1031
- `${pc.cyan("•")} Language: ${answers.language === "ts" ? "TypeScript" : "JavaScript"}`,
1032
- `${pc.cyan("•")} Renderer: ${answers.renderer}`,
1033
- `${pc.cyan("•")} Linter: ${linter === "biome" ? "Biome" : "ESLint"}`,
1034
- `${pc.cyan("•")} Tailwind: ${tailwind ? "Yes" : "No"}`,
1035
- `${pc.cyan("•")} shadcn UI: ${answers.useShadcn ? "Yes" : "No"}`,
1036
- `${pc.cyan("•")} src/ dir: ${srcDir ? "Yes (app/src/)" : "No"}`,
1037
- `${pc.cyan("•")} Import as: ${answers.importAlias}`,
253
+ `${pc.cyan("•")} Template: Full demo (blog, dashboard, API, Image/PDF/Video/Font)`,
254
+ `${pc.cyan("•")} Language: TypeScript`,
255
+ `${pc.cyan("•")} Styling: Tailwind CSS`,
256
+ `${pc.cyan("•")} Linter: Biome`,
1038
257
  `${pc.cyan("•")} Install: ${install ? "Yes" : "No"}`,
1039
258
  ].join("\n");
1040
259
  if (quiet) {
@@ -1053,31 +272,18 @@ async function main() {
1053
272
  }
1054
273
  console.log(`Scaffolding ${pc.cyan(projectName === "." ? "." : projectName)}…`);
1055
274
  try {
1056
- await writeProjectFiles(target, answers);
275
+ await copyTemplate(target, projectName);
1057
276
  console.log(`✓ Scaffolded project into ${pc.green(target)}`);
1058
- let installSucceeded = false;
1059
277
  if (install) {
1060
278
  console.log("Running bun install…");
1061
279
  try {
1062
280
  await runInstall(target);
1063
281
  console.log("✓ Installed dependencies");
1064
- installSucceeded = true;
1065
282
  }
1066
283
  catch (_err) {
1067
284
  console.error("Failed to install dependencies. Run `bun install` manually.");
1068
285
  }
1069
286
  }
1070
- if (answers.useShadcn && installSucceeded) {
1071
- console.log("Fetching canonical shadcn components…");
1072
- const ok = await runShadcnAdd(target, SHADCN_DEFAULT_COMPONENTS);
1073
- if (ok) {
1074
- console.log(`✓ Added ${SHADCN_DEFAULT_COMPONENTS.length} shadcn components from the registry`);
1075
- }
1076
- else {
1077
- const cwdArg = projectName === "." ? "." : `./${projectName}`;
1078
- 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}`)}`);
1079
- }
1080
- }
1081
287
  const next = projectName === "." ? "bun run dev" : `cd ${projectName}\nbun run dev`;
1082
288
  console.log(`\n${pc.green("Done!")} Next steps:\n${pc.cyan(next)}`);
1083
289
  }