shadcn-scaffold 1.0.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.
- package/README.md +15 -0
- package/dist/create.js +95 -0
- package/dist/index.js +16 -0
- package/dist/templates.js +161 -0
- package/dist/ui.js +32 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# cli-tool
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { appTsxCode, indexCss, mainTsxCode, modeToggleCode, themeProviderCode, viteConfig, } from "./templates";
|
|
7
|
+
export async function create(projectName, packages) {
|
|
8
|
+
const spinner = ora("Scaffolding Vite React app...").start();
|
|
9
|
+
try {
|
|
10
|
+
await execa("bun", [
|
|
11
|
+
"create",
|
|
12
|
+
"vite@latest",
|
|
13
|
+
projectName,
|
|
14
|
+
"--template",
|
|
15
|
+
"react-ts",
|
|
16
|
+
]);
|
|
17
|
+
spinner.succeed("Vite React app created");
|
|
18
|
+
spinner.start("Installing Tailwind CSS...");
|
|
19
|
+
await execa("bun", ["add", "tailwindcss", "@tailwindcss/vite"], {
|
|
20
|
+
cwd: projectName,
|
|
21
|
+
});
|
|
22
|
+
spinner.succeed("Tailwind CSS installed");
|
|
23
|
+
spinner.start("Configuring Tailwind CSS...");
|
|
24
|
+
fs.writeFileSync(path.join(projectName, "vite.config.ts"), viteConfig);
|
|
25
|
+
fs.writeFileSync(path.join(projectName, "src", "index.css"), indexCss);
|
|
26
|
+
fs.unlinkSync(path.join(projectName, "src", "App.css"));
|
|
27
|
+
spinner.succeed("Tailwind CSS configured");
|
|
28
|
+
const tsconfigAppPath = path.join(projectName, "tsconfig.app.json");
|
|
29
|
+
let tsconfigAppContent = fs.readFileSync(tsconfigAppPath, "utf-8");
|
|
30
|
+
tsconfigAppContent = tsconfigAppContent.replace('"compilerOptions": {', `"compilerOptions": {\n "ignoreDeprecations": "6.0",\n "baseUrl": ".",\n "paths": {\n "@/*": [\n "./src/*"\n ]\n },`);
|
|
31
|
+
fs.writeFileSync(tsconfigAppPath, tsconfigAppContent);
|
|
32
|
+
const tsconfigPath = path.join(projectName, "tsconfig.json");
|
|
33
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, "utf-8"));
|
|
34
|
+
tsconfig.compilerOptions = {
|
|
35
|
+
baseUrl: ".",
|
|
36
|
+
ignoreDeprecations: "6.0",
|
|
37
|
+
paths: {
|
|
38
|
+
"@/*": ["./src/*"],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
42
|
+
spinner.start("Initializing shadcn/ui...");
|
|
43
|
+
await execa("bunx", [
|
|
44
|
+
"--bun",
|
|
45
|
+
"shadcn@latest",
|
|
46
|
+
"init",
|
|
47
|
+
"-t",
|
|
48
|
+
"vite",
|
|
49
|
+
"-d",
|
|
50
|
+
"-y",
|
|
51
|
+
"--cwd",
|
|
52
|
+
projectName,
|
|
53
|
+
]);
|
|
54
|
+
spinner.succeed("shadcn/ui initialized");
|
|
55
|
+
spinner.start("Adding shadcn button component...");
|
|
56
|
+
await execa("bunx", [
|
|
57
|
+
"--bun",
|
|
58
|
+
"shadcn@latest",
|
|
59
|
+
"add",
|
|
60
|
+
"button",
|
|
61
|
+
"-y",
|
|
62
|
+
"--cwd",
|
|
63
|
+
projectName,
|
|
64
|
+
]);
|
|
65
|
+
spinner.succeed("shadcn button added");
|
|
66
|
+
spinner.start("Setting up custom project structure and theme toggle...");
|
|
67
|
+
fs.mkdirSync(path.join(projectName, "src", "providers"), {
|
|
68
|
+
recursive: true,
|
|
69
|
+
});
|
|
70
|
+
fs.mkdirSync(path.join(projectName, "src", "features", "theme"), {
|
|
71
|
+
recursive: true,
|
|
72
|
+
});
|
|
73
|
+
fs.writeFileSync(path.join(projectName, "src", "providers", "theme-provider.tsx"), themeProviderCode);
|
|
74
|
+
fs.writeFileSync(path.join(projectName, "src", "features", "theme", "mode-toggle.tsx"), modeToggleCode);
|
|
75
|
+
fs.writeFileSync(path.join(projectName, "src", "main.tsx"), mainTsxCode);
|
|
76
|
+
fs.writeFileSync(path.join(projectName, "src", "App.tsx"), appTsxCode);
|
|
77
|
+
spinner.succeed("Project structure and theme configured");
|
|
78
|
+
if (packages.length > 0) {
|
|
79
|
+
spinner.start(`Installing extra packages: ${packages.join(", ")}...`);
|
|
80
|
+
await execa("bun", ["add", ...packages], {
|
|
81
|
+
cwd: projectName,
|
|
82
|
+
});
|
|
83
|
+
spinner.succeed("Extra packages installed");
|
|
84
|
+
}
|
|
85
|
+
console.log(`\n${chalk.green("✔")} Successfully created ${chalk.cyan(projectName)}!`);
|
|
86
|
+
console.log(`\nNext steps:`);
|
|
87
|
+
console.log(` ${chalk.cyan(`cd ${projectName}`)}`);
|
|
88
|
+
console.log(` ${chalk.cyan("bun run dev")}\n`);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
spinner.fail("An error occurred during setup");
|
|
92
|
+
console.error(error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { App } from "./ui.js";
|
|
6
|
+
import { render } from "ink";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name("scaffold")
|
|
10
|
+
.description("Sets up your personal React stack instantly")
|
|
11
|
+
.version("1.0.0")
|
|
12
|
+
.argument("<project-name>", "Name of your project")
|
|
13
|
+
.action((projectName) => {
|
|
14
|
+
render(_jsx(App, { projectName: projectName }));
|
|
15
|
+
});
|
|
16
|
+
program.parse();
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export const viteConfig = `import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
4
|
+
import react from "@vitejs/plugin-react";
|
|
5
|
+
import { defineConfig } from "vite";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
plugins: [react(), tailwindcss()],
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
"@": path.resolve(__dirname, "./src"),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
`;
|
|
18
|
+
export const indexCss = `@import "tailwindcss";
|
|
19
|
+
`;
|
|
20
|
+
export const themeProviderCode = `import { createContext, useContext, useEffect, useState } from "react"
|
|
21
|
+
|
|
22
|
+
type Theme = "dark" | "light" | "system"
|
|
23
|
+
|
|
24
|
+
type ThemeProviderProps = {
|
|
25
|
+
children: React.ReactNode
|
|
26
|
+
defaultTheme?: Theme
|
|
27
|
+
storageKey?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ThemeProviderState = {
|
|
31
|
+
theme: Theme
|
|
32
|
+
setTheme: (theme: Theme) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ThemeProviderContext = createContext<ThemeProviderState | undefined>(
|
|
36
|
+
undefined
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export function ThemeProvider({
|
|
40
|
+
children,
|
|
41
|
+
defaultTheme = "system",
|
|
42
|
+
storageKey = "vite-ui-theme",
|
|
43
|
+
...props
|
|
44
|
+
}: ThemeProviderProps) {
|
|
45
|
+
const [theme, setTheme] = useState<Theme>(
|
|
46
|
+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const root = window.document.documentElement
|
|
51
|
+
|
|
52
|
+
root.classList.remove("light", "dark")
|
|
53
|
+
|
|
54
|
+
if (theme === "system") {
|
|
55
|
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
56
|
+
.matches
|
|
57
|
+
? "dark"
|
|
58
|
+
: "light"
|
|
59
|
+
|
|
60
|
+
root.classList.add(systemTheme)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
root.classList.add(theme)
|
|
65
|
+
}, [theme])
|
|
66
|
+
|
|
67
|
+
const value = {
|
|
68
|
+
theme,
|
|
69
|
+
setTheme: (theme: Theme) => {
|
|
70
|
+
localStorage.setItem(storageKey, theme)
|
|
71
|
+
setTheme(theme)
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
77
|
+
{children}
|
|
78
|
+
</ThemeProviderContext.Provider>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const useTheme = () => {
|
|
83
|
+
const context = useContext(ThemeProviderContext)
|
|
84
|
+
|
|
85
|
+
if (!context)
|
|
86
|
+
throw new Error("useTheme must be used within a ThemeProvider")
|
|
87
|
+
|
|
88
|
+
return context
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
export const mainTsxCode = `import { StrictMode } from 'react'
|
|
92
|
+
import { createRoot } from 'react-dom/client'
|
|
93
|
+
import './index.css'
|
|
94
|
+
import App from './App.tsx'
|
|
95
|
+
import { ThemeProvider } from "@/providers/theme-provider"
|
|
96
|
+
|
|
97
|
+
createRoot(document.getElementById('root')!).render(
|
|
98
|
+
<StrictMode>
|
|
99
|
+
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
|
100
|
+
<App />
|
|
101
|
+
</ThemeProvider>
|
|
102
|
+
</StrictMode>,
|
|
103
|
+
)
|
|
104
|
+
`;
|
|
105
|
+
export const modeToggleCode = `import { Moon, Sun } from "lucide-react"
|
|
106
|
+
|
|
107
|
+
import { Button } from "@/components/ui/button"
|
|
108
|
+
import { useTheme } from "@/providers/theme-provider"
|
|
109
|
+
|
|
110
|
+
export function ModeToggle() {
|
|
111
|
+
const { theme, setTheme } = useTheme()
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Button
|
|
115
|
+
variant="outline"
|
|
116
|
+
size="icon"
|
|
117
|
+
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
|
118
|
+
>
|
|
119
|
+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
120
|
+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
121
|
+
<span className="sr-only">Toggle theme</span>
|
|
122
|
+
</Button>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
export const appTsxCode = `import { ModeToggle } from "@/features/theme/mode-toggle"
|
|
127
|
+
import { Button } from "@/components/ui/button"
|
|
128
|
+
|
|
129
|
+
function App() {
|
|
130
|
+
return (
|
|
131
|
+
<div className="flex min-h-screen flex-col items-center justify-center p-24 gap-4">
|
|
132
|
+
<div className="absolute top-4 right-4">
|
|
133
|
+
<ModeToggle />
|
|
134
|
+
</div>
|
|
135
|
+
<h1 className="text-4xl font-bold">Hello World</h1>
|
|
136
|
+
<Button>Click me</Button>
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default App
|
|
142
|
+
`;
|
|
143
|
+
export const tsconfigCode = `{
|
|
144
|
+
"files": [],
|
|
145
|
+
"references": [
|
|
146
|
+
{
|
|
147
|
+
"path": "./tsconfig.app.json"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"path": "./tsconfig.node.json"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"compilerOptions": {
|
|
154
|
+
"baseUrl": ".",
|
|
155
|
+
"ignoreDeprecations": "6.0",
|
|
156
|
+
"paths": {
|
|
157
|
+
"@/*": ["./src/*"]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`;
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Text, Box, useApp } from "ink";
|
|
4
|
+
import { MultiSelect } from "@inkjs/ui";
|
|
5
|
+
import { create } from "./create.js";
|
|
6
|
+
export function App({ projectName }) {
|
|
7
|
+
const { exit } = useApp();
|
|
8
|
+
const [isDone, setIsDone] = useState(false);
|
|
9
|
+
const options = [
|
|
10
|
+
{ label: "Zustand (State Management)", value: "zustand" },
|
|
11
|
+
{ label: "React Router DOM (Routing)", value: "react-router-dom" },
|
|
12
|
+
{ label: "TanStack Query (Data Fetching/Caching)", value: "@tanstack/react-query" },
|
|
13
|
+
{ label: "Axios (HTTP Client)", value: "axios" },
|
|
14
|
+
{ label: "Framer Motion (Animations)", value: "framer-motion" },
|
|
15
|
+
{ label: "React Hook Form (Forms)", value: "react-hook-form" },
|
|
16
|
+
{ label: "Zod (Schema Validation)", value: "zod" },
|
|
17
|
+
];
|
|
18
|
+
const handleSubmit = async (selectedValues) => {
|
|
19
|
+
setIsDone(true);
|
|
20
|
+
exit();
|
|
21
|
+
try {
|
|
22
|
+
await create(projectName, selectedValues);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
if (isDone) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsxs(Text, { color: "orange", children: ["Select extra packages for ", projectName, " (Space to select, Enter to submit);"] }), _jsx(MultiSelect, { options: options, onSubmit: handleSubmit })] }));
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shadcn-scaffold",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"scaffold": "./dist/index.js"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"@types/node": "^25.7.0",
|
|
16
|
+
"@types/react": "^19.2.14",
|
|
17
|
+
"tsx": "^4.21.0",
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@inkjs/ui": "^2.0.0",
|
|
22
|
+
"chalk": "^5.6.2",
|
|
23
|
+
"commander": "^14.0.3",
|
|
24
|
+
"execa": "^9.6.1",
|
|
25
|
+
"ink": "^7.0.3",
|
|
26
|
+
"ora": "^9.4.0",
|
|
27
|
+
"react": "^19.2.6"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "tsx src/index.tsx",
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"start": "node dist/index.js",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
}
|
|
35
|
+
}
|