UNPKG

create-next-plus

Version:

![Next+ Banner](banner.png)

546 lines (523 loc) 15.7 kB
#! /usr/bin/env node // src/cli.ts import { Command } from "commander"; import path10 from "node:path"; // src/actions/intro.ts import figlet from "figlet"; // src/utils/log.ts import chalk from "chalk"; import "chalk-animation"; import gradient from "gradient-string"; var log = (value, opts) => { if (!!opts) { if (gradient) console.log(gradient.morning(value)); } else { console.log(chalk.green(value)); } }; // src/actions/intro.ts var intro = async () => { await figlet( "Next+", { font: "Ogre" }, (err, data) => { if (err) { log("Something went wrong..."); return; } log(data, { gradient: true }); } ); log("Welcome to the Next+ CLI tool, a supercharged create-next-app", { gradient: true }); }; // src/actions/init.ts import prompts from "prompts"; var init = async (defaultFlags) => { const res = await prompts([ { type: "text", name: "name", message: "What should we call your project?" }, { type: defaultFlags.noGit ? null : "confirm", name: "noGit", message: "Do you want to skip git init?" }, { type: defaultFlags.noInstall ? null : "confirm", name: "noInstall", message: "Do you want to skip dependency installation?" }, { type: "confirm", name: "db-confirm", message: "Do you want to start with a database?" }, { type: (prev) => prev === true ? "select" : null, name: "db", message: "Please select a dabatase provider: ", choices: [ { title: "supabase", description: "Supabase (Postgres)", value: "sb" }, { title: "firestore", description: "Firestore (NoSQL)", value: "fs" }, { title: "planetscale", description: "PlanetScale (MySQL)", value: "ps" } ] }, { type: "multiselect", name: "extras", message: "Do you want to install any extras?", choices: [ { title: "langchain", value: "langchain" }, { title: "million.js", value: "millionjs" }, { title: "zustand", value: "zustand" } ] } ]); return res; }; // src/actions/create.ts import chalk2 from "chalk"; import fs from "fs-extra"; import path2 from "node:path"; // src/utils/path.ts import path from "node:path"; import { fileURLToPath } from "node:url"; var __filename = fileURLToPath(import.meta.url); var distPath = path.dirname(__filename); var PKG_ROOT = path.join(distPath, "../"); // src/actions/create.ts var create = async ({ name }) => { if (name === "" || name === void 0) { console.error(chalk2.red("Project name cannot be empty!")); process.exit(1); } const projectPath = path2.join(process.cwd(), name); if (fs.existsSync(projectPath)) { console.error(chalk2.red(`Error: Directory "${name}" already exists.`)); process.exit(1); } fs.mkdirSync(projectPath); const templatePath = path2.join(`${PKG_ROOT}`, "template/base"); try { await fs.copy(templatePath, projectPath); } catch (err) { console.error(chalk2.red(`Error copying template files: ${err}`)); process.exit(1); } }; // src/actions/deps.ts import chalk3 from "chalk"; import { execa } from "execa"; var exec = async (projectDir, options) => { const { args = ["install"], stdout = "pipe" } = options; execa("bun", args, { cwd: projectDir, stdout }); }; var runInstallCommand = async (projectDir) => { exec(projectDir, { stdout: "ignore" }); }; var installDependencies = async (projectDir) => { console.log(chalk3.yellow("Installing dependencies...")); await runInstallCommand(projectDir); log("Successfully installed dependencies!\n", { gradient: true }); }; // src/actions/git.ts import { execSync } from "node:child_process"; import path3 from "node:path"; import fs2 from "fs-extra"; import { execa as execa2 } from "execa"; import chalk4 from "chalk"; import prompts2 from "prompts"; var isGitInstalled = (dir) => { try { execSync("git --version", { cwd: dir }); return true; } catch (_e) { return false; } }; var isRootGitRepo = (dir) => { return fs2.existsSync(path3.join(dir, ".git")); }; var isInsideGitRepo = async (dir) => { try { await execa2("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir, stdout: "ignore" }); return true; } catch (_e) { return false; } }; var getGitVersion = () => { const stdout = execSync("git --version").toString().trim(); const gitVersionTag = stdout.split(" ")[2]; const major = gitVersionTag?.split(".")[0]; const minor = gitVersionTag?.split(".")[1]; return { major: Number(major), minor: Number(minor) }; }; var getDefaultBranch = () => { const stdout = execSync("git config --global init.defaultBranch || echo main").toString().trim(); return stdout; }; var initGit = async (projectPath) => { if (!isGitInstalled(projectPath)) { console.log( chalk4.red("Git already installed. Skipping git initialization.") ); return; } log("Initializing git repository...", { gradient: true }); const isRoot = isRootGitRepo(projectPath); const isInside = await isInsideGitRepo(projectPath); const dirName = path3.parse(projectPath).name; if (isInside && isRoot) { const overwriteGit = await prompts2({ type: "confirm", initial: false, name: "overwriteGit", message: chalk4.redBright.bold( `Directory ${chalk4.cyan( dirName )} is already a git repository. Do you want to overwrite it?` ) }); if (!overwriteGit) { console.log(chalk4.blue("Skipping Git initialization.")); return; } fs2.removeSync(path3.join(projectPath, ".git")); } else if (isInside && !isRoot) { const initializeChildGitRepo = await prompts2({ type: "confirm", initial: false, name: "initializeChildGitRepo", message: chalk4.redBright.bold( `Directory ${chalk4.cyan( dirName )} is inside a git worktree. Do you want to initialize it as a git repository? ` ) }); if (!initializeChildGitRepo) { console.log(chalk4.blue("Skipping Git initialization.")); return; } } try { const branchName = getDefaultBranch(); const { major, minor } = getGitVersion(); if (major < 2 || major == 2 && minor < 28) { await execa2("git", ["init"], { cwd: projectPath }); await execa2("git", ["symbolic-ref", "HEAD", `refs/heads/${branchName}`], { cwd: projectPath }); } else { await execa2("git", ["init", `--initial-branch=${branchName}`], { cwd: projectPath }); } await execa2("git", ["add", "."], { cwd: projectPath }); log("Successfully initialized and staged git", { gradient: true }); } catch (error) { console.log( chalk4.red( "Failed to initialize git repository. Please report this as a bug on GitHub!" ) ); } }; // src/installers/drizzle.ts import path5 from "node:path"; import fs4 from "fs-extra"; // src/utils/deps.ts import path4 from "node:path"; import fs3 from "fs-extra"; import sortPackageJson from "sort-package-json"; // src/installers/dependencyVersionMap.ts var dependencyVersionMap = { // Drizzle "@planetscale/database": "^1.11.0", "drizzle-orm": "^0.29.3", "drizzle-kit": "^0.20.9", firebase: "^10.7.1", mysql2: "^3.6.1", pg: "^8.11.3", postgres: "^3.4.3", langchain: "^0.1.2", "@langchain/openai": "^0.0.11", "@redux-devtools/extension": "^3.3.0", zustand: "^4.4.7", million: "^2.6.4" }; // src/utils/deps.ts var addPackageDependency = (opts) => { const { dependencies, devMode, projectDir } = opts; const pkgJson = fs3.readJSONSync(path4.join(projectDir, "package.json")); dependencies.forEach((pkgName) => { const version = dependencyVersionMap[pkgName]; if (devMode && pkgJson.devDependencies) { pkgJson.devDependencies[pkgName] = version; } else if (pkgJson.dependencies) { pkgJson.dependencies[pkgName] = version; } }); const sortedPkgJson = sortPackageJson(pkgJson); fs3.writeJSONSync(path4.join(projectDir, "package.json"), sortedPkgJson, { spaces: 2 }); }; // src/installers/drizzle.ts var drizzleInstaller = ({ projectDir, scopedAppName }, type) => { const extrasDir = path5.join(PKG_ROOT, "template/extra"); if (type === "ps") { addPackageDependency({ projectDir, dependencies: ["drizzle-kit", "mysql2"], devMode: true }); addPackageDependency({ projectDir, dependencies: ["drizzle-orm", "@planetscale/database"], devMode: false }); } if (type === "sb") { addPackageDependency({ projectDir, dependencies: ["drizzle-kit", "pg"], devMode: true }); addPackageDependency({ projectDir, dependencies: ["drizzle-orm", "postgres"], devMode: false }); } const configFile = path5.join(extrasDir, `config/drizzle-${type}.config.ts`); const configDest = path5.join(projectDir, "drizzle.config.ts"); const schemaScr = path5.join( extrasDir, "src/server/db", `drizzle-${type}-schema.ts` ); const schemaDest = path5.join(projectDir, "src/server/db/schema.ts"); let schemaContent = fs4.readFileSync(schemaScr, "utf-8"); schemaContent = schemaContent.replace( "project1_${name}", `${scopedAppName}_\${name}` ); let configContent = fs4.readFileSync(configFile, "utf-8"); configContent = configContent.replace("project1_*", `${scopedAppName}_*`); const clientSrc = path5.join( extrasDir, `src/server/db/index-drizzle-${type}.ts` ); const clientDest = path5.join(projectDir, "src/server/db/index.ts"); const trpcSrc = path5.join(extrasDir, "src/server/trpc/with-db.ts"); const trpcDest = path5.join(projectDir, "src/server/trpc.ts"); const pkgJsonPath = path5.join(projectDir, "package.json"); const pkgJsonContent = fs4.readJSONSync(pkgJsonPath); pkgJsonContent.scripts = { ...pkgJsonContent.scripts, "db:push": type === "sb" ? "drizzle-kit push:pg" : "drizzle-kit push:mysql", "db:studio": "drizzle-kit studio" }; fs4.copySync(configFile, configDest); fs4.mkdirSync(path5.dirname(schemaDest), { recursive: true }); fs4.writeFileSync(schemaDest, schemaContent); fs4.writeFileSync(configDest, configContent); fs4.copySync(clientSrc, clientDest); fs4.copySync(trpcSrc, trpcDest); fs4.writeJSONSync(pkgJsonPath, pkgJsonContent, { spaces: 2 }); log("Drizzle has been installed and setup.", { gradient: true }); }; // src/installers/firestore.ts import path6 from "node:path"; import fs5 from "fs-extra"; var firestoreInstaller = ({ projectDir }) => { const extrasDir = path6.join(PKG_ROOT, "template/extra"); addPackageDependency({ projectDir, dependencies: ["firebase"], devMode: false }); const libSrc = path6.join(extrasDir, "src/lib/firebase.ts"); const libDest = path6.join(projectDir, "src/lib/firebase.ts"); const clientSrc = path6.join(extrasDir, "src/server/db/index-firestore.ts"); const clientDest = path6.join(projectDir, "src/server/db.ts"); const trpcSrc = path6.join(extrasDir, "src/server/trpc/with-db.ts"); const trpcDest = path6.join(projectDir, "src/server/trpc.ts"); fs5.copySync(libSrc, libDest); fs5.copySync(clientSrc, clientDest); fs5.copySync(trpcSrc, trpcDest); log("Firebase has been installed and Firestore has been setup.", { gradient: true }); }; // src/installers/langchain.ts import path7 from "node:path"; import fs6 from "fs-extra"; var langchainInstaller = ({ projectDir }) => { const extrasDir = path7.join(PKG_ROOT, "template/extra"); addPackageDependency({ projectDir, dependencies: ["langchain", "@langchain/openai"], devMode: false }); const clientSrc = path7.join(extrasDir, "src/langchain"); const clientDest = path7.join(projectDir, "src/langchain"); fs6.copySync(clientSrc, clientDest); log("Langchain has been installed and setup", { gradient: true }); }; // src/installers/million.ts import path8 from "node:path"; import fs7 from "fs-extra"; var millionInstaller = ({ projectDir }) => { const extrasDir = path8.join(PKG_ROOT, "template/extra"); addPackageDependency({ projectDir, dependencies: ["million"], devMode: false }); const configFile = path8.join(extrasDir, `config/next-million.config.mjs`); const configDest = path8.join(projectDir, "next.config.mjs"); fs7.copySync(configFile, configDest); log("Million.js has been installed and setup.", { gradient: true }); }; // src/installers/zustand.ts import path9 from "node:path"; import fs8 from "fs-extra"; var zustandInstaller = ({ projectDir }) => { const extrasDir = path9.join(PKG_ROOT, "template/extra"); addPackageDependency({ projectDir, dependencies: ["zustand"], devMode: false }); addPackageDependency({ projectDir, dependencies: ["@redux-devtools/extension"], devMode: true }); const clientSrc = path9.join(extrasDir, "src/store"); const clientDest = path9.join(projectDir, "src/store"); fs8.copySync(clientSrc, clientDest); log("Zustand has been installed and setup", { gradient: true }); }; // src/utils/version.ts import fs9 from "fs-extra"; var getVersion = async () => { try { const pkgJson = await fs9.readJson(`${PKG_ROOT}/package.json`); return pkgJson.version; } catch (err) { console.error(`Error reading package.json: ${err}`); throw err; } }; // src/cli.ts var program = new Command(); var runCli = async () => { program.name("create-next-plus").description("Supercharged create-next-app!").version(await getVersion()); program.option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (opts) => { await intro(); const { name, db, extras } = await init(opts); await create({ name }); const projectPath = path10.join(process.cwd(), name); if (!opts.noGit) { await initGit(projectPath); } if (!opts.noInstall) { await installDependencies(projectPath); } if (db && db !== "fs") { drizzleInstaller( { noInstall: opts.noInstall, pkgManager: "bun", projectDir: projectPath, projectName: name, scopedAppName: name }, db ); } if (db === "fs") { firestoreInstaller({ noInstall: opts.noInstall, pkgManager: "bun", projectDir: projectPath, projectName: name, scopedAppName: name }); } if (extras.includes("langchain")) { langchainInstaller({ noInstall: opts.noInstall, pkgManager: "bun", projectDir: projectPath, projectName: name, scopedAppName: name }); } if (extras.includes("zustand")) { zustandInstaller({ noInstall: opts.noInstall, pkgManager: "bun", projectDir: projectPath, projectName: name, scopedAppName: name }); } if (extras.includes("millionjs")) { millionInstaller({ noInstall: opts.noInstall, pkgManager: "bun", projectDir: projectPath, projectName: name, scopedAppName: name }); } log("Congratulations! Everything is setup.", { gradient: true }); }); program.parse(process.argv); }; // src/index.ts var main = async () => { await runCli(); }; main().catch((err) => { console.error("Aborting installation, due to error: ", err); process.exit(1); });