import { parseArgs } from "node:util"; import { PROJECT_NAME_PATTERN } from "./utils.js"; /** * Flag-driven configuration for non-interactive scaffolding. * * Every field is optional — `main()` only skips the corresponding prompt when * the field is set, so partial flag use still drops the user into prompts for * the remaining choices. With `--yes`, missing fields fall back to sensible * defaults (or to `"my-site"` for the project name). */ export interface ParsedFlags { /** Positional project name (or "."). Validated against PROJECT_NAME_PATTERN. */ name?: string; platform?: Platform; template?: TemplateKey; packageManager?: PackageManager; /** `--install` / `--no-install`. Undefined means "ask". */ install?: boolean; /** `--yes` — auto-accept remaining defaults and skip overwrite prompts. */ yes: boolean; /** * `--force` — proceed when the target directory is non-empty. Required * to overwrite a non-empty target under `--yes`; otherwise we refuse to * silently clobber files. */ force: boolean; /** `--help` — print usage and exit. */ help: boolean; } export type Platform = "node" | "cloudflare"; export type TemplateKey = "blog" | "starter" | "marketing" | "portfolio"; export type PackageManager = "pnpm" | "npm" | "yarn" | "bun"; const PLATFORMS: readonly Platform[] = ["node", "cloudflare"] as const; const TEMPLATES: readonly TemplateKey[] = ["blog", "starter", "marketing", "portfolio"] as const; const PACKAGE_MANAGERS: readonly PackageManager[] = ["pnpm", "npm", "yarn", "bun"] as const; /** * Thrown by `parseFlags()` for malformed input. `main()` catches and prints a * red error line plus the help text, then exits non-zero. */ export class FlagError extends Error { constructor(message: string) { super(message); this.name = "FlagError"; } } function isPlatform(value: string): value is Platform { return (PLATFORMS as readonly string[]).includes(value); } function isTemplate(value: string): value is TemplateKey { return (TEMPLATES as readonly string[]).includes(value); } function isPackageManager(value: string): value is PackageManager { return (PACKAGE_MANAGERS as readonly string[]).includes(value); } /** * Quick scan for `--help` / `-h` in raw argv, used to short-circuit help * before strict argument parsing runs. Without this, a user who types * `npm create emdash@latest --help --template nope` gets the parse error * for the bad template instead of the help they asked for. */ export function wantsHelp(argv: string[]): boolean { return argv.slice(2).some((arg) => arg === "--help" || arg === "-h"); } /** * Parse `process.argv`-style array into a {@link ParsedFlags}. * * Accepted forms (lifted from established `create-*` tools): * - Positional: `[name]` — the project directory (or `.` for cwd). * - `--template ` — one of `blog | starter | marketing | portfolio`, * or the combined form `: