diff --git a/bin/cli.js b/bin/cli.js index c3785e8..da3ec26 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -22,6 +22,10 @@ program "--folders", "Generate recommended folder structure" ) + .option( + "--tailwind", + "Set up Tailwind CSS" + ) .parse(); const projectName = program.args[0]; diff --git a/src/cleaners/cra.js b/src/cleaners/cra.js index eb2a1c5..86d5c92 100644 --- a/src/cleaners/cra.js +++ b/src/cleaners/cra.js @@ -3,13 +3,10 @@ const { writeFile, } = require("../utils/files"); -function cleanCRA(projectPath, typescript) { +function cleanCRA(projectPath, typescript, tailwind) { const ext = typescript ? "tsx" : "js"; - writeFile( - projectPath, - `src/App.${ext}`, -`function App() { + const appContent = `function App() { return (

App

@@ -18,16 +15,19 @@ function cleanCRA(projectPath, typescript) { } export default App; -` - ); +`; + + writeFile(projectPath, `src/App.${ext}`, appContent); + + const indexCSSImport = tailwind ? `import "./index.css";\n` : ""; writeFile( projectPath, `src/index.${ext}`, -typescript -? `import React from "react"; + typescript + ? `import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; +${indexCSSImport}import App from "./App"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement @@ -39,9 +39,9 @@ root.render( ); ` -: `import React from "react"; + : `import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; +${indexCSSImport}import App from "./App"; const root = ReactDOM.createRoot( document.getElementById("root") @@ -69,4 +69,4 @@ root.render( }); } -module.exports = cleanCRA; \ No newline at end of file +module.exports = cleanCRA; diff --git a/src/creators/vite.js b/src/creators/vite.js index 6b3c6d1..a98ad37 100644 --- a/src/creators/vite.js +++ b/src/creators/vite.js @@ -36,6 +36,15 @@ export default defineConfig({ }) `; +const VITE_CONFIG_JS_TAILWIND = `import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}) +`; + const INDEX_HTML = (projectName) => ` @@ -86,6 +95,30 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ) `; +const MAIN_JSX_TAILWIND = `import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) +`; + +const MAIN_TSX_TAILWIND = `import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + +) +`; + const APP_JSX = `function App() { return (
@@ -97,6 +130,7 @@ const APP_JSX = `function App() { export default App `; + const TSCONFIG = JSON.stringify({ compilerOptions: { target: "ES2020", @@ -118,7 +152,7 @@ const TSCONFIG = JSON.stringify({ include: ["src"], }, null, 2); -function createVite(projectName, typescript) { +function createVite(projectName, typescript, tailwind) { const projectPath = path.join(process.cwd(), projectName); const ext = typescript ? "tsx" : "jsx"; @@ -135,7 +169,7 @@ function createVite(projectName, typescript) { // vite.config fs.writeFileSync( path.join(projectPath, `vite.config.${typescript ? "ts" : "js"}`), - VITE_CONFIG_JS + tailwind ? VITE_CONFIG_JS_TAILWIND : VITE_CONFIG_JS ); // index.html @@ -144,16 +178,17 @@ function createVite(projectName, typescript) { typescript ? INDEX_HTML_TS(projectName) : INDEX_HTML(projectName) ); - // src/main - fs.writeFileSync( - path.join(projectPath, `src/main.${ext}`), - typescript ? MAIN_TSX : MAIN_JSX - ); + // src/main — include index.css import when tailwind is active + const mainContent = tailwind + ? (typescript ? MAIN_TSX_TAILWIND : MAIN_JSX_TAILWIND) + : (typescript ? MAIN_TSX : MAIN_JSX); + + fs.writeFileSync(path.join(projectPath, `src/main.${ext}`), mainContent); - // src/App + // src/App — minimal starter component fs.writeFileSync( - path.join(projectPath, `src/App.${ext}`), - APP_JSX + path.join(projectPath, `src/App.${ext}`), + APP_JSX ); // tsconfig if needed @@ -172,4 +207,4 @@ function createVite(projectName, typescript) { }); } -module.exports = createVite; \ No newline at end of file +module.exports = createVite; diff --git a/src/generators/tailwind.js b/src/generators/tailwind.js new file mode 100644 index 0000000..04fd120 --- /dev/null +++ b/src/generators/tailwind.js @@ -0,0 +1,36 @@ +const { execSync } = require("child_process"); +const { writeFile } = require("../utils/files"); + +// Tailwind v4: PostCSS plugin for CRA (webpack-based) +const POSTCSS_CONFIG_CRA = `module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + }, +} +`; + +// Tailwind v4: single CSS import replaces the three @tailwind directives +const INDEX_CSS = `@import "tailwindcss"; +`; + +function generateTailwind(projectPath, cra) { + if (cra) { + // CRA uses PostCSS — write the postcss config + writeFile(projectPath, "postcss.config.js", POSTCSS_CONFIG_CRA); + } + // Vite uses @tailwindcss/vite plugin (wired into vite.config) — no postcss config needed + + writeFile(projectPath, "src/index.css", INDEX_CSS); + + const deps = cra + ? "tailwindcss @tailwindcss/postcss" + : "tailwindcss @tailwindcss/vite"; + + execSync(`npm install -D ${deps}`, { + cwd: projectPath, + stdio: "inherit", + shell: true, + }); +} + +module.exports = generateTailwind; diff --git a/src/index.js b/src/index.js index 71b696b..d38bf35 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ const createVite = require("./creators/vite"); const cleanCRA = require("./cleaners/cra"); const generateFolders = require("./generators/folders"); +const generateTailwind = require("./generators/tailwind"); const { success, info, error } = require("./utils/logger"); @@ -35,6 +36,7 @@ async function run(projectName, options) { const useCRA = options.cra; const typescript = options.ts; + const tailwind = options.tailwind; info(`Using ${useCRA ? "Create React App" : "Vite"}`); @@ -45,7 +47,7 @@ async function run(projectName, options) { createCRA(projectName, typescript); } else { spinner.stop(); // npm install output needs to show - createVite(projectName, typescript); + createVite(projectName, typescript, tailwind); } } catch (e) { spinner.stop(); @@ -63,11 +65,19 @@ async function run(projectName, options) { // CRA needs cleanup — Vite is already scaffolded clean if (useCRA) { spinner.start("Cleaning starter files..."); - cleanCRA(projectPath, typescript); + cleanCRA(projectPath, typescript, tailwind); spinner.stop(); success("Starter boilerplate removed"); } + // Tailwind setup + if (tailwind) { + spinner.stop(); + info("Setting up Tailwind CSS..."); + generateTailwind(projectPath, useCRA); + success("Tailwind CSS configured"); + } + // Generate folders if (options.folders) { generateFolders(projectPath); @@ -85,4 +95,4 @@ async function run(projectName, options) { } } -module.exports = run; \ No newline at end of file +module.exports = run;