UNPKG

@toxuh/create-imp

Version:

Next.js starter (Next 15 + shadcn + next-themes + optional Prisma).

335 lines (318 loc) 9.92 kB
#!/usr/bin/env node // src/services/shell.service.ts import { spawn } from "child_process"; var exec = (cmd, args, cwd) => { return new Promise((resolve, reject) => { const child = spawn(cmd, args, { cwd, stdio: "inherit", shell: process.platform === "win32" }); child.on( "close", (code) => code === 0 ? resolve({ code }) : reject(new Error(`${cmd} ${args.join(" ")} failed with ${code}`)) ); }); }; // src/steps/step-create-next.ts var useFlag = (pm) => { switch (pm) { case "npm": return "--use-npm"; case "yarn": return "--use-yarn"; case "pnpm": return "--use-pnpm"; case "bun": return "--use-bun"; default: return "--use-npm"; } }; var stepCreateNext = { name: "", // hidden step: don't show in logs run: async ({ cwd, packageManager }) => { await exec( "npx", [ "create-next-app@latest", ".", "--ts", "--eslint", "--tailwind", "--app", "--import-alias", "@/*", useFlag(packageManager), "--skip-install", "--yes" ], cwd ); } }; // src/services/pm.service.ts import path from "path"; import fs from "fs/promises"; var removeOtherLockfiles = async (pm, cwd) => { const locks = { npm: "package-lock.json", yarn: "yarn.lock", pnpm: "pnpm-lock.yaml", bun: "bun.lockb" }; const toRemove = Object.entries(locks).filter(([k]) => k !== pm).map(([, file]) => path.join(cwd, file)); await Promise.all(toRemove.map((p) => fs.rm(p, { force: true }))); }; // src/steps/step-npm-install.ts var stepNpmInstall = { name: "install dependencies", run: async ({ cwd }) => { await removeOtherLockfiles("npm", cwd); await exec("npm", ["install"], cwd); } }; // src/services/fs.service.ts import fs2 from "fs/promises"; import { existsSync } from "fs"; import path2 from "path"; var ensureDir = async (p) => { await fs2.mkdir(p, { recursive: true }).catch(() => { }); }; var writeIfChanged = async (file, content) => { const prev = existsSync(file) ? await fs2.readFile(file, "utf8") : ""; if (prev !== content) await fs2.writeFile(file, content, "utf8"); }; var removeAllInDir = async (dir) => { if (!existsSync(dir)) return; const items = await fs2.readdir(dir); await Promise.all( items.map(async (name) => { const p = path2.join(dir, name); await fs2.rm(p, { recursive: true, force: true }); }) ); }; // src/steps/step-clean-boilerplate.ts import path3 from "path"; var stepCleanBoilerplate = { name: "clean boilerplate", run: async ({ cwd }) => { await removeAllInDir(path3.join(cwd, "public")); } }; // src/steps/step-prettier.ts import path4 from "path"; var stepPrettier = { name: "add prettier", run: async ({ cwd }) => { await exec("npm", ["install", "-D", "prettier", "prettier-plugin-tailwindcss"], cwd); const prettierrc = JSON.stringify( { singleQuote: true, trailingComma: "all", printWidth: 100, plugins: ["prettier-plugin-tailwindcss"] }, null, 2 ); const prettierignore = ["node_modules", ".next", "dist"].join("\n") + "\n"; await writeIfChanged(path4.join(cwd, ".prettierrc"), prettierrc + "\n"); await writeIfChanged(path4.join(cwd, ".prettierignore"), prettierignore); } }; // src/steps/step-shadcn-init.ts var stepShadcnInit = { name: "shadcn init + next-themes", run: async ({ cwd }) => { try { await exec("npx", ["shadcn@latest", "init", "-d"], cwd); } catch { await exec("npx", ["shadcn@canary", "init", "-d"], cwd); } await exec("npm", ["install", "next-themes"], cwd); } }; // src/steps/step-theme-provider.ts import path6 from "path"; import { fileURLToPath } from "url"; // src/services/template.service.ts import path5 from "path"; import { readFile } from "fs/promises"; var tpl = async (cwd, name) => { const primary = path5.join(cwd, "templates", name); try { return await readFile(primary, "utf8"); } catch (e) { if (e?.code !== "ENOENT") throw e; } const fallback = path5.join(cwd, "..", "templates", name); return readFile(fallback, "utf8"); }; // src/steps/step-theme-provider.ts var stepThemeProvider = { name: "layout/page to arrow + ThemeProvider", run: async ({ cwd, withHttp }) => { const componentsDir = path6.join(cwd, "components"); await ensureDir(componentsDir); const distDir = path6.dirname(fileURLToPath(import.meta.url)); const providerSrc = await tpl(distDir, "theme-provider.tsx.tpl"); await writeIfChanged(path6.join(componentsDir, "theme-provider.tsx"), providerSrc); if (withHttp) { const queryProviderSrc = await tpl(distDir, "query-provider.tsx.tpl"); await writeIfChanged(path6.join(componentsDir, "query-provider.tsx"), queryProviderSrc); } const appDir = path6.join(cwd, "app"); await ensureDir(appDir); const layoutTpl = await tpl(distDir, withHttp ? "layout.http.tsx.tpl" : "layout.tsx.tpl"); const pageTpl = await tpl(distDir, "page.tsx.tpl"); await writeIfChanged(path6.join(appDir, "layout.tsx"), layoutTpl); await writeIfChanged(path6.join(appDir, "page.tsx"), pageTpl); } }; // src/steps/step-prisma.ts import path7 from "path"; import { readFile as readFile2, writeFile } from "fs/promises"; import { fileURLToPath as fileURLToPath2 } from "url"; var stepPrisma = { name: "prisma init + custom output", run: async ({ cwd }) => { await exec("npm", ["install", "-D", "prisma"], cwd); await exec("npm", ["install", "@prisma/client"], cwd); await exec("npx", ["prisma", "init", "--datasource-provider", "postgresql"], cwd); const envPath = path7.join(cwd, ".env"); const envSrc = await readFile2(envPath, "utf8").catch(() => ""); const envNext = envSrc.match(/^DATABASE_URL=.*$/m) ? envSrc.replace(/^DATABASE_URL=.*$/m, 'DATABASE_URL=""') : (envSrc ? envSrc.trimEnd() + "\n" : "") + 'DATABASE_URL=""\n'; await writeFile(envPath, envNext, "utf8"); const prismaDir = path7.join(cwd, "prisma"); await ensureDir(prismaDir); const schemaPath = path7.join(prismaDir, "schema.prisma"); const base = await readFile2(schemaPath, "utf8").catch(() => { return [ "datasource db {", ' provider = "postgresql"', ' url = env("DATABASE_URL")', "}", "", "generator client {", ' provider = "prisma-client-js"', ' output = "../generated/client"', "}", "" ].join("\n"); }); const updated = base.match(/generator\s+client\s*\{/) ? base.replace( /generator\s+client\s*\{[^}]*\}/s, `generator client { provider = "prisma-client-js" output = "../generated/client" }` ) : `${base} generator client { provider = "prisma-client-js" output = "../generated/client" } `; await writeFile(schemaPath, updated, "utf8"); const libDir = path7.join(cwd, "lib"); await ensureDir(libDir); const distDir = path7.dirname(fileURLToPath2(import.meta.url)); const prismaSrc = await tpl(distDir, "prisma.ts.tpl"); await writeIfChanged(path7.join(libDir, "prisma.ts"), prismaSrc); const gitignorePath = path7.join(cwd, ".gitignore"); const giPrev = await readFile2(gitignorePath, "utf8").catch(() => ""); const lines = new Set(giPrev.split("\n").map((l) => l.trim())); lines.add("/generated"); const giNext = Array.from(lines).filter(Boolean).join("\n") + "\n"; await writeFile(gitignorePath, giNext, "utf8"); await exec("npx", ["prisma", "generate"], cwd); } }; // src/steps/step-http-bundle.ts import path8 from "path"; import { fileURLToPath as fileURLToPath3 } from "url"; var stepHttpBundle = { name: "API toolkit (React Query + Axios + Zod + RHF)", run: async ({ cwd }) => { await exec( "npm", [ "install", "@tanstack/react-query", "axios", "zod", "react-hook-form", "@hookform/resolvers" ], cwd ); await ensureDir(path8.join(cwd, "hooks")); const libDir = path8.join(cwd, "lib"); await ensureDir(libDir); const distDir = path8.dirname(fileURLToPath3(import.meta.url)); const httpSrc = await tpl(distDir, "http.ts.tpl"); await writeIfChanged(path8.join(libDir, "http.ts"), httpSrc); } }; // src/steps/index.ts var buildSteps = (opts) => { const base = [ stepCreateNext, stepNpmInstall, stepCleanBoilerplate, stepPrettier, stepShadcnInit, ...opts.withHttp ? [stepHttpBundle] : [], stepThemeProvider ]; return opts.withPrisma ? [...base, stepPrisma] : base; }; var runSteps = async (opts) => { for (const s of buildSteps(opts)) { if (s.name) console.log(` \u2014 ${s.name}`); await s.run(opts); } }; // src/index.ts import prompts from "prompts"; var run = async (options) => { let packageManager = options?.packageManager ?? "npm"; let withPrisma = options?.withPrisma; let withHttp = options?.withHttp; if (withPrisma === void 0) { const res = await prompts({ type: "toggle", name: "withPrisma", message: "Do you want to set up Prisma ORM?", initial: true, active: "Yes", inactive: "No" }); withPrisma = res.withPrisma; } if (withHttp === void 0) { const res = await prompts({ type: "toggle", name: "withHttp", message: "Include API Toolkit (React Query + Axios + Zod + React Hook Form)?", initial: true, active: "Yes", inactive: "No" }); withHttp = res.withHttp; } const opts = { cwd: process.cwd(), packageManager, withPrisma: Boolean(withPrisma), withHttp: Boolean(withHttp), debug: options?.debug ?? false }; await runSteps(opts); console.clear(); console.log("IMP was installed. Enjoy."); }; export { run }; //# sourceMappingURL=chunk-YOEUNZ57.js.map