{"version":3,"file":"config-DtCmKjkD.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, access } from 'node:fs/promises'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\n\nimport type { KickCliPlugin } from './plugin/types'\n\n/** A custom command that developers can register via kick.config.ts */\nexport interface KickCommandDefinition {\n  /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */\n  name: string\n  /** Description shown in --help */\n  description: string\n  /**\n   * Shell command(s) to run. Can be a single string or an array of\n   * sequential steps. Use {args} as a placeholder for CLI arguments.\n   *\n   * @example\n   * 'npx drizzle-kit migrate'\n   * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']\n   */\n  steps: string | string[]\n  /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */\n  aliases?: string[]\n}\n\n/** Project pattern — controls what generators produce and which deps are installed */\nexport type ProjectPattern = 'rest' | 'minimal'\n\n/** Package manager used for `kick add` and other dep-installing commands */\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/**\n * Built-in repository type with first-class code generation support.\n *\n * Only `inmemory` remains built-in (zero-dep, framework-owned). The\n * `prisma` and `drizzle` ORM presets are **deprecated** — they now\n * scaffold a generic custom-repository stub like any other name (see\n * {@link DEPRECATED_REPO_TYPES}). Bring your own DB by passing a name:\n * `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that used to have dedicated ORM generators. They still\n * work — they just produce the same generic stub every other custom\n * name does — but the CLI prints a one-line deprecation note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a deprecation note when a generator is asked for a\n * `prisma`/`drizzle` repo. No-op for every other name. Returns whether\n * a note was printed (handy for tests).\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n  if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n  console.warn(\n    `  Note: the '${repo}' repository preset is deprecated. Generating a generic ` +\n      `custom repository named '${repo}' instead — wire it to your DB by hand. ` +\n      `Pass any name via \\`--repo <name>\\` or \\`modules.repo: { name: '<name>' }\\`.`,\n  )\n  return true\n}\n\n/** Custom repository type — generates a stub with TODO markers */\nexport interface CustomRepoType {\n  name: string\n}\n\n/** Repository type — built-in string or custom object */\nexport type RepoTypeConfig = BuiltinRepoType | CustomRepoType\n\n/**\n * Supported schema validators for `kick typegen` body/query/params\n * type extraction.\n *\n * - `'zod'` — emits `import('zod').infer<typeof schema>` (default)\n * - `'kickjs-schema'` — emits `InferSchemaOutput<typeof schema>` from\n *   `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any\n *   Standard Schema v1 or KickSchema adapter\n * - `false` — disables schema-driven body typing (fields stay `unknown`)\n */\nexport type SchemaValidator = 'zod' | 'kickjs-schema' | false\n\n/**\n * One entry in the typed `assetMap` config record (`assets-plan.md`).\n * Each entry names a source directory whose files become addressable\n * via the `assets.<name>.*` typed accessor at runtime.\n */\nexport interface AssetMapEntry {\n  /**\n   * Source directory, relative to project root. Required. The directory\n   * must exist when `kick build` runs — `loadKickConfig` warns when an\n   * entry points at a missing directory but doesn't fail the load\n   * (the typegen + build steps surface the error in context instead).\n   */\n  src: string\n  /**\n   * Destination directory inside `dist/`. Defaults to `dist/<name>/`\n   * where `<name>` is the assetMap key. Override when the consumer of\n   * the assets expects a non-standard layout (e.g. an existing\n   * downstream tool reads from `dist/templates/...`).\n   */\n  dest?: string\n  /**\n   * Glob pattern for which files to include. Defaults to `**\\/*` (all\n   * files). Files that don't match are NOT copied — `assetMap` is\n   * selective by design (unlike `copyDirs` which copies everything).\n   */\n  glob?: string\n  /**\n   * How file extensions feed into manifest keys. Default `'auto'`.\n   *\n   * - `'strip'` — drop the extension. `pages/index.pug` →\n   *   `'pages/index'`. Two siblings with the same basename collide;\n   *   last-walk-order wins, others are silently dropped. Opt-in\n   *   only — kept for backward compatibility with projects that\n   *   relied on this contract.\n   * - `'with-extension'` — keep every extension on every key. Best\n   *   when the namespace holds extension siblings (`index.pug` +\n   *   `index.html` + `index.css` in `src/pages/`); every file\n   *   reaches the manifest under its full path.\n   * - `'auto'` — strip when basenames are unique, keep extensions\n   *   on collision groups. Singleton files keep their short key;\n   *   `pages/index.{pug,html,css}` becomes\n   *   `pages/index.pug` / `pages/index.html` / `pages/index.css`.\n   *   No data loss; non-colliding namespaces stay on the short\n   *   keys they had before.\n   *\n   * @default 'auto'\n   */\n  keys?: 'auto' | 'strip' | 'with-extension'\n}\n\n/**\n * Database settings consumed by `kick db generate`, `kick db migrate`,\n * and the M4.C composite-type detection gate. Mirrors the runtime\n * `DbConfig` shape from `@forinda/kickjs-db` — duplicated here so\n * adopters who only install `@forinda/kickjs-cli` can set the block\n * without pulling `@forinda/kickjs-db` types into their kick.config.ts\n * resolution. The CLI loads kick.config.ts through `loadKickConfig`,\n * then `resolveDbConfig` re-reads this block from the same module and\n * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).\n *\n * @example\n * ```ts\n * defineConfig({\n *   db: {\n *     schemaPath: 'src/db/schema.ts',\n *     migrationsDir: 'db/migrations',\n *     dialect: 'postgres',\n *     connectionString: process.env.DATABASE_URL,\n *   },\n * })\n * ```\n */\nexport interface KickDbConfigBlock {\n  /**\n   * Path to the schema module (`pgEnum` / `table` declarations).\n   * Defaults to `'src/db/schema.ts'`.\n   */\n  schemaPath?: string\n  /**\n   * Where `kick db generate` writes migration directories. Defaults\n   * to `'db/migrations'`.\n   */\n  migrationsDir?: string\n  /** SQL dialect. Defaults to `'postgres'`. */\n  dialect?: 'postgres' | 'sqlite' | 'mysql'\n  /**\n   * Postgres connection string for the built-in pgAdapter path. Read\n   * from the `DATABASE_URL` env var when omitted. Used by\n   * `kick db migrate*` and the M4.C composite-type gate at\n   * `kick db generate`.\n   */\n  connectionString?: string\n  /**\n   * Escape hatch: a factory returning a fully-constructed\n   * MigrationAdapter (typed loosely here so the CLI types don't pull\n   * `@forinda/kickjs-db` into adopter projects that don't import it).\n   * Takes precedence over `connectionString` when both are set.\n   */\n  adapter?: () => unknown | Promise<unknown>\n}\n\n/** Typegen settings — controls .kickjs/types/* generation */\nexport interface TypegenConfig {\n  /**\n   * Source directory to scan for controllers and decorators.\n   * Defaults to `'src'`.\n   */\n  srcDir?: string\n  /**\n   * Output directory for generated `.d.ts` files.\n   * Defaults to `'.kickjs/types'`.\n   */\n  outDir?: string\n  /**\n   * Schema validator used to derive `body` types from route metadata.\n   *\n   * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema\n   *   referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.\n   * - `false` — disable schema-driven body typing.\n   *\n   * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`\n   * escape hatch for custom adapters.\n   *\n   * @default 'zod'\n   */\n  schemaValidator?: SchemaValidator\n  /**\n   * Path to the project's env schema file (relative to project root).\n   * Must default-export a `defineEnv(...)` schema for typegen to emit\n   * the typed `KickEnv` global registry.\n   *\n   * Set to `false` to disable env typing entirely.\n   *\n   * @default 'src/env.ts'\n   */\n  envFile?: string | false\n  /**\n   * Built-in or user typegen plugin ids to skip during `kick typegen`,\n   * `kick dev`, and `kick typegen --watch`.\n   *\n   * The plugin still loads and merge-time conflict detection still\n   * runs — only the `generate()` invocation is skipped — so adopters\n   * who want to hand-write `KickDbRegister` (manual typeof-schema\n   * augmentation) can disable `'kick/db'` and keep the rest:\n   *\n   * @example\n   * typegen: {\n   *   disable: ['kick/db'],   // hand-written register.ts owns the type\n   * }\n   *\n   * Unrecognised ids are ignored — the list is treated as a wishlist,\n   * not a strict registry.\n   */\n  disable?: string[]\n}\n\n/** Module generation settings — controls how `kick g module` produces code */\nexport interface ModuleConfig {\n  /** Where modules live (default: 'src/modules') */\n  dir?: string\n  /**\n   * Default repository implementation for generators.\n   *\n   * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`\n   * — generate fully working repository code.\n   *\n   * Custom types (object): `{ name: 'typeorm' }`\n   * — generate a stub repository with TODO markers.\n   *\n   * @example\n   * repo: 'prisma'                // built-in\n   * repo: { name: 'typeorm' }     // custom\n   */\n  repo?: RepoTypeConfig\n  /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */\n  schemaDir?: string\n  /**\n   * Whether to pluralize module names in generated code.\n   * When true (default), `kick g module user` creates `src/modules/users/`.\n   * When false, it creates `src/modules/user/` and uses singular names throughout.\n   */\n  pluralize?: boolean\n  /**\n   * Import path for the Prisma generated client in `--repo prisma` templates.\n   * Must resolve within `src/` for path alias compatibility.\n   *\n   * @default '@prisma/client' (Prisma 5/6)\n   * @example\n   * prismaClientPath: '@/generated/prisma/client'  // Prisma 7+\n   * prismaClientPath: './generated/prisma/client'   // relative\n   */\n  prismaClientPath?: string\n  /**\n   * Module declaration style emitted by `kick g module` and the\n   * project scaffold.\n   *\n   * - `'define'` (default) — `defineModule({ name, build: () => ({...}) })`\n   *   factory form. Mirrors `defineAdapter` / `definePlugin` /\n   *   `defineContextDecorator`.\n   * - `'class'` — legacy `class FooModule implements AppModule { ... }`\n   *   form. Still fully supported by the framework loader; pin to this\n   *   value for projects that prefer the class shape (existing-codebase\n   *   consistency, class-decorator setups, etc).\n   *\n   * The framework runtime accepts both shapes regardless of this\n   * setting — the flag controls codegen output only. `kick g module`\n   * inserts the matching call form into `src/modules/index.ts`\n   * (`Module()` vs `Module`); `kick rm module` matches both.\n   *\n   * @default 'define'\n   */\n  style?: 'define' | 'class'\n}\n\n/** Configuration for the kick.config.ts file */\nexport interface KickConfig {\n  /**\n   * Project pattern — controls default generator behavior.\n   * - 'rest' — Express + Swagger (default)\n   * - 'ddd' — Full DDD modules with use cases, entities, value objects\n   * - 'cqrs' — CQRS with commands, queries, events, WebSocket + queue\n   * - 'minimal' — Bare Express with no scaffolding\n   */\n  pattern?: ProjectPattern\n  /**\n   * Module generation settings — directory, repo type, pluralization, schema dir.\n   *\n   * @example\n   * modules: {\n   *   dir: 'src/modules',\n   *   repo: 'prisma',\n   *   pluralize: false,\n   *   schemaDir: 'prisma/',\n   * }\n   */\n  modules?: ModuleConfig\n  /**\n   * Package manager used by `kick add` (and any future dep-installing command)\n   * to install dependencies. When set, overrides lockfile auto-detection so\n   * commands always use the project's intended package manager.\n   *\n   * Priority (highest first):\n   * 1. `--pm` flag on the CLI\n   * 2. `packageManager` in kick.config\n   * 3. `packageManager` field in package.json (corepack convention)\n   * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)\n   * 5. `'npm'`\n   *\n   * @example\n   * packageManager: 'pnpm'\n   */\n  packageManager?: PackageManager\n\n  /**\n   * The HTTP runtime the app boots on — the engine passed to\n   * `bootstrap({ runtime })`. Written by `kick new --runtime`, and read by\n   * dep-aware commands so they install / validate the engine-correct peers:\n   * - `kick add upload` picks the multipart driver (express → `multer`,\n   *   fastify → `@fastify/multipart`, h3 → built-in, no driver)\n   * - `kick doctor` checks the engine peers + upload driver are present\n   * - the `kick/runtime` typegen emits the `KickRuntimeRegister` augmentation\n   *   that flips the runtime-typed escape hatches (`AdapterContext.app`,\n   *   `getRuntimeApp()`) to the engine's native types\n   *\n   * Defaults to `'express'` when unset (the default engine).\n   *\n   * @example\n   * runtime: 'fastify'\n   */\n  runtime?: 'express' | 'fastify' | 'h3'\n\n  /**\n   * DI token scope prefix used by code generators. Every scaffolded\n   * `createToken<T>('<scope>/<area>/<key>')` substitutes this string\n   * for `<scope>`. Generators emit org-scoped tokens out of the box\n   * so adopter projects pass `kick-lint`'s `token-reserved-prefix`\n   * rule (which forbids the reserved `kick/` prefix on third-party\n   * code) without manual rename.\n   *\n   * Resolution order (highest first):\n   * 1. This field, when set\n   * 2. `package.json` `name` field — `@scope/pkg` → `'scope'`,\n   *    bare `pkg` → `'pkg'`\n   * 3. Fallback `'app'`\n   *\n   * @example\n   * tokenScope: 'mycorp'\n   * // → createToken<...>('mycorp/users/repository')\n   */\n  tokenScope?: string\n\n  /**\n   * Directories to copy to dist/ after build.\n   * Useful for EJS templates, email templates, static assets, etc.\n   *\n   * @example\n   * ```ts\n   * copyDirs: [\n   *   'src/views',                          // copies to dist/src/views\n   *   { src: 'src/views', dest: 'dist/views' }, // custom dest\n   *   'src/emails',\n   * ]\n   * ```\n   */\n  copyDirs?: Array<string | { src: string; dest?: string }>\n  /**\n   * Build output settings. The asset manager + `kick build`'s copy\n   * steps honour these — adopters who use Vite's `build.outDir =\n   * 'out'` (or any non-default) should mirror the value here so\n   * `assets.x.y()` paths line up with where Vite actually wrote.\n   *\n   * @example\n   * ```ts\n   * build: { outDir: 'out' }\n   * ```\n   */\n  build?: {\n    /**\n     * Output directory, relative to project root. Defaults to\n     * `'dist'`. The asset manager emits its manifest + copies\n     * assetMap entries into this directory (under a per-namespace\n     * subdirectory by default; override per-entry via `dest`).\n     */\n    outDir?: string\n  }\n  /**\n   * Typed, addressable assets — see `assets-plan.md`. Each entry maps\n   * a logical namespace name to a source directory. The build pipeline\n   * auto-derives the necessary copy step + emits a manifest at\n   * `dist/.kickjs-assets.json`; the runtime exposes\n   * `import { assets } from '@forinda/kickjs'` so adopters can resolve\n   * paths without dev/prod branching.\n   *\n   * `copyDirs` is unchanged — `assetMap` is a separate, opt-in surface.\n   * Adopters who want raw directory copies keep using `copyDirs`; those\n   * who want typed addressable assets add `assetMap` entries.\n   *\n   * @example\n   * ```ts\n   * assetMap: {\n   *   mails: { src: 'src/templates/mails' },\n   *   reports: { src: 'src/templates/reports', glob: '**\\/*.{ejs,html}' },\n   *   schemas: { src: 'src/schemas', glob: '**\\/*.json' },\n   * }\n   * ```\n   */\n  assetMap?: Record<string, AssetMapEntry>\n  /**\n   * Typegen settings — controls `.kickjs/types/*` generation including\n   * the schema validator used for body type extraction.\n   *\n   * @example\n   * ```ts\n   * typegen: {\n   *   schemaValidator: 'zod',\n   * }\n   * ```\n   */\n  typegen?: TypegenConfig\n  /**\n   * Dev-server (`kick dev`) settings.\n   */\n  dev?: {\n    /**\n     * Run the project's TypeScript checker (`tsgo --noEmit`, falling\n     * back to `tsc --noEmit`) after each debounced change and surface\n     * diagnostics in the dev console + a `kickjs:typecheck` HMR event.\n     * Equivalent to the `kick dev --typecheck` flag.\n     *\n     * @default false\n     */\n    typecheck?: boolean\n  }\n  /**\n   * Database settings — schema path, migrations dir, dialect,\n   * connection string, optional adapter factory. Consumed by\n   * `kick db generate` / `kick db migrate*`. See {@link KickDbConfigBlock}.\n   */\n  db?: KickDbConfigBlock\n  /** Custom commands that extend the CLI */\n  commands?: KickCommandDefinition[]\n  /**\n   * CLI plugins — bundled commands + typegens contributed by external\n   * packages (e.g. `@forinda/kickjs-cli-drizzle`). Plugin commands\n   * appear first; adopter `commands` overrides plugin commands of the\n   * same name. Duplicate commands or typegen ids across two plugins\n   * fail-fast at CLI startup.\n   *\n   * @example\n   * import { drizzlePlugin } from '@forinda/kickjs-cli-drizzle'\n   * export default defineConfig({\n   *   plugins: [drizzlePlugin({ schemaPath: 'src/db/schema' })],\n   * })\n   */\n  plugins?: KickCliPlugin[]\n  /** Code style overrides (auto-detected from prettier when possible) */\n  style?: {\n    semicolons?: boolean\n    quotes?: 'single' | 'double'\n    trailingComma?: 'all' | 'es5' | 'none'\n    indent?: number\n  }\n\n  /**\n   * Extensibility hook for `kick doctor`. Adopters add their own\n   * environment / project-shape checks here; each function receives\n   * the same context the built-in checks see and returns a result (or\n   * `null` to skip).\n   *\n   * The framework stays ORM- and stack-agnostic — Prisma-specific,\n   * Drizzle-specific, deploy-target-specific checks belong in adopter\n   * config (or in adapter packages that ship doctor extensions),\n   * never in core.\n   *\n   * @example\n   * ```ts\n   * import { existsSync } from 'node:fs'\n   * import { join } from 'node:path'\n   * import { defineConfig } from '@forinda/kickjs-cli'\n   *\n   * export default defineConfig({\n   *   doctor: {\n   *     checks: [\n   *       (ctx) => {\n   *         if (!existsSync(join(ctx.cwd, 'prisma/schema.prisma'))) return null\n   *         const generated = join(ctx.cwd, 'node_modules/@prisma/client/default.js')\n   *         return existsSync(generated)\n   *           ? { name: 'Prisma client generated', status: 'pass' }\n   *           : {\n   *               name: 'Prisma client generated',\n   *               status: 'fail',\n   *               fix: 'Run: pnpm exec prisma generate',\n   *             }\n   *       },\n   *     ],\n   *   },\n   * })\n   * ```\n   */\n  doctor?: import('./commands/doctor').DoctorExtension\n}\n\n/** Helper to define a type-safe kick.config.ts */\nexport function defineConfig(config: KickConfig): KickConfig {\n  return config\n}\n\n/** Resolve module config from `modules.*` block. */\n/**\n * Resolve the project's DI token scope for code generators.\n * Falls back through kick.config.ts → package.json → `'app'`.\n *\n * @param config Loaded `kick.config.ts` (null when not present)\n * @param cwd Project root — used to read package.json\n */\nexport function resolveTokenScope(config: KickConfig | null, cwd: string): string {\n  if (config?.tokenScope && typeof config.tokenScope === 'string' && config.tokenScope.length > 0) {\n    const sanitised = sanitizeScope(config.tokenScope)\n    // Configured tokenScope can sanitise down to an empty string (e.g.\n    // '___' or '!!') — falling through to the package.json chain is\n    // safer than emitting an invalid `'/users/repository'` token.\n    if (sanitised.length > 0) return sanitised\n  }\n\n  // Read package.json synchronously — this runs once per generator\n  // invocation, so a sync read is cheaper than the async dance + lets\n  // the call sites (template builders) stay synchronous.\n  try {\n    const pkgPath = join(cwd, 'package.json')\n    if (existsSync(pkgPath)) {\n      const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: unknown }\n      if (typeof pkg.name === 'string' && pkg.name.length > 0) {\n        const scoped = pkg.name.match(/^@([^/]+)\\//)\n        const candidate = scoped ? sanitizeScope(scoped[1]) : sanitizeScope(pkg.name)\n        if (candidate.length > 0) return candidate\n        // Same empty-after-sanitize guard.\n      }\n    }\n  } catch {\n    // package.json missing or malformed — fall through to default\n  }\n\n  return 'app'\n}\n\n/** Lowercase + strip characters that would break a token literal. */\nfunction sanitizeScope(raw: string): string {\n  return raw\n    .toLowerCase()\n    .replace(/[^a-z0-9-]/g, '-') // collapse anything weird to a hyphen\n    .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n    .replace(/-{2,}/g, '-') // collapse runs of hyphens\n}\n\nexport function resolveModuleConfig(config: KickConfig | null): ModuleConfig {\n  if (!config) return {}\n  const mc: ModuleConfig = {\n    dir: config.modules?.dir,\n    repo: config.modules?.repo,\n    schemaDir: config.modules?.schemaDir,\n    pluralize: config.modules?.pluralize,\n    prismaClientPath: config.modules?.prismaClientPath,\n    style: config.modules?.style,\n  }\n  // Validate `style` — silently coerce unknown values to the default.\n  // The CLI surface only documents 'define' and 'class'; anything else\n  // is a typo (e.g. 'defineModule') we'd rather see than silently\n  // emit class form.\n  if (mc.style !== undefined && mc.style !== 'define' && mc.style !== 'class') {\n    console.warn(\n      `  Warning: modules.style '${mc.style as string}' is not a valid value ` +\n        `(expected 'define' or 'class'). Falling back to 'define'.`,\n    )\n    mc.style = 'define'\n  }\n\n  // Warn if a string repo value isn't a known built-in. Deprecated ORM\n  // presets get their own note; any other bare string suggests the\n  // `{ name }` form to silence the stub-repo warning.\n  if (mc.repo && typeof mc.repo === 'string' && !BUILTIN_REPO_TYPES.includes(mc.repo)) {\n    if (!warnIfDeprecatedRepo(mc.repo)) {\n      console.warn(\n        `  Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(', ')}).` +\n          ` It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`,\n      )\n    }\n  }\n\n  return mc\n}\n\nconst CONFIG_FILES = ['kick.config.ts', 'kick.config.js', 'kick.config.mjs', 'kick.config.json']\n\n/**\n * Load `kick.config.*` starting from `startDir` and walking up toward\n * the filesystem root until a config file is found. Returns `null`\n * when no config exists anywhere on the way up.\n *\n * Walking up means adopters can run `kick <cmd>` from any subdirectory\n * (e.g. `src/modules/users/`) and still pick up the project's config —\n * before this change a nested-cwd invocation silently saw `null` and\n * fell back to framework defaults.\n *\n * TypeScript configs (`.ts`) are loaded via `jiti` when available;\n * `.js` / `.mjs` use native `import()`; `.json` uses `JSON.parse`. The\n * jiti import is dynamic + best-effort: if the dep is missing we\n * surface a warning telling the adopter how to install it, instead of\n * silently dropping the config (which is what the previous bare-catch\n * did).\n */\nexport async function loadKickConfig(startDir: string): Promise<KickConfig | null> {\n  const { findProjectRoot } = await import('./utils/project-root')\n  const root = findProjectRoot(startDir)\n\n  for (const filename of CONFIG_FILES) {\n    const filepath = join(root, filename)\n    try {\n      await access(filepath)\n    } catch {\n      continue\n    }\n\n    if (filename.endsWith('.json')) {\n      const content = await readFile(filepath, 'utf-8')\n      return JSON.parse(content)\n    }\n\n    const isTs = filename.endsWith('.ts')\n\n    if (isTs) {\n      // Split the import-jiti step from the load-user-config step so the\n      // diagnostic blames the right thing. Both surface as Node module-\n      // resolution errors with similar shapes (`Cannot find package …`,\n      // `ERR_MODULE_NOT_FOUND`), but the remedies differ:\n      //\n      // - Missing jiti  → adopter needs to install jiti.\n      // - Missing dep referenced from kick.config.ts → adopter needs to\n      //   install THAT package; telling them to install jiti would send\n      //   them to the wrong fix and bury the real missing module.\n      let jitiModule: typeof import('jiti')\n      try {\n        jitiModule = await import('jiti')\n      } catch (err) {\n        const msg = err instanceof Error ? err.message : String(err)\n        if (msg.includes(\"Cannot find package 'jiti'\") || msg.includes('ERR_MODULE_NOT_FOUND')) {\n          console.warn(\n            `Warning: Failed to load ${filename} — 'jiti' is required for TypeScript configs. ` +\n              \"Run `pnpm add -D jiti` (or your package manager's equivalent), or rename the file \" +\n              'to kick.config.js / kick.config.mjs / kick.config.json.',\n          )\n        } else {\n          console.warn(`Warning: Failed to initialize jiti for ${filename}: ${msg}`)\n        }\n        continue\n      }\n\n      try {\n        const jiti = jitiModule.createJiti(root, { interopDefault: true, fsCache: false })\n        const config = (await jiti.import(filepath, { default: true })) as KickConfig\n        const warnings = validateAssetMap(config, root)\n        for (const warning of warnings) console.warn(`  Warning: ${warning}`)\n        writeAssetConfigSnapshot(root, config)\n        return config\n      } catch (err) {\n        const msg = err instanceof Error ? err.message : String(err)\n        console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n        continue\n      }\n    }\n\n    try {\n      const { pathToFileURL } = await import('node:url')\n      const mod = await import(pathToFileURL(filepath).href)\n      const config = (mod.default ?? mod) as KickConfig\n      const warnings = validateAssetMap(config, root)\n      for (const warning of warnings) console.warn(`  Warning: ${warning}`)\n      writeAssetConfigSnapshot(root, config)\n      return config\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : String(err)\n      console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n      continue\n    }\n  }\n  return null\n}\n\n/**\n * Mirror the JSON-serialisable slice of a TS/JS `kick.config` that the\n * runtime asset resolver needs (`assetMap` + `build.outDir`) into\n * `.kickjs/kick.config.json`.\n *\n * Why: the runtime resolver in `@forinda/kickjs` reads its config\n * *synchronously* (it sits on the hot path of `assets.x.y()` /\n * `resolveAsset()`), so it can only parse `kick.config.{json,js,cjs}` —\n * it deliberately can't transpile `kick.config.ts`. For a `.ts`-config\n * project in dev (no built `dist/.kickjs-assets.json` yet), that left\n * the resolver with no config to synthesise a manifest from, so\n * `assets.x.y()` threw `UnknownAssetError` until the first build.\n *\n * The CLI already transpiles the `.ts` config here, so it drops this\n * tiny snapshot the runtime can read with a plain `JSON.parse`. Best\n * effort: any failure (read-only fs, etc.) is swallowed — a missing\n * snapshot just falls back to the previous behaviour.\n */\nexport function writeAssetConfigSnapshot(root: string, config: KickConfig | null): void {\n  // Only meaningful for asset-manager users — don't litter `.kickjs/`\n  // for projects that never declare an assetMap.\n  if (!config?.assetMap || Object.keys(config.assetMap).length === 0) return\n  try {\n    const dir = join(root, '.kickjs')\n    mkdirSync(dir, { recursive: true })\n    const snapshot = {\n      // Marker so a future shape change can be detected/migrated.\n      version: 1 as const,\n      assetMap: config.assetMap,\n      ...(config.build?.outDir ? { build: { outDir: config.build.outDir } } : {}),\n    }\n    writeFileSync(join(dir, 'kick.config.json'), JSON.stringify(snapshot, null, 2) + '\\n', 'utf-8')\n  } catch {\n    // Best effort — snapshot is an optimisation, not a correctness\n    // requirement. Production reads the real dist manifest.\n  }\n}\n\n/**\n * Validate `assetMap` entries on a loaded config. Returns a list of\n * human-readable warnings; the caller decides how to surface them\n * (typically `console.warn`). Never throws — `kick g` and other\n * unrelated commands should keep working even when the assetMap is\n * misconfigured.\n *\n * Checks:\n *\n * - Each entry's `src` is a non-empty string.\n * - The `src` directory exists on disk (otherwise the typegen + build\n *   steps will fail later with cryptic errors).\n * - `dest` doesn't escape the project root (defensive — a `dest:\n *   '../../etc'` typo could write files outside the workspace).\n * - The namespace key is a non-empty string and doesn't include a\n *   `/` (would conflict with the `<namespace>/<key>` manifest format).\n */\nexport function validateAssetMap(config: KickConfig | null, cwd: string): string[] {\n  const warnings: string[] = []\n  if (!config?.assetMap) return warnings\n\n  const root = resolve(cwd)\n  for (const [namespace, entry] of Object.entries(config.assetMap)) {\n    if (!namespace || namespace.includes('/')) {\n      warnings.push(\n        `assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`,\n      )\n      continue\n    }\n    if (typeof entry?.src !== 'string' || entry.src.length === 0) {\n      warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`)\n      continue\n    }\n    const srcAbs = resolve(cwd, entry.src)\n    if (!existsSync(srcAbs)) {\n      warnings.push(\n        `assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`,\n      )\n    }\n    if (entry.dest) {\n      const destAbs = resolve(cwd, entry.dest)\n      // path.relative is the right primitive for \"is X inside Y?\" —\n      // a raw startsWith() prefix match has two failure modes the\n      // earlier version hit: (a) `/app` is a prefix of `/app2/...`\n      // even though they're different directories, and (b) it's\n      // case-sensitive on filesystems that aren't (macOS default,\n      // Windows). path.relative handles both correctly + accounts\n      // for `..` traversal in the destination.\n      if (escapesRoot(destAbs, root)) {\n        warnings.push(\n          `assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`,\n        )\n      }\n    }\n  }\n  return warnings\n}\n\n/**\n * Returns true when `path` (absolute) resolves outside of `root`\n * (also absolute). Uses `path.relative` for accuracy:\n *\n * - The result is empty when paths are identical (inside).\n * - It starts with `..` when the path traverses outside the root.\n * - It's absolute (Windows: cross-drive) when there's no relative\n *   path between them.\n *\n * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`\n * matching `/app2/...`, or case-mismatches on macOS / Windows).\n */\nfunction escapesRoot(path: string, root: string): boolean {\n  const rel = relative(root, path)\n  return rel === '' ? false : rel.startsWith('..') || isAbsolute(rel)\n}\n"],"mappings":"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"}