dev-hub-cli
Version:
CLI tool for creating development project boilerplates
415 lines (368 loc) • 13 kB
JavaScript
const fs = require("fs-extra");
const path = require("path");
const { exec } = require("child_process");
async function runCommand(command, cwd) {
return new Promise((resolve, reject) => {
exec(command, { cwd }, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
}
async function createHtmlProject(projectPath, options = {}) {
try {
// Ensure the project directory exists
await fs.ensureDir(projectPath);
// Handle meta tags first
let metaTags =
'<meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">';
if (options.meta) {
if (options.meta.seo) {
metaTags += `
<meta name="description" content="Description of your site">
<meta name="keywords" content="your, keywords">
<meta name="author" content="Your Name">
<meta name="robots" content="index, follow">`;
}
if (options.meta.social) {
metaTags += `
<meta property="og:title" content="Your Site Title">
<meta property="og:description" content="Description of your site">
<meta property="og:image" content="image.jpg">
<meta property="og:url" content="your-site-url">
<meta name="twitter:card" content="summary_large_image">`;
}
}
let cssImports = "";
let cssContent = "";
let packageJson = null;
let htmlContent = "";
// Handle CSS framework selection
if (options["css-framework"]) {
if (options["css-framework"].tailwind) {
// Create directories first
await fs.ensureDir(path.join(projectPath, "src"));
await fs.ensureDir(path.join(projectPath, "dist"));
// Create package.json first
packageJson = {
name: path.basename(projectPath),
version: "1.0.0",
scripts: {
dev: "tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch",
build:
"tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
},
devDependencies: {
tailwindcss: "^3.3.0",
},
};
// Write package.json before running any npm commands
await fs.writeFile(
path.join(projectPath, "package.json"),
JSON.stringify(packageJson, null, 2)
);
// Create tailwind config and styles
const tailwindConfig = `
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}`;
await fs.writeFile(
path.join(projectPath, "tailwind.config.js"),
tailwindConfig
);
await fs.writeFile(
path.join(projectPath, "src/styles.css"),
"@tailwind base;\n@tailwind components;\n@tailwind utilities;"
);
// Install dependencies
await runCommand("npm install", projectPath);
// Create HTML template
htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
${metaTags}
<title>My Website</title>
<link href="./dist/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<header class="bg-white shadow-lg">
<nav class="container mx-auto px-6 py-4">
<h1 class="text-3xl font-bold text-gray-800">Welcome to My Website</h1>
</nav>
</header>
<main class="container mx-auto px-6 py-8">
<section class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Getting Started</h2>
<p class="text-gray-600">Edit this template to start building your website!</p>
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">
Learn More
</button>
</section>
</main>
<footer class="bg-gray-800 text-white mt-12">
<div class="container mx-auto px-6 py-4">
<p class="text-center">© ${new Date().getFullYear()} Your Name</p>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>`;
// Initial Tailwind build
await runCommand(
"npx tailwindcss -i ./src/styles.css -o ./dist/styles.css",
projectPath
);
} else if (options["css-framework"].bootstrap) {
cssImports = `
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>`;
cssContent = `
/* Custom styles */
body {
padding-top: 60px;
}`;
} else if (options["css-framework"].sass) {
await fs.ensureDir(path.join(projectPath, "scss"));
await fs.ensureDir(path.join(projectPath, "scss/components"));
await fs.ensureDir(path.join(projectPath, "scss/layouts"));
const scssContent = `
// Variables
$primary-color: #333;
$secondary-color: #666;
$accent-color: #aa3c53;
$font-stack: 'Segoe UI', Arial, sans-serif;
// Mixins
flex-center {
display: flex;
justify-content: center;
align-items: center;
}
// Base styles
body {
margin: 0;
padding: 20px;
font-family: $font-stack;
color: $primary-color;
}
// Import partials
'components/buttons';
'components/forms';
'layouts/header';
'layouts/footer';
`;
const buttonStyles = `
.button {
padding: 10px 20px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.3s ease;
&--primary {
background-color: $accent-color;
color: white;
&:hover {
background-color: darken($accent-color, 10%);
}
}
}`;
const formStyles = `
.form {
&__group {
margin-bottom: 1rem;
}
&__label {
display: block;
margin-bottom: 0.5rem;
}
&__input {
width: 100%;
padding: 0.5rem;
border: 1px solid $secondary-color;
border-radius: 4px;
}
}`;
const headerStyles = `
.header {
flex-center;
padding: 1rem;
background-color: $primary-color;
color: white;
}`;
const footerStyles = `
.footer {
padding: 1rem;
background-color: $primary-color;
color: white;
text-align: center;
margin-top: 2rem;
}`;
await fs.writeFile(
path.join(projectPath, "scss/main.scss"),
scssContent
);
await fs.writeFile(
path.join(projectPath, "scss/components/_buttons.scss"),
buttonStyles
);
await fs.writeFile(
path.join(projectPath, "scss/components/_forms.scss"),
formStyles
);
await fs.writeFile(
path.join(projectPath, "scss/layouts/_header.scss"),
headerStyles
);
await fs.writeFile(
path.join(projectPath, "scss/layouts/_footer.scss"),
footerStyles
);
packageJson = {
name: path.basename(projectPath),
version: "1.0.0",
scripts: {
sass: "sass scss/main.scss css/styles.css --watch",
"sass:build":
"sass scss/main.scss css/styles.css --style compressed",
},
devDependencies: {
sass: "^1.69.5",
},
};
await fs.writeFile(
path.join(projectPath, "package.json"),
JSON.stringify(packageJson, null, 2)
);
await runCommand("npm install", projectPath);
await runCommand("npm run sass:build", projectPath);
const readmeContent = `# ${path.basename(projectPath)}
## SASS/SCSS Setup
1. Install dependencies:
\`\`\`
npm install
\`\`\`
2. Watch SCSS changes:
\`\`\`
npm run sass
\`\`\`
3. Build for production:
\`\`\`
npm run sass:build
\`\`\`
## Project Structure
- scss/main.scss (main styles)
- scss/components/ (component styles)
- _buttons.scss
- _forms.scss
- scss/layouts/ (layout styles)
- _header.scss
- _footer.scss
## Using the Styles
- Use the predefined mixins and variables from main.scss
- Extend existing components or create new ones in the components directory
- Add new layout files in the layouts directory
`;
await fs.writeFile(path.join(projectPath, "README.md"), readmeContent);
} else {
cssContent = `
/* Base styles */
body {
margin: 0;
padding: 20px;
font-family: 'Segoe UI', Arial, sans-serif;
}
/* Layout */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
/* Components */
.button {
padding: 10px 20px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
/* Utilities */
.text-center {
text-align: center;
}`;
}
}
// Create HTML file with selected options if not already created
if (!htmlContent) {
htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
${metaTags}
<title>My Website</title>
${cssImports}
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header class="header">
<div class="container">
<h1>Welcome to My Website</h1>
</div>
</header>
<main class="container">
<section class="content">
<p>Your content goes here...</p>
</section>
</main>
<footer class="footer">
<div class="container">
<p>© ${new Date().getFullYear()} Your Name</p>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>`;
}
// Handle CSS Reset
if (options.reset) {
const normalizeContent = `
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}`;
if (options.reset.normalize) {
await fs.writeFile(
path.join(projectPath, "css/normalize.css"),
normalizeContent
);
cssImports = `<link rel="stylesheet" href="css/normalize.css">\n ${cssImports}`;
}
}
// Create basic directory structure
if (!options["css-framework"]?.tailwind) {
await fs.ensureDir(path.join(projectPath, "css"));
}
await fs.ensureDir(path.join(projectPath, "js"));
await fs.ensureDir(path.join(projectPath, "images"));
// Write files
await fs.writeFile(path.join(projectPath, "index.html"), htmlContent);
if (!options["css-framework"]?.tailwind) {
await fs.writeFile(path.join(projectPath, "css/styles.css"), cssContent);
}
await fs.writeFile(
path.join(projectPath, "js/main.js"),
'console.log("Website loaded successfully!");'
);
} catch (error) {
console.error("Error in createHtmlProject:", error);
throw error;
}
}
module.exports = { createHtmlProject };