create-bawo-frontend
Version:
Scaffold React (Vite) or Next.js with Tailwind, Zustand, Prettier, optional shadcn/ui, Framer Motion, and GSAP.
107 lines (94 loc) • 6.03 kB
JavaScript
/* eslint-disable no-console */
const { path, write, ensure, read, run } = require("../utils");
const T = require("../templates");
const { setupShadcn } = require("../setup/shadcn");
function scaffoldNext(projectDir, answers) {
const deps = ["react", "react-dom", "next"];
const dev = ["tailwindcss@3.4.14", "postcss", "autoprefixer"];
if (answers.ts) dev.push("typescript", "@types/react", "@types/react-dom");
if (answers.redux || answers.rtkQuery) deps.push("@reduxjs/toolkit", "react-redux");
if (answers.reactQuery) deps.push("@tanstack/react-query");
if (answers.swr) deps.push("swr");
run("npm", ["i", ...deps], projectDir);
run("npm", ["i", "-D", ...dev], projectDir);
const pkgPath = path.join(projectDir, "package.json");
const pkg = JSON.parse(read(pkgPath));
pkg.scripts = { ...(pkg.scripts || {}), dev: "next dev", build: "next build", start: "next start", lint: 'echo "(add eslint if you want)" && exit 0', format: 'echo "(add prettier if you want)" && exit 0' };
write(pkgPath, JSON.stringify(pkg, null, 2));
// next config + ts
if (answers.ts) {
write(path.join(projectDir, "next.config.ts"), `import type { NextConfig } from "next";\nconst nextConfig: NextConfig = {};\nexport default nextConfig;\n`);
write(path.join(projectDir, "tsconfig.json"), JSON.stringify({
compilerOptions: { target: "ES2022", lib: ["ES2022","DOM","DOM.Iterable"], allowJs: false, skipLibCheck: true, strict: true, noEmit: true, esModuleInterop: true, module: "ESNext", moduleResolution: "Bundler", resolveJsonModule: true, isolatedModules: true, jsx: "preserve", incremental: true },
include: ["next-env.d.ts","**/*.ts","**/*.tsx",".next/types/**/*.ts"], exclude: ["node_modules"]
}, null, 2));
write(path.join(projectDir, "next-env.d.ts"), `/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n`);
} else {
write(path.join(projectDir, "next.config.mjs"), `const nextConfig = {};\nexport default nextConfig;\n`);
}
// Tailwind
write(path.join(projectDir, "tailwind.config.cjs"), `module.exports = { content: ["./app/**/*.{js,ts,jsx,tsx}","./components/**/*.{js,ts,jsx,tsx}"], theme: { extend: {} }, plugins: [] };`);
write(path.join(projectDir, "postcss.config.cjs"), `module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } };`);
write(path.join(projectDir, "app", "globals.css"), `@tailwind base;\n@tailwind components;\n@tailwind utilities;`);
const layoutExt = answers.ts ? "tsx" : "jsx";
const layout = answers.ts
? `
export const metadata = { title: "Next + Tailwind", description: "Scaffolded by create-bawo-frontend" };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (<html lang="en"><body className="min-h-screen bg-white text-gray-900">{children}</body></html>);
}
`.trimStart()
: `
export const metadata = { title: "Next + Tailwind", description: "Scaffolded by create-bawo-frontend" };
export default function RootLayout({ children }) {
return (<html lang="en"><body className="min-h-screen bg-white text-gray-900">{children}</body></html>);
}
`.trimStart();
write(path.join(projectDir, "app", `layout.${layoutExt}`), layout);
const pageExt = answers.ts ? "tsx" : "jsx";
write(path.join(projectDir, "app", `page.${pageExt}`), `
export default function Page() {
return (
<main className="p-6 max-w-3xl mx-auto">
<h1 className="text-3xl font-bold">Hello 👋 Next.js + Tailwind (${answers.ts ? "TS" : "JS"})</h1>
<p className="mt-3 text-gray-600">Edit <code className="px-1 rounded bg-gray-200">app/page.${pageExt}</code>.</p>
</main>
);
}
`.trimStart());
// Zustand (optional)
if (answers.zustand) {
run("npm", ["i", "zustand"], projectDir);
ensure(path.join(projectDir, "store"));
const storeExt = answers.ts ? "ts" : "js";
const zustand = answers.ts
? `
import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware";
type Theme = "light" | "dark";
interface AppState { theme: Theme; count: number; setTheme: (t: Theme) => void; inc: () => void; dec: () => void; reset: () => void; }
export const useAppStore = create<AppState>()(devtools(persist((set)=>({
theme:"light", count:0, setTheme:(t)=>set({theme:t}), inc:()=>set(s=>({count:s.count+1})), dec:()=>set(s=>({count:s.count-1})), reset:()=>set({count:0})
}),{ name:"app-store", storage: createJSONStorage(()=>localStorage)})));
`.trim()
: `
import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware";
export const useAppStore = create()(devtools(persist((set)=>({
theme:"light", count:0, setTheme:(t)=>set({theme:t}), inc:()=>set(s=>({count:s.count+1})), dec:()=>set(s=>({count:s.count-1})), reset:()=>set({count:0})
}),{ name:"app-store", storage: createJSONStorage(()=>localStorage)})));
`.trim();
write(path.join(projectDir, "store", `useAppStore.${storeExt}`), zustand);
}
if (answers.pt) {
run("npm", ["i", "-D", "prettier", "prettier-plugin-tailwindcss"], projectDir);
write(path.join(projectDir, ".prettierrc"), JSON.stringify({ plugins: ["prettier-plugin-tailwindcss"] }, null, 2));
write(path.join(projectDir, ".prettierignore"), "node_modules\n.next\nbuild\ndist\n");
}
if (answers.ui === "shadcn") setupShadcn(projectDir, { isVite: false });
// Optional demos for Next (keep minimal)
ensure(path.join(projectDir, "components", "demo"));
if (answers.framer) { run("npm", ["i", "framer-motion"], projectDir); write(path.join(projectDir, "components", "demo", `FramerDemo.${answers.ts ? "tsx" : "jsx"}`), T.FRAMER_DEMO_NEXT); }
if (answers.gsap) { run("npm", ["i", "gsap"], projectDir); write(path.join(projectDir, "components", "demo", `GsapDemo.${answers.ts ? "tsx" : "jsx"}`), answers.ts ? T.GSAP_DEMO_NEXT : T.GSAP_DEMO_NEXT_JS); }
}
module.exports = { scaffoldNext };