muscle-config 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/LICENSE +21 -0
- package/README.md +430 -0
- package/dist/commands/create.command.js +104 -0
- package/dist/config/projectConfig.js +1 -0
- package/dist/features/architecture.feature.js +49 -0
- package/dist/features/css.feature.js +107 -0
- package/dist/features/feature.interface.js +1 -0
- package/dist/features/git.feature.js +52 -0
- package/dist/features/mui.feature.js +138 -0
- package/dist/features/prettier.feature.js +73 -0
- package/dist/features/tailwind.feature.js +116 -0
- package/dist/generators/architecture.generator.js +75 -0
- package/dist/generators/css-plain.generator.js +215 -0
- package/dist/generators/css.generator.js +45 -0
- package/dist/generators/mui.generator.js +176 -0
- package/dist/generators/react.generator.js +38 -0
- package/dist/generators/tailwind.config.generator.js +17 -0
- package/dist/generators/toggle.generator.js +26 -0
- package/dist/index.js +11 -0
- package/dist/prompts/architecture.prompt.js +131 -0
- package/dist/prompts/css.prompt.js +217 -0
- package/dist/prompts/directory.prompt.js +16 -0
- package/dist/prompts/framework.prompt.js +15 -0
- package/dist/prompts/git.prompt.js +23 -0
- package/dist/prompts/mui.prompt.js +104 -0
- package/dist/prompts/prettier.prompt.js +42 -0
- package/dist/prompts/projectName.prompt.js +13 -0
- package/dist/prompts/styling.prompt.js +17 -0
- package/dist/prompts/tailwind.prompt.js +175 -0
- package/dist/templates/react/css/App.jsx +41 -0
- package/dist/templates/react/css/App.tsx +41 -0
- package/dist/templates/react/mui/App.jsx +42 -0
- package/dist/templates/react/mui/App.tsx +42 -0
- package/dist/templates/react/tailwind-v4/index.css +20 -0
- package/dist/templates/react/tailwind-v4/vite.config.js +7 -0
- package/dist/templates/react/tailwind-v4/vite.config.ts +7 -0
- package/dist/utils/directory.js +43 -0
- package/dist/utils/install.js +7 -0
- package/dist/utils/logger.js +10 -0
- package/dist/utils/rollback.js +63 -0
- package/dist/utils/spinner.js +13 -0
- package/dist/utils/welcome.js +46 -0
- package/package.json +57 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// ─── Helpers ───
|
|
2
|
+
const colorMap = {
|
|
3
|
+
indigo: "#6366f1",
|
|
4
|
+
emerald: "#10b981",
|
|
5
|
+
neutral: "#737373",
|
|
6
|
+
};
|
|
7
|
+
const fontMap = {
|
|
8
|
+
inter: "Inter, sans-serif",
|
|
9
|
+
poppins: "Poppins, sans-serif",
|
|
10
|
+
cairo: "Cairo, sans-serif",
|
|
11
|
+
};
|
|
12
|
+
const fontImportMap = {
|
|
13
|
+
inter: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap",
|
|
14
|
+
poppins: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap",
|
|
15
|
+
cairo: "https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;700&display=swap",
|
|
16
|
+
};
|
|
17
|
+
function getPrimaryColor(config) {
|
|
18
|
+
return config.colorPreset === "custom"
|
|
19
|
+
? (config.customColor ?? "#6366f1")
|
|
20
|
+
: (colorMap[config.colorPreset] ?? "");
|
|
21
|
+
}
|
|
22
|
+
// ─── reset.css ───
|
|
23
|
+
export function generateCssReset() {
|
|
24
|
+
return `
|
|
25
|
+
*, *::before, *::after {
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
html {
|
|
32
|
+
-webkit-text-size-adjust: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
min-height: 100vh;
|
|
37
|
+
line-height: 1.5;
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
-moz-osx-font-smoothing: grayscale;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
img, picture, video, canvas, svg {
|
|
43
|
+
display: block;
|
|
44
|
+
max-width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
input, button, textarea, select {
|
|
48
|
+
font: inherit;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
p, h1, h2, h3, h4, h5, h6 {
|
|
52
|
+
overflow-wrap: break-word;
|
|
53
|
+
}
|
|
54
|
+
`.trim();
|
|
55
|
+
}
|
|
56
|
+
// ─── variables.css ───
|
|
57
|
+
export function generateCssVariables(config) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
const primaryColor = getPrimaryColor(config);
|
|
60
|
+
if (config.font !== "none") {
|
|
61
|
+
lines.push(`@import url('${fontImportMap[config.font]}');`);
|
|
62
|
+
lines.push("");
|
|
63
|
+
}
|
|
64
|
+
lines.push(":root {");
|
|
65
|
+
if (primaryColor) {
|
|
66
|
+
lines.push(` --color-primary: ${primaryColor};`);
|
|
67
|
+
lines.push(` --color-primary-hover: ${primaryColor}cc;`);
|
|
68
|
+
}
|
|
69
|
+
if (config.font !== "none") {
|
|
70
|
+
lines.push(` --font-base: ${fontMap[config.font]};`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(" --radius: 0.5rem;");
|
|
73
|
+
lines.push(" --spacing: 1rem;");
|
|
74
|
+
lines.push("}");
|
|
75
|
+
if (config.darkMode === "class") {
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push(".dark {");
|
|
78
|
+
if (primaryColor)
|
|
79
|
+
lines.push(` --color-primary: ${primaryColor};`);
|
|
80
|
+
lines.push(" color-scheme: dark;");
|
|
81
|
+
lines.push("}");
|
|
82
|
+
}
|
|
83
|
+
if (config.darkMode === "media") {
|
|
84
|
+
lines.push("");
|
|
85
|
+
lines.push("@media (prefers-color-scheme: dark) {");
|
|
86
|
+
lines.push(" :root {");
|
|
87
|
+
lines.push(" color-scheme: dark;");
|
|
88
|
+
lines.push(" }");
|
|
89
|
+
lines.push("}");
|
|
90
|
+
}
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
// ─── typography.css ───
|
|
94
|
+
export function generateCssTypography(config) {
|
|
95
|
+
const fontFamily = config.font !== "none"
|
|
96
|
+
? `var(--font-base)`
|
|
97
|
+
: `system-ui, Avenir, Helvetica, Arial, sans-serif`;
|
|
98
|
+
return `
|
|
99
|
+
body {
|
|
100
|
+
font-family: ${fontFamily};
|
|
101
|
+
font-size: 1rem;
|
|
102
|
+
font-weight: 400;
|
|
103
|
+
color: #171717;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
h1 { font-size: 2.25rem; font-weight: 700; line-height: 1.2; }
|
|
107
|
+
h2 { font-size: 1.875rem; font-weight: 600; line-height: 1.3; }
|
|
108
|
+
h3 { font-size: 1.5rem; font-weight: 600; line-height: 1.4; }
|
|
109
|
+
h4 { font-size: 1.25rem; font-weight: 500; }
|
|
110
|
+
h5 { font-size: 1.125rem; font-weight: 500; }
|
|
111
|
+
h6 { font-size: 1rem; font-weight: 500; }
|
|
112
|
+
|
|
113
|
+
p { line-height: 1.7; }
|
|
114
|
+
|
|
115
|
+
a {
|
|
116
|
+
color: var(--color-primary, #6366f1);
|
|
117
|
+
text-decoration: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
a:hover {
|
|
121
|
+
text-decoration: underline;
|
|
122
|
+
}
|
|
123
|
+
`.trim();
|
|
124
|
+
}
|
|
125
|
+
// ─── index.css (single file mode) ───
|
|
126
|
+
export function generateCssIndex(config) {
|
|
127
|
+
const lines = [];
|
|
128
|
+
const primaryColor = getPrimaryColor(config);
|
|
129
|
+
if (config.font !== "none") {
|
|
130
|
+
lines.push(`@import url('${fontImportMap[config.font]}');`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
if (config.reset) {
|
|
134
|
+
lines.push("/* ─── Reset ─────────────────────────────── */");
|
|
135
|
+
lines.push(generateCssReset());
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
138
|
+
lines.push("/* ─── Variables ─────────────────────────── */");
|
|
139
|
+
lines.push(":root {");
|
|
140
|
+
if (primaryColor) {
|
|
141
|
+
lines.push(` --color-primary: ${primaryColor};`);
|
|
142
|
+
lines.push(` --color-primary-hover: ${primaryColor}cc;`);
|
|
143
|
+
}
|
|
144
|
+
if (config.font !== "none") {
|
|
145
|
+
lines.push(` --font-base: ${fontMap[config.font]};`);
|
|
146
|
+
}
|
|
147
|
+
lines.push(" --radius: 0.5rem;");
|
|
148
|
+
lines.push(" --spacing: 1rem;");
|
|
149
|
+
lines.push("}");
|
|
150
|
+
if (config.darkMode === "class") {
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push(".dark {");
|
|
153
|
+
lines.push(" color-scheme: dark;");
|
|
154
|
+
lines.push("}");
|
|
155
|
+
}
|
|
156
|
+
if (config.darkMode === "media") {
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push("@media (prefers-color-scheme: dark) {");
|
|
159
|
+
lines.push(" :root {");
|
|
160
|
+
lines.push(" color-scheme: dark;");
|
|
161
|
+
lines.push(" }");
|
|
162
|
+
lines.push("}");
|
|
163
|
+
}
|
|
164
|
+
lines.push("");
|
|
165
|
+
lines.push("/* ─── Typography ────────────────────────── */");
|
|
166
|
+
lines.push(`body {`);
|
|
167
|
+
lines.push(` font-family: ${config.font !== "none" ? "var(--font-base)" : "system-ui, Avenir, Helvetica, Arial, sans-serif"};`);
|
|
168
|
+
lines.push(` font-size: 1rem;`);
|
|
169
|
+
lines.push(` color: #171717;`);
|
|
170
|
+
lines.push(`}`);
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
|
173
|
+
// ─── index.css (separate files mode) ───
|
|
174
|
+
export function generateCssIndexWithImports(config) {
|
|
175
|
+
const lines = [];
|
|
176
|
+
if (config.reset)
|
|
177
|
+
lines.push(`@import "./styles/reset.css";`);
|
|
178
|
+
lines.push(`@import "./styles/variables.css";`);
|
|
179
|
+
lines.push(`@import "./styles/typography.css";`);
|
|
180
|
+
return lines.join("\n");
|
|
181
|
+
}
|
|
182
|
+
// ─── ThemeToggle.tsx / .jsx (plain CSS version) ───
|
|
183
|
+
export function generateCssThemeToggle(isTypeScript) {
|
|
184
|
+
const stateType = isTypeScript ? "<boolean>" : "";
|
|
185
|
+
return `
|
|
186
|
+
import { useState, useEffect } from "react";
|
|
187
|
+
|
|
188
|
+
export default function ThemeToggle() {
|
|
189
|
+
const [dark, setDark] = useState${stateType}(() => {
|
|
190
|
+
return localStorage.getItem("theme") === "dark";
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
document.documentElement.classList.toggle("dark", dark);
|
|
195
|
+
localStorage.setItem("theme", dark ? "dark" : "light");
|
|
196
|
+
}, [dark]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<button
|
|
200
|
+
onClick={() => setDark(!dark)}
|
|
201
|
+
style={{
|
|
202
|
+
padding: "0.4rem 0.8rem",
|
|
203
|
+
border: "1px solid currentColor",
|
|
204
|
+
borderRadius: "var(--radius, 0.5rem)",
|
|
205
|
+
cursor: "pointer",
|
|
206
|
+
background: "transparent",
|
|
207
|
+
fontSize: "1rem",
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{dark ? "🌙 Dark" : "☀️ Light"}
|
|
211
|
+
</button>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
`.trim();
|
|
215
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function generateIndexCss(config) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
lines.push('@import "tailwindcss";');
|
|
4
|
+
// Font imports
|
|
5
|
+
if (config.font === "inter") {
|
|
6
|
+
lines.push(`@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');`);
|
|
7
|
+
}
|
|
8
|
+
if (config.font === "poppins") {
|
|
9
|
+
lines.push(`@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap');`);
|
|
10
|
+
}
|
|
11
|
+
if (config.font === "cairo") {
|
|
12
|
+
lines.push(`@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;700&display=swap');`);
|
|
13
|
+
}
|
|
14
|
+
if (config.darkMode === "class") {
|
|
15
|
+
lines.push(`@custom-variant dark (&:where(.dark, .dark *));`);
|
|
16
|
+
}
|
|
17
|
+
lines.push("");
|
|
18
|
+
lines.push(":root {");
|
|
19
|
+
// Font Application
|
|
20
|
+
const fontFamilies = {
|
|
21
|
+
inter: "Inter, sans-serif",
|
|
22
|
+
poppins: "Poppins, sans-serif",
|
|
23
|
+
cairo: "Cairo, sans-serif",
|
|
24
|
+
};
|
|
25
|
+
// Applying
|
|
26
|
+
if (config.font !== "none")
|
|
27
|
+
lines.push(` font-family: ${fontFamilies[config.font]};`);
|
|
28
|
+
if (config.colorPreset !== "none") {
|
|
29
|
+
const colors = {
|
|
30
|
+
indigo: "#6366f1",
|
|
31
|
+
emerald: "#10b981",
|
|
32
|
+
neutral: "#737373",
|
|
33
|
+
custom: config.customColor ?? "#6366f1",
|
|
34
|
+
};
|
|
35
|
+
lines.push(` --color-primary: ${colors[config.colorPreset]};`);
|
|
36
|
+
}
|
|
37
|
+
lines.push("}");
|
|
38
|
+
if (config.darkMode === "class") {
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push(".dark {");
|
|
41
|
+
lines.push(" color-scheme: dark;");
|
|
42
|
+
lines.push("}");
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// ─── Helpers ───
|
|
2
|
+
const colorMap = {
|
|
3
|
+
blue: "#1976d2",
|
|
4
|
+
purple: "#9c27b0",
|
|
5
|
+
green: "#2e7d32",
|
|
6
|
+
};
|
|
7
|
+
const fontImportMap = {
|
|
8
|
+
inter: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap",
|
|
9
|
+
poppins: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap",
|
|
10
|
+
cairo: "https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;700&display=swap",
|
|
11
|
+
};
|
|
12
|
+
const fontFamilyMap = {
|
|
13
|
+
inter: "Inter, sans-serif",
|
|
14
|
+
poppins: "Poppins, sans-serif",
|
|
15
|
+
cairo: "Cairo, sans-serif",
|
|
16
|
+
};
|
|
17
|
+
function getPrimaryColor(config) {
|
|
18
|
+
return config.colorPreset === "custom"
|
|
19
|
+
? (config.customColor ?? "#1976d2")
|
|
20
|
+
: colorMap[config.colorPreset];
|
|
21
|
+
}
|
|
22
|
+
function getInitialMode(config) {
|
|
23
|
+
const systemMode = `window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"`;
|
|
24
|
+
return config.themeMode === "system"
|
|
25
|
+
? `saved || (${systemMode})`
|
|
26
|
+
: `saved || "${config.themeMode}"`;
|
|
27
|
+
}
|
|
28
|
+
// ─── theme.ts ───
|
|
29
|
+
export function generateMuiTheme(config, isTypeScript) {
|
|
30
|
+
const primaryColor = getPrimaryColor(config);
|
|
31
|
+
const tsImport = isTypeScript
|
|
32
|
+
? `\nimport type { PaletteMode } from "@mui/material";`
|
|
33
|
+
: "";
|
|
34
|
+
const modeType = isTypeScript ? "mode: PaletteMode" : "mode";
|
|
35
|
+
return `
|
|
36
|
+
import { createTheme } from "@mui/material/styles";${tsImport}
|
|
37
|
+
|
|
38
|
+
export function createAppTheme(${modeType}) {
|
|
39
|
+
return createTheme({
|
|
40
|
+
palette: {
|
|
41
|
+
mode,
|
|
42
|
+
primary: {
|
|
43
|
+
main: "${primaryColor}",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
`.trim();
|
|
49
|
+
}
|
|
50
|
+
// ─── ThemeToggle.tsx / .jsx ───
|
|
51
|
+
export function generateMuiThemeToggle() {
|
|
52
|
+
return `
|
|
53
|
+
import IconButton from "@mui/material/IconButton";
|
|
54
|
+
import Brightness4Icon from "@mui/icons-material/Brightness4";
|
|
55
|
+
import Brightness7Icon from "@mui/icons-material/Brightness7";
|
|
56
|
+
import { useTheme } from "../context/ThemeContextProvider";
|
|
57
|
+
|
|
58
|
+
export default function ThemeToggle() {
|
|
59
|
+
const { theme, toggleTheme } = useTheme();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<IconButton onClick={toggleTheme} color="inherit">
|
|
63
|
+
{theme === "dark" ? <Brightness7Icon /> : <Brightness4Icon />}
|
|
64
|
+
</IconButton>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
`.trim();
|
|
68
|
+
}
|
|
69
|
+
// ─── ThemeContextProvider.tsx / .jsx ───
|
|
70
|
+
export function generateThemeContextProvider(config, isTypeScript) {
|
|
71
|
+
const initialMode = getInitialMode(config);
|
|
72
|
+
const tsBlock = isTypeScript
|
|
73
|
+
? `
|
|
74
|
+
type ThemeMode = "light" | "dark";
|
|
75
|
+
|
|
76
|
+
interface ThemeContextType {
|
|
77
|
+
theme: ThemeMode;
|
|
78
|
+
toggleTheme: () => void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ThemeContext = createContext<ThemeContextType>({} as ThemeContextType);`
|
|
82
|
+
: `
|
|
83
|
+
const ThemeContext = createContext({});`;
|
|
84
|
+
const savedType = isTypeScript ? " as ThemeMode | null" : "";
|
|
85
|
+
const stateType = isTypeScript ? "<ThemeMode>" : "";
|
|
86
|
+
const returnType = isTypeScript ? ": ThemeContextType" : "";
|
|
87
|
+
const childrenType = isTypeScript
|
|
88
|
+
? "({ children }: { children: React.ReactNode })"
|
|
89
|
+
: "({ children })";
|
|
90
|
+
return `
|
|
91
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
92
|
+
|
|
93
|
+
const THEME_KEY = "theme";
|
|
94
|
+
${tsBlock}
|
|
95
|
+
|
|
96
|
+
export function ThemeContextProvider${childrenType} {
|
|
97
|
+
const [theme, setTheme] = useState${stateType}(() => {
|
|
98
|
+
const saved = localStorage.getItem(THEME_KEY)${savedType};
|
|
99
|
+
return ${initialMode};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
104
|
+
}, [theme]);
|
|
105
|
+
|
|
106
|
+
const toggleTheme = () =>
|
|
107
|
+
setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
|
111
|
+
{children}
|
|
112
|
+
</ThemeContext.Provider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function useTheme()${returnType} {
|
|
117
|
+
return useContext(ThemeContext);
|
|
118
|
+
}
|
|
119
|
+
`.trim();
|
|
120
|
+
}
|
|
121
|
+
// ─── AppMuiThemeProvider.tsx / .jsx ───
|
|
122
|
+
export function generateAppMuiThemeProvider(isTypeScript) {
|
|
123
|
+
const propsType = isTypeScript
|
|
124
|
+
? "({ children }: { children: React.ReactNode })"
|
|
125
|
+
: "({ children })";
|
|
126
|
+
return `
|
|
127
|
+
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
|
|
128
|
+
import CssBaseline from "@mui/material/CssBaseline";
|
|
129
|
+
import { useTheme } from "../context/ThemeContextProvider";
|
|
130
|
+
import { createAppTheme } from "./theme";
|
|
131
|
+
|
|
132
|
+
export default function AppMuiThemeProvider${propsType} {
|
|
133
|
+
const { theme } = useTheme();
|
|
134
|
+
const muiTheme = createAppTheme(theme);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<MuiThemeProvider theme={muiTheme}>
|
|
138
|
+
<CssBaseline />
|
|
139
|
+
{children}
|
|
140
|
+
</MuiThemeProvider>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
`.trim();
|
|
144
|
+
}
|
|
145
|
+
// ─── main.tsx / main.jsx ───
|
|
146
|
+
export function generateMuiMain(isTypeScript) {
|
|
147
|
+
return `
|
|
148
|
+
import { StrictMode } from "react";
|
|
149
|
+
import { createRoot } from "react-dom/client";
|
|
150
|
+
import { ThemeContextProvider } from "./context/ThemeContextProvider";
|
|
151
|
+
import AppMuiThemeProvider from "./themes/AppMuiThemeProvider";
|
|
152
|
+
import App from "./App";
|
|
153
|
+
|
|
154
|
+
createRoot(document.getElementById("root")${isTypeScript ? "!" : ""}).render(
|
|
155
|
+
<StrictMode>
|
|
156
|
+
<ThemeContextProvider>
|
|
157
|
+
<AppMuiThemeProvider>
|
|
158
|
+
<App />
|
|
159
|
+
</AppMuiThemeProvider>
|
|
160
|
+
</ThemeContextProvider>
|
|
161
|
+
</StrictMode>
|
|
162
|
+
);
|
|
163
|
+
`.trim();
|
|
164
|
+
}
|
|
165
|
+
// ─── index.css (MUI font setup) ───
|
|
166
|
+
export function generateMuiIndexCss(config) {
|
|
167
|
+
if (config.font === "none")
|
|
168
|
+
return "";
|
|
169
|
+
return `
|
|
170
|
+
@import url('${fontImportMap[config.font]}');
|
|
171
|
+
|
|
172
|
+
body {
|
|
173
|
+
font-family: ${fontFamilyMap[config.font]};
|
|
174
|
+
}
|
|
175
|
+
`.trim();
|
|
176
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { spinner } from "../utils/spinner.js";
|
|
4
|
+
import path from "path";
|
|
5
|
+
export async function generateReactProject(config) {
|
|
6
|
+
const template = config.framework === "react-ts" ? "react-ts" : "react";
|
|
7
|
+
const targetDir = config.directoryMode === "current" ? "." : config.projectName;
|
|
8
|
+
const projectPath = config.directoryMode === "current"
|
|
9
|
+
? process.cwd()
|
|
10
|
+
: path.join(process.cwd(), config.projectName);
|
|
11
|
+
logger.info(`Creating project '${targetDir}' using template '${template}'...`);
|
|
12
|
+
try {
|
|
13
|
+
spinner.start("Scaffolding project with Vite...");
|
|
14
|
+
// Start Creating the project
|
|
15
|
+
await execa("npm", [
|
|
16
|
+
"create",
|
|
17
|
+
"vite@latest",
|
|
18
|
+
targetDir,
|
|
19
|
+
"--",
|
|
20
|
+
"--template",
|
|
21
|
+
template,
|
|
22
|
+
"--no-interactive",
|
|
23
|
+
], { stdio: "ignore" });
|
|
24
|
+
spinner.succeed("Project scaffolded!");
|
|
25
|
+
// Installing dependencies
|
|
26
|
+
spinner.start("Installing dependencies — do not cancel this step...");
|
|
27
|
+
await execa("npm", ["install"], {
|
|
28
|
+
cwd: projectPath,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
});
|
|
31
|
+
spinner.succeed("Dependencies installed!");
|
|
32
|
+
console.log("");
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
spinner.fail("Failed to scaffold project.");
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function generateTailwindConfig(config) {
|
|
2
|
+
const darkMode = config.darkMode ? `darkMode: "${config.darkMode}",` : "";
|
|
3
|
+
return `
|
|
4
|
+
/** @type {import('tailwindcss').Config} */
|
|
5
|
+
export default {
|
|
6
|
+
${darkMode}
|
|
7
|
+
content: [
|
|
8
|
+
"./index.html",
|
|
9
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
10
|
+
],
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {},
|
|
13
|
+
},
|
|
14
|
+
plugins: [],
|
|
15
|
+
};
|
|
16
|
+
`.trim();
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function generateThemeToggle(isTypeScript) {
|
|
2
|
+
const stateType = isTypeScript ? "<boolean>" : "";
|
|
3
|
+
return `
|
|
4
|
+
import { useState, useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
export default function ThemeToggle() {
|
|
7
|
+
const [dark, setDark] = useState${stateType}(() => {
|
|
8
|
+
return localStorage.getItem("theme") === "dark";
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
document.documentElement.classList.toggle("dark", dark);
|
|
13
|
+
localStorage.setItem("theme", dark ? "dark" : "light");
|
|
14
|
+
}, [dark]);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
onClick={() => setDark(!dark)}
|
|
19
|
+
className="px-3 py-1 border rounded"
|
|
20
|
+
>
|
|
21
|
+
{dark ? "🌙 Dark" : "☀️ Light"}
|
|
22
|
+
</button>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
`.trim();
|
|
26
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createProject } from "./commands/create.command.js";
|
|
3
|
+
import { welcome } from "./utils/welcome.js";
|
|
4
|
+
const main = async () => {
|
|
5
|
+
await welcome();
|
|
6
|
+
await createProject();
|
|
7
|
+
};
|
|
8
|
+
main().catch((err) => {
|
|
9
|
+
console.error(err);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
const allFolderChoices = [
|
|
3
|
+
{ name: "components", value: "components" },
|
|
4
|
+
{ name: "hooks", value: "hooks" },
|
|
5
|
+
{ name: "pages", value: "pages" },
|
|
6
|
+
{ name: "layouts", value: "layouts" },
|
|
7
|
+
{ name: "services", value: "services" },
|
|
8
|
+
{ name: "utils", value: "utils" },
|
|
9
|
+
{ name: "types", value: "types" },
|
|
10
|
+
{ name: "assets", value: "assets" },
|
|
11
|
+
{ name: "api", value: "api" },
|
|
12
|
+
{ name: "validators", value: "validators" },
|
|
13
|
+
{ name: "templates", value: "templates" },
|
|
14
|
+
{ name: "themes", value: "themes" },
|
|
15
|
+
{ name: "lib", value: "lib" },
|
|
16
|
+
];
|
|
17
|
+
const featurePresets = [
|
|
18
|
+
{ name: "auth", value: "auth" },
|
|
19
|
+
{ name: "dashboard", value: "dashboard" },
|
|
20
|
+
{ name: "profile", value: "profile" },
|
|
21
|
+
{ name: "settings", value: "settings" },
|
|
22
|
+
{ name: "home", value: "home" },
|
|
23
|
+
{ name: "products", value: "products" },
|
|
24
|
+
{ name: "cart", value: "cart" },
|
|
25
|
+
{ name: "checkout", value: "checkout" },
|
|
26
|
+
];
|
|
27
|
+
export async function askArchitecture() {
|
|
28
|
+
const { style } = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: "list",
|
|
31
|
+
name: "style",
|
|
32
|
+
message: "Choose architecture style:",
|
|
33
|
+
choices: [
|
|
34
|
+
{ name: "Layered (recommended for small/mid apps) - more popular", value: "layered" },
|
|
35
|
+
{ name: "Feature-based (recommended for large apps)", value: "feature-based" },
|
|
36
|
+
{ name: "Skip", value: "skip" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
if (style === "skip") {
|
|
41
|
+
return {
|
|
42
|
+
style,
|
|
43
|
+
features: [],
|
|
44
|
+
featureFolders: [],
|
|
45
|
+
sharedFolders: [],
|
|
46
|
+
indexFiles: [],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (style === "feature-based") {
|
|
50
|
+
const { features } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: "checkbox",
|
|
53
|
+
name: "features",
|
|
54
|
+
message: "Select features to generate:",
|
|
55
|
+
choices: featurePresets,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
const { featureFolders } = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: "checkbox",
|
|
61
|
+
name: "featureFolders",
|
|
62
|
+
message: "Select folders to generate inside each feature:",
|
|
63
|
+
choices: [
|
|
64
|
+
{ name: "components", value: "components", checked: true },
|
|
65
|
+
{ name: "hooks", value: "hooks", checked: true },
|
|
66
|
+
{ name: "services", value: "services", checked: true },
|
|
67
|
+
{ name: "utils", value: "utils" },
|
|
68
|
+
{ name: "types", value: "types" },
|
|
69
|
+
{ name: "validators", value: "validators" },
|
|
70
|
+
{ name: "api", value: "api" },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
const { sharedFolders } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: "checkbox",
|
|
77
|
+
name: "sharedFolders",
|
|
78
|
+
message: "Select shared folders to generate in src/:",
|
|
79
|
+
choices: allFolderChoices.map((f) => ({
|
|
80
|
+
...f,
|
|
81
|
+
checked: ["components", "hooks", "layouts", "pages"].includes(f.value),
|
|
82
|
+
})),
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
const allSelected = [
|
|
86
|
+
...new Set([...featureFolders, ...sharedFolders]),
|
|
87
|
+
];
|
|
88
|
+
const { indexFiles } = await inquirer.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: "checkbox",
|
|
91
|
+
name: "indexFiles",
|
|
92
|
+
message: "Select folders to add an index file to:",
|
|
93
|
+
choices: allSelected.map((f) => ({ name: f, value: f })),
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
return {
|
|
97
|
+
style,
|
|
98
|
+
features,
|
|
99
|
+
featureFolders,
|
|
100
|
+
sharedFolders,
|
|
101
|
+
indexFiles,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Layered
|
|
105
|
+
const { sharedFolders } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: "checkbox",
|
|
108
|
+
name: "sharedFolders",
|
|
109
|
+
message: "Select folders to generate in src/:",
|
|
110
|
+
choices: allFolderChoices.map((f) => ({
|
|
111
|
+
...f,
|
|
112
|
+
checked: ["components", "hooks", "pages", "layouts"].includes(f.value),
|
|
113
|
+
})),
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
const { indexFiles } = await inquirer.prompt([
|
|
117
|
+
{
|
|
118
|
+
type: "checkbox",
|
|
119
|
+
name: "indexFiles",
|
|
120
|
+
message: "Select folders to add an index file to:",
|
|
121
|
+
choices: sharedFolders.map((f) => ({ name: f, value: f })),
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
return {
|
|
125
|
+
style,
|
|
126
|
+
features: [],
|
|
127
|
+
featureFolders: [],
|
|
128
|
+
sharedFolders,
|
|
129
|
+
indexFiles,
|
|
130
|
+
};
|
|
131
|
+
}
|