create-esyt
Version:
A tool to create a new esyt project with your preferred stack
291 lines (263 loc) • 8.6 kB
JavaScript
// Utility: parse CLI flags in an order-independent way and provide helpers.
export function mapNpmToLabel(token) {
const map = {
tailwindcss: "TailwindCSS",
dotenv: "DotENV",
"react-icons": "React Icons",
"framer-motion": "Framer Motion",
ogl: "OGL",
axios: "Axios",
firebase: "Firebase",
clerk: "Clerk",
appwrite: "Appwrite",
prisma: "Prisma",
"react-router": "React Router",
"react-router-dom": "React Router",
"next-auth": "next-auth",
"@next/font": "@next/font",
"next-seo": "next-seo",
"next-sitemap": "next-sitemap",
"next-pwa": "next-pwa",
tanstack: "TanStack",
"tanstack-query": "TanStack",
"@tanstack/react-query": "TanStack",
"@tanstack/react-query-devtools": "TanStack",
"@tanstack/react-table": "TanStack",
"@tanstack/react-virtual": "TanStack",
"@tanstack/react-form": "TanStack",
"@tanstack/react-hotkeys": "TanStack",
};
return map[token] || null;
}
export function printHelp() {
console.log(`
Usage: bun create esyt@latest -- [framework] [language] [projectName] [packages...] [options]
Positional / brief
projectName First non-dash token is treated as the project name (no spaces).
Example: bun create esyt@latest -- my-app
Framework / language
-vite, --vite, vite Create a Vite project (default in interactive flow)
-next, --next, next Create a Next.js project
-js, --js, js Use JavaScript
-ts, --ts, ts Use TypeScript
Packages
Any token starting with -- or a single - followed by >2 chars is treated as a package token.
Examples: --tailwindcss --tanstack -tailwindcss --dotenv -dotenv
Unknown tokens are installed as npm packages.
Install / Git / Editor / Dev
-git Initialize a git repository (interactive default was changed to No)
-i, -install, --install Run the detected package manager's install command after creation
-dev Run the development server after install
-nvim, -zed, -code, -cursor, -trae Select editor to open the project (Zed, VSCode, Cursor, Trae)
Negations (explicit overrides)
--no-git, -no-git Disable git init
--no-install, -no-install, -no-i
Disable running the install step
--no-dev, -no-dev Disable running the dev server
--no-editor, -no-ide Disable opening an editor (sets editor to 'None')
Note: only these specific --no-... names are handled. Unknown --no-... tokens are ignored.
Automation / testing
--yes, -y, --no-interactive
Accept sensible defaults and skip prompts (non-interactive).
Defaults used by --yes can be overridden by explicit flags like --no-git.
--dry-run, -d Print the shell commands the CLI would run without executing them.
Help / version
-h, --help, help Print this help text and exit
-v, --version, version Print the package version and exit
Notes
- When running via wrapper commands (bun/pnpm/npm create), use a double-dash (--) to forward
flags and positional args to this CLI: bun create esyt@latest -- my-app --tailwindcss
- The CLI accepts flexible token forms (single-dash long tokens like -tailwindcss are supported).
`);
}
export function parseFlags(argv) {
const result = {
framework: undefined, // 'Vite' | 'Next.js'
language: undefined, // 'JavaScript' | 'TypeScript'
projectName: undefined,
packages: [], // raw tokens like '--dotenv' or '-dotenv'
npmPackages: [], // raw npm package names
gitInit: undefined,
installDeps: undefined,
selectedIDE: undefined,
runDevServer: undefined,
help: false,
version: false,
};
if (!argv || argv.length === 0) return result;
// Helper to normalize tokens (strip leading dashes)
const strip = (t) =>
t.startsWith("--") ? t.slice(2) : t.startsWith("-") ? t.slice(1) : t;
for (let i = 0; i < argv.length; i++) {
const t = argv[i];
if (!t) continue;
// Handle explicit `--no-` overrides (e.g. --no-git)
if (t.startsWith("--no-") || t.startsWith("-no-")) {
const key = t.replace(/^--?no-/, "");
switch (key) {
case "git":
result.gitInit = false;
break;
case "install":
case "i":
result.installDeps = false;
break;
case "dev":
result.runDevServer = false;
break;
case "editor":
case "ide":
result.selectedIDE = "None";
break;
default:
// unknown --no- flag; ignore
break;
}
continue;
}
// Help / version
if (t === "-h" || t === "--help" || t === "help") {
result.help = true;
continue;
}
if (t === "-v" || t === "--version" || t === "version") {
result.version = true;
continue;
}
// Non-interactive / yes
if (t === "--yes" || t === "-y" || t === "--no-interactive") {
result.yes = true;
continue;
}
// Dry-run
if (t === "--dry-run" || t === "-d") {
result.dryRun = true;
continue;
}
// Framework
if (["-vite", "--vite", "vite"].includes(t) || t === "vite") {
result.framework = "Vite";
continue;
}
if (["-next", "--next", "next"].includes(t) || t === "next") {
result.framework = "Next.js";
continue;
}
// Language
if (
[
"-js",
"--js",
"js",
"-javascript",
"--javascript",
"javascript",
].includes(t)
) {
result.language = "JavaScript";
continue;
}
if (
[
"-ts",
"--ts",
"ts",
"-typescript",
"--typescript",
"typescript",
].includes(t)
) {
result.language = "TypeScript";
continue;
}
// Git
if (t === "-git") {
result.gitInit = true;
continue;
}
if (t === "-") {
// explicit placeholder: treat as nothing/none for position-sensitive defaults
continue;
}
// Install
if (t === "-i" || t === "-install" || t === "--install") {
result.installDeps = true;
continue;
}
// Editor
if (t === "-nvim") {
result.selectedIDE = "Nvim";
continue;
}
if (t === "-zed") {
result.selectedIDE = "Zed";
continue;
}
if (t === "-code") {
result.selectedIDE = "VSCode";
continue;
}
if (t === "-cursor") {
result.selectedIDE = "Cursor";
continue;
}
if (t === "-trae") {
result.selectedIDE = "Trae";
continue;
}
// Dev server
if (t === "-dev") {
result.runDevServer = true;
continue;
}
// Packages: tokens starting with --pkg or -pkg (but not short flags like -v)
if (t.startsWith("--") || (t.startsWith("-") && t.length > 2)) {
const pkg = strip(t);
result.packages.push(t);
result.npmPackages.push(pkg);
continue;
}
// If we reach here and token doesn't start with dash, it's likely the project name
if (!result.projectName) {
// validate no spaces
if (/\s/.test(t)) {
// ignore and let interactive flow ask for a valid name
continue;
}
result.projectName = t;
continue;
}
// Unknown token: if it's not a flag and we already have projectName, treat as package
const fallbackPkg = strip(t);
result.packages.push(t);
result.npmPackages.push(fallbackPkg);
}
return result;
}
export function validateFlags(flags) {
const invalid = {};
// framework
if (flags.framework !== undefined) {
if (!(flags.framework === "Vite" || flags.framework === "Next.js")) {
invalid.framework = "unsupported";
}
}
// language
if (flags.language !== undefined) {
if (!(flags.language === "JavaScript" || flags.language === "TypeScript")) {
invalid.language = "unsupported";
}
}
// projectName
if (flags.projectName !== undefined) {
if (/\s/.test(flags.projectName) || flags.projectName.length === 0) {
invalid.projectName = "invalid";
}
}
// selectedIDE
if (flags.selectedIDE !== undefined) {
const allowed = ["Nvim", "Zed", "VSCode", "Cursor", "Trae", "None"];
if (!allowed.includes(flags.selectedIDE)) invalid.selectedIDE = "invalid";
}
return invalid;
}