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,41 @@
|
|
|
1
|
+
import ThemeToggle from "./components/ThemeToggle";
|
|
2
|
+
|
|
3
|
+
export default function App() {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{
|
|
6
|
+
minHeight: "100vh",
|
|
7
|
+
display: "flex",
|
|
8
|
+
flexDirection: "column",
|
|
9
|
+
alignItems: "center",
|
|
10
|
+
justifyContent: "center",
|
|
11
|
+
gap: "1.5rem",
|
|
12
|
+
fontFamily: "var(--font-base, system-ui)",
|
|
13
|
+
backgroundColor: "var(--bg, #ffffff)",
|
|
14
|
+
color: "var(--text, #171717)",
|
|
15
|
+
}}>
|
|
16
|
+
<h1>CSS is ready 🎉</h1>
|
|
17
|
+
|
|
18
|
+
<p style={{ color: "var(--color-muted, #555)" }}>
|
|
19
|
+
Your CSS variables and reset are set up and ready to use.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<ThemeToggle />
|
|
23
|
+
|
|
24
|
+
<button style={{
|
|
25
|
+
padding: "0.5rem 1.25rem",
|
|
26
|
+
backgroundColor: "var(--color-primary, #6366f1)",
|
|
27
|
+
color: "#fff",
|
|
28
|
+
border: "none",
|
|
29
|
+
borderRadius: "var(--radius, 0.5rem)",
|
|
30
|
+
cursor: "pointer",
|
|
31
|
+
fontSize: "1rem",
|
|
32
|
+
}}>
|
|
33
|
+
Everything is working
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<small style={{ color: "var(--color-muted, #999)" }}>
|
|
37
|
+
You can delete this demo and start building your app.
|
|
38
|
+
</small>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Button from "@mui/material/Button";
|
|
2
|
+
import Typography from "@mui/material/Typography";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import ThemeToggle from "./components/ThemeToggle";
|
|
5
|
+
import { useTheme } from "./context/ThemeContextProvider";
|
|
6
|
+
|
|
7
|
+
export default function App() {
|
|
8
|
+
const { theme } = useTheme();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Box
|
|
12
|
+
sx={{
|
|
13
|
+
minHeight: "100vh",
|
|
14
|
+
display: "flex",
|
|
15
|
+
flexDirection: "column",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
justifyContent: "center",
|
|
18
|
+
gap: 3,
|
|
19
|
+
bgcolor: "background.default",
|
|
20
|
+
color: "text.primary",
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<Typography variant="h3" fontWeight="bold">
|
|
24
|
+
MUI is ready 🎉
|
|
25
|
+
</Typography>
|
|
26
|
+
|
|
27
|
+
<Typography variant="body1" color="text.secondary">
|
|
28
|
+
Current theme: <strong>{theme}</strong>
|
|
29
|
+
</Typography>
|
|
30
|
+
|
|
31
|
+
<ThemeToggle />
|
|
32
|
+
|
|
33
|
+
<Button variant="contained" size="large">
|
|
34
|
+
Everything is working
|
|
35
|
+
</Button>
|
|
36
|
+
|
|
37
|
+
<Typography variant="caption" color="text.disabled">
|
|
38
|
+
You can delete this demo and start building your app.
|
|
39
|
+
</Typography>
|
|
40
|
+
</Box>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Button from "@mui/material/Button";
|
|
2
|
+
import Typography from "@mui/material/Typography";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import ThemeToggle from "./components/ThemeToggle";
|
|
5
|
+
import { useTheme } from "./context/ThemeContextProvider";
|
|
6
|
+
|
|
7
|
+
export default function App() {
|
|
8
|
+
const { theme } = useTheme();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Box
|
|
12
|
+
sx={{
|
|
13
|
+
minHeight: "100vh",
|
|
14
|
+
display: "flex",
|
|
15
|
+
flexDirection: "column",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
justifyContent: "center",
|
|
18
|
+
gap: 3,
|
|
19
|
+
bgcolor: "background.default",
|
|
20
|
+
color: "text.primary",
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<Typography variant="h3" fontWeight="bold">
|
|
24
|
+
MUI is ready 🎉
|
|
25
|
+
</Typography>
|
|
26
|
+
|
|
27
|
+
<Typography variant="body1" color="text.secondary">
|
|
28
|
+
Current theme: <strong>{theme}</strong>
|
|
29
|
+
</Typography>
|
|
30
|
+
|
|
31
|
+
<ThemeToggle />
|
|
32
|
+
|
|
33
|
+
<Button variant="contained" size="large">
|
|
34
|
+
Everything is working
|
|
35
|
+
</Button>
|
|
36
|
+
|
|
37
|
+
<Typography variant="caption" color="text.disabled">
|
|
38
|
+
You can delete this demo and start building your app.
|
|
39
|
+
</Typography>
|
|
40
|
+
</Box>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
5
|
+
line-height: 1.5;
|
|
6
|
+
font-weight: 400;
|
|
7
|
+
color-scheme: light dark;
|
|
8
|
+
color: rgba(255, 255, 255, 0.87);
|
|
9
|
+
background-color: #242424;
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
margin: 0;
|
|
18
|
+
min-width: 320px;
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
export async function cleanupDirectory(dirPath) {
|
|
6
|
+
const files = await fs.readdir(dirPath);
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
await fs.rm(path.join(dirPath, file), {
|
|
9
|
+
recursive: true,
|
|
10
|
+
force: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function checkCurrentDirectory() {
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const files = await fs.readdir(cwd);
|
|
17
|
+
if (files.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
console.log("\n");
|
|
20
|
+
logger.warn("Current directory is not empty.");
|
|
21
|
+
logger.dim("Files detected:");
|
|
22
|
+
files.forEach((file) => logger.dim(` - ${file}`));
|
|
23
|
+
console.log("\n");
|
|
24
|
+
const { action } = await inquirer.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: "list",
|
|
27
|
+
name: "action",
|
|
28
|
+
message: "Creating a project here will overwrite existing files. What would you like to do?",
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: "Cancel (recommended)", value: "cancel" },
|
|
31
|
+
{ name: "Continue anyway (deletes existing files)", value: "continue" },
|
|
32
|
+
],
|
|
33
|
+
default: "cancel",
|
|
34
|
+
},
|
|
35
|
+
]);
|
|
36
|
+
if (action === "cancel") {
|
|
37
|
+
logger.info("Cancelled. No files were changed.");
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
logger.warn("Cleaning up existing files...");
|
|
41
|
+
await cleanupDirectory(cwd);
|
|
42
|
+
logger.success("Directory cleared!");
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
export const logger = {
|
|
3
|
+
info: (msg) => console.log(`${chalk.cyan("●")} ${chalk.cyan(msg)}`),
|
|
4
|
+
success: (msg) => console.log(`${chalk.green("✓")} ${chalk.green(msg)}`),
|
|
5
|
+
warn: (msg) => console.log(`${chalk.bold.yellow("⚠")} ${chalk.yellow(msg)}`),
|
|
6
|
+
error: (msg) => console.log(`${chalk.red("✕")} ${chalk.red(msg)}`),
|
|
7
|
+
debug: (msg) => console.log(`${chalk.magenta("◆")} ${chalk.magenta(msg)}`),
|
|
8
|
+
step: (msg) => console.log(`${chalk.blue("›")} ${chalk.white(msg)}`),
|
|
9
|
+
dim: (msg) => console.log(chalk.dim(msg)),
|
|
10
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { cleanupDirectory } from "./directory.js";
|
|
4
|
+
// Level 1 — full project rollback
|
|
5
|
+
// Used in create.command.ts if scaffolding fails entirely
|
|
6
|
+
export async function rollbackProject(projectPath, directoryMode) {
|
|
7
|
+
try {
|
|
8
|
+
logger.warn("Rolling back — removing created files...");
|
|
9
|
+
if (directoryMode === "new") {
|
|
10
|
+
await fs.rm(projectPath, { recursive: true, force: true });
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
await cleanupDirectory(projectPath);
|
|
14
|
+
}
|
|
15
|
+
logger.success("Rollback complete. No files were left behind.");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
logger.error("Rollback failed — please manually delete the project folder.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Level 2 — feature rollback
|
|
22
|
+
// Used in tailwind.feature.ts / mui.feature.ts if feature setup fails
|
|
23
|
+
// Deletes only specific paths the feature was responsible for
|
|
24
|
+
export async function rollbackFeature(paths) {
|
|
25
|
+
try {
|
|
26
|
+
logger.warn("Rolling back feature — removing generated files...");
|
|
27
|
+
for (const filePath of paths) {
|
|
28
|
+
await fs.rm(filePath, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
logger.success("Feature rollback complete. Your base project is intact.");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
logger.error("Feature rollback failed — some files may need manual cleanup.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Snapshot store — saves original file content before overwriting
|
|
37
|
+
const snapshots = new Map();
|
|
38
|
+
export async function saveSnapshot(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
41
|
+
snapshots.set(filePath, content);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// File didn't exist before — snapshot as null so we delete it on rollback
|
|
45
|
+
snapshots.set(filePath, "");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function restoreSnapshots() {
|
|
49
|
+
for (const [filePath, content] of snapshots.entries()) {
|
|
50
|
+
try {
|
|
51
|
+
if (content === "") {
|
|
52
|
+
await fs.rm(filePath, { force: true });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await fs.writeFile(filePath, content);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
logger.error(`Failed to restore ${filePath}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
snapshots.clear();
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
let spinnerInstance = null;
|
|
3
|
+
export const spinner = {
|
|
4
|
+
start: (text) => {
|
|
5
|
+
spinnerInstance = ora(text).start();
|
|
6
|
+
},
|
|
7
|
+
succeed: (text) => {
|
|
8
|
+
spinnerInstance?.succeed(text);
|
|
9
|
+
},
|
|
10
|
+
fail: (text) => {
|
|
11
|
+
spinnerInstance?.fail(text);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import figlet from "figlet";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version } = require("../../package.json");
|
|
6
|
+
export const sleep = (ms = 1000) => new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
function terminalLink(text, url) {
|
|
8
|
+
return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
|
|
9
|
+
}
|
|
10
|
+
export async function welcome() {
|
|
11
|
+
// await sleep();
|
|
12
|
+
const [muscle, config] = await Promise.all([
|
|
13
|
+
new Promise((resolve, reject) => {
|
|
14
|
+
figlet.text("MUSCLE", { font: "ANSI Shadow" }, (err, result) => {
|
|
15
|
+
if (err)
|
|
16
|
+
reject(err);
|
|
17
|
+
else
|
|
18
|
+
resolve(chalk.bold.cyan(result || ""));
|
|
19
|
+
});
|
|
20
|
+
}),
|
|
21
|
+
new Promise((resolve, reject) => {
|
|
22
|
+
figlet.text("CONFIG", { font: "ANSI Shadow" }, (err, result) => {
|
|
23
|
+
if (err)
|
|
24
|
+
reject(err);
|
|
25
|
+
else
|
|
26
|
+
resolve(chalk.bold.cyan(result || ""));
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
]);
|
|
30
|
+
const data = `${muscle}\n${config}`;
|
|
31
|
+
console.clear();
|
|
32
|
+
console.log("\n");
|
|
33
|
+
console.log(chalk.cyan(data));
|
|
34
|
+
const width = 62;
|
|
35
|
+
const divider = chalk.cyan("─".repeat(width));
|
|
36
|
+
const githubLink = terminalLink("Omar Ashraf", "https://github.com/Cat-Div7");
|
|
37
|
+
console.log(divider);
|
|
38
|
+
console.log(chalk.bold.white(" MC — MUSCLE CONFIG"));
|
|
39
|
+
console.log(chalk.gray(" Frontend scaffolding done right, every time"));
|
|
40
|
+
console.log(chalk.dim(` v${version}`) +
|
|
41
|
+
chalk.dim(" by ") +
|
|
42
|
+
chalk.cyan(githubLink));
|
|
43
|
+
console.log(divider);
|
|
44
|
+
console.log("\n");
|
|
45
|
+
console.log(chalk.red('The Only Available Frontend Framework for now is [ React ]!!\n'));
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "muscle-config",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stop configuring. Start building.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"build": "tsc && shx rm -rf dist/templates && shx cp -r src/templates dist/templates",
|
|
10
|
+
"start": "node dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"muscle-config": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"scaffold",
|
|
21
|
+
"react",
|
|
22
|
+
"vite",
|
|
23
|
+
"tailwind",
|
|
24
|
+
"mui",
|
|
25
|
+
"typescript",
|
|
26
|
+
"frontend",
|
|
27
|
+
"boilerplate",
|
|
28
|
+
"starter",
|
|
29
|
+
"template"
|
|
30
|
+
],
|
|
31
|
+
"author": "Cat-Div7",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Cat-Div7/muscle-config.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/Cat-Div7/muscle-config#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Cat-Div7/muscle-config/issues"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
|
+
"execa": "^9.6.1",
|
|
47
|
+
"figlet": "^1.10.0",
|
|
48
|
+
"inquirer": "^8.2.7",
|
|
49
|
+
"ora": "^9.3.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/inquirer": "^9.0.9",
|
|
53
|
+
"@types/node": "^25.3.3",
|
|
54
|
+
"shx": "^0.4.0",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
56
|
+
}
|
|
57
|
+
}
|