@noy-db/create 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bin/create.ts","../../src/wizard/run.ts","../../src/wizard/render.ts","../../src/wizard/detect.ts","../../src/wizard/augment.ts","../../src/wizard/i18n/en.ts","../../src/wizard/i18n/th.ts","../../src/wizard/i18n/index.ts","../../src/bin/parse-args.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `create` bin of `@noy-db/create`. Invoked by the scoped-initializer\n * idioms `npm create @noy-db`, `pnpm create @noy-db`, `yarn create @noy-db`,\n * and `bun create @noy-db`.\n *\n * Why the bin is called `create` (no suffix): npm's `npm init @scope` is\n * sugar for `npm exec @scope/create`, and `npm exec` picks the bin whose\n * name matches the package's \"stripped\" name. For `@noy-db/create` that's\n * literally `create`. If we named the bin anything else, the shortcut\n * `npm create @noy-db` would break.\n *\n * Argument convention follows the de-facto standard for `create-*` tools:\n * the first positional argument (if any) becomes the project name. Flags:\n *\n * --yes / -y Skip every prompt; use defaults for missing values\n * --adapter <name> Pre-select adapter (browser | file | memory)\n * --no-sample-data Don't include the seed invoices\n * --help / -h Print usage and exit 0\n *\n * The bin is intentionally tiny: it parses argv, calls `runWizard`, and\n * exits with the right code. Everything else lives in the wizard module\n * so it's reusable from tests and from other tooling.\n */\n\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { runWizard } from '../wizard/run.js'\nimport { parseArgs, type ParsedArgs } from './parse-args.js'\nimport { detectLocale, loadMessages } from '../wizard/i18n/index.js'\n\nconst HELP = `Usage: npm create @noy-db [project-name] [options]\n\nThe wizard auto-detects whether cwd is an existing Nuxt 4 project:\n\n - If nuxt.config.{ts,js,mjs} + a package.json with \\`nuxt\\` in\n deps are both present, it enters AUGMENT mode — patches the\n existing config via magicast, shows a unified diff, and asks\n for confirmation before writing.\n\n - Otherwise it enters FRESH mode — creates a new project\n directory with a full Nuxt 4 + Pinia + @noy-db/nuxt starter.\n\nOptions:\n -y, --yes Skip prompts; use defaults for missing values\n --adapter <name> Adapter: browser (default) | file | memory\n --no-sample-data (fresh mode) Skip the seed invoice records\n --dry-run (augment mode) Show the diff without writing\n --force-fresh Force fresh-project mode even when cwd looks\n like an existing Nuxt project\n --lang <code> UI language: en (default) | th\n When omitted, auto-detected from LC_ALL/LANG\n -h, --help Show this message and exit\n\nExamples:\n # Fresh project in a new directory\n npm create @noy-db my-app\n npm create @noy-db my-app --yes --adapter file\n\n # Augment an existing Nuxt 4 project (run from its root)\n cd ~/my-existing-nuxt-app\n npm create @noy-db # preview the diff and confirm\n npm create @noy-db --dry-run # print the diff, no write\n npm create @noy-db --yes # non-interactive, write immediately\n`\n\nasync function main(): Promise<void> {\n let parsed: ParsedArgs\n try {\n parsed = parseArgs(process.argv.slice(2))\n } catch (err) {\n process.stderr.write(`${pc.red('error:')} ${(err as Error).message}\\n\\n${HELP}`)\n process.exit(2)\n return // unreachable, but narrows `parsed` for the type checker\n }\n\n if (parsed.help) {\n process.stdout.write(HELP)\n return\n }\n\n // Only run the intro/outro decorations in interactive mode. Tests\n // call `runWizard` directly and shouldn't see these.\n if (!parsed.options.yes) {\n // Resolve the same locale the wizard will pick so the intro\n // banner matches the rest of the flow. Explicit --lang wins;\n // otherwise we fall back to env-var detection.\n const msg = loadMessages(parsed.options.locale ?? detectLocale())\n p.intro(pc.bgCyan(pc.black(' @noy-db/create ')))\n p.note(msg.wizardIntro)\n }\n\n try {\n await runWizard(parsed.options)\n } catch (err) {\n p.cancel((err as Error).message)\n process.exit(1)\n }\n}\n\nvoid main()\n","/**\n * The wizard entry point — `runWizard()`.\n *\n * Two modes:\n *\n * 1. **Interactive (default).** Uses `@clack/prompts` to ask the user\n * for project name, adapter, and sample-data inclusion. Cancellation\n * at any prompt aborts cleanly with a non-zero exit code.\n *\n * 2. **Non-interactive (`yes: true`).** Skips every prompt and uses the\n * values supplied in `WizardOptions`. Missing values become defaults.\n * This is the path tests take — no terminal needed, fully scriptable.\n *\n * The function never spawns child processes (no `npm install` etc.). It\n * only writes files and returns. The shell wrapper around `npm create` is\n * responsible for installing — we keep this layer pure so it's trivially\n * testable and so adding a `--no-install` flag later is a no-op.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { renderTemplate, templateDir, type RenderTokens } from './render.js'\nimport type {\n WizardAdapter,\n WizardOptions,\n WizardResult,\n WizardFreshResult,\n WizardAugmentResult,\n} from './types.js'\nimport { detectNuxtProject } from './detect.js'\nimport { augmentNuxtConfig, writeAugmentedConfig } from './augment.js'\nimport { detectLocale, loadMessages, type WizardMessages } from './i18n/index.js'\n\n/**\n * Default invoice records the wizard injects when `sampleData: true`.\n * Kept here (not in the template) so the same records can be reused by\n * the `noy-db add` command for collections with `--seed`. Three records\n * is enough to demo a query that returns more than one row.\n */\nconst DEFAULT_SEED = [\n {\n id: 'inv-001',\n client: 'Acme Holdings',\n amount: 1500,\n status: 'open',\n dueDate: '2026-05-01',\n },\n {\n id: 'inv-002',\n client: 'Globex Inc',\n amount: 4200,\n status: 'paid',\n dueDate: '2026-04-15',\n },\n {\n id: 'inv-003',\n client: 'Initech LLC',\n amount: 850,\n status: 'overdue',\n dueDate: '2026-03-20',\n },\n]\n\nfunction adapterLabels(msg: WizardMessages): Record<WizardAdapter, string> {\n return {\n browser: msg.adapterBrowserLabel,\n file: msg.adapterFileLabel,\n memory: msg.adapterMemoryLabel,\n }\n}\n\n/**\n * Validates the project name. Rules are intentionally narrow:\n * - non-empty\n * - lowercase letters, digits, hyphens, dots, underscores only\n * - cannot start with a hyphen, dot, or underscore\n * - max 214 chars (npm package-name limit)\n *\n * The narrow rule set means whatever the user types is also a valid npm\n * package name, so the generated `package.json#name` field is always safe.\n */\nexport function validateProjectName(name: string): string | null {\n if (!name || name.trim() === '') return 'Project name cannot be empty'\n if (name.length > 214) return 'Project name must be 214 characters or fewer'\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(name)) {\n return 'Project name must start with a lowercase letter or digit and contain only lowercase letters, digits, hyphens, dots, or underscores'\n }\n return null\n}\n\n/**\n * Main entry point. Detects whether `cwd` is an existing Nuxt 4\n * project and routes to one of two modes:\n *\n * - **Fresh mode** (the original v0.3.1 behavior): prompts for\n * project name, creates a new directory, renders the Nuxt 4\n * starter template. Returns a `WizardFreshResult`.\n *\n * - **Augment mode** (new in v0.5, #37): patches the existing\n * `nuxt.config.ts` via magicast to add `@noy-db/nuxt` to the\n * modules array and a `noydb:` config key. Shows a unified\n * diff and asks for confirmation before writing. Supports\n * `--dry-run`. Returns a `WizardAugmentResult`.\n *\n * The auto-detection rule: if cwd has both a `nuxt.config.ts`\n * (or `.js`/`.mjs`) AND a `package.json` that lists `nuxt` in any\n * dependency section, augment mode fires. Otherwise fresh mode.\n * Users can force fresh mode via `forceFresh: true` (CLI:\n * `--force-fresh`) when they want to create a sub-project inside\n * an existing Nuxt workspace.\n *\n * Both modes refuse to clobber existing work: fresh mode rejects\n * non-empty target dirs; augment mode rejects unsupported config\n * shapes (opaque exports, non-array modules, etc.).\n */\nexport async function runWizard(options: WizardOptions = {}): Promise<WizardResult> {\n const cwd = options.cwd ?? process.cwd()\n const yes = options.yes ?? false\n\n // Resolve the message bundle once at the top so every downstream\n // helper sees a consistent locale. Explicit `options.locale` wins;\n // otherwise we fall back to POSIX env-var detection (LC_ALL → LANG).\n // Tests pin the locale to keep snapshots deterministic.\n const msg = loadMessages(options.locale ?? detectLocale())\n\n // ── Detect existing Nuxt project ───────────────────────────────────\n // Runs BEFORE any prompts so the interactive flow branches cleanly\n // into fresh vs augment without asking the user questions that\n // don't apply to their mode. `forceFresh` short-circuits the\n // detection — CI tests use this to scaffold into a temp dir that\n // happens to sit under an existing Nuxt project.\n const detection = options.forceFresh\n ? null\n : await detectNuxtProject(cwd)\n\n if (detection?.existing && detection.configPath) {\n return runAugmentMode(options, cwd, detection.configPath, msg)\n }\n\n return runFreshMode(options, cwd, yes, msg)\n}\n\n/**\n * The original v0.3.1 fresh-project path, factored out of the\n * main entry so the augment branch above can coexist. Behavior\n * is unchanged from v0.3.1 except the return shape now includes\n * the `kind: 'fresh'` discriminator.\n */\nasync function runFreshMode(\n options: WizardOptions,\n cwd: string,\n yes: boolean,\n msg: WizardMessages,\n): Promise<WizardFreshResult> {\n // ── Resolve answers ────────────────────────────────────────────────\n // In non-interactive mode every prompt is short-circuited; in\n // interactive mode we only prompt for fields the caller didn't supply.\n const projectName = yes\n ? options.projectName ?? 'my-noy-db-app'\n : await promptProjectName(options.projectName, msg)\n\n const adapter: WizardAdapter = yes\n ? options.adapter ?? 'browser'\n : await promptAdapter(options.adapter, msg)\n\n const sampleData: boolean = yes\n ? options.sampleData ?? true\n : await promptSampleData(options.sampleData, msg)\n\n // ── Validate target directory ──────────────────────────────────────\n // We refuse to write into a non-empty directory. Empty + missing are\n // both fine — fs.mkdir { recursive: true } handles both. The `Cancelled`\n // exit is intentional: cleaner than throwing an Error from a wizard.\n const projectPath = path.resolve(cwd, projectName)\n await assertWritableTarget(projectPath)\n\n // ── Render the template ────────────────────────────────────────────\n const tokens: RenderTokens = {\n PROJECT_NAME: projectName,\n ADAPTER: adapter,\n DEVTOOLS: 'true',\n SEED_INVOICES: sampleData\n ? JSON.stringify(DEFAULT_SEED, null, 2).replace(/\\n/g, '\\n ')\n : '[]',\n }\n\n await fs.mkdir(projectPath, { recursive: true })\n const files = await renderTemplate(\n templateDir('nuxt-default'),\n projectPath,\n tokens,\n )\n\n if (!yes) {\n // Print a friendly summary so the user knows what happened. We don't\n // run `npm install` ourselves — the user picks the package manager.\n p.note(\n [\n `${pc.bold('cd')} ${projectName}`,\n `${pc.bold('pnpm install')} ${pc.dim('(or npm/yarn/bun)')}`,\n `${pc.bold('pnpm dev')}`,\n ].join('\\n'),\n msg.freshNextStepsTitle,\n )\n p.outro(pc.green(msg.freshOutroDone))\n }\n\n return {\n kind: 'fresh',\n options: {\n projectName,\n adapter,\n sampleData,\n cwd,\n },\n projectPath,\n files,\n }\n}\n\n/**\n * The new v0.5 augment-existing-project path (#37). Runs magicast\n * on the detected nuxt.config, shows a unified diff, asks for\n * confirmation, and writes. Supports `--dry-run` to see the diff\n * without touching disk.\n *\n * Three outcomes:\n * - **written**: the file was patched successfully\n * - **already-configured**: both target mutations are already\n * present (idempotent no-op)\n * - **cancelled**: user said no at the confirmation prompt\n * - **dry-run**: `options.dryRun` was set, we showed the diff\n * and returned without writing\n * - **unsupported-shape**: the config file uses a shape we can't\n * safely mutate (opaque export, non-array modules, etc.)\n */\nasync function runAugmentMode(\n options: WizardOptions,\n cwd: string,\n configPath: string,\n msg: WizardMessages,\n): Promise<WizardAugmentResult> {\n const yes = options.yes ?? false\n const dryRun = options.dryRun ?? false\n\n if (!yes) {\n p.note(\n [\n `${pc.dim(msg.augmentDetectedPrefix)}`,\n ` ${pc.cyan(configPath)}`,\n '',\n msg.augmentDescription,\n ].join('\\n'),\n msg.augmentModeTitle,\n )\n }\n\n // In augment mode we only need ONE prompt from the user: which\n // adapter to wire into the `noydb: { adapter }` key. Everything\n // else is decided by the config we're patching.\n const adapter: WizardAdapter = yes\n ? options.adapter ?? 'browser'\n : await promptAdapter(options.adapter, msg)\n\n const result = await augmentNuxtConfig({\n configPath,\n adapter,\n dryRun,\n })\n\n if (result.kind === 'already-configured') {\n if (!yes) {\n p.note(\n `${pc.yellow(msg.augmentNothingToDo)} ${result.reason}`,\n msg.augmentAlreadyConfiguredTitle,\n )\n p.outro(pc.green(msg.augmentAlreadyOutro))\n }\n return {\n kind: 'augment',\n configPath,\n adapter,\n changed: false,\n reason: 'already-configured',\n }\n }\n\n if (result.kind === 'unsupported-shape') {\n if (!yes) {\n p.cancel(`${pc.red(msg.augmentUnsupportedPrefix)} ${result.reason}`)\n }\n return {\n kind: 'augment',\n configPath,\n adapter,\n changed: false,\n reason: 'unsupported-shape',\n }\n }\n\n // result.kind === 'proposed-change' — print the diff and either\n // write (after confirmation) or bail in dry-run mode.\n if (!yes || dryRun) {\n p.note(renderDiff(result.diff), msg.augmentProposedChangesTitle)\n }\n\n if (dryRun) {\n if (!yes) p.outro(pc.green(msg.augmentDryRunOutro))\n return {\n kind: 'augment',\n configPath,\n adapter,\n changed: false,\n reason: 'dry-run',\n diff: result.diff,\n }\n }\n\n let shouldWrite = yes\n if (!yes) {\n const confirmed = await p.confirm({\n message: msg.augmentApplyConfirm,\n initialValue: true,\n })\n if (p.isCancel(confirmed) || confirmed !== true) {\n p.cancel(msg.augmentAborted)\n return {\n kind: 'augment',\n configPath,\n adapter,\n changed: false,\n reason: 'cancelled',\n diff: result.diff,\n }\n }\n shouldWrite = true\n }\n\n if (shouldWrite) {\n await writeAugmentedConfig(configPath, result.newCode)\n if (!yes) {\n p.note(\n [\n pc.dim(msg.augmentInstallIntro),\n '',\n `${pc.bold('pnpm add')} @noy-db/nuxt @noy-db/pinia @noy-db/core @noy-db/browser @pinia/nuxt pinia`,\n pc.dim(msg.augmentInstallPmHint),\n ].join('\\n'),\n msg.augmentNextStepTitle,\n )\n p.outro(pc.green(msg.augmentDoneOutro))\n }\n }\n\n return {\n kind: 'augment',\n configPath,\n adapter,\n changed: true,\n reason: 'written',\n diff: result.diff,\n }\n}\n\n/**\n * Clean up a unified diff for terminal display. Strips the `===`\n * separators the `diff` package emits, keeps a reasonable max\n * width, and drops the Index/------ preamble that's only useful\n * for `patch -p1`. The result is a short, colorized-ready string\n * that fits inside a clack `note()` block.\n */\nfunction renderDiff(diff: string): string {\n const lines = diff.split('\\n')\n const keep: string[] = []\n for (const line of lines) {\n if (line.startsWith('Index:')) continue\n if (line.startsWith('=')) continue\n if (line.startsWith('---') || line.startsWith('+++')) continue\n if (line.startsWith('+')) keep.push(pc.green(line))\n else if (line.startsWith('-')) keep.push(pc.red(line))\n else if (line.startsWith('@@')) keep.push(pc.dim(line))\n else keep.push(line)\n }\n return keep.join('\\n').trim()\n}\n\n// ─── Prompt helpers ──────────────────────────────────────────────────────\n\nasync function promptProjectName(initial: string | undefined, msg: WizardMessages): Promise<string> {\n if (initial) {\n const err = validateProjectName(initial)\n if (err) throw new Error(err)\n return initial\n }\n const result = await p.text({\n message: msg.promptProjectName,\n placeholder: msg.promptProjectNamePlaceholder,\n initialValue: 'my-noy-db-app',\n validate: (v) => validateProjectName(v ?? '') ?? undefined,\n })\n if (p.isCancel(result)) {\n p.cancel(msg.cancelled)\n process.exit(1)\n }\n return result\n}\n\nasync function promptAdapter(initial: WizardAdapter | undefined, msg: WizardMessages): Promise<WizardAdapter> {\n if (initial) return initial\n const labels = adapterLabels(msg)\n const result = await p.select<WizardAdapter>({\n message: msg.promptAdapter,\n options: (['browser', 'file', 'memory'] as const).map((value) => ({\n value,\n label: labels[value],\n })),\n initialValue: 'browser',\n })\n if (p.isCancel(result)) {\n p.cancel(msg.cancelled)\n process.exit(1)\n }\n return result\n}\n\nasync function promptSampleData(initial: boolean | undefined, msg: WizardMessages): Promise<boolean> {\n if (typeof initial === 'boolean') return initial\n const result = await p.confirm({\n message: msg.promptSampleData,\n initialValue: true,\n })\n if (p.isCancel(result)) {\n p.cancel(msg.cancelled)\n process.exit(1)\n }\n return result\n}\n\nasync function assertWritableTarget(target: string): Promise<void> {\n try {\n const entries = await fs.readdir(target)\n if (entries.length > 0) {\n throw new Error(\n `Target directory '${target}' already exists and is not empty. Refusing to overwrite — pick a different project name or remove the directory first.`,\n )\n }\n } catch (err: unknown) {\n // ENOENT is the happy path — the directory doesn't exist yet.\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n throw err\n }\n}\n","/**\n * Template loader and renderer.\n *\n * Templates live under `packages/create-noy-db/templates/<template-name>/`\n * as a literal directory tree. Files are read verbatim from disk; the\n * renderer walks the tree, substitutes a small set of placeholder tokens,\n * and writes the result to the target directory.\n *\n * The placeholder syntax is intentionally minimal: `{{TOKEN}}` (uppercase,\n * no spaces, no nested expressions). This avoids dragging in handlebars\n * or any templating library, keeps the bundle small, and means there's\n * exactly one rule to learn when authoring a template.\n *\n * Files whose names start with `_` are renamed to `.` on copy. This is\n * how we ship `_gitignore` (`.gitignore`) without npm trying to interpret\n * it as part of the package contents.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * Tokens the renderer recognizes. Authoring rule for templates: every\n * placeholder must be a member of this set, otherwise template authors\n * will hit silent no-ops at render time.\n */\nexport interface RenderTokens {\n /** The user-supplied project name. Used for `package.json#name` etc. */\n PROJECT_NAME: string\n /** The chosen adapter (browser/file/memory). */\n ADAPTER: string\n /** \"true\" or \"false\" — written into `nuxt.config.ts` for `noydb.devtools`. */\n DEVTOOLS: string\n /** A literal `[...]` block of seed records, or `[]` if `sampleData` is false. */\n SEED_INVOICES: string\n}\n\n/**\n * Walks `src` recursively and copies every file into `dest`, substituting\n * `{{TOKEN}}` placeholders along the way.\n *\n * Returns the relative paths of every file written, sorted alphabetically.\n * Tests use this to assert that the expected file set was produced without\n * having to walk `dest` themselves.\n *\n * The function is intentionally synchronous-feeling (uses `await` inside\n * a depth-first walk) so the order of writes is deterministic — easier\n * to reason about when something fails halfway through.\n */\nexport async function renderTemplate(\n src: string,\n dest: string,\n tokens: RenderTokens,\n): Promise<string[]> {\n const written: string[] = []\n await walk(src, dest, '', tokens, written)\n written.sort()\n return written\n}\n\nasync function walk(\n srcRoot: string,\n destRoot: string,\n rel: string,\n tokens: RenderTokens,\n written: string[],\n): Promise<void> {\n const srcDir = path.join(srcRoot, rel)\n const entries = await fs.readdir(srcDir, { withFileTypes: true })\n\n for (const entry of entries) {\n const srcEntry = path.join(srcDir, entry.name)\n // _gitignore → .gitignore, _npmrc → .npmrc, etc.\n // npm strips files starting with `.` from the published tarball, so\n // shipping them as `_<name>` is the standard workaround.\n const destName = entry.name.startsWith('_')\n ? `.${entry.name.slice(1)}`\n : entry.name\n const destRel = rel ? path.join(rel, destName) : destName\n const destEntry = path.join(destRoot, destRel)\n\n if (entry.isDirectory()) {\n await fs.mkdir(destEntry, { recursive: true })\n await walk(srcRoot, destRoot, path.join(rel, entry.name), tokens, written)\n continue\n }\n\n const raw = await fs.readFile(srcEntry, 'utf8')\n const rendered = applyTokens(raw, tokens)\n await fs.mkdir(path.dirname(destEntry), { recursive: true })\n await fs.writeFile(destEntry, rendered, 'utf8')\n written.push(destRel)\n }\n}\n\n/**\n * Substitutes every `{{KEY}}` in `input` with the corresponding value\n * from `tokens`. Unknown keys are left untouched (so the user can spot\n * them visually in the generated output if a template author makes a\n * typo). Empty values are allowed.\n */\nexport function applyTokens(input: string, tokens: RenderTokens): string {\n const bag = tokens as unknown as Record<string, string>\n return input.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value = bag[key]\n return value === undefined ? match : value\n })\n}\n\n/**\n * Resolves the absolute path of a template directory shipped inside the\n * package. The package layout is:\n *\n * packages/create-noy-db/\n * dist/\n * wizard/render.js ← we are here at runtime (after tsup build)\n * templates/\n * nuxt-default/ ← we want to point at this\n *\n * In the published tarball the same relative path holds: `dist/` and\n * `templates/` are siblings under the package root. Computing the path\n * relative to `import.meta.url` lets us avoid hardcoding any assumption\n * about whether we're running from source, from `dist/`, or from a\n * globally installed copy.\n */\nexport function templateDir(name: string): string {\n // From `dist/wizard/render.js` we go up two levels to the package root,\n // then into `templates/<name>`.\n const here = fileURLToPath(import.meta.url)\n const packageRoot = path.resolve(path.dirname(here), '..', '..')\n return path.join(packageRoot, 'templates', name)\n}\n","/**\n * Detect whether the wizard is being invoked inside an existing\n * Nuxt 4 project that should be augmented in place, vs a blank\n * directory where the wizard should create a fresh starter.\n *\n * The detection rule is intentionally narrow so we don't trip on\n * random `package.json` files sitting in the parent tree: a project\n * counts as \"existing Nuxt 4\" only when the SAME directory has\n * BOTH `nuxt.config.ts` (or `.js`) AND a `package.json` that lists\n * `nuxt` in any of the dependency sections. Either alone is\n * ambiguous:\n *\n * - `nuxt.config.ts` without a package.json could be a stray\n * template scratch file the user moved here.\n * - `package.json` with `nuxt` but no config file could be a\n * half-deleted project or a pnpm workspace root that pulls\n * nuxt in transitively for docs.\n *\n * Requiring both avoids both false positives.\n *\n * We deliberately do NOT walk upward to find a parent Nuxt project.\n * The user's cwd is load-bearing — if they wanted to augment a\n * parent dir, they should cd there first. Walking up would make\n * \"run the wizard to create a fresh app inside my existing\n * monorepo\" silently augment the monorepo root instead.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport interface NuxtDetection {\n /** Whether cwd contains an existing Nuxt 4 project. */\n readonly existing: boolean\n /** Absolute path of the detected config file, if any. */\n readonly configPath: string | null\n /** Absolute path of the detected package.json, if any. */\n readonly packageJsonPath: string | null\n /** Reason strings explaining the detection outcome — useful for diagnostic messages. */\n readonly reasons: readonly string[]\n}\n\n/**\n * Inspect `cwd` for an existing Nuxt 4 project. Returns a detailed\n * result object so the caller can both branch on `existing` and\n * surface the specific reasons to the user.\n *\n * Pure in terms of filesystem reads — no writes, no network, no\n * caching. Callers who need cached detection should memoize on\n * their side.\n */\nexport async function detectNuxtProject(cwd: string): Promise<NuxtDetection> {\n const reasons: string[] = []\n\n // Step 1: look for a Nuxt config file. Both extensions are\n // valid; Nuxt itself accepts either.\n const configCandidates = ['nuxt.config.ts', 'nuxt.config.js', 'nuxt.config.mjs']\n let configPath: string | null = null\n for (const name of configCandidates) {\n const candidate = path.join(cwd, name)\n if (await pathExists(candidate)) {\n configPath = candidate\n reasons.push(`Found ${name}`)\n break\n }\n }\n if (!configPath) {\n reasons.push('No nuxt.config.{ts,js,mjs} in cwd')\n return {\n existing: false,\n configPath: null,\n packageJsonPath: null,\n reasons,\n }\n }\n\n // Step 2: look for a package.json in the same directory.\n const pkgPath = path.join(cwd, 'package.json')\n if (!(await pathExists(pkgPath))) {\n reasons.push('Config file present but no package.json — ambiguous, skipping')\n return {\n existing: false,\n configPath,\n packageJsonPath: null,\n reasons,\n }\n }\n\n // Step 3: verify `nuxt` is in one of the dependency sections.\n // We're lenient about WHICH section (dependencies, devDependencies,\n // peerDependencies) because real-world projects put it in all of\n // them depending on the tooling.\n let pkg: Record<string, unknown>\n try {\n pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8')) as Record<string, unknown>\n } catch (err) {\n reasons.push(`package.json is not valid JSON: ${(err as Error).message}`)\n return {\n existing: false,\n configPath,\n packageJsonPath: pkgPath,\n reasons,\n }\n }\n\n const depSections = ['dependencies', 'devDependencies', 'peerDependencies'] as const\n let nuxtVersion: string | undefined\n for (const section of depSections) {\n const deps = pkg[section]\n if (deps && typeof deps === 'object' && 'nuxt' in deps) {\n nuxtVersion = (deps as Record<string, string>)['nuxt']\n reasons.push(`Found nuxt@${nuxtVersion} in ${section}`)\n break\n }\n }\n if (!nuxtVersion) {\n reasons.push('Config file present, but package.json does not list `nuxt` as a dependency')\n return {\n existing: false,\n configPath,\n packageJsonPath: pkgPath,\n reasons,\n }\n }\n\n return {\n existing: true,\n configPath,\n packageJsonPath: pkgPath,\n reasons,\n }\n}\n\n/** Cheap fs.access wrapper that returns a boolean instead of throwing. */\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await fs.access(target)\n return true\n } catch {\n return false\n }\n}\n","/**\n * Augment an existing Nuxt 4 project with `@noy-db/nuxt`.\n *\n * The entry point is `augmentNuxtConfig()`, which reads a\n * `nuxt.config.ts` (or `.js` / `.mjs`) via magicast, mutates the\n * AST to:\n *\n * 1. Add `'@noy-db/nuxt'` to the `modules` array (creating the\n * array if it doesn't exist)\n * 2. Add the `noydb: { adapter, pinia: true, devtools: true }`\n * config key (creating it if it doesn't exist)\n *\n * then generates the new source code, computes a unified diff\n * against the original, and either writes the result (with\n * optional user confirmation) or returns the diff unchanged for\n * `--dry-run`.\n *\n * ## Idempotency\n *\n * Re-running the wizard on an already-augmented project is a\n * no-op: we check whether the target values are already present\n * before mutating, and if both are, we short-circuit before\n * calling `generateCode()`. The caller sees\n * `{ changed: false, reason: 'already configured' }` and can\n * exit cleanly.\n *\n * ## Edge cases\n *\n * - `export default defineNuxtConfig({...})` — the common case,\n * handled directly.\n * - `export default {...}` — plain object literal, also handled.\n * - `export default someVar` — opaque reference, we bail with a\n * clear error telling the user to edit manually. We don't try\n * to chase the variable.\n * - `modules` declared as an object instead of an array — rare\n * but possible in some Nuxt tooling. We bail.\n * - `modules` already contains `'@noy-db/nuxt'` — idempotent skip.\n * - `noydb` key already present — we preserve the existing value\n * UNLESS `force: true` is passed (not yet surfaced in the CLI;\n * reserved for a future `--overwrite-config` flag).\n *\n * ## Why magicast instead of a regex/string-patch\n *\n * Regex-based config patching produces correct-looking code 90%\n * of the time and silently wrong code the other 10%. Anything\n * non-trivial (nested options, comments, multi-line strings,\n * conditional exports) breaks. Magicast walks a real Babel AST,\n * preserves unrelated formatting, and round-trips the file\n * cleanly — including comments, trailing commas, and property\n * order.\n */\n\nimport { promises as fs } from 'node:fs'\nimport { loadFile, generateCode, builders } from 'magicast'\nimport { createPatch } from 'diff'\nimport type { WizardAdapter } from './types.js'\n\nexport interface AugmentOptions {\n /** Absolute path of the nuxt.config.{ts,js,mjs} to patch. */\n configPath: string\n /** Adapter string to write into `noydb: { adapter }`. */\n adapter: WizardAdapter\n /**\n * When true, skip the write and return the would-be result.\n * The CLI's `--dry-run` flag threads through to here.\n */\n dryRun?: boolean\n}\n\nexport type AugmentResult =\n | {\n readonly kind: 'already-configured'\n readonly configPath: string\n readonly reason: string\n }\n | {\n readonly kind: 'proposed-change'\n readonly configPath: string\n readonly originalCode: string\n readonly newCode: string\n /** Unified diff string suitable for printing to the terminal. */\n readonly diff: string\n /** `true` when `options.dryRun` was set — the caller skips the write. */\n readonly dryRun: boolean\n }\n | {\n readonly kind: 'unsupported-shape'\n readonly configPath: string\n readonly reason: string\n }\n\n/**\n * Parse, mutate, and compute the diff. Does NOT write the file —\n * that's the caller's responsibility, after (optionally) prompting\n * the user for confirmation.\n *\n * The split exists so the CLI can interleave the prompt between\n * \"show the diff\" and \"write the file\", and so tests can inspect\n * the proposed change without any filesystem side effect beyond\n * the initial read.\n */\nexport async function augmentNuxtConfig(\n options: AugmentOptions,\n): Promise<AugmentResult> {\n const originalCode = await fs.readFile(options.configPath, 'utf8')\n const mod = await loadFile(options.configPath)\n\n // The entry point is whatever `export default` points at. In a\n // Nuxt config this is either `defineNuxtConfig({...})` or a\n // plain object literal.\n const exported = mod.exports.default as unknown\n if (exported === undefined || exported === null) {\n return {\n kind: 'unsupported-shape',\n configPath: options.configPath,\n reason: `${options.configPath} has no default export. Expected \\`export default defineNuxtConfig({...})\\` or \\`export default {...}\\`.`,\n }\n }\n\n // Both the `defineNuxtConfig({...})` call and the plain-object\n // case expose the config as a proxified object. For the function\n // call, magicast's proxy makes `exported.$args[0]` the config\n // object; for a plain object it's `exported` itself. We test\n // both shapes and pick the first one that has `modules` or can\n // accept a `modules` write.\n //\n // This looks a bit like JavaScript duck-typing because it IS\n // duck-typing — magicast's proxy tolerates either shape and the\n // API surface is identical once you reach the config object.\n const config = resolveConfigObject(exported)\n if (!config) {\n return {\n kind: 'unsupported-shape',\n configPath: options.configPath,\n reason: `Could not find the config object in ${options.configPath}. ` +\n `Expected \\`export default defineNuxtConfig({ modules: [], ... })\\` or a plain object literal.`,\n }\n }\n\n const skipReasons: string[] = []\n\n // --- Modules array -------------------------------------------------\n // Ensure `modules` is an array; create it if missing. Magicast's\n // proxy handles the array-vs-missing distinction via typeof checks\n // on the array's length property.\n const modulesRaw: unknown = config.modules\n let modulesWasMissing = false\n if (modulesRaw === undefined) {\n modulesWasMissing = true\n config.modules = []\n } else if (typeof modulesRaw !== 'object' || !isProxyArray(modulesRaw)) {\n return {\n kind: 'unsupported-shape',\n configPath: options.configPath,\n reason: `\\`modules\\` in ${options.configPath} is not an array literal. ` +\n `Edit it manually and re-run the wizard if you want to continue.`,\n }\n }\n\n // Push '@noy-db/nuxt' if it's not already listed. We compare by\n // stringification because magicast's proxy yields primitive\n // strings for literal entries.\n const modules = config.modules as string[]\n const alreadyHasModule = Array.from(modules).some((m) => String(m) === '@noy-db/nuxt')\n if (alreadyHasModule) {\n skipReasons.push('`@noy-db/nuxt` already in modules')\n } else {\n // Insert as a literal string so magicast writes `'@noy-db/nuxt'`\n // rather than an object wrapper.\n modules.push('@noy-db/nuxt')\n }\n\n // --- `noydb` config key --------------------------------------------\n // If the user already has a noydb key, preserve it — they might\n // have custom options we don't want to clobber. If it doesn't\n // exist, add it with the adapter the wizard collected.\n const noydbRaw: unknown = config.noydb\n if (noydbRaw !== undefined) {\n skipReasons.push('`noydb` key already set')\n } else {\n // `builders.raw` parses the literal into an AST node, so the\n // output is a real object expression in the generated code\n // instead of a stringified opaque blob.\n config.noydb = builders.raw(\n `{ adapter: '${options.adapter}', pinia: true, devtools: true }`,\n )\n }\n\n // If nothing changed, short-circuit. `modulesWasMissing` is a\n // change even if we didn't push anything, because we wrote an\n // empty array into the config — but that's an unusual case so\n // we lump it in with the regular \"changed\" path.\n if (skipReasons.length === 2 && !modulesWasMissing) {\n return {\n kind: 'already-configured',\n configPath: options.configPath,\n reason: skipReasons.join('; '),\n }\n }\n\n // Generate the new source, then compute a unified diff against\n // the original. `createPatch` is from the `diff` package — same\n // one `jest-diff` and `vitest` use under the hood. The empty\n // header strings keep the diff output compact for terminal\n // display; a full unified diff header isn't needed since we're\n // going to print the diff inline, not pipe it to `patch -p1`.\n const generated = generateCode(mod).code\n const diff = createPatch(\n options.configPath,\n originalCode,\n generated,\n '',\n '',\n { context: 3 },\n )\n\n return {\n kind: 'proposed-change',\n configPath: options.configPath,\n originalCode,\n newCode: generated,\n diff,\n dryRun: options.dryRun === true,\n }\n}\n\n/**\n * Write the augmented config to disk. Call this after the user\n * has seen and approved the diff from `augmentNuxtConfig()`. The\n * split between \"compute\" and \"write\" is deliberate — see the\n * module docstring.\n */\nexport async function writeAugmentedConfig(\n configPath: string,\n newCode: string,\n): Promise<void> {\n await fs.writeFile(configPath, newCode, 'utf8')\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────\n\n/**\n * Given the proxified default export, return the config object\n * itself. Handles both `defineNuxtConfig({...})` (proxified function\n * call whose `$args[0]` is the config) and `{...}` (plain object\n * proxy). Returns null for shapes we don't recognize.\n *\n * Important: magicast's `$args` is a PROXY, not a real array, so\n * `Array.isArray($args)` returns false. We access `$args[0]`\n * directly and check whether the result is a usable object.\n */\nfunction resolveConfigObject(\n exported: unknown,\n): Record<string, unknown> | null {\n if (!exported || typeof exported !== 'object') return null\n const proxy = exported as Record<string, unknown> & {\n $type?: string\n $args?: Record<number, unknown>\n }\n\n // defineNuxtConfig(...) call — magicast exposes the args through\n // a proxied $args accessor. The config literal is at $args[0].\n if (proxy.$type === 'function-call' && proxy.$args) {\n const firstArg = proxy.$args[0]\n if (firstArg && typeof firstArg === 'object') {\n return firstArg as Record<string, unknown>\n }\n return null\n }\n\n // Plain object literal export — the proxy IS the config.\n if (proxy.$type === 'object' || proxy.$type === undefined) {\n return proxy\n }\n\n return null\n}\n\n/**\n * Check whether a magicast-proxied value is an array. The proxy\n * reports `$type === 'array'` for array literals; plain objects\n * and strings come back with different tags.\n */\nfunction isProxyArray(value: unknown): boolean {\n if (!value || typeof value !== 'object') return false\n const proxy = value as { $type?: string }\n return proxy.$type === 'array'\n}\n","/**\n * English (`en`) wizard messages — the default locale.\n *\n * Editing note: if you change a key's value here, update the\n * matching key in every other locale bundle (`th.ts`, future\n * additions). The key-parity test in `__tests__/i18n.test.ts`\n * is the safety net: it fails if any locale is missing a key\n * or has an extra one, so a forgotten update surfaces in CI\n * rather than in a user report.\n */\n\nimport type { WizardMessages } from './types.js'\n\nexport const en: WizardMessages = {\n wizardIntro:\n 'A wizard for noy-db — None Of Your DataBase.\\n' +\n 'Generates a fresh Nuxt 4 + Pinia + encrypted-store starter.',\n\n promptProjectName: 'Project name',\n promptProjectNamePlaceholder: 'my-noy-db-app',\n promptAdapter: 'Storage adapter',\n adapterBrowserLabel:\n 'browser — localStorage / IndexedDB (recommended for web apps)',\n adapterFileLabel:\n 'file — JSON files on disk (Electron / Tauri / USB workflows)',\n adapterMemoryLabel:\n 'memory — no persistence (ideal for tests and demos)',\n promptSampleData: 'Include sample invoice records?',\n\n freshNextStepsTitle: 'Next steps',\n freshOutroDone: '✔ Done — happy encrypting!',\n\n augmentModeTitle: 'Augment mode',\n augmentDetectedPrefix: 'Detected existing Nuxt 4 project:',\n augmentDescription:\n 'The wizard will add @noy-db/nuxt to your modules array\\n' +\n 'and a noydb: config key. You can review the diff before\\n' +\n 'anything is written to disk.',\n\n augmentProposedChangesTitle: 'Proposed changes',\n augmentApplyConfirm: 'Apply these changes?',\n\n augmentAlreadyConfiguredTitle: 'Already configured',\n augmentNothingToDo: 'Nothing to do:',\n augmentAlreadyOutro: '✔ Your Nuxt config is already wired up.',\n augmentAborted: 'Aborted — your config is unchanged.',\n augmentDryRunOutro: '✔ Dry run — no files were modified.',\n augmentNextStepTitle: 'Next step',\n augmentInstallIntro:\n 'Install the @noy-db packages your config now depends on:',\n augmentInstallPmHint: '(or use npm/yarn/bun as appropriate)',\n augmentDoneOutro: '✔ Config updated — happy encrypting!',\n augmentUnsupportedPrefix: 'Cannot safely patch this config:',\n\n cancelled: 'Cancelled.',\n}\n","/**\n * Thai (`th`) wizard messages.\n *\n * Editing note: this bundle MUST stay in key-parity with `en.ts`.\n * The key-parity test in `__tests__/i18n.test.ts` fails if any key\n * is missing or extra here, so a forgotten translation surfaces in\n * CI rather than as a runtime crash for a Thai-speaking user.\n *\n * Translation conventions:\n * - Technical identifiers (`modules`, `noydb:`, `nuxt.config.ts`,\n * `Nuxt 4`, `Pinia`, adapter names like `browser`/`file`/`memory`)\n * stay in English. This matches how Thai developers actually\n * read and write code — translating them would just add a\n * mental round-trip.\n * - Product name \"noy-db\" and the \"None Of Your DataBase\" tagline\n * also stay in English; they're brand strings, not prose.\n * - Validation/error strings are NOT in this file — they stay in\n * English so bug reports look the same in any locale (see the\n * docstring on `WizardMessages` in `types.ts` for the rationale).\n */\n\nimport type { WizardMessages } from './types.js'\n\nexport const th: WizardMessages = {\n wizardIntro:\n 'ตัวช่วยสร้างสำหรับ noy-db — None Of Your DataBase\\n' +\n 'สร้างโปรเจกต์เริ่มต้น Nuxt 4 + Pinia พร้อมที่เก็บข้อมูลแบบเข้ารหัส',\n\n promptProjectName: 'ชื่อโปรเจกต์',\n promptProjectNamePlaceholder: 'my-noy-db-app',\n promptAdapter: 'อะแดปเตอร์จัดเก็บข้อมูล',\n adapterBrowserLabel:\n 'browser — localStorage / IndexedDB (แนะนำสำหรับเว็บแอป)',\n adapterFileLabel:\n 'file — ไฟล์ JSON บนดิสก์ (Electron / Tauri / USB)',\n adapterMemoryLabel:\n 'memory — ไม่บันทึกข้อมูล (เหมาะสำหรับการทดสอบและตัวอย่าง)',\n promptSampleData: 'เพิ่มข้อมูลตัวอย่างใบแจ้งหนี้หรือไม่?',\n\n freshNextStepsTitle: 'ขั้นตอนถัดไป',\n freshOutroDone: '✔ เสร็จเรียบร้อย — ขอให้สนุกกับการเข้ารหัส!',\n\n augmentModeTitle: 'โหมดเสริมโปรเจกต์เดิม',\n augmentDetectedPrefix: 'พบโปรเจกต์ Nuxt 4 ที่มีอยู่แล้ว:',\n augmentDescription:\n 'ตัวช่วยจะเพิ่ม @noy-db/nuxt เข้าใน modules\\n' +\n 'และเพิ่มคีย์ noydb: ในไฟล์ config คุณสามารถดู diff\\n' +\n 'ก่อนที่จะเขียนไฟล์ลงดิสก์ได้',\n\n augmentProposedChangesTitle: 'รายการเปลี่ยนแปลงที่จะทำ',\n augmentApplyConfirm: 'ยืนยันการเปลี่ยนแปลงเหล่านี้?',\n\n augmentAlreadyConfiguredTitle: 'ตั้งค่าไว้แล้ว',\n augmentNothingToDo: 'ไม่มีอะไรต้องทำ:',\n augmentAlreadyOutro: '✔ ไฟล์ Nuxt config ของคุณตั้งค่าครบแล้ว',\n augmentAborted: 'ยกเลิก — ไฟล์ config ของคุณไม่ถูกแก้ไข',\n augmentDryRunOutro: '✔ Dry run — ไม่มีไฟล์ใดถูกแก้ไข',\n augmentNextStepTitle: 'ขั้นตอนถัดไป',\n augmentInstallIntro:\n 'ติดตั้งแพ็กเกจ @noy-db ที่ config ของคุณต้องใช้:',\n augmentInstallPmHint: '(หรือใช้ npm/yarn/bun ตามความเหมาะสม)',\n augmentDoneOutro: '✔ อัปเดต config เรียบร้อย — ขอให้สนุกกับการเข้ารหัส!',\n augmentUnsupportedPrefix: 'ไม่สามารถแก้ไข config นี้ได้อย่างปลอดภัย:',\n\n cancelled: 'ยกเลิกแล้ว',\n}\n","/**\n * i18n entrypoint for the `@noy-db/create` wizard.\n *\n * Three responsibilities:\n *\n * 1. Re-export `Locale` and `WizardMessages` so callers don't\n * need to know about the bundle layout.\n * 2. `detectLocale(env)` — pure function that maps Unix-style\n * `LC_ALL` / `LANG` / `LANGUAGE` env vars to a supported\n * `Locale`. Returns `'en'` for anything we don't recognise.\n * 3. `loadMessages(locale)` — synchronous lookup that returns\n * the message bundle for a locale. Synchronous (not dynamic\n * `import()`) on purpose: bundles are tiny (< 2 KB each), the\n * wizard reads them on every prompt, and async would force\n * every caller to be async. tsup tree-shakes unused locales\n * out of the bin only if we use top-level `import`s.\n *\n * ## Why env-var detection instead of `Intl.DateTimeFormat().resolvedOptions().locale`\n *\n * The Intl approach reads the JS engine's *display* locale, which\n * on most CI runners and Docker images is `en-US` regardless of\n * the user's actual setup. The Unix env vars (`LC_ALL`, `LANG`)\n * are how shells, terminals, and CLI tools have negotiated locale\n * for 30+ years — that's what a Thai-speaking dev's terminal will\n * actually have set. Following that convention also means power\n * users can override per-invocation with `LANG=th_TH.UTF-8 npm\n * create @noy-db`, no flag required.\n */\n\nimport { en } from './en.js'\nimport { th } from './th.js'\nimport type { Locale, WizardMessages } from './types.js'\n\nexport type { Locale, WizardMessages } from './types.js'\n\nconst BUNDLES: Record<Locale, WizardMessages> = { en, th }\n\n/** Every locale we ship a bundle for. Used by tests and `--lang` validation. */\nexport const SUPPORTED_LOCALES: readonly Locale[] = ['en', 'th'] as const\n\n/**\n * Resolve a locale code to its message bundle. Falls back to `en`\n * if the requested locale isn't shipped — defensive, since\n * `Locale` is a union type and TS already prevents this at compile\n * time, but `--lang` parsing comes from user input at runtime.\n */\nexport function loadMessages(locale: Locale): WizardMessages {\n return BUNDLES[locale] ?? BUNDLES.en\n}\n\n/**\n * Auto-detect a locale from POSIX env vars. Returns `'en'` when\n * nothing is set or when the value doesn't match a supported\n * locale — never throws.\n *\n * Inspection order matches the POSIX spec:\n * 1. `LC_ALL` (overrides everything)\n * 2. `LC_MESSAGES` (the category we actually care about)\n * 3. `LANG` (system default)\n * 4. `LANGUAGE` (GNU extension, comma-separated preference list)\n *\n * The first non-empty value wins. We then strip the encoding\n * suffix (`th_TH.UTF-8` → `th_TH`) and the region (`th_TH` → `th`)\n * before matching against `SUPPORTED_LOCALES`.\n */\nexport function detectLocale(env: NodeJS.ProcessEnv = process.env): Locale {\n const candidates = [\n env.LC_ALL,\n env.LC_MESSAGES,\n env.LANG,\n // LANGUAGE is a comma-separated preference list — take the first\n // entry. We deliberately do NOT walk the whole list; the wizard\n // ships exactly two locales, so a \"best fit\" walk would be\n // overkill and would obscure unexpected behaviour.\n env.LANGUAGE?.split(':')[0]?.split(',')[0],\n ]\n\n for (const raw of candidates) {\n if (!raw) continue\n const normalised = raw\n .split('.')[0]! // strip encoding: th_TH.UTF-8 → th_TH\n .split('@')[0]! // strip modifier: en_US@euro → en_US\n .toLowerCase()\n .split('_')[0]! // strip region: th_th → th\n\n if ((SUPPORTED_LOCALES as readonly string[]).includes(normalised)) {\n return normalised as Locale\n }\n }\n\n return 'en'\n}\n\n/**\n * Parse a `--lang` CLI argument into a `Locale`. Throws a clear\n * error for unsupported values — the caller (parse-args) catches\n * and reformats into a usage message.\n */\nexport function parseLocaleFlag(value: string): Locale {\n const normalised = value.toLowerCase().trim()\n if ((SUPPORTED_LOCALES as readonly string[]).includes(normalised)) {\n return normalised as Locale\n }\n throw new Error(\n `Unsupported --lang value: \"${value}\". Supported: ${SUPPORTED_LOCALES.join(', ')}`,\n )\n}\n","/**\n * Pure argv parser for `create-noy-db`. Lives in its own module so tests\n * (and other bin wrappers) can import it without triggering the bin's\n * top-level side effects.\n */\n\nimport type { WizardAdapter, WizardOptions } from '../wizard/types.js'\nimport { parseLocaleFlag } from '../wizard/i18n/index.js'\n\nexport interface ParsedArgs {\n options: WizardOptions\n help: boolean\n}\n\n/**\n * Tiny hand-rolled argv parser. Intentionally lightweight — the surface\n * area is three flags and one positional, and we want to keep the\n * install graph for `npm create` as small as possible.\n */\nexport function parseArgs(argv: string[]): ParsedArgs {\n const options: WizardOptions = {}\n let help = false\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '-h' || arg === '--help') {\n help = true\n continue\n }\n if (arg === '-y' || arg === '--yes') {\n options.yes = true\n continue\n }\n if (arg === '--no-sample-data') {\n options.sampleData = false\n continue\n }\n if (arg === '--dry-run') {\n options.dryRun = true\n continue\n }\n if (arg === '--force-fresh') {\n options.forceFresh = true\n continue\n }\n if (arg === '--lang') {\n const next = argv[++i]\n if (!next) {\n throw new Error('--lang requires a value (en or th)')\n }\n // parseLocaleFlag throws on unsupported values; let the\n // bin's top-level catch surface the message to the user.\n options.locale = parseLocaleFlag(next)\n continue\n }\n if (arg === '--adapter') {\n const next = argv[++i]\n if (next !== 'browser' && next !== 'file' && next !== 'memory') {\n throw new Error(\n `--adapter must be one of: browser, file, memory (got: ${next ?? '(missing)'})`,\n )\n }\n options.adapter = next as WizardAdapter\n continue\n }\n if (arg && arg.startsWith('-')) {\n throw new Error(`Unknown option: ${arg}`)\n }\n if (arg && !options.projectName) {\n options.projectName = arg\n continue\n }\n if (arg) {\n throw new Error(`Unexpected positional argument: ${arg}`)\n }\n }\n return { options, help }\n}\n"],"mappings":";;;AAyBA,YAAYA,QAAO;AACnB,OAAOC,SAAQ;;;ACPf,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AACjB,YAAY,OAAO;AACnB,OAAO,QAAQ;;;ACJf,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AACjB,SAAS,qBAAqB;AA8B9B,eAAsB,eACpB,KACA,MACA,QACmB;AACnB,QAAM,UAAoB,CAAC;AAC3B,QAAM,KAAK,KAAK,MAAM,IAAI,QAAQ,OAAO;AACzC,UAAQ,KAAK;AACb,SAAO;AACT;AAEA,eAAe,KACb,SACA,UACA,KACA,QACA,SACe;AACf,QAAM,SAAS,KAAK,KAAK,SAAS,GAAG;AACrC,QAAM,UAAU,MAAM,GAAG,QAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;AAEhE,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,KAAK,KAAK,QAAQ,MAAM,IAAI;AAI7C,UAAM,WAAW,MAAM,KAAK,WAAW,GAAG,IACtC,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,KACvB,MAAM;AACV,UAAM,UAAU,MAAM,KAAK,KAAK,KAAK,QAAQ,IAAI;AACjD,UAAM,YAAY,KAAK,KAAK,UAAU,OAAO;AAE7C,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,YAAM,KAAK,SAAS,UAAU,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,QAAQ,OAAO;AACzE;AAAA,IACF;AAEA,UAAM,MAAM,MAAM,GAAG,SAAS,UAAU,MAAM;AAC9C,UAAM,WAAW,YAAY,KAAK,MAAM;AACxC,UAAM,GAAG,MAAM,KAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,UAAM,GAAG,UAAU,WAAW,UAAU,MAAM;AAC9C,YAAQ,KAAK,OAAO;AAAA,EACtB;AACF;AAQO,SAAS,YAAY,OAAe,QAA8B;AACvE,QAAM,MAAM;AACZ,SAAO,MAAM,QAAQ,kBAAkB,CAAC,OAAO,QAAgB;AAC7D,UAAM,QAAQ,IAAI,GAAG;AACrB,WAAO,UAAU,SAAY,QAAQ;AAAA,EACvC,CAAC;AACH;AAkBO,SAAS,YAAY,MAAsB;AAGhD,QAAM,OAAO,cAAc,YAAY,GAAG;AAC1C,QAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,IAAI,GAAG,MAAM,IAAI;AAC/D,SAAO,KAAK,KAAK,aAAa,aAAa,IAAI;AACjD;;;ACzGA,SAAS,YAAYC,WAAU;AAC/B,OAAOC,WAAU;AAsBjB,eAAsB,kBAAkB,KAAqC;AAC3E,QAAM,UAAoB,CAAC;AAI3B,QAAM,mBAAmB,CAAC,kBAAkB,kBAAkB,iBAAiB;AAC/E,MAAI,aAA4B;AAChC,aAAW,QAAQ,kBAAkB;AACnC,UAAM,YAAYA,MAAK,KAAK,KAAK,IAAI;AACrC,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,mBAAa;AACb,cAAQ,KAAK,SAAS,IAAI,EAAE;AAC5B;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,YAAY;AACf,YAAQ,KAAK,mCAAmC;AAChD,WAAO;AAAA,MACL,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAUA,MAAK,KAAK,KAAK,cAAc;AAC7C,MAAI,CAAE,MAAM,WAAW,OAAO,GAAI;AAChC,YAAQ,KAAK,oEAA+D;AAC5E,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAMA,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,MAAMD,IAAG,SAAS,SAAS,MAAM,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,YAAQ,KAAK,mCAAoC,IAAc,OAAO,EAAE;AACxE,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,CAAC,gBAAgB,mBAAmB,kBAAkB;AAC1E,MAAI;AACJ,aAAW,WAAW,aAAa;AACjC,UAAM,OAAO,IAAI,OAAO;AACxB,QAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,MAAM;AACtD,oBAAe,KAAgC,MAAM;AACrD,cAAQ,KAAK,cAAc,WAAW,OAAO,OAAO,EAAE;AACtD;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK,4EAA4E;AACzF,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,EACF;AACF;AAGA,eAAe,WAAW,QAAkC;AAC1D,MAAI;AACF,UAAMA,IAAG,OAAO,MAAM;AACtB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxFA,SAAS,YAAYE,WAAU;AAC/B,SAAS,UAAU,cAAc,gBAAgB;AACjD,SAAS,mBAAmB;AA+C5B,eAAsB,kBACpB,SACwB;AACxB,QAAM,eAAe,MAAMA,IAAG,SAAS,QAAQ,YAAY,MAAM;AACjE,QAAM,MAAM,MAAM,SAAS,QAAQ,UAAU;AAK7C,QAAM,WAAW,IAAI,QAAQ;AAC7B,MAAI,aAAa,UAAa,aAAa,MAAM;AAC/C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,QAAQ;AAAA,MACpB,QAAQ,GAAG,QAAQ,UAAU;AAAA,IAC/B;AAAA,EACF;AAYA,QAAM,SAAS,oBAAoB,QAAQ;AAC3C,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,QAAQ;AAAA,MACpB,QAAQ,uCAAuC,QAAQ,UAAU;AAAA,IAEnE;AAAA,EACF;AAEA,QAAM,cAAwB,CAAC;AAM/B,QAAM,aAAsB,OAAO;AACnC,MAAI,oBAAoB;AACxB,MAAI,eAAe,QAAW;AAC5B,wBAAoB;AACpB,WAAO,UAAU,CAAC;AAAA,EACpB,WAAW,OAAO,eAAe,YAAY,CAAC,aAAa,UAAU,GAAG;AACtE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,QAAQ;AAAA,MACpB,QAAQ,kBAAkB,QAAQ,UAAU;AAAA,IAE9C;AAAA,EACF;AAKA,QAAM,UAAU,OAAO;AACvB,QAAM,mBAAmB,MAAM,KAAK,OAAO,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,MAAM,cAAc;AACrF,MAAI,kBAAkB;AACpB,gBAAY,KAAK,mCAAmC;AAAA,EACtD,OAAO;AAGL,YAAQ,KAAK,cAAc;AAAA,EAC7B;AAMA,QAAM,WAAoB,OAAO;AACjC,MAAI,aAAa,QAAW;AAC1B,gBAAY,KAAK,yBAAyB;AAAA,EAC5C,OAAO;AAIL,WAAO,QAAQ,SAAS;AAAA,MACtB,eAAe,QAAQ,OAAO;AAAA,IAChC;AAAA,EACF;AAMA,MAAI,YAAY,WAAW,KAAK,CAAC,mBAAmB;AAClD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY,QAAQ;AAAA,MACpB,QAAQ,YAAY,KAAK,IAAI;AAAA,IAC/B;AAAA,EACF;AAQA,QAAM,YAAY,aAAa,GAAG,EAAE;AACpC,QAAM,OAAO;AAAA,IACX,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,SAAS,EAAE;AAAA,EACf;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,QAAQ,QAAQ,WAAW;AAAA,EAC7B;AACF;AAQA,eAAsB,qBACpB,YACA,SACe;AACf,QAAMA,IAAG,UAAU,YAAY,SAAS,MAAM;AAChD;AAcA,SAAS,oBACP,UACgC;AAChC,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,QAAQ;AAOd,MAAI,MAAM,UAAU,mBAAmB,MAAM,OAAO;AAClD,UAAM,WAAW,MAAM,MAAM,CAAC;AAC9B,QAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,UAAU,YAAY,MAAM,UAAU,QAAW;AACzD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOA,SAAS,aAAa,OAAyB;AAC7C,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,QAAQ;AACd,SAAO,MAAM,UAAU;AACzB;;;AClRO,IAAM,KAAqB;AAAA,EAChC,aACE;AAAA,EAGF,mBAAmB;AAAA,EACnB,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,qBACE;AAAA,EACF,kBACE;AAAA,EACF,oBACE;AAAA,EACF,kBAAkB;AAAA,EAElB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAEhB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,oBACE;AAAA,EAIF,6BAA6B;AAAA,EAC7B,qBAAqB;AAAA,EAErB,+BAA+B;AAAA,EAC/B,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,qBACE;AAAA,EACF,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,0BAA0B;AAAA,EAE1B,WAAW;AACb;;;AChCO,IAAM,KAAqB;AAAA,EAChC,aACE;AAAA,EAGF,mBAAmB;AAAA,EACnB,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,qBACE;AAAA,EACF,kBACE;AAAA,EACF,oBACE;AAAA,EACF,kBAAkB;AAAA,EAElB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAEhB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,oBACE;AAAA,EAIF,6BAA6B;AAAA,EAC7B,qBAAqB;AAAA,EAErB,+BAA+B;AAAA,EAC/B,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,qBACE;AAAA,EACF,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,0BAA0B;AAAA,EAE1B,WAAW;AACb;;;AC9BA,IAAM,UAA0C,EAAE,IAAI,GAAG;AAGlD,IAAM,oBAAuC,CAAC,MAAM,IAAI;AAQxD,SAAS,aAAa,QAAgC;AAC3D,SAAO,QAAQ,MAAM,KAAK,QAAQ;AACpC;AAiBO,SAAS,aAAa,MAAyB,QAAQ,KAAa;AACzE,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,IAKJ,IAAI,UAAU,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC;AAAA,EAC3C;AAEA,aAAW,OAAO,YAAY;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,aAAa,IAChB,MAAM,GAAG,EAAE,CAAC,EACZ,MAAM,GAAG,EAAE,CAAC,EACZ,YAAY,EACZ,MAAM,GAAG,EAAE,CAAC;AAEf,QAAK,kBAAwC,SAAS,UAAU,GAAG;AACjE,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,gBAAgB,OAAuB;AACrD,QAAM,aAAa,MAAM,YAAY,EAAE,KAAK;AAC5C,MAAK,kBAAwC,SAAS,UAAU,GAAG;AACjE,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AAAA,IACR,8BAA8B,KAAK,iBAAiB,kBAAkB,KAAK,IAAI,CAAC;AAAA,EAClF;AACF;;;ANjEA,IAAM,eAAe;AAAA,EACnB;AAAA,IACE,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AACF;AAEA,SAAS,cAAc,KAAoD;AACzE,SAAO;AAAA,IACL,SAAS,IAAI;AAAA,IACb,MAAM,IAAI;AAAA,IACV,QAAQ,IAAI;AAAA,EACd;AACF;AAYO,SAAS,oBAAoB,MAA6B;AAC/D,MAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,GAAI,QAAO;AACxC,MAAI,KAAK,SAAS,IAAK,QAAO;AAC9B,MAAI,CAAC,yBAAyB,KAAK,IAAI,GAAG;AACxC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AA2BA,eAAsB,UAAU,UAAyB,CAAC,GAA0B;AAClF,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,MAAM,QAAQ,OAAO;AAM3B,QAAM,MAAM,aAAa,QAAQ,UAAU,aAAa,CAAC;AAQzD,QAAM,YAAY,QAAQ,aACtB,OACA,MAAM,kBAAkB,GAAG;AAE/B,MAAI,WAAW,YAAY,UAAU,YAAY;AAC/C,WAAO,eAAe,SAAS,KAAK,UAAU,YAAY,GAAG;AAAA,EAC/D;AAEA,SAAO,aAAa,SAAS,KAAK,KAAK,GAAG;AAC5C;AAQA,eAAe,aACb,SACA,KACA,KACA,KAC4B;AAI5B,QAAM,cAAc,MAChB,QAAQ,eAAe,kBACvB,MAAM,kBAAkB,QAAQ,aAAa,GAAG;AAEpD,QAAM,UAAyB,MAC3B,QAAQ,WAAW,YACnB,MAAM,cAAc,QAAQ,SAAS,GAAG;AAE5C,QAAM,aAAsB,MACxB,QAAQ,cAAc,OACtB,MAAM,iBAAiB,QAAQ,YAAY,GAAG;AAMlD,QAAM,cAAcC,MAAK,QAAQ,KAAK,WAAW;AACjD,QAAM,qBAAqB,WAAW;AAGtC,QAAM,SAAuB;AAAA,IAC3B,cAAc;AAAA,IACd,SAAS;AAAA,IACT,UAAU;AAAA,IACV,eAAe,aACX,KAAK,UAAU,cAAc,MAAM,CAAC,EAAE,QAAQ,OAAO,MAAM,IAC3D;AAAA,EACN;AAEA,QAAMC,IAAG,MAAM,aAAa,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAM,QAAQ,MAAM;AAAA,IAClB,YAAY,cAAc;AAAA,IAC1B;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,KAAK;AAGR,IAAE;AAAA,MACA;AAAA,QACE,GAAG,GAAG,KAAK,IAAI,CAAC,IAAI,WAAW;AAAA,QAC/B,GAAG,GAAG,KAAK,cAAc,CAAC,QAAQ,GAAG,IAAI,mBAAmB,CAAC;AAAA,QAC7D,GAAG,GAAG,KAAK,UAAU,CAAC;AAAA,MACxB,EAAE,KAAK,IAAI;AAAA,MACX,IAAI;AAAA,IACN;AACA,IAAE,QAAM,GAAG,MAAM,IAAI,cAAc,CAAC;AAAA,EACtC;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAkBA,eAAe,eACb,SACA,KACA,YACA,KAC8B;AAC9B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,SAAS,QAAQ,UAAU;AAEjC,MAAI,CAAC,KAAK;AACR,IAAE;AAAA,MACA;AAAA,QACE,GAAG,GAAG,IAAI,IAAI,qBAAqB,CAAC;AAAA,QACpC,KAAK,GAAG,KAAK,UAAU,CAAC;AAAA,QACxB;AAAA,QACA,IAAI;AAAA,MACN,EAAE,KAAK,IAAI;AAAA,MACX,IAAI;AAAA,IACN;AAAA,EACF;AAKA,QAAM,UAAyB,MAC3B,QAAQ,WAAW,YACnB,MAAM,cAAc,QAAQ,SAAS,GAAG;AAE5C,QAAM,SAAS,MAAM,kBAAkB;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,OAAO,SAAS,sBAAsB;AACxC,QAAI,CAAC,KAAK;AACR,MAAE;AAAA,QACA,GAAG,GAAG,OAAO,IAAI,kBAAkB,CAAC,IAAI,OAAO,MAAM;AAAA,QACrD,IAAI;AAAA,MACN;AACA,MAAE,QAAM,GAAG,MAAM,IAAI,mBAAmB,CAAC;AAAA,IAC3C;AACA,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,qBAAqB;AACvC,QAAI,CAAC,KAAK;AACR,MAAE,SAAO,GAAG,GAAG,IAAI,IAAI,wBAAwB,CAAC,IAAI,OAAO,MAAM,EAAE;AAAA,IACrE;AACA,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IACV;AAAA,EACF;AAIA,MAAI,CAAC,OAAO,QAAQ;AAClB,IAAE,OAAK,WAAW,OAAO,IAAI,GAAG,IAAI,2BAA2B;AAAA,EACjE;AAEA,MAAI,QAAQ;AACV,QAAI,CAAC,IAAK,CAAE,QAAM,GAAG,MAAM,IAAI,kBAAkB,CAAC;AAClD,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,MAAM,OAAO;AAAA,IACf;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,MAAI,CAAC,KAAK;AACR,UAAM,YAAY,MAAQ,UAAQ;AAAA,MAChC,SAAS,IAAI;AAAA,MACb,cAAc;AAAA,IAChB,CAAC;AACD,QAAM,WAAS,SAAS,KAAK,cAAc,MAAM;AAC/C,MAAE,SAAO,IAAI,cAAc;AAC3B,aAAO;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM,OAAO;AAAA,MACf;AAAA,IACF;AACA,kBAAc;AAAA,EAChB;AAEA,MAAI,aAAa;AACf,UAAM,qBAAqB,YAAY,OAAO,OAAO;AACrD,QAAI,CAAC,KAAK;AACR,MAAE;AAAA,QACA;AAAA,UACE,GAAG,IAAI,IAAI,mBAAmB;AAAA,UAC9B;AAAA,UACA,GAAG,GAAG,KAAK,UAAU,CAAC;AAAA,UACtB,GAAG,IAAI,IAAI,oBAAoB;AAAA,QACjC,EAAE,KAAK,IAAI;AAAA,QACX,IAAI;AAAA,MACN;AACA,MAAE,QAAM,GAAG,MAAM,IAAI,gBAAgB,CAAC;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,MAAM,OAAO;AAAA,EACf;AACF;AASA,SAAS,WAAW,MAAsB;AACxC,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,OAAiB,CAAC;AACxB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,QAAQ,EAAG;AAC/B,QAAI,KAAK,WAAW,GAAG,EAAG;AAC1B,QAAI,KAAK,WAAW,KAAK,KAAK,KAAK,WAAW,KAAK,EAAG;AACtD,QAAI,KAAK,WAAW,GAAG,EAAG,MAAK,KAAK,GAAG,MAAM,IAAI,CAAC;AAAA,aACzC,KAAK,WAAW,GAAG,EAAG,MAAK,KAAK,GAAG,IAAI,IAAI,CAAC;AAAA,aAC5C,KAAK,WAAW,IAAI,EAAG,MAAK,KAAK,GAAG,IAAI,IAAI,CAAC;AAAA,QACjD,MAAK,KAAK,IAAI;AAAA,EACrB;AACA,SAAO,KAAK,KAAK,IAAI,EAAE,KAAK;AAC9B;AAIA,eAAe,kBAAkB,SAA6B,KAAsC;AAClG,MAAI,SAAS;AACX,UAAM,MAAM,oBAAoB,OAAO;AACvC,QAAI,IAAK,OAAM,IAAI,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,SAAS,MAAQ,OAAK;AAAA,IAC1B,SAAS,IAAI;AAAA,IACb,aAAa,IAAI;AAAA,IACjB,cAAc;AAAA,IACd,UAAU,CAAC,MAAM,oBAAoB,KAAK,EAAE,KAAK;AAAA,EACnD,CAAC;AACD,MAAM,WAAS,MAAM,GAAG;AACtB,IAAE,SAAO,IAAI,SAAS;AACtB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,cAAc,SAAoC,KAA6C;AAC5G,MAAI,QAAS,QAAO;AACpB,QAAM,SAAS,cAAc,GAAG;AAChC,QAAM,SAAS,MAAQ,SAAsB;AAAA,IAC3C,SAAS,IAAI;AAAA,IACb,SAAU,CAAC,WAAW,QAAQ,QAAQ,EAAY,IAAI,CAAC,WAAW;AAAA,MAChE;AAAA,MACA,OAAO,OAAO,KAAK;AAAA,IACrB,EAAE;AAAA,IACF,cAAc;AAAA,EAChB,CAAC;AACD,MAAM,WAAS,MAAM,GAAG;AACtB,IAAE,SAAO,IAAI,SAAS;AACtB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,iBAAiB,SAA8B,KAAuC;AACnG,MAAI,OAAO,YAAY,UAAW,QAAO;AACzC,QAAM,SAAS,MAAQ,UAAQ;AAAA,IAC7B,SAAS,IAAI;AAAA,IACb,cAAc;AAAA,EAChB,CAAC;AACD,MAAM,WAAS,MAAM,GAAG;AACtB,IAAE,SAAO,IAAI,SAAS;AACtB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,qBAAqB,QAA+B;AACjE,MAAI;AACF,UAAM,UAAU,MAAMA,IAAG,QAAQ,MAAM;AACvC,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,qBAAqB,MAAM;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,SAAS,KAAc;AAErB,QAAK,IAA8B,SAAS,SAAU;AACtD,UAAM;AAAA,EACR;AACF;;;AOlbO,SAAS,UAAU,MAA4B;AACpD,QAAM,UAAyB,CAAC;AAChC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,QAAQ,QAAQ,UAAU;AACpC,aAAO;AACP;AAAA,IACF;AACA,QAAI,QAAQ,QAAQ,QAAQ,SAAS;AACnC,cAAQ,MAAM;AACd;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB;AAC9B,cAAQ,aAAa;AACrB;AAAA,IACF;AACA,QAAI,QAAQ,aAAa;AACvB,cAAQ,SAAS;AACjB;AAAA,IACF;AACA,QAAI,QAAQ,iBAAiB;AAC3B,cAAQ,aAAa;AACrB;AAAA,IACF;AACA,QAAI,QAAQ,UAAU;AACpB,YAAM,OAAO,KAAK,EAAE,CAAC;AACrB,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,MAAM,oCAAoC;AAAA,MACtD;AAGA,cAAQ,SAAS,gBAAgB,IAAI;AACrC;AAAA,IACF;AACA,QAAI,QAAQ,aAAa;AACvB,YAAM,OAAO,KAAK,EAAE,CAAC;AACrB,UAAI,SAAS,aAAa,SAAS,UAAU,SAAS,UAAU;AAC9D,cAAM,IAAI;AAAA,UACR,yDAAyD,QAAQ,WAAW;AAAA,QAC9E;AAAA,MACF;AACA,cAAQ,UAAU;AAClB;AAAA,IACF;AACA,QAAI,OAAO,IAAI,WAAW,GAAG,GAAG;AAC9B,YAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAAA,IAC1C;AACA,QAAI,OAAO,CAAC,QAAQ,aAAa;AAC/B,cAAQ,cAAc;AACtB;AAAA,IACF;AACA,QAAI,KAAK;AACP,YAAM,IAAI,MAAM,mCAAmC,GAAG,EAAE;AAAA,IAC1D;AAAA,EACF;AACA,SAAO,EAAE,SAAS,KAAK;AACzB;;;AR7CA,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCb,eAAe,OAAsB;AACnC,MAAI;AACJ,MAAI;AACF,aAAS,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,EAC1C,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,GAAGC,IAAG,IAAI,QAAQ,CAAC,IAAK,IAAc,OAAO;AAAA;AAAA,EAAO,IAAI,EAAE;AAC/E,YAAQ,KAAK,CAAC;AACd;AAAA,EACF;AAEA,MAAI,OAAO,MAAM;AACf,YAAQ,OAAO,MAAM,IAAI;AACzB;AAAA,EACF;AAIA,MAAI,CAAC,OAAO,QAAQ,KAAK;AAIvB,UAAM,MAAM,aAAa,OAAO,QAAQ,UAAU,aAAa,CAAC;AAChE,IAAE,SAAMA,IAAG,OAAOA,IAAG,MAAM,kBAAkB,CAAC,CAAC;AAC/C,IAAE,QAAK,IAAI,WAAW;AAAA,EACxB;AAEA,MAAI;AACF,UAAM,UAAU,OAAO,OAAO;AAAA,EAChC,SAAS,KAAK;AACZ,IAAE,UAAQ,IAAc,OAAO;AAC/B,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK,KAAK;","names":["p","pc","fs","path","fs","path","fs","path","fs","pc"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node