@toxuh/create-imp
Version:
Next.js starter (Next 15 + shadcn + next-themes + optional Prisma).
335 lines (318 loc) • 9.92 kB
JavaScript
// 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