@kubb/cli 5.14.3 → 5.14.5

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.
Files changed (58) hide show
  1. package/dist/{Telemetry-wrNtjGQD.js → Telemetry-Dc79_cIA.js} +2 -2
  2. package/dist/{Telemetry-wrNtjGQD.js.map → Telemetry-Dc79_cIA.js.map} +1 -1
  3. package/dist/{Telemetry-BHq3R4zU.cjs → Telemetry-Dc9DVxKQ.cjs} +2 -2
  4. package/dist/{Telemetry-BHq3R4zU.cjs.map → Telemetry-Dc9DVxKQ.cjs.map} +1 -1
  5. package/dist/{background-DunubYZl.js → background-3_KudPH-.js} +2 -2
  6. package/dist/{background-DunubYZl.js.map → background-3_KudPH-.js.map} +1 -1
  7. package/dist/{background-Bml5-XDk.js → background-C877JFxE.js} +1 -1
  8. package/dist/{background-B_7FXwrJ.cjs → background-v5pDy3IJ.cjs} +2 -2
  9. package/dist/{background-B_7FXwrJ.cjs.map → background-v5pDy3IJ.cjs.map} +1 -1
  10. package/dist/index.cjs +18 -18
  11. package/dist/index.js +18 -18
  12. package/dist/{registry-Dw1AO5RZ.cjs → registry-BInbtBm7.cjs} +3 -3
  13. package/dist/{registry-Dw1AO5RZ.cjs.map → registry-BInbtBm7.cjs.map} +1 -1
  14. package/dist/{registry-Cq5tkjuA.js → registry-ov1RYcD4.js} +3 -3
  15. package/dist/{registry-Cq5tkjuA.js.map → registry-ov1RYcD4.js.map} +1 -1
  16. package/dist/{reporters-DcmzptJ2.js → reporters-BtD41K-C.js} +4 -4
  17. package/dist/{reporters-DcmzptJ2.js.map → reporters-BtD41K-C.js.map} +1 -1
  18. package/dist/{reporters-D-4ffpKz.cjs → reporters-DQz--AdE.cjs} +4 -4
  19. package/dist/{reporters-D-4ffpKz.cjs.map → reporters-DQz--AdE.cjs.map} +1 -1
  20. package/dist/{run-Bn4Zqt5R.cjs → run-1oXPIkli.cjs} +7 -7
  21. package/dist/{run-Bn4Zqt5R.cjs.map → run-1oXPIkli.cjs.map} +1 -1
  22. package/dist/{run-DJJL3r-7.cjs → run-B03V0Poj.cjs} +6 -6
  23. package/dist/{run-DJJL3r-7.cjs.map → run-B03V0Poj.cjs.map} +1 -1
  24. package/dist/{run-ByKt3awy.js → run-BK-A9JDf.js} +2 -2
  25. package/dist/{run-ByKt3awy.js.map → run-BK-A9JDf.js.map} +1 -1
  26. package/dist/{run-knuxVA3t.cjs → run-BUzdEUaE.cjs} +4 -4
  27. package/dist/{run-knuxVA3t.cjs.map → run-BUzdEUaE.cjs.map} +1 -1
  28. package/dist/{run-CFzm5zI3.js → run-DDwdeHgU.js} +7 -7
  29. package/dist/{run-CFzm5zI3.js.map → run-DDwdeHgU.js.map} +1 -1
  30. package/dist/{run-DI1SBQa5.js → run-UsAMlUyy.js} +4 -4
  31. package/dist/{run-DI1SBQa5.js.map → run-UsAMlUyy.js.map} +1 -1
  32. package/dist/{run-Bl4N5GSj.js → run-YMSJnvYl.js} +6 -6
  33. package/dist/{run-Bl4N5GSj.js.map → run-YMSJnvYl.js.map} +1 -1
  34. package/dist/{run-a_sEesoQ.cjs → run-cBSr2F1q.cjs} +2 -2
  35. package/dist/{run-a_sEesoQ.cjs.map → run-cBSr2F1q.cjs.map} +1 -1
  36. package/dist/{run-D1VCdBe6.js → run-v5aW1jjQ.js} +1 -1
  37. package/dist/{snapshot-CfNavltT.cjs → snapshot-BrwynBfC.cjs} +6 -6
  38. package/dist/{snapshot-CfNavltT.cjs.map → snapshot-BrwynBfC.cjs.map} +1 -1
  39. package/dist/{snapshot-Do-Aicct.js → snapshot-D2E7nhEf.js} +6 -6
  40. package/dist/{snapshot-Do-Aicct.js.map → snapshot-D2E7nhEf.js.map} +1 -1
  41. package/dist/{status-CGowDsPH.js → status-BHIOMCM0.js} +3 -3
  42. package/dist/{status-CGowDsPH.js.map → status-BHIOMCM0.js.map} +1 -1
  43. package/dist/{status-Dx0IAK1i.cjs → status-CxzhvjKs.cjs} +3 -3
  44. package/dist/{status-Dx0IAK1i.cjs.map → status-CxzhvjKs.cjs.map} +1 -1
  45. package/dist/studioWorker.js +2 -2
  46. package/dist/{summary-fjAo4dRr.js → summary-B219y888.js} +3 -3
  47. package/dist/{summary-fjAo4dRr.js.map → summary-B219y888.js.map} +1 -1
  48. package/dist/{summary-BYeb0-zS.cjs → summary-BOYsqub3.cjs} +3 -3
  49. package/dist/{summary-BYeb0-zS.cjs.map → summary-BOYsqub3.cjs.map} +1 -1
  50. package/dist/{text-DszZ14XJ.cjs → text-CC2xdpGL.cjs} +2 -2
  51. package/dist/{text-DszZ14XJ.cjs.map → text-CC2xdpGL.cjs.map} +1 -1
  52. package/dist/{text-9khn8L1h.js → text-CEyJhBnh.js} +2 -2
  53. package/dist/{text-9khn8L1h.js.map → text-CEyJhBnh.js.map} +1 -1
  54. package/dist/{utils-BrqhV7wQ.cjs → utils-8E1nF4C1.cjs} +3 -3
  55. package/dist/{utils-BrqhV7wQ.cjs.map → utils-8E1nF4C1.cjs.map} +1 -1
  56. package/dist/{utils-DRyjNlug.js → utils-D8VN40bN.js} +3 -3
  57. package/dist/{utils-DRyjNlug.js.map → utils-D8VN40bN.js.map} +1 -1
  58. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"utils-BrqhV7wQ.cjs","names":["runtime","access","readFile","resolve","writeFile","mkdir","dirname","relative","isAbsolute","styleText","formatMs","writer","styleText","relative","process","readdir","join"],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/promise.ts","../src/loggers/banner.ts","../src/ui/spinner.ts","../src/runners/generate/utils.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { styleText } from 'node:util'\nimport { formatMs } from '@internals/utils'\n\n/**\n * xterm-256 color for each pixel letter of the mascot. Fixed codes rather than named colors, so a\n * terminal theme cannot repaint it.\n */\nconst colors: Record<string, number> = {\n /** Hoodie. */\n O: 208,\n /** Shadow where the hood meets the face. */\n R: 166,\n /** Face. */\n F: 223,\n /** Eyes. */\n K: 16,\n /** Blush. */\n B: 217,\n /** Mouth. */\n M: 131,\n}\n\n/**\n * The two pixel rows of the face around the eyes, for each expression.\n */\nconst eyes = {\n open: ['FFFFFFFFFF', 'FFKFFFFKFF'],\n left: ['FFFFFFFFFF', 'FKFFFFKFFF'],\n right: ['FFFFFFFFFF', 'FFFKFFFFKF'],\n blink: ['FFFFFFFFFF', 'FKKFFFFKKF'],\n happy: ['FFKFFFFKFF', 'FKFKFFKFKF'],\n} as const\n\nexport type Expression = keyof typeof eyes\n\n/**\n * The frames the mascot plays when the CLI starts, each with how long it stays on screen. It ends on\n * open eyes, which stay.\n */\nexport const wakeUp: ReadonlyArray<readonly [Expression, number]> = [\n ['blink', 100],\n ['open', 180],\n ['left', 140],\n ['right', 140],\n ['open', 80],\n ['happy', 220],\n ['open', 0],\n]\n\n/**\n * The kid in the orange hoodie from the Kubb logo, one letter per pixel and `.` for none. Each\n * 2 × 2 block becomes one terminal cell, so a block holds at most two colors.\n */\nexport function pixels(expression: Expression): Array<string> {\n const [top, bottom] = eyes[expression]\n\n // prettier-ignore\n return [\n '....OOOOOOOOOO....',\n '..OORRRRRRRRRROO..',\n `..OO${top}OO..`,\n `OOFF${bottom}FFOO`,\n 'OOFFBBFFMMFFBBFFOO',\n '..OOOOFFFFFFOOOO..',\n ]\n}\n\n/**\n * Quadrant glyphs indexed by which quarters are filled: top left 1, top right 2, bottom left 4,\n * bottom right 8.\n */\nconst quadrants = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'\n\n/**\n * Draws each 2 × 2 block of pixels as one quadrant glyph. The glyph's filled quarters take the first\n * color in the block and the rest take the other.\n */\nfunction drawHead(expression: Expression): Array<string> {\n const rows = pixels(expression)\n const lines: Array<string> = []\n\n for (let y = 0; y < rows.length; y += 2) {\n let line = ''\n for (let x = 0; x < rows[y]!.length; x += 2) {\n const block = [rows[y]![x]!, rows[y]![x + 1]!, rows[y + 1]![x]!, rows[y + 1]![x + 1]!]\n const fg = block.find((pixel) => pixel in colors)\n if (!fg) {\n line += ' '\n continue\n }\n const bg = colors[block.find((pixel) => pixel !== fg) ?? '.']\n const mask = block.reduce((bits, pixel, index) => (pixel === fg ? bits | (1 << index) : bits), 0)\n line += `\\x1b[38;5;${colors[fg]}${bg === undefined ? '' : `;48;5;${bg}`}m${quadrants[mask]}\\x1b[0m`\n }\n lines.push(line)\n }\n\n return lines\n}\n\n/**\n * Generates the Kubb mascot welcome banner as an ANSI-colored string: the head with the version,\n * title, and description beside it.\n *\n * @example\n * ```ts\n * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', expression: 'open' }))\n * ```\n */\nexport function getIntro({\n title,\n description,\n version,\n expression,\n}: {\n /**\n * Name of the active configuration or tool being started.\n */\n title: string\n /**\n * Short subtitle shown next to the arrow prompt.\n */\n description: string\n /**\n * Kubb version string rendered in the header.\n */\n version: string\n /**\n * Which face the mascot makes.\n */\n expression: Expression\n}): string {\n // The hoodie's fixed code, so the title is Kubb orange whatever the terminal theme does to `yellow`.\n const brand = (value: string, bold = false) => `\\x1b[${bold ? '1;' : ''}38;5;${colors.O}m${value}\\x1b[0m`\n const text = [brand(`KUBB v${version}`, true), styleText('gray', title), `${brand('➜')} ${styleText('white', description)}`]\n\n return drawHead(expression)\n .map((line, index) => ` ${line} ${text[index]}`)\n .join('\\n')\n}\n\n/**\n * Formats a millisecond duration with a threshold-based ANSI color.\n * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.\n *\n * @example\n * ```ts\n * formatMsWithColor(200) // '\\x1b[32m200ms\\x1b[39m'\n * formatMsWithColor(800) // '\\x1b[33m800ms\\x1b[39m'\n * formatMsWithColor(1500) // '\\x1b[31m1.50s\\x1b[39m'\n * ```\n */\nexport function formatMsWithColor(ms: number): string {\n const formatted = formatMs(ms)\n if (ms <= 500) return styleText('green', formatted)\n if (ms <= 1000) return styleText('yellow', formatted)\n\n return styleText('red', formatted)\n}\n\n/**\n * A `size`-cell progress bar: `done` of `total` as filled cells, the rest light. Any progress shows\n * at least one cell, and only a finished bar fills the last one.\n */\nexport function formatBar(done: number, total: number, size = 20): string {\n const filled = !done ? 0 : done >= total ? size : Math.min(size - 1, Math.max(1, Math.round((done / total) * size)))\n return styleText('magenta', '█'.repeat(filled)) + styleText('dim', '░'.repeat(size - filled))\n}\n","import type { WriterSpinner } from '../loggers/types.ts'\nimport { writer } from './log.ts'\n\n/**\n * A progress spinner, or a writer that prints each message it is given when the terminal cannot\n * animate one. Callers drive both the same way, including the failure state.\n */\nexport function createSpinner(): WriterSpinner {\n return writer().spinner()\n}\n\n/** How long a wait may run before a spinner shows, so a fast answer draws nothing. */\nconst SPINNER_DELAY_MS = 150\n\n/**\n * Awaits `promise`, showing a spinner with `message` only when it takes longer than 150 ms.\n */\nexport async function withSpinner<T>({ promise, message }: { promise: Promise<T>; message: string }): Promise<T> {\n let spinner: WriterSpinner | undefined\n const timer = setTimeout(() => {\n spinner = createSpinner()\n spinner.start(message)\n }, SPINNER_DELAY_MS)\n try {\n return await promise\n } finally {\n clearTimeout(timer)\n spinner?.clear()\n }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { inParallel, matchesStored, read } from '@internals/utils'\nimport type { Hookable, KubbHooks, OutputManifest, Storage } from '@kubb/core'\n\n/** Glob pattern for paths the file watcher ignores. */\nconst WATCHER_IGNORED_PATHS = '**/{.git,node_modules}/**' as const\n\n/**\n * Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.\n * It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.\n */\nconst WATCHER_DEBOUNCE_MS = 250\n\n/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */\nexport function formatChange({ target, removed = false, now = new Date() }: { target: string; removed?: boolean; now?: Date }): string {\n return `${styleText('dim', now.toTimeString().slice(0, 8))} ${styleText('bold', target)} ${removed ? 'removed' : 'changed'}`\n}\n\ntype WatcherLog = {\n info: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Starts a file watcher on the given paths and calls `cb` on any change.\n * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)\n * are debounced into one call. `cb` must never reject: its result is not awaited, so the caller\n * serializes and reports its own builds. Resolves to a function that stops watching.\n */\nexport async function startWatcher(\n path: Array<string>,\n cb: (path: Array<string>) => Promise<void>,\n log: WatcherLog = { info: console.log, error: console.log },\n): Promise<() => Promise<void>> {\n const { watch } = await import('chokidar')\n // `ignoreInitial` skips the `add` events chokidar fires for existing files at startup, which\n // would otherwise rebuild right after the initial run.\n const watcher = watch(path, { ignorePermissionErrors: true, ignored: WATCHER_IGNORED_PATHS, ignoreInitial: true })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => void watcher.close())\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n watcher.on('all', (type, file) => {\n log.info(formatChange({ target: relative(process.cwd(), file), removed: type === 'unlink' }))\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n void cb(path)\n }, WATCHER_DEBOUNCE_MS)\n })\n\n return async () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n await watcher.close()\n }\n}\n\n/**\n * What one build produced: the schema and operation names the plugins generated.\n */\nexport type BuildSnapshot = {\n schemas: Set<string>\n operations: Set<string>\n}\n\nfunction createSnapshot(): BuildSnapshot {\n return { schemas: new Set(), operations: new Set() }\n}\n\n/**\n * Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished\n * build's snapshot and starts the next one.\n */\nexport function trackBuild(hooks: Hookable<KubbHooks>): { take(): BuildSnapshot } {\n let current = createSnapshot()\n\n hooks.hook('kubb:generate:schema', (node) => {\n if (node.name) current.schemas.add(node.name)\n })\n hooks.hook('kubb:generate:operation', (node) => {\n current.operations.add(node.operationId)\n })\n\n return {\n take() {\n const snapshot = current\n current = createSnapshot()\n return snapshot\n },\n }\n}\n\ntype NameChange = { added: number; removed: number }\n\n/**\n * How a rebuild differs from the build before it: the spec's added and removed schemas and operations.\n */\nexport type BuildDiff = {\n schemas: NameChange\n operations: NameChange\n}\n\nfunction diffNames(previous: Set<string>, next: Set<string>): NameChange {\n return { added: [...next].filter((name) => !previous.has(name)).length, removed: [...previous].filter((name) => !next.has(name)).length }\n}\n\n/**\n * Compares two {@link BuildSnapshot}s by name.\n */\nexport function diffBuilds(previous: BuildSnapshot, next: BuildSnapshot): BuildDiff {\n return {\n schemas: diffNames(previous.schemas, next.schemas),\n operations: diffNames(previous.operations, next.operations),\n }\n}\n\n/**\n * Compares file contents or fingerprints by path.\n */\nexport function diffFiles(previous: ReadonlyMap<string, string>, next: ReadonlyMap<string, string>): { files: Array<FileChange>; unchanged: number } {\n const files: Array<FileChange> = []\n let unchanged = 0\n for (const [path, fingerprint] of next) {\n const before = previous.get(path)\n if (before === fingerprint) unchanged++\n else files.push({ status: before === undefined ? 'A' : 'M', path })\n }\n for (const path of previous.keys()) {\n if (!next.has(path)) files.push({ status: 'D', path })\n }\n\n return { files, unchanged }\n}\n\n/** A changed file with how many lines it gains and loses, counted as a multiset, which is cheap and close enough for a plan. */\nexport type FileChange = { status: 'A' | 'M' | 'D'; path: string; added?: number; removed?: number }\n\nfunction countLineChanges(before: string, after: string): { added: number; removed: number } {\n const remaining = new Map<string, number>()\n for (const line of before ? before.split('\\n') : []) remaining.set(line, (remaining.get(line) ?? 0) + 1)\n let added = 0\n for (const line of after ? after.split('\\n') : []) {\n const count = remaining.get(line) ?? 0\n if (count > 0) remaining.set(line, count - 1)\n else added++\n }\n const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0)\n return { added, removed }\n}\n\n/**\n * Reads the output before generation, so cleaning the directory cannot erase the diff baseline.\n * A missing directory or an unreadable file is skipped.\n */\nexport async function readOutputFiles(outputPath: string): Promise<Map<string, string>> {\n const entries = await readdir(outputPath, { recursive: true, withFileTypes: true }).catch(() => [])\n const files = new Map<string, string>()\n await inParallel({\n items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),\n limit: 50,\n run: async (filePath) => {\n const source = await read(filePath).catch(() => null)\n if (source !== null) files.set(filePath, source)\n },\n })\n return files\n}\n\n/**\n * Compares generated output with the files captured before generation. The manifest recognizes\n * output rewritten by a formatter, and `clean` includes files the run would delete.\n */\nexport async function diffOutput({\n storage,\n paths,\n clean,\n manifest,\n previousFiles,\n}: {\n storage: Storage\n paths: ReadonlyArray<string>\n clean: boolean\n manifest?: Pick<OutputManifest, 'isUpToDate'>\n /**\n * The output captured before a real run, including files removed by cleaning.\n */\n previousFiles: ReadonlyMap<string, string>\n}): Promise<{ files: Array<FileChange>; unchanged: number }> {\n const generated = new Set(paths)\n const changes: Array<FileChange | undefined> = []\n let unchanged = 0\n await inParallel({\n items: paths,\n limit: 50,\n run: async (filePath, index) => {\n const next = await storage.readItem(filePath)\n if (next === null) return\n const previous = previousFiles.get(filePath) ?? null\n if (previous !== null && (matchesStored({ stored: previous, source: next }) || manifest?.isUpToDate({ key: filePath, source: next, disk: previous }))) {\n unchanged++\n return\n }\n changes[index] = {\n status: previous === null ? 'A' : 'M',\n path: filePath,\n ...countLineChanges(previous?.trim() ?? '', previous === null ? next.trimEnd() : next.trim()),\n }\n },\n })\n const files = changes.filter((file) => file !== undefined)\n if (clean) {\n for (const [filePath, source] of previousFiles) {\n if (!generated.has(filePath)) files.push({ status: 'D', path: filePath, ...countLineChanges(source.trimEnd(), '') })\n }\n }\n\n return { files, unchanged }\n}\n\ntype KeyInput = Pick<NodeJS.ReadStream, 'isTTY' | 'setRawMode' | 'resume' | 'pause' | 'setEncoding' | 'on'>\n\ntype KeyOptions = {\n rebuild: () => void\n /**\n * Ends the process with this exit code: `0` for `q`, `130` for ctrl+c, which raw mode turns into a key.\n */\n quit: (code: number) => void\n input?: KeyInput\n}\n\n/**\n * Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c\n * working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw\n * mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode\n * is restored on exit.\n */\nexport function listenForKeys({ rebuild, quit, input = process.stdin }: KeyOptions): (() => void) | undefined {\n if (!input.isTTY) return undefined\n\n const arm = () => {\n input.setRawMode(true)\n input.resume()\n }\n input.setEncoding('utf8')\n input.on('data', (data: string) => {\n const key = data.trim()\n if (key === 'r') {\n rebuild()\n return\n }\n if (key === 'q' || data === '\\u0003') {\n input.setRawMode(false)\n input.pause()\n quit(key === 'q' ? 0 : 130)\n }\n })\n process.once('exit', () => input.setRawMode(false))\n arm()\n\n return arm\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils-8E1nF4C1.cjs","names":["runtime","access","readFile","resolve","writeFile","mkdir","dirname","relative","isAbsolute","styleText","formatMs","writer","styleText","relative","process","readdir","join"],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/promise.ts","../src/loggers/banner.ts","../src/ui/spinner.ts","../src/runners/generate/utils.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { styleText } from 'node:util'\nimport { formatMs } from '@internals/utils'\n\n/**\n * xterm-256 color for each pixel letter of the mascot. Fixed codes rather than named colors, so a\n * terminal theme cannot repaint it.\n */\nconst colors: Record<string, number> = {\n /** Hoodie. */\n O: 208,\n /** Shadow where the hood meets the face. */\n R: 166,\n /** Face. */\n F: 223,\n /** Eyes. */\n K: 16,\n /** Blush. */\n B: 217,\n /** Mouth. */\n M: 131,\n}\n\n/**\n * The two pixel rows of the face around the eyes, for each expression.\n */\nconst eyes = {\n open: ['FFFFFFFFFF', 'FFKFFFFKFF'],\n left: ['FFFFFFFFFF', 'FKFFFFKFFF'],\n right: ['FFFFFFFFFF', 'FFFKFFFFKF'],\n blink: ['FFFFFFFFFF', 'FKKFFFFKKF'],\n happy: ['FFKFFFFKFF', 'FKFKFFKFKF'],\n} as const\n\nexport type Expression = keyof typeof eyes\n\n/**\n * The frames the mascot plays when the CLI starts, each with how long it stays on screen. It ends on\n * open eyes, which stay.\n */\nexport const wakeUp: ReadonlyArray<readonly [Expression, number]> = [\n ['blink', 100],\n ['open', 180],\n ['left', 140],\n ['right', 140],\n ['open', 80],\n ['happy', 220],\n ['open', 0],\n]\n\n/**\n * The kid in the orange hoodie from the Kubb logo, one letter per pixel and `.` for none. Each\n * 2 × 2 block becomes one terminal cell, so a block holds at most two colors.\n */\nexport function pixels(expression: Expression): Array<string> {\n const [top, bottom] = eyes[expression]\n\n // prettier-ignore\n return [\n '....OOOOOOOOOO....',\n '..OORRRRRRRRRROO..',\n `..OO${top}OO..`,\n `OOFF${bottom}FFOO`,\n 'OOFFBBFFMMFFBBFFOO',\n '..OOOOFFFFFFOOOO..',\n ]\n}\n\n/**\n * Quadrant glyphs indexed by which quarters are filled: top left 1, top right 2, bottom left 4,\n * bottom right 8.\n */\nconst quadrants = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'\n\n/**\n * Draws each 2 × 2 block of pixels as one quadrant glyph. The glyph's filled quarters take the first\n * color in the block and the rest take the other.\n */\nfunction drawHead(expression: Expression): Array<string> {\n const rows = pixels(expression)\n const lines: Array<string> = []\n\n for (let y = 0; y < rows.length; y += 2) {\n let line = ''\n for (let x = 0; x < rows[y]!.length; x += 2) {\n const block = [rows[y]![x]!, rows[y]![x + 1]!, rows[y + 1]![x]!, rows[y + 1]![x + 1]!]\n const fg = block.find((pixel) => pixel in colors)\n if (!fg) {\n line += ' '\n continue\n }\n const bg = colors[block.find((pixel) => pixel !== fg) ?? '.']\n const mask = block.reduce((bits, pixel, index) => (pixel === fg ? bits | (1 << index) : bits), 0)\n line += `\\x1b[38;5;${colors[fg]}${bg === undefined ? '' : `;48;5;${bg}`}m${quadrants[mask]}\\x1b[0m`\n }\n lines.push(line)\n }\n\n return lines\n}\n\n/**\n * Generates the Kubb mascot welcome banner as an ANSI-colored string: the head with the version,\n * title, and description beside it.\n *\n * @example\n * ```ts\n * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', expression: 'open' }))\n * ```\n */\nexport function getIntro({\n title,\n description,\n version,\n expression,\n}: {\n /**\n * Name of the active configuration or tool being started.\n */\n title: string\n /**\n * Short subtitle shown next to the arrow prompt.\n */\n description: string\n /**\n * Kubb version string rendered in the header.\n */\n version: string\n /**\n * Which face the mascot makes.\n */\n expression: Expression\n}): string {\n // The hoodie's fixed code, so the title is Kubb orange whatever the terminal theme does to `yellow`.\n const brand = (value: string, bold = false) => `\\x1b[${bold ? '1;' : ''}38;5;${colors.O}m${value}\\x1b[0m`\n const text = [brand(`KUBB v${version}`, true), styleText('gray', title), `${brand('➜')} ${styleText('white', description)}`]\n\n return drawHead(expression)\n .map((line, index) => ` ${line} ${text[index]}`)\n .join('\\n')\n}\n\n/**\n * Formats a millisecond duration with a threshold-based ANSI color.\n * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.\n *\n * @example\n * ```ts\n * formatMsWithColor(200) // '\\x1b[32m200ms\\x1b[39m'\n * formatMsWithColor(800) // '\\x1b[33m800ms\\x1b[39m'\n * formatMsWithColor(1500) // '\\x1b[31m1.50s\\x1b[39m'\n * ```\n */\nexport function formatMsWithColor(ms: number): string {\n const formatted = formatMs(ms)\n if (ms <= 500) return styleText('green', formatted)\n if (ms <= 1000) return styleText('yellow', formatted)\n\n return styleText('red', formatted)\n}\n\n/**\n * A `size`-cell progress bar: `done` of `total` as filled cells, the rest light. Any progress shows\n * at least one cell, and only a finished bar fills the last one.\n */\nexport function formatBar(done: number, total: number, size = 20): string {\n const filled = !done ? 0 : done >= total ? size : Math.min(size - 1, Math.max(1, Math.round((done / total) * size)))\n return styleText('magenta', '█'.repeat(filled)) + styleText('dim', '░'.repeat(size - filled))\n}\n","import type { WriterSpinner } from '../loggers/types.ts'\nimport { writer } from './log.ts'\n\n/**\n * A progress spinner, or a writer that prints each message it is given when the terminal cannot\n * animate one. Callers drive both the same way, including the failure state.\n */\nexport function createSpinner(): WriterSpinner {\n return writer().spinner()\n}\n\n/** How long a wait may run before a spinner shows, so a fast answer draws nothing. */\nconst SPINNER_DELAY_MS = 150\n\n/**\n * Awaits `promise`, showing a spinner with `message` only when it takes longer than 150 ms.\n */\nexport async function withSpinner<T>({ promise, message }: { promise: Promise<T>; message: string }): Promise<T> {\n let spinner: WriterSpinner | undefined\n const timer = setTimeout(() => {\n spinner = createSpinner()\n spinner.start(message)\n }, SPINNER_DELAY_MS)\n try {\n return await promise\n } finally {\n clearTimeout(timer)\n spinner?.clear()\n }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { inParallel, matchesStored, read } from '@internals/utils'\nimport type { Hookable, KubbHooks, OutputManifest, Storage } from '@kubb/core'\n\n/** Glob pattern for paths the file watcher ignores. */\nconst WATCHER_IGNORED_PATHS = '**/{.git,node_modules}/**' as const\n\n/**\n * Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.\n * It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.\n */\nconst WATCHER_DEBOUNCE_MS = 250\n\n/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */\nexport function formatChange({ target, removed = false, now = new Date() }: { target: string; removed?: boolean; now?: Date }): string {\n return `${styleText('dim', now.toTimeString().slice(0, 8))} ${styleText('bold', target)} ${removed ? 'removed' : 'changed'}`\n}\n\ntype WatcherLog = {\n info: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Starts a file watcher on the given paths and calls `cb` on any change.\n * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)\n * are debounced into one call. `cb` must never reject: its result is not awaited, so the caller\n * serializes and reports its own builds. Resolves to a function that stops watching.\n */\nexport async function startWatcher(\n path: Array<string>,\n cb: (path: Array<string>) => Promise<void>,\n log: WatcherLog = { info: console.log, error: console.log },\n): Promise<() => Promise<void>> {\n const { watch } = await import('chokidar')\n // `ignoreInitial` skips the `add` events chokidar fires for existing files at startup, which\n // would otherwise rebuild right after the initial run.\n const watcher = watch(path, { ignorePermissionErrors: true, ignored: WATCHER_IGNORED_PATHS, ignoreInitial: true })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => void watcher.close())\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n watcher.on('all', (type, file) => {\n log.info(formatChange({ target: relative(process.cwd(), file), removed: type === 'unlink' }))\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n void cb(path)\n }, WATCHER_DEBOUNCE_MS)\n })\n\n return async () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n await watcher.close()\n }\n}\n\n/**\n * What one build produced: the schema and operation names the plugins generated.\n */\nexport type BuildSnapshot = {\n schemas: Set<string>\n operations: Set<string>\n}\n\nfunction createSnapshot(): BuildSnapshot {\n return { schemas: new Set(), operations: new Set() }\n}\n\n/**\n * Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished\n * build's snapshot and starts the next one.\n */\nexport function trackBuild(hooks: Hookable<KubbHooks>): { take(): BuildSnapshot } {\n let current = createSnapshot()\n\n hooks.hook('kubb:generate:schema', (node) => {\n if (node.name) current.schemas.add(node.name)\n })\n hooks.hook('kubb:generate:operation', (node) => {\n current.operations.add(node.operationId)\n })\n\n return {\n take() {\n const snapshot = current\n current = createSnapshot()\n return snapshot\n },\n }\n}\n\ntype NameChange = { added: number; removed: number }\n\n/**\n * How a rebuild differs from the build before it: the spec's added and removed schemas and operations.\n */\nexport type BuildDiff = {\n schemas: NameChange\n operations: NameChange\n}\n\nfunction diffNames(previous: Set<string>, next: Set<string>): NameChange {\n return { added: [...next].filter((name) => !previous.has(name)).length, removed: [...previous].filter((name) => !next.has(name)).length }\n}\n\n/**\n * Compares two {@link BuildSnapshot}s by name.\n */\nexport function diffBuilds(previous: BuildSnapshot, next: BuildSnapshot): BuildDiff {\n return {\n schemas: diffNames(previous.schemas, next.schemas),\n operations: diffNames(previous.operations, next.operations),\n }\n}\n\n/**\n * Compares file contents or fingerprints by path.\n */\nexport function diffFiles(previous: ReadonlyMap<string, string>, next: ReadonlyMap<string, string>): { files: Array<FileChange>; unchanged: number } {\n const files: Array<FileChange> = []\n let unchanged = 0\n for (const [path, fingerprint] of next) {\n const before = previous.get(path)\n if (before === fingerprint) unchanged++\n else files.push({ status: before === undefined ? 'A' : 'M', path })\n }\n for (const path of previous.keys()) {\n if (!next.has(path)) files.push({ status: 'D', path })\n }\n\n return { files, unchanged }\n}\n\n/** A changed file with how many lines it gains and loses, counted as a multiset, which is cheap and close enough for a plan. */\nexport type FileChange = { status: 'A' | 'M' | 'D'; path: string; added?: number; removed?: number }\n\nfunction countLineChanges(before: string, after: string): { added: number; removed: number } {\n const remaining = new Map<string, number>()\n for (const line of before ? before.split('\\n') : []) remaining.set(line, (remaining.get(line) ?? 0) + 1)\n let added = 0\n for (const line of after ? after.split('\\n') : []) {\n const count = remaining.get(line) ?? 0\n if (count > 0) remaining.set(line, count - 1)\n else added++\n }\n const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0)\n return { added, removed }\n}\n\n/**\n * Reads the output before generation, so cleaning the directory cannot erase the diff baseline.\n * A missing directory or an unreadable file is skipped.\n */\nexport async function readOutputFiles(outputPath: string): Promise<Map<string, string>> {\n const entries = await readdir(outputPath, { recursive: true, withFileTypes: true }).catch(() => [])\n const files = new Map<string, string>()\n await inParallel({\n items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),\n limit: 50,\n run: async (filePath) => {\n const source = await read(filePath).catch(() => null)\n if (source !== null) files.set(filePath, source)\n },\n })\n return files\n}\n\n/**\n * Compares generated output with the files captured before generation. The manifest recognizes\n * output rewritten by a formatter, and `clean` includes files the run would delete.\n */\nexport async function diffOutput({\n storage,\n paths,\n clean,\n manifest,\n previousFiles,\n}: {\n storage: Storage\n paths: ReadonlyArray<string>\n clean: boolean\n manifest?: Pick<OutputManifest, 'isUpToDate'>\n /**\n * The output captured before a real run, including files removed by cleaning.\n */\n previousFiles: ReadonlyMap<string, string>\n}): Promise<{ files: Array<FileChange>; unchanged: number }> {\n const generated = new Set(paths)\n const changes: Array<FileChange | undefined> = []\n let unchanged = 0\n await inParallel({\n items: paths,\n limit: 50,\n run: async (filePath, index) => {\n const next = await storage.readItem(filePath)\n if (next === null) return\n const previous = previousFiles.get(filePath) ?? null\n if (previous !== null && (matchesStored({ stored: previous, source: next }) || manifest?.isUpToDate({ key: filePath, source: next, disk: previous }))) {\n unchanged++\n return\n }\n changes[index] = {\n status: previous === null ? 'A' : 'M',\n path: filePath,\n ...countLineChanges(previous?.trim() ?? '', previous === null ? next.trimEnd() : next.trim()),\n }\n },\n })\n const files = changes.filter((file) => file !== undefined)\n if (clean) {\n for (const [filePath, source] of previousFiles) {\n if (!generated.has(filePath)) files.push({ status: 'D', path: filePath, ...countLineChanges(source.trimEnd(), '') })\n }\n }\n\n return { files, unchanged }\n}\n\ntype KeyInput = Pick<NodeJS.ReadStream, 'isTTY' | 'setRawMode' | 'resume' | 'pause' | 'setEncoding' | 'on'>\n\ntype KeyOptions = {\n rebuild: () => void\n /**\n * Ends the process with this exit code: `0` for `q`, `130` for ctrl+c, which raw mode turns into a key.\n */\n quit: (code: number) => void\n input?: KeyInput\n}\n\n/**\n * Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c\n * working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw\n * mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode\n * is restored on exit.\n */\nexport function listenForKeys({ rebuild, quit, input = process.stdin }: KeyOptions): (() => void) | undefined {\n if (!input.isTTY) return undefined\n\n const arm = () => {\n input.setRawMode(true)\n input.resume()\n }\n input.setEncoding('utf8')\n input.on('data', (data: string) => {\n const key = data.trim()\n if (key === 'r') {\n rebuild()\n return\n }\n if (key === 'q' || data === '\\u0003') {\n input.setRawMode(false)\n input.pause()\n quit(key === 'q' ? 0 : 130)\n }\n })\n process.once('exit', () => input.setRawMode(false))\n arm()\n\n return arm\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
2
- import { i as formatMs, o as runtime } from "./Telemetry-wrNtjGQD.js";
3
- import { p as writer } from "./text-9khn8L1h.js";
2
+ import { i as formatMs, o as runtime } from "./Telemetry-Dc79_cIA.js";
3
+ import { p as writer } from "./text-CEyJhBnh.js";
4
4
  import { styleText } from "node:util";
5
5
  import process from "node:process";
6
6
  import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
@@ -533,4 +533,4 @@ function listenForKeys({ rebuild, quit, input = process.stdin }) {
533
533
  //#endregion
534
534
  export { write as _, readOutputFiles as a, createSpinner as c, formatMsWithColor as d, getIntro as f, read as g, isPathInside as h, listenForKeys as i, withSpinner as l, exists as m, diffFiles as n, startWatcher as o, wakeUp as p, diffOutput as r, trackBuild as s, diffBuilds as t, formatBar as u, writeRaw as v };
535
535
 
536
- //# sourceMappingURL=utils-DRyjNlug.js.map
536
+ //# sourceMappingURL=utils-D8VN40bN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils-DRyjNlug.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/promise.ts","../src/loggers/banner.ts","../src/ui/spinner.ts","../src/runners/generate/utils.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { styleText } from 'node:util'\nimport { formatMs } from '@internals/utils'\n\n/**\n * xterm-256 color for each pixel letter of the mascot. Fixed codes rather than named colors, so a\n * terminal theme cannot repaint it.\n */\nconst colors: Record<string, number> = {\n /** Hoodie. */\n O: 208,\n /** Shadow where the hood meets the face. */\n R: 166,\n /** Face. */\n F: 223,\n /** Eyes. */\n K: 16,\n /** Blush. */\n B: 217,\n /** Mouth. */\n M: 131,\n}\n\n/**\n * The two pixel rows of the face around the eyes, for each expression.\n */\nconst eyes = {\n open: ['FFFFFFFFFF', 'FFKFFFFKFF'],\n left: ['FFFFFFFFFF', 'FKFFFFKFFF'],\n right: ['FFFFFFFFFF', 'FFFKFFFFKF'],\n blink: ['FFFFFFFFFF', 'FKKFFFFKKF'],\n happy: ['FFKFFFFKFF', 'FKFKFFKFKF'],\n} as const\n\nexport type Expression = keyof typeof eyes\n\n/**\n * The frames the mascot plays when the CLI starts, each with how long it stays on screen. It ends on\n * open eyes, which stay.\n */\nexport const wakeUp: ReadonlyArray<readonly [Expression, number]> = [\n ['blink', 100],\n ['open', 180],\n ['left', 140],\n ['right', 140],\n ['open', 80],\n ['happy', 220],\n ['open', 0],\n]\n\n/**\n * The kid in the orange hoodie from the Kubb logo, one letter per pixel and `.` for none. Each\n * 2 × 2 block becomes one terminal cell, so a block holds at most two colors.\n */\nexport function pixels(expression: Expression): Array<string> {\n const [top, bottom] = eyes[expression]\n\n // prettier-ignore\n return [\n '....OOOOOOOOOO....',\n '..OORRRRRRRRRROO..',\n `..OO${top}OO..`,\n `OOFF${bottom}FFOO`,\n 'OOFFBBFFMMFFBBFFOO',\n '..OOOOFFFFFFOOOO..',\n ]\n}\n\n/**\n * Quadrant glyphs indexed by which quarters are filled: top left 1, top right 2, bottom left 4,\n * bottom right 8.\n */\nconst quadrants = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'\n\n/**\n * Draws each 2 × 2 block of pixels as one quadrant glyph. The glyph's filled quarters take the first\n * color in the block and the rest take the other.\n */\nfunction drawHead(expression: Expression): Array<string> {\n const rows = pixels(expression)\n const lines: Array<string> = []\n\n for (let y = 0; y < rows.length; y += 2) {\n let line = ''\n for (let x = 0; x < rows[y]!.length; x += 2) {\n const block = [rows[y]![x]!, rows[y]![x + 1]!, rows[y + 1]![x]!, rows[y + 1]![x + 1]!]\n const fg = block.find((pixel) => pixel in colors)\n if (!fg) {\n line += ' '\n continue\n }\n const bg = colors[block.find((pixel) => pixel !== fg) ?? '.']\n const mask = block.reduce((bits, pixel, index) => (pixel === fg ? bits | (1 << index) : bits), 0)\n line += `\\x1b[38;5;${colors[fg]}${bg === undefined ? '' : `;48;5;${bg}`}m${quadrants[mask]}\\x1b[0m`\n }\n lines.push(line)\n }\n\n return lines\n}\n\n/**\n * Generates the Kubb mascot welcome banner as an ANSI-colored string: the head with the version,\n * title, and description beside it.\n *\n * @example\n * ```ts\n * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', expression: 'open' }))\n * ```\n */\nexport function getIntro({\n title,\n description,\n version,\n expression,\n}: {\n /**\n * Name of the active configuration or tool being started.\n */\n title: string\n /**\n * Short subtitle shown next to the arrow prompt.\n */\n description: string\n /**\n * Kubb version string rendered in the header.\n */\n version: string\n /**\n * Which face the mascot makes.\n */\n expression: Expression\n}): string {\n // The hoodie's fixed code, so the title is Kubb orange whatever the terminal theme does to `yellow`.\n const brand = (value: string, bold = false) => `\\x1b[${bold ? '1;' : ''}38;5;${colors.O}m${value}\\x1b[0m`\n const text = [brand(`KUBB v${version}`, true), styleText('gray', title), `${brand('➜')} ${styleText('white', description)}`]\n\n return drawHead(expression)\n .map((line, index) => ` ${line} ${text[index]}`)\n .join('\\n')\n}\n\n/**\n * Formats a millisecond duration with a threshold-based ANSI color.\n * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.\n *\n * @example\n * ```ts\n * formatMsWithColor(200) // '\\x1b[32m200ms\\x1b[39m'\n * formatMsWithColor(800) // '\\x1b[33m800ms\\x1b[39m'\n * formatMsWithColor(1500) // '\\x1b[31m1.50s\\x1b[39m'\n * ```\n */\nexport function formatMsWithColor(ms: number): string {\n const formatted = formatMs(ms)\n if (ms <= 500) return styleText('green', formatted)\n if (ms <= 1000) return styleText('yellow', formatted)\n\n return styleText('red', formatted)\n}\n\n/**\n * A `size`-cell progress bar: `done` of `total` as filled cells, the rest light. Any progress shows\n * at least one cell, and only a finished bar fills the last one.\n */\nexport function formatBar(done: number, total: number, size = 20): string {\n const filled = !done ? 0 : done >= total ? size : Math.min(size - 1, Math.max(1, Math.round((done / total) * size)))\n return styleText('magenta', '█'.repeat(filled)) + styleText('dim', '░'.repeat(size - filled))\n}\n","import type { WriterSpinner } from '../loggers/types.ts'\nimport { writer } from './log.ts'\n\n/**\n * A progress spinner, or a writer that prints each message it is given when the terminal cannot\n * animate one. Callers drive both the same way, including the failure state.\n */\nexport function createSpinner(): WriterSpinner {\n return writer().spinner()\n}\n\n/** How long a wait may run before a spinner shows, so a fast answer draws nothing. */\nconst SPINNER_DELAY_MS = 150\n\n/**\n * Awaits `promise`, showing a spinner with `message` only when it takes longer than 150 ms.\n */\nexport async function withSpinner<T>({ promise, message }: { promise: Promise<T>; message: string }): Promise<T> {\n let spinner: WriterSpinner | undefined\n const timer = setTimeout(() => {\n spinner = createSpinner()\n spinner.start(message)\n }, SPINNER_DELAY_MS)\n try {\n return await promise\n } finally {\n clearTimeout(timer)\n spinner?.clear()\n }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { inParallel, matchesStored, read } from '@internals/utils'\nimport type { Hookable, KubbHooks, OutputManifest, Storage } from '@kubb/core'\n\n/** Glob pattern for paths the file watcher ignores. */\nconst WATCHER_IGNORED_PATHS = '**/{.git,node_modules}/**' as const\n\n/**\n * Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.\n * It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.\n */\nconst WATCHER_DEBOUNCE_MS = 250\n\n/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */\nexport function formatChange({ target, removed = false, now = new Date() }: { target: string; removed?: boolean; now?: Date }): string {\n return `${styleText('dim', now.toTimeString().slice(0, 8))} ${styleText('bold', target)} ${removed ? 'removed' : 'changed'}`\n}\n\ntype WatcherLog = {\n info: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Starts a file watcher on the given paths and calls `cb` on any change.\n * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)\n * are debounced into one call. `cb` must never reject: its result is not awaited, so the caller\n * serializes and reports its own builds. Resolves to a function that stops watching.\n */\nexport async function startWatcher(\n path: Array<string>,\n cb: (path: Array<string>) => Promise<void>,\n log: WatcherLog = { info: console.log, error: console.log },\n): Promise<() => Promise<void>> {\n const { watch } = await import('chokidar')\n // `ignoreInitial` skips the `add` events chokidar fires for existing files at startup, which\n // would otherwise rebuild right after the initial run.\n const watcher = watch(path, { ignorePermissionErrors: true, ignored: WATCHER_IGNORED_PATHS, ignoreInitial: true })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => void watcher.close())\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n watcher.on('all', (type, file) => {\n log.info(formatChange({ target: relative(process.cwd(), file), removed: type === 'unlink' }))\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n void cb(path)\n }, WATCHER_DEBOUNCE_MS)\n })\n\n return async () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n await watcher.close()\n }\n}\n\n/**\n * What one build produced: the schema and operation names the plugins generated.\n */\nexport type BuildSnapshot = {\n schemas: Set<string>\n operations: Set<string>\n}\n\nfunction createSnapshot(): BuildSnapshot {\n return { schemas: new Set(), operations: new Set() }\n}\n\n/**\n * Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished\n * build's snapshot and starts the next one.\n */\nexport function trackBuild(hooks: Hookable<KubbHooks>): { take(): BuildSnapshot } {\n let current = createSnapshot()\n\n hooks.hook('kubb:generate:schema', (node) => {\n if (node.name) current.schemas.add(node.name)\n })\n hooks.hook('kubb:generate:operation', (node) => {\n current.operations.add(node.operationId)\n })\n\n return {\n take() {\n const snapshot = current\n current = createSnapshot()\n return snapshot\n },\n }\n}\n\ntype NameChange = { added: number; removed: number }\n\n/**\n * How a rebuild differs from the build before it: the spec's added and removed schemas and operations.\n */\nexport type BuildDiff = {\n schemas: NameChange\n operations: NameChange\n}\n\nfunction diffNames(previous: Set<string>, next: Set<string>): NameChange {\n return { added: [...next].filter((name) => !previous.has(name)).length, removed: [...previous].filter((name) => !next.has(name)).length }\n}\n\n/**\n * Compares two {@link BuildSnapshot}s by name.\n */\nexport function diffBuilds(previous: BuildSnapshot, next: BuildSnapshot): BuildDiff {\n return {\n schemas: diffNames(previous.schemas, next.schemas),\n operations: diffNames(previous.operations, next.operations),\n }\n}\n\n/**\n * Compares file contents or fingerprints by path.\n */\nexport function diffFiles(previous: ReadonlyMap<string, string>, next: ReadonlyMap<string, string>): { files: Array<FileChange>; unchanged: number } {\n const files: Array<FileChange> = []\n let unchanged = 0\n for (const [path, fingerprint] of next) {\n const before = previous.get(path)\n if (before === fingerprint) unchanged++\n else files.push({ status: before === undefined ? 'A' : 'M', path })\n }\n for (const path of previous.keys()) {\n if (!next.has(path)) files.push({ status: 'D', path })\n }\n\n return { files, unchanged }\n}\n\n/** A changed file with how many lines it gains and loses, counted as a multiset, which is cheap and close enough for a plan. */\nexport type FileChange = { status: 'A' | 'M' | 'D'; path: string; added?: number; removed?: number }\n\nfunction countLineChanges(before: string, after: string): { added: number; removed: number } {\n const remaining = new Map<string, number>()\n for (const line of before ? before.split('\\n') : []) remaining.set(line, (remaining.get(line) ?? 0) + 1)\n let added = 0\n for (const line of after ? after.split('\\n') : []) {\n const count = remaining.get(line) ?? 0\n if (count > 0) remaining.set(line, count - 1)\n else added++\n }\n const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0)\n return { added, removed }\n}\n\n/**\n * Reads the output before generation, so cleaning the directory cannot erase the diff baseline.\n * A missing directory or an unreadable file is skipped.\n */\nexport async function readOutputFiles(outputPath: string): Promise<Map<string, string>> {\n const entries = await readdir(outputPath, { recursive: true, withFileTypes: true }).catch(() => [])\n const files = new Map<string, string>()\n await inParallel({\n items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),\n limit: 50,\n run: async (filePath) => {\n const source = await read(filePath).catch(() => null)\n if (source !== null) files.set(filePath, source)\n },\n })\n return files\n}\n\n/**\n * Compares generated output with the files captured before generation. The manifest recognizes\n * output rewritten by a formatter, and `clean` includes files the run would delete.\n */\nexport async function diffOutput({\n storage,\n paths,\n clean,\n manifest,\n previousFiles,\n}: {\n storage: Storage\n paths: ReadonlyArray<string>\n clean: boolean\n manifest?: Pick<OutputManifest, 'isUpToDate'>\n /**\n * The output captured before a real run, including files removed by cleaning.\n */\n previousFiles: ReadonlyMap<string, string>\n}): Promise<{ files: Array<FileChange>; unchanged: number }> {\n const generated = new Set(paths)\n const changes: Array<FileChange | undefined> = []\n let unchanged = 0\n await inParallel({\n items: paths,\n limit: 50,\n run: async (filePath, index) => {\n const next = await storage.readItem(filePath)\n if (next === null) return\n const previous = previousFiles.get(filePath) ?? null\n if (previous !== null && (matchesStored({ stored: previous, source: next }) || manifest?.isUpToDate({ key: filePath, source: next, disk: previous }))) {\n unchanged++\n return\n }\n changes[index] = {\n status: previous === null ? 'A' : 'M',\n path: filePath,\n ...countLineChanges(previous?.trim() ?? '', previous === null ? next.trimEnd() : next.trim()),\n }\n },\n })\n const files = changes.filter((file) => file !== undefined)\n if (clean) {\n for (const [filePath, source] of previousFiles) {\n if (!generated.has(filePath)) files.push({ status: 'D', path: filePath, ...countLineChanges(source.trimEnd(), '') })\n }\n }\n\n return { files, unchanged }\n}\n\ntype KeyInput = Pick<NodeJS.ReadStream, 'isTTY' | 'setRawMode' | 'resume' | 'pause' | 'setEncoding' | 'on'>\n\ntype KeyOptions = {\n rebuild: () => void\n /**\n * Ends the process with this exit code: `0` for `q`, `130` for ctrl+c, which raw mode turns into a key.\n */\n quit: (code: number) => void\n input?: KeyInput\n}\n\n/**\n * Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c\n * working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw\n * mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode\n * is restored on exit.\n */\nexport function listenForKeys({ rebuild, quit, input = process.stdin }: KeyOptions): (() => void) | undefined {\n if (!input.isTTY) return undefined\n\n const arm = () => {\n input.setRawMode(true)\n input.resume()\n }\n input.setEncoding('utf8')\n input.on('data', (data: string) => {\n const key = data.trim()\n if (key === 'r') {\n rebuild()\n return\n }\n if (key === 'q' || data === '\\u0003') {\n input.setRawMode(false)\n input.pause()\n quit(key === 'q' ? 0 : 130)\n }\n })\n process.once('exit', () => input.setRawMode(false))\n arm()\n\n return arm\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils-D8VN40bN.js","names":[],"sources":["../../../internals/utils/src/fs.ts","../../../internals/utils/src/promise.ts","../src/loggers/banner.ts","../src/ui/spinner.ts","../src/runners/generate/utils.ts"],"sourcesContent":["import { access, glob as globNode, mkdir, readFile, writeFile } from 'node:fs/promises'\n\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { runtime } from './runtime.ts'\n\nexport { existsSync } from 'node:fs'\nexport { mkdir, mkdtemp, rm } from 'node:fs/promises'\n\n/**\n * Resolves to `true` when the file or directory at `path` exists.\n * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.\n *\n * @example\n * ```ts\n * if (await exists('./kubb.config.ts')) {\n * const content = await read('./kubb.config.ts')\n * }\n * ```\n */\nexport async function exists(path: string): Promise<boolean> {\n if (runtime.isBun) {\n return Bun.file(path).exists()\n }\n return access(path).then(\n () => true,\n () => false,\n )\n}\n\n/**\n * Reads the file at `path` as a UTF-8 string.\n * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.\n *\n * @example\n * ```ts\n * const source = await read('./src/Pet.ts')\n * ```\n */\nexport async function read(path: string): Promise<string> {\n if (runtime.isBun) {\n return Bun.file(path).text()\n }\n return readFile(path, { encoding: 'utf8' })\n}\n\ntype WriteOptions = {\n /**\n * Previously read content, or `null` when the file does not exist.\n * Omitting this value reads the file before writing.\n */\n stored?: string | null\n /**\n * When `true`, re-reads the file immediately after writing and throws if the\n * content does not match — useful for catching write failures on unreliable file systems.\n */\n sanity?: boolean\n}\n\n/**\n * Whether `stored` already holds `source`, comparing on the trimmed text rather than the exact\n * bytes. Surrounding whitespace is what a formatter adds and what editors strip, and neither is a\n * reason to rewrite the file.\n *\n * Both sides are trimmed, so a storage that keeps bytes verbatim settles on the same answer as one\n * that normalizes what it stores. Trimming only `stored` would leave a source with leading\n * whitespace rewritten on every build, since the stored copy keeps the whitespace the comparison\n * has already dropped.\n */\nexport function matchesStored({ stored, source }: { stored: string; source: string }): boolean {\n return stored.trim() === source.trim()\n}\n\n/**\n * Writes `data` to `path`, trimming surrounding whitespace and ending the file with a single newline\n * the way prettier, biome, and oxfmt all do.\n * Skips the write when the trimmed content is empty, or when the file already holds that content.\n * Creates any missing parent directories automatically.\n * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.\n *\n * @example\n * ```ts\n * await write('./src/Pet.ts', source) // writes and returns the trimmed content plus a newline\n * await write('./src/Pet.ts', source) // null — file unchanged\n * await write('./src/Pet.ts', ' ') // null — empty content skipped\n * ```\n */\nexport async function write(path: string, data: string, options: WriteOptions = {}): Promise<string | null> {\n const trimmed = data.trim()\n if (trimmed === '') return null\n\n const content = `${trimmed}\\n`\n const resolved = resolve(path)\n let stored = options.stored\n\n if (stored === undefined) {\n if (runtime.isBun) {\n const file = Bun.file(resolved)\n stored = (await file.exists()) ? await file.text() : null\n } else {\n try {\n stored = await readFile(resolved, { encoding: 'utf-8' })\n } catch {\n /* file doesn't exist yet */\n stored = null\n }\n }\n }\n if (matchesStored({ stored: stored ?? '', source: trimmed })) return null\n\n await writeRaw(resolved, content)\n\n if (options.sanity) {\n const savedData = await read(resolved)\n if (savedData !== content) {\n throw new Error(`Sanity check failed for ${path}\\n\\nData[${data.length}]:\\n${data}\\n\\nSaved[${savedData.length}]:\\n${savedData}\\n`)\n }\n return savedData\n }\n\n return content\n}\n\n/**\n * Writes UTF-8 content verbatim, including empty files and surrounding whitespace.\n * Uses Bun.write under Bun and creates missing parent directories under either runtime.\n *\n * @example\n * await writeRaw('./cache/output.ts', ' // keep these bytes\\n\\n')\n */\nexport async function writeRaw(path: string, content: string): Promise<void> {\n const resolved = resolve(path)\n if (runtime.isBun) {\n await Bun.write(resolved, content)\n return\n }\n try {\n await writeFile(resolved, content, { encoding: 'utf-8' })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n await mkdir(dirname(resolved), { recursive: true })\n await writeFile(resolved, content, { encoding: 'utf-8' })\n }\n}\n\n/**\n * Lists non-hidden regular files below `directory` as relative POSIX paths, excluding directories and symlinks.\n * Uses Bun.Glob under Bun because its Node glob compatibility API lacks directory-entry support.\n *\n * @example\n * for await (const path of listFiles('./src')) console.log(path)\n */\nexport async function* listFiles(directory: string): AsyncGenerator<string> {\n const cwd = resolve(directory)\n if (runtime.isBun) {\n yield* new Bun.Glob('**/*').scan({ cwd, dot: false, onlyFiles: true, followSymlinks: false })\n return\n }\n for await (const entry of globNode('**/*', { cwd, withFileTypes: true })) {\n if (entry.isFile()) yield toPosixPath(relative(cwd, join(entry.parentPath, entry.name)))\n }\n}\n\n/**\n * Converts a filesystem path to use POSIX (`/`) separators.\n *\n * Most of the codebase compares and composes paths as strings (prefix matching, joining for\n * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated\n * paths, but on Windows it returns `\\`-separated paths, which breaks every such comparison.\n *\n * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the\n * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is\n * exercisable from POSIX CI.\n *\n * @example\n * toPosixPath('C:\\\\repo\\\\src\\\\pet.ts') // 'C:/repo/src/pet.ts'\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replaceAll('\\\\', '/')\n}\n\n/**\n * Strips the file extension from a path or file name.\n * Only removes the last `.ext` segment when the dot is not part of a directory name.\n *\n * @example\n * trimExtName('petStore.ts') // 'petStore'\n * trimExtName('/src/models/pet.ts') // '/src/models/pet'\n * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'\n * trimExtName('noExtension') // 'noExtension'\n */\nexport function trimExtName(text: string): string {\n const dotIndex = text.lastIndexOf('.')\n if (dotIndex > 0 && !text.includes('/', dotIndex)) {\n return text.slice(0, dotIndex)\n }\n return text\n}\n\n/**\n * Resolves to `true` when `path` is `parent` itself or nested inside it. Both sides are resolved\n * to absolute paths first, so relative and `..`-containing inputs compare correctly.\n *\n * Guards a destructive or an out-of-tree operation: before wiping an output directory, check that\n * it does not contain the project root, and before loading a path a caller supplied, check that it\n * did not escape the directory it is allowed to read from.\n *\n * @example\n * isPathInside('./src/gen', '.') // true — nested inside the root\n * isPathInside('.', '.') // true — the same directory counts as inside\n * isPathInside('.', './src/gen') // false — the root is not inside its own output\n * isPathInside('../other', '.') // false — escapes the root\n */\nexport function isPathInside(path: string, parent: string): boolean {\n const resolvedPath = resolve(path)\n const resolvedParent = resolve(parent)\n if (resolvedPath === resolvedParent) {\n return true\n }\n\n const rel = relative(resolvedParent, resolvedPath)\n\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n}\n","/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { styleText } from 'node:util'\nimport { formatMs } from '@internals/utils'\n\n/**\n * xterm-256 color for each pixel letter of the mascot. Fixed codes rather than named colors, so a\n * terminal theme cannot repaint it.\n */\nconst colors: Record<string, number> = {\n /** Hoodie. */\n O: 208,\n /** Shadow where the hood meets the face. */\n R: 166,\n /** Face. */\n F: 223,\n /** Eyes. */\n K: 16,\n /** Blush. */\n B: 217,\n /** Mouth. */\n M: 131,\n}\n\n/**\n * The two pixel rows of the face around the eyes, for each expression.\n */\nconst eyes = {\n open: ['FFFFFFFFFF', 'FFKFFFFKFF'],\n left: ['FFFFFFFFFF', 'FKFFFFKFFF'],\n right: ['FFFFFFFFFF', 'FFFKFFFFKF'],\n blink: ['FFFFFFFFFF', 'FKKFFFFKKF'],\n happy: ['FFKFFFFKFF', 'FKFKFFKFKF'],\n} as const\n\nexport type Expression = keyof typeof eyes\n\n/**\n * The frames the mascot plays when the CLI starts, each with how long it stays on screen. It ends on\n * open eyes, which stay.\n */\nexport const wakeUp: ReadonlyArray<readonly [Expression, number]> = [\n ['blink', 100],\n ['open', 180],\n ['left', 140],\n ['right', 140],\n ['open', 80],\n ['happy', 220],\n ['open', 0],\n]\n\n/**\n * The kid in the orange hoodie from the Kubb logo, one letter per pixel and `.` for none. Each\n * 2 × 2 block becomes one terminal cell, so a block holds at most two colors.\n */\nexport function pixels(expression: Expression): Array<string> {\n const [top, bottom] = eyes[expression]\n\n // prettier-ignore\n return [\n '....OOOOOOOOOO....',\n '..OORRRRRRRRRROO..',\n `..OO${top}OO..`,\n `OOFF${bottom}FFOO`,\n 'OOFFBBFFMMFFBBFFOO',\n '..OOOOFFFFFFOOOO..',\n ]\n}\n\n/**\n * Quadrant glyphs indexed by which quarters are filled: top left 1, top right 2, bottom left 4,\n * bottom right 8.\n */\nconst quadrants = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'\n\n/**\n * Draws each 2 × 2 block of pixels as one quadrant glyph. The glyph's filled quarters take the first\n * color in the block and the rest take the other.\n */\nfunction drawHead(expression: Expression): Array<string> {\n const rows = pixels(expression)\n const lines: Array<string> = []\n\n for (let y = 0; y < rows.length; y += 2) {\n let line = ''\n for (let x = 0; x < rows[y]!.length; x += 2) {\n const block = [rows[y]![x]!, rows[y]![x + 1]!, rows[y + 1]![x]!, rows[y + 1]![x + 1]!]\n const fg = block.find((pixel) => pixel in colors)\n if (!fg) {\n line += ' '\n continue\n }\n const bg = colors[block.find((pixel) => pixel !== fg) ?? '.']\n const mask = block.reduce((bits, pixel, index) => (pixel === fg ? bits | (1 << index) : bits), 0)\n line += `\\x1b[38;5;${colors[fg]}${bg === undefined ? '' : `;48;5;${bg}`}m${quadrants[mask]}\\x1b[0m`\n }\n lines.push(line)\n }\n\n return lines\n}\n\n/**\n * Generates the Kubb mascot welcome banner as an ANSI-colored string: the head with the version,\n * title, and description beside it.\n *\n * @example\n * ```ts\n * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', expression: 'open' }))\n * ```\n */\nexport function getIntro({\n title,\n description,\n version,\n expression,\n}: {\n /**\n * Name of the active configuration or tool being started.\n */\n title: string\n /**\n * Short subtitle shown next to the arrow prompt.\n */\n description: string\n /**\n * Kubb version string rendered in the header.\n */\n version: string\n /**\n * Which face the mascot makes.\n */\n expression: Expression\n}): string {\n // The hoodie's fixed code, so the title is Kubb orange whatever the terminal theme does to `yellow`.\n const brand = (value: string, bold = false) => `\\x1b[${bold ? '1;' : ''}38;5;${colors.O}m${value}\\x1b[0m`\n const text = [brand(`KUBB v${version}`, true), styleText('gray', title), `${brand('➜')} ${styleText('white', description)}`]\n\n return drawHead(expression)\n .map((line, index) => ` ${line} ${text[index]}`)\n .join('\\n')\n}\n\n/**\n * Formats a millisecond duration with a threshold-based ANSI color.\n * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.\n *\n * @example\n * ```ts\n * formatMsWithColor(200) // '\\x1b[32m200ms\\x1b[39m'\n * formatMsWithColor(800) // '\\x1b[33m800ms\\x1b[39m'\n * formatMsWithColor(1500) // '\\x1b[31m1.50s\\x1b[39m'\n * ```\n */\nexport function formatMsWithColor(ms: number): string {\n const formatted = formatMs(ms)\n if (ms <= 500) return styleText('green', formatted)\n if (ms <= 1000) return styleText('yellow', formatted)\n\n return styleText('red', formatted)\n}\n\n/**\n * A `size`-cell progress bar: `done` of `total` as filled cells, the rest light. Any progress shows\n * at least one cell, and only a finished bar fills the last one.\n */\nexport function formatBar(done: number, total: number, size = 20): string {\n const filled = !done ? 0 : done >= total ? size : Math.min(size - 1, Math.max(1, Math.round((done / total) * size)))\n return styleText('magenta', '█'.repeat(filled)) + styleText('dim', '░'.repeat(size - filled))\n}\n","import type { WriterSpinner } from '../loggers/types.ts'\nimport { writer } from './log.ts'\n\n/**\n * A progress spinner, or a writer that prints each message it is given when the terminal cannot\n * animate one. Callers drive both the same way, including the failure state.\n */\nexport function createSpinner(): WriterSpinner {\n return writer().spinner()\n}\n\n/** How long a wait may run before a spinner shows, so a fast answer draws nothing. */\nconst SPINNER_DELAY_MS = 150\n\n/**\n * Awaits `promise`, showing a spinner with `message` only when it takes longer than 150 ms.\n */\nexport async function withSpinner<T>({ promise, message }: { promise: Promise<T>; message: string }): Promise<T> {\n let spinner: WriterSpinner | undefined\n const timer = setTimeout(() => {\n spinner = createSpinner()\n spinner.start(message)\n }, SPINNER_DELAY_MS)\n try {\n return await promise\n } finally {\n clearTimeout(timer)\n spinner?.clear()\n }\n}\n","import { readdir } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { inParallel, matchesStored, read } from '@internals/utils'\nimport type { Hookable, KubbHooks, OutputManifest, Storage } from '@kubb/core'\n\n/** Glob pattern for paths the file watcher ignores. */\nconst WATCHER_IGNORED_PATHS = '**/{.git,node_modules}/**' as const\n\n/**\n * Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.\n * It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.\n */\nconst WATCHER_DEBOUNCE_MS = 250\n\n/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */\nexport function formatChange({ target, removed = false, now = new Date() }: { target: string; removed?: boolean; now?: Date }): string {\n return `${styleText('dim', now.toTimeString().slice(0, 8))} ${styleText('bold', target)} ${removed ? 'removed' : 'changed'}`\n}\n\ntype WatcherLog = {\n info: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Starts a file watcher on the given paths and calls `cb` on any change.\n * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)\n * are debounced into one call. `cb` must never reject: its result is not awaited, so the caller\n * serializes and reports its own builds. Resolves to a function that stops watching.\n */\nexport async function startWatcher(\n path: Array<string>,\n cb: (path: Array<string>) => Promise<void>,\n log: WatcherLog = { info: console.log, error: console.log },\n): Promise<() => Promise<void>> {\n const { watch } = await import('chokidar')\n // `ignoreInitial` skips the `add` events chokidar fires for existing files at startup, which\n // would otherwise rebuild right after the initial run.\n const watcher = watch(path, { ignorePermissionErrors: true, ignored: WATCHER_IGNORED_PATHS, ignoreInitial: true })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => void watcher.close())\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n watcher.on('all', (type, file) => {\n log.info(formatChange({ target: relative(process.cwd(), file), removed: type === 'unlink' }))\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n void cb(path)\n }, WATCHER_DEBOUNCE_MS)\n })\n\n return async () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n await watcher.close()\n }\n}\n\n/**\n * What one build produced: the schema and operation names the plugins generated.\n */\nexport type BuildSnapshot = {\n schemas: Set<string>\n operations: Set<string>\n}\n\nfunction createSnapshot(): BuildSnapshot {\n return { schemas: new Set(), operations: new Set() }\n}\n\n/**\n * Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished\n * build's snapshot and starts the next one.\n */\nexport function trackBuild(hooks: Hookable<KubbHooks>): { take(): BuildSnapshot } {\n let current = createSnapshot()\n\n hooks.hook('kubb:generate:schema', (node) => {\n if (node.name) current.schemas.add(node.name)\n })\n hooks.hook('kubb:generate:operation', (node) => {\n current.operations.add(node.operationId)\n })\n\n return {\n take() {\n const snapshot = current\n current = createSnapshot()\n return snapshot\n },\n }\n}\n\ntype NameChange = { added: number; removed: number }\n\n/**\n * How a rebuild differs from the build before it: the spec's added and removed schemas and operations.\n */\nexport type BuildDiff = {\n schemas: NameChange\n operations: NameChange\n}\n\nfunction diffNames(previous: Set<string>, next: Set<string>): NameChange {\n return { added: [...next].filter((name) => !previous.has(name)).length, removed: [...previous].filter((name) => !next.has(name)).length }\n}\n\n/**\n * Compares two {@link BuildSnapshot}s by name.\n */\nexport function diffBuilds(previous: BuildSnapshot, next: BuildSnapshot): BuildDiff {\n return {\n schemas: diffNames(previous.schemas, next.schemas),\n operations: diffNames(previous.operations, next.operations),\n }\n}\n\n/**\n * Compares file contents or fingerprints by path.\n */\nexport function diffFiles(previous: ReadonlyMap<string, string>, next: ReadonlyMap<string, string>): { files: Array<FileChange>; unchanged: number } {\n const files: Array<FileChange> = []\n let unchanged = 0\n for (const [path, fingerprint] of next) {\n const before = previous.get(path)\n if (before === fingerprint) unchanged++\n else files.push({ status: before === undefined ? 'A' : 'M', path })\n }\n for (const path of previous.keys()) {\n if (!next.has(path)) files.push({ status: 'D', path })\n }\n\n return { files, unchanged }\n}\n\n/** A changed file with how many lines it gains and loses, counted as a multiset, which is cheap and close enough for a plan. */\nexport type FileChange = { status: 'A' | 'M' | 'D'; path: string; added?: number; removed?: number }\n\nfunction countLineChanges(before: string, after: string): { added: number; removed: number } {\n const remaining = new Map<string, number>()\n for (const line of before ? before.split('\\n') : []) remaining.set(line, (remaining.get(line) ?? 0) + 1)\n let added = 0\n for (const line of after ? after.split('\\n') : []) {\n const count = remaining.get(line) ?? 0\n if (count > 0) remaining.set(line, count - 1)\n else added++\n }\n const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0)\n return { added, removed }\n}\n\n/**\n * Reads the output before generation, so cleaning the directory cannot erase the diff baseline.\n * A missing directory or an unreadable file is skipped.\n */\nexport async function readOutputFiles(outputPath: string): Promise<Map<string, string>> {\n const entries = await readdir(outputPath, { recursive: true, withFileTypes: true }).catch(() => [])\n const files = new Map<string, string>()\n await inParallel({\n items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),\n limit: 50,\n run: async (filePath) => {\n const source = await read(filePath).catch(() => null)\n if (source !== null) files.set(filePath, source)\n },\n })\n return files\n}\n\n/**\n * Compares generated output with the files captured before generation. The manifest recognizes\n * output rewritten by a formatter, and `clean` includes files the run would delete.\n */\nexport async function diffOutput({\n storage,\n paths,\n clean,\n manifest,\n previousFiles,\n}: {\n storage: Storage\n paths: ReadonlyArray<string>\n clean: boolean\n manifest?: Pick<OutputManifest, 'isUpToDate'>\n /**\n * The output captured before a real run, including files removed by cleaning.\n */\n previousFiles: ReadonlyMap<string, string>\n}): Promise<{ files: Array<FileChange>; unchanged: number }> {\n const generated = new Set(paths)\n const changes: Array<FileChange | undefined> = []\n let unchanged = 0\n await inParallel({\n items: paths,\n limit: 50,\n run: async (filePath, index) => {\n const next = await storage.readItem(filePath)\n if (next === null) return\n const previous = previousFiles.get(filePath) ?? null\n if (previous !== null && (matchesStored({ stored: previous, source: next }) || manifest?.isUpToDate({ key: filePath, source: next, disk: previous }))) {\n unchanged++\n return\n }\n changes[index] = {\n status: previous === null ? 'A' : 'M',\n path: filePath,\n ...countLineChanges(previous?.trim() ?? '', previous === null ? next.trimEnd() : next.trim()),\n }\n },\n })\n const files = changes.filter((file) => file !== undefined)\n if (clean) {\n for (const [filePath, source] of previousFiles) {\n if (!generated.has(filePath)) files.push({ status: 'D', path: filePath, ...countLineChanges(source.trimEnd(), '') })\n }\n }\n\n return { files, unchanged }\n}\n\ntype KeyInput = Pick<NodeJS.ReadStream, 'isTTY' | 'setRawMode' | 'resume' | 'pause' | 'setEncoding' | 'on'>\n\ntype KeyOptions = {\n rebuild: () => void\n /**\n * Ends the process with this exit code: `0` for `q`, `130` for ctrl+c, which raw mode turns into a key.\n */\n quit: (code: number) => void\n input?: KeyInput\n}\n\n/**\n * Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c\n * working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw\n * mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode\n * is restored on exit.\n */\nexport function listenForKeys({ rebuild, quit, input = process.stdin }: KeyOptions): (() => void) | undefined {\n if (!input.isTTY) return undefined\n\n const arm = () => {\n input.setRawMode(true)\n input.resume()\n }\n input.setEncoding('utf8')\n input.on('data', (data: string) => {\n const key = data.trim()\n if (key === 'r') {\n rebuild()\n return\n }\n if (key === 'q' || data === '\\u0003') {\n input.setRawMode(false)\n input.pause()\n quit(key === 'q' ? 0 : 130)\n }\n })\n process.once('exit', () => input.setRawMode(false))\n arm()\n\n return arm\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/cli",
3
- "version": "5.14.3",
3
+ "version": "5.14.5",
4
4
  "description": "Official CLI for Kubb. Run kubb generate, kubb init, kubb validate, kubb studio, and kubb mcp to manage the full code generation lifecycle.",
5
5
  "keywords": [
6
6
  "cli",
@@ -68,7 +68,7 @@
68
68
  "dependencies": {
69
69
  "@clack/prompts": "^1.8.1",
70
70
  "@gunshi/plugin-dryrun": "^0.37.3",
71
- "@kubb/core": "5.14.3",
71
+ "@kubb/core": "5.14.5",
72
72
  "chokidar": "^6.0.0",
73
73
  "gunshi": "^0.37.3",
74
74
  "jiti": "^2.7.0",
@@ -79,13 +79,13 @@
79
79
  "devDependencies": {
80
80
  "@internals/shared": "0.0.1",
81
81
  "@internals/utils": "0.0.1",
82
- "@kubb/adapter-oas": "5.14.3",
83
- "@kubb/studio": "5.14.3"
82
+ "@kubb/adapter-oas": "5.14.5",
83
+ "@kubb/studio": "5.14.5"
84
84
  },
85
85
  "peerDependencies": {
86
- "@kubb/adapter-oas": "5.14.3",
87
- "@kubb/mcp": "5.14.3",
88
- "@kubb/studio": "5.14.3"
86
+ "@kubb/adapter-oas": "5.14.5",
87
+ "@kubb/mcp": "5.14.5",
88
+ "@kubb/studio": "5.14.5"
89
89
  },
90
90
  "peerDependenciesMeta": {
91
91
  "@kubb/adapter-oas": {