UNPKG

@kubb/cli

Version:

Command-line interface for Kubb, enabling easy generation of TypeScript, React-Query, Zod, and other code from OpenAPI specifications.

1,372 lines (1,357 loc) 53.8 kB
const require_chunk = require('./chunk-CNbaEX1y.cjs'); const require_package = require('./package-oo3QhWS5.cjs'); let citty = require("citty"); let node_path = require("node:path"); node_path = require_chunk.__toESM(node_path); let node_process = require("node:process"); node_process = require_chunk.__toESM(node_process); let node_util = require("node:util"); let _clack_prompts = require("@clack/prompts"); _clack_prompts = require_chunk.__toESM(_clack_prompts); let _kubb_core = require("@kubb/core"); let _kubb_core_utils = require("@kubb/core/utils"); let tinyexec = require("tinyexec"); let node_crypto = require("node:crypto"); let node_stream = require("node:stream"); let _kubb_core_fs = require("@kubb/core/fs"); let cosmiconfig = require("cosmiconfig"); let jiti = require("jiti"); //#region src/utils/formatMsWithColor.ts /** * Formats milliseconds with color based on duration thresholds: * - Green: <= 500ms * - Yellow: > 500ms and <= 1000ms * - Red: > 1000ms */ function formatMsWithColor(ms) { const formatted = (0, _kubb_core_utils.formatMs)(ms); if (ms <= 500) return (0, node_util.styleText)("green", formatted); if (ms <= 1e3) return (0, node_util.styleText)("yellow", formatted); return (0, node_util.styleText)("red", formatted); } //#endregion //#region src/utils/getIntro.ts /** * ANSI True Color (24-bit) utilities for terminal output * Supports hex color codes without external dependencies like chalk */ /** * Convert hex color to ANSI 24-bit true color escape sequence * @param color - Hex color code (with or without #), e.g., '#FF5500' or 'FF5500' * @returns Function that wraps text with the color */ function hex(color) { const cleanHex = color.replace("#", ""); const r = Number.parseInt(cleanHex.slice(0, 2), 16); const g = Number.parseInt(cleanHex.slice(2, 4), 16); const b = Number.parseInt(cleanHex.slice(4, 6), 16); const safeR = Number.isNaN(r) ? 255 : r; const safeG = Number.isNaN(g) ? 255 : g; const safeB = Number.isNaN(b) ? 255 : b; return (text) => `\x1b[38;2;${safeR};${safeG};${safeB}m${text}\x1b[0m`; } function hexToRgb(color) { const c = color.replace("#", ""); return { r: Number.parseInt(c.slice(0, 2), 16), g: Number.parseInt(c.slice(2, 4), 16), b: Number.parseInt(c.slice(4, 6), 16) }; } function gradient(colors) { return (text) => { const chars = [...text]; return chars.map((char, i) => { const t = chars.length <= 1 ? 0 : i / (chars.length - 1); const seg = Math.min(Math.floor(t * (colors.length - 1)), colors.length - 2); const lt = t * (colors.length - 1) - seg; const from = hexToRgb(colors[seg]); const to = hexToRgb(colors[seg + 1]); return `\x1b[38;2;${Math.round(from.r + (to.r - from.r) * lt)};${Math.round(from.g + (to.g - from.g) * lt)};${Math.round(from.b + (to.b - from.b) * lt)}m${char}\x1b[0m`; }).join(""); }; } const colors = { lid: hex("#F55A17"), woodTop: hex("#F5A217"), woodMid: hex("#F58517"), woodBase: hex("#B45309"), eye: hex("#FFFFFF"), highlight: hex("#adadc6"), blush: hex("#FDA4AF") }; /** * Generates the Kubb mascot face welcome message * @param version - The version string to display * @returns Formatted mascot face string */ function getIntro({ title, description, version, areEyesOpen }) { const kubbVersion = gradient([ "#F58517", "#F5A217", "#F55A17" ])(`KUBB v${version}`); const eyeTop = areEyesOpen ? colors.eye("█▀█") : colors.eye("───"); const eyeBottom = areEyesOpen ? colors.eye("▀▀▀") : colors.eye("───"); return ` ${colors.lid("▄▄▄▄▄▄▄▄▄▄▄▄▄")} ${colors.woodTop("█ ")}${colors.highlight("▄▄")}${colors.woodTop(" ")}${colors.highlight("▄▄")}${colors.woodTop(" █")} ${kubbVersion} ${colors.woodMid("█ ")}${eyeTop}${colors.woodMid(" ")}${eyeTop}${colors.woodMid(" █")} ${(0, node_util.styleText)("gray", title)} ${colors.woodMid("█ ")}${eyeBottom}${colors.woodMid(" ")}${colors.blush("◡")}${colors.woodMid(" ")}${eyeBottom}${colors.woodMid(" █")} ${(0, node_util.styleText)("yellow", "➜")} ${(0, node_util.styleText)("white", description)} ${colors.woodBase("▀▀▀▀▀▀▀▀▀▀▀▀▀")} `; } //#endregion //#region src/utils/randomColor.ts function randomColor(text) { if (!text) return "white"; const defaultColors = [ "black", "red", "green", "yellow", "blue", "red", "green", "magenta", "cyan", "gray" ]; return defaultColors[(0, node_crypto.createHash)("sha256").update(text).digest().readUInt32BE(0) % defaultColors.length] ?? "white"; } function randomCliColor(text) { if (!text) return ""; return (0, node_util.styleText)(randomColor(text), text); } //#endregion //#region src/utils/getSummary.ts function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) { const duration = (0, _kubb_core_utils.formatHrtime)(hrStart); const pluginsCount = config.plugins?.length || 0; const successCount = pluginsCount - failedPlugins.size; const meta = { plugins: status === "success" ? `${(0, node_util.styleText)("green", `${successCount} successful`)}, ${pluginsCount} total` : `${(0, node_util.styleText)("green", `${successCount} successful`)}, ${(0, node_util.styleText)("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`, pluginsFailed: status === "failed" ? [...failedPlugins]?.map(({ plugin }) => randomCliColor(plugin.name))?.join(", ") : void 0, filesCreated, time: (0, node_util.styleText)("green", duration), output: node_path.default.isAbsolute(config.root) ? node_path.default.resolve(config.root, config.output.path) : config.root }; const labels = { plugins: "Plugins:", failed: "Failed:", generated: "Generated:", pluginTimings: "Plugin Timings:", output: "Output:" }; const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length)); const summaryLines = []; summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`); if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`); summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`); if (pluginTimings && pluginTimings.size > 0) { const TIME_SCALE_DIVISOR = 100; const MAX_BAR_LENGTH = 10; const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]); if (sortedTimings.length > 0) { summaryLines.push(`${labels.pluginTimings}`); sortedTimings.forEach(([name, time]) => { const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`; const barLength = Math.min(Math.ceil(time / TIME_SCALE_DIVISOR), MAX_BAR_LENGTH); const bar = (0, node_util.styleText)("dim", "█".repeat(barLength)); summaryLines.push(`${(0, node_util.styleText)("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`); }); } } summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`); return summaryLines; } //#endregion //#region src/utils/Writables.ts var ClackWritable = class extends node_stream.Writable { taskLog; constructor(taskLog, opts) { super(opts); this.taskLog = taskLog; } _write(chunk, _encoding, callback) { this.taskLog.message(`${(0, node_util.styleText)("dim", chunk?.toString())}`); callback(); } }; //#endregion //#region src/loggers/clackLogger.ts /** * Clack adapter for local TTY environments * Provides a beautiful CLI UI with flat structure inspired by Claude's CLI patterns */ const clackLogger = (0, _kubb_core.defineLogger)({ name: "clack", install(context, options) { const logLevel = options?.logLevel || _kubb_core.LogLevel.info; const state = { totalPlugins: 0, completedPlugins: 0, failedPlugins: 0, totalFiles: 0, processedFiles: 0, hrStart: node_process.default.hrtime(), spinner: _clack_prompts.spinner(), isSpinning: false, activeProgress: /* @__PURE__ */ new Map() }; function reset() { for (const [_key, active] of state.activeProgress) { if (active.interval) clearInterval(active.interval); active.progressBar?.stop(); } state.totalPlugins = 0; state.completedPlugins = 0; state.failedPlugins = 0; state.totalFiles = 0; state.processedFiles = 0; state.hrStart = node_process.default.hrtime(); state.spinner = _clack_prompts.spinner(); state.isSpinning = false; state.activeProgress.clear(); } function showProgressStep() { if (logLevel <= _kubb_core.LogLevel.silent) return; const parts = []; const duration = (0, _kubb_core_utils.formatHrtime)(state.hrStart); if (state.totalPlugins > 0) { const pluginStr = state.failedPlugins > 0 ? `Plugins ${(0, node_util.styleText)("green", state.completedPlugins.toString())}/${state.totalPlugins} ${(0, node_util.styleText)("red", `(${state.failedPlugins} failed)`)}` : `Plugins ${(0, node_util.styleText)("green", state.completedPlugins.toString())}/${state.totalPlugins}`; parts.push(pluginStr); } if (state.totalFiles > 0) parts.push(`Files ${(0, node_util.styleText)("green", state.processedFiles.toString())}/${state.totalFiles}`); if (parts.length > 0) { parts.push(`${(0, node_util.styleText)("green", duration)} elapsed`); _clack_prompts.log.step(getMessage(parts.join((0, node_util.styleText)("dim", " | ")))); } } function getMessage(message) { if (logLevel >= _kubb_core.LogLevel.verbose) return [(0, node_util.styleText)("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })}]`), message].join(" "); return message; } function startSpinner(text) { state.spinner.start(text); state.isSpinning = true; } function stopSpinner(text) { state.spinner.stop(text); state.isSpinning = false; } context.on("info", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ (0, node_util.styleText)("blue", "ℹ"), message, (0, node_util.styleText)("dim", info) ].join(" ")); if (state.isSpinning) state.spinner.message(text); else _clack_prompts.log.info(text); }); context.on("success", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ (0, node_util.styleText)("blue", "✓"), message, logLevel >= _kubb_core.LogLevel.info ? (0, node_util.styleText)("dim", info) : void 0 ].filter(Boolean).join(" ")); if (state.isSpinning) stopSpinner(text); else _clack_prompts.log.success(text); }); context.on("warn", (message, info) => { if (logLevel < _kubb_core.LogLevel.warn) return; const text = getMessage([ (0, node_util.styleText)("yellow", "⚠"), message, logLevel >= _kubb_core.LogLevel.info && info ? (0, node_util.styleText)("dim", info) : void 0 ].filter(Boolean).join(" ")); _clack_prompts.log.warn(text); }); context.on("error", (error) => { const caused = error.cause; const text = [(0, node_util.styleText)("red", "✗"), error.message].join(" "); if (state.isSpinning) stopSpinner(getMessage(text)); else _clack_prompts.log.error(getMessage(text)); if (logLevel >= _kubb_core.LogLevel.debug && error.stack) { const frames = error.stack.split("\n").slice(1, 4); for (const frame of frames) _clack_prompts.log.message(getMessage((0, node_util.styleText)("dim", frame.trim()))); if (caused?.stack) { _clack_prompts.log.message((0, node_util.styleText)("dim", `└─ caused by ${caused.message}`)); const frames = caused.stack.split("\n").slice(1, 4); for (const frame of frames) _clack_prompts.log.message(getMessage(` ${(0, node_util.styleText)("dim", frame.trim())}`)); } } }); context.on("version:new", (version, latestVersion) => { if (logLevel <= _kubb_core.LogLevel.silent) return; _clack_prompts.box(`\`v${version}\` → \`v${latestVersion}\` Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", { width: "auto", formatBorder: (s) => (0, node_util.styleText)("yellow", s), rounded: true, withGuide: false, contentAlign: "center", titleAlign: "center" }); }); context.on("lifecycle:start", async (version) => { console.log(`\n${getIntro({ title: "The ultimate toolkit for working with APIs", description: "Ready to start", version, areEyesOpen: true })}\n`); reset(); }); context.on("config:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration started"); _clack_prompts.intro(text); startSpinner(getMessage("Configuration loading")); }); context.on("config:end", (_configs) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration completed"); _clack_prompts.outro(text); }); context.on("generation:start", (config) => { state.totalPlugins = config.plugins?.length || 0; const text = getMessage(["Generation started", config.name ? `for ${(0, node_util.styleText)("dim", config.name)}` : void 0].filter(Boolean).join(" ")); _clack_prompts.intro(text); reset(); }); context.on("plugin:start", (plugin) => { if (logLevel <= _kubb_core.LogLevel.silent) return; stopSpinner(); const progressBar = _clack_prompts.progress({ style: "block", max: 100, size: 30 }); const text = getMessage(`Generating ${(0, node_util.styleText)("bold", plugin.name)}`); progressBar.start(text); const interval = setInterval(() => { progressBar.advance(); }, 100); state.activeProgress.set(plugin.name, { progressBar, interval }); }); context.on("plugin:end", (plugin, { duration, success }) => { stopSpinner(); const active = state.activeProgress.get(plugin.name); if (!active || logLevel === _kubb_core.LogLevel.silent) return; clearInterval(active.interval); if (success) state.completedPlugins++; else state.failedPlugins++; const durationStr = formatMsWithColor(duration); const text = getMessage(success ? `${(0, node_util.styleText)("bold", plugin.name)} completed in ${durationStr}` : `${(0, node_util.styleText)("bold", plugin.name)} failed in ${(0, node_util.styleText)("red", (0, _kubb_core_utils.formatMs)(duration))}`); active.progressBar.stop(text); state.activeProgress.delete(plugin.name); showProgressStep(); }); context.on("files:processing:start", (files) => { if (logLevel <= _kubb_core.LogLevel.silent) return; stopSpinner(); state.totalFiles = files.length; state.processedFiles = 0; const text = `Writing ${files.length} files`; const progressBar = _clack_prompts.progress({ style: "block", max: files.length, size: 30 }); context.emit("info", text); progressBar.start(getMessage(text)); state.activeProgress.set("files", { progressBar }); }); context.on("file:processing:update", ({ file, config }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; stopSpinner(); state.processedFiles++; const text = `Writing ${(0, node_path.relative)(config.root, file.path)}`; const active = state.activeProgress.get("files"); if (!active) return; active.progressBar.advance(void 0, text); }); context.on("files:processing:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; stopSpinner(); const text = getMessage("Files written successfully"); const active = state.activeProgress.get("files"); if (!active) return; active.progressBar.stop(text); state.activeProgress.delete("files"); showProgressStep(); }); context.on("generation:end", (config) => { const text = getMessage(config.name ? `Generation completed for ${(0, node_util.styleText)("dim", config.name)}` : "Generation completed"); _clack_prompts.outro(text); }); context.on("format:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format started"); _clack_prompts.intro(text); }); context.on("format:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format completed"); _clack_prompts.outro(text); }); context.on("lint:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint started"); _clack_prompts.intro(text); }); context.on("lint:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint completed"); _clack_prompts.outro(text); }); context.on("hook:start", async ({ id, command, args }) => { const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command; const text = getMessage(`Hook ${(0, node_util.styleText)("dim", commandWithArgs)} started`); if (!id) return; if (logLevel <= _kubb_core.LogLevel.silent) { try { const result = await (0, tinyexec.x)(command, [...args ?? []], { nodeOptions: { detached: true }, throwOnError: true }); await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [result.stdout.trimEnd()] }); await context.emit("hook:end", { command, args, id, success: true, error: null }); } catch (err) { const error = err; const stderr = error.output?.stderr ?? ""; const stdout = error.output?.stdout ?? ""; await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [stdout, stderr].filter(Boolean) }); if (stderr) console.error(stderr); if (stdout) console.log(stdout); const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`); await context.emit("hook:end", { command, args, id, success: false, error: errorMessage }); await context.emit("error", errorMessage); } return; } _clack_prompts.intro(text); const logger = _clack_prompts.taskLog({ title: getMessage(["Executing hook", logLevel >= _kubb_core.LogLevel.info ? (0, node_util.styleText)("dim", commandWithArgs) : void 0].filter(Boolean).join(" ")) }); const writable = new ClackWritable(logger); try { const proc = (0, tinyexec.x)(command, [...args ?? []], { nodeOptions: { detached: true }, throwOnError: true }); for await (const line of proc) writable.write(line); const result = await proc; await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [result.stdout.trimEnd()] }); await context.emit("hook:end", { command, args, id, success: true, error: null }); } catch (err) { const error = err; const stderr = error.output?.stderr ?? ""; const stdout = error.output?.stdout ?? ""; await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [stdout, stderr].filter(Boolean) }); if (stderr) logger.error(stderr); if (stdout) logger.message(stdout); const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`); await context.emit("hook:end", { command, args, id, success: false, error: errorMessage }); await context.emit("error", errorMessage); } }); context.on("hook:end", ({ command, args }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Hook ${(0, node_util.styleText)("dim", args?.length ? `${command} ${args.join(" ")}` : command)} successfully executed`); _clack_prompts.outro(text); }); context.on("generation:summary", (config, { pluginTimings, failedPlugins, filesCreated, status, hrStart }) => { const summary = getSummary({ failedPlugins, filesCreated, config, status, hrStart, pluginTimings: logLevel >= _kubb_core.LogLevel.verbose ? pluginTimings : void 0 }); const title = config.name || ""; summary.unshift("\n"); summary.push("\n"); if (status === "success") { _clack_prompts.box(summary.join("\n"), getMessage(title), { width: "auto", formatBorder: (s) => (0, node_util.styleText)("green", s), rounded: true, withGuide: false, contentAlign: "left", titleAlign: "center" }); return; } _clack_prompts.box(summary.join("\n"), getMessage(title), { width: "auto", formatBorder: (s) => (0, node_util.styleText)("red", s), rounded: true, withGuide: false, contentAlign: "left", titleAlign: "center" }); }); context.on("lifecycle:end", () => { reset(); }); } }); //#endregion //#region src/loggers/envDetection.ts /** * Check if running in GitHub Actions environment */ function isGitHubActions() { return !!process.env.GITHUB_ACTIONS; } /** * Check if running in any CI environment */ function isCIEnvironment() { return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.JENKINS_URL || process.env.BUILDKITE); } /** * Check if TTY is available for interactive output */ function canUseTTY() { return !!process.stdout.isTTY && !isCIEnvironment(); } //#endregion //#region src/loggers/fileSystemLogger.ts /** * FileSystem logger for debug log persistence * Captures debug and verbose events and writes them to files in .kubb directory * * Note: Logs are written on lifecycle:end or process exit. If the process crashes * before these events, some cached logs may be lost. */ const fileSystemLogger = (0, _kubb_core.defineLogger)({ name: "filesystem", install(context) { const state = { cachedLogs: /* @__PURE__ */ new Set(), startDate: Date.now() }; function reset() { state.cachedLogs = /* @__PURE__ */ new Set(); state.startDate = Date.now(); } async function writeLogs(name) { if (state.cachedLogs.size === 0) return []; const files = {}; for (const log of state.cachedLogs) { const baseName = log.fileName || `${[ "kubb", name, state.startDate ].filter(Boolean).join("-")}.log`; const pathName = (0, node_path.resolve)(process.cwd(), ".kubb", baseName); if (!files[pathName]) files[pathName] = []; if (log.logs.length > 0) { const timestamp = log.date.toLocaleString(); files[pathName].push(`[${timestamp}]\n${log.logs.join("\n")}`); } } await Promise.all(Object.entries(files).map(async ([fileName, logs]) => { return (0, _kubb_core_fs.write)(fileName, logs.join("\n\n")); })); return Object.keys(files); } context.on("info", (message, info) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`ℹ ${message} ${info}`], fileName: void 0 }); }); context.on("success", (message, info) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`✓ ${message} ${info}`], fileName: void 0 }); }); context.on("warn", (message, info) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`⚠ ${message} ${info}`], fileName: void 0 }); }); context.on("error", (error) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`✗ ${error.message}`, error.stack || "unknown stack"], fileName: void 0 }); }); context.on("debug", (message) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: message.logs, fileName: void 0 }); }); context.on("plugin:start", (plugin) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`Generating ${plugin.name}`], fileName: void 0 }); }); context.on("plugin:end", (plugin, { duration, success }) => { const durationStr = (0, _kubb_core_utils.formatMs)(duration); state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`], fileName: void 0 }); }); context.on("files:processing:start", (files) => { state.cachedLogs.add({ date: /* @__PURE__ */ new Date(), logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)], fileName: void 0 }); }); context.on("generation:end", async (config) => { const writtenFilePaths = await writeLogs(config.name); if (writtenFilePaths.length > 0) { const files = writtenFilePaths.map((f) => (0, node_path.relative)(process.cwd(), f)); await context.emit("info", "Debug files written to:", files.join(", ")); } reset(); }); context.on("lifecycle:end", async () => {}); const exitHandler = () => { if (state.cachedLogs.size > 0) writeLogs().catch(() => {}); }; process.once("exit", exitHandler); process.once("SIGINT", exitHandler); process.once("SIGTERM", exitHandler); } }); //#endregion //#region src/loggers/githubActionsLogger.ts /** * GitHub Actions adapter for CI environments * Uses Github group annotations for collapsible sections */ const githubActionsLogger = (0, _kubb_core.defineLogger)({ name: "github-actions", install(context, options) { const logLevel = options?.logLevel || _kubb_core.LogLevel.info; const state = { totalPlugins: 0, completedPlugins: 0, failedPlugins: 0, totalFiles: 0, processedFiles: 0, hrStart: process.hrtime(), currentConfigs: [] }; function reset() { state.totalPlugins = 0; state.completedPlugins = 0; state.failedPlugins = 0; state.totalFiles = 0; state.processedFiles = 0; state.hrStart = process.hrtime(); } function showProgressStep() { if (logLevel <= _kubb_core.LogLevel.silent) return; const parts = []; const duration = (0, _kubb_core_utils.formatHrtime)(state.hrStart); if (state.totalPlugins > 0) { const pluginStr = state.failedPlugins > 0 ? `Plugins ${(0, node_util.styleText)("green", state.completedPlugins.toString())}/${state.totalPlugins} ${(0, node_util.styleText)("red", `(${state.failedPlugins} failed)`)}` : `Plugins ${(0, node_util.styleText)("green", state.completedPlugins.toString())}/${state.totalPlugins}`; parts.push(pluginStr); } if (state.totalFiles > 0) parts.push(`Files ${(0, node_util.styleText)("green", state.processedFiles.toString())}/${state.totalFiles}`); if (parts.length > 0) { parts.push(`${(0, node_util.styleText)("green", duration)} elapsed`); console.log(getMessage(parts.join((0, node_util.styleText)("dim", " | ")))); } } function getMessage(message) { if (logLevel >= _kubb_core.LogLevel.verbose) return [(0, node_util.styleText)("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })}]`), message].join(" "); return message; } function openGroup(name) { console.log(`::group::${name}`); } function closeGroup(_name) { console.log("::endgroup::"); } context.on("info", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ (0, node_util.styleText)("blue", "ℹ"), message, (0, node_util.styleText)("dim", info) ].join(" ")); console.log(text); }); context.on("success", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ (0, node_util.styleText)("blue", "✓"), message, logLevel >= _kubb_core.LogLevel.info ? (0, node_util.styleText)("dim", info) : void 0 ].filter(Boolean).join(" ")); console.log(text); }); context.on("warn", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ (0, node_util.styleText)("yellow", "⚠"), message, logLevel >= _kubb_core.LogLevel.info ? (0, node_util.styleText)("dim", info) : void 0 ].filter(Boolean).join(" ")); console.warn(`::warning::${text}`); }); context.on("error", (error) => { const caused = error.cause; if (logLevel <= _kubb_core.LogLevel.silent) return; const message = error.message || String(error); console.error(`::error::${message}`); if (logLevel >= _kubb_core.LogLevel.debug && error.stack) { const frames = error.stack.split("\n").slice(1, 4); for (const frame of frames) console.log(getMessage((0, node_util.styleText)("dim", frame.trim()))); if (caused?.stack) { console.log((0, node_util.styleText)("dim", `└─ caused by ${caused.message}`)); const frames = caused.stack.split("\n").slice(1, 4); for (const frame of frames) console.log(getMessage(` ${(0, node_util.styleText)("dim", frame.trim())}`)); } } }); context.on("lifecycle:start", (version) => { console.log((0, node_util.styleText)("yellow", `Kubb ${version} 🧩`)); reset(); }); context.on("config:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration started"); openGroup("Configuration"); console.log(text); }); context.on("config:end", (configs) => { state.currentConfigs = configs; if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration completed"); console.log(text); closeGroup("Configuration"); }); context.on("generation:start", (config) => { state.totalPlugins = config.plugins?.length || 0; const text = config.name ? `Generation for ${(0, node_util.styleText)("bold", config.name)}` : "Generation"; if (state.currentConfigs.length > 1) openGroup(text); if (state.currentConfigs.length === 1) console.log(getMessage(text)); reset(); }); context.on("plugin:start", (plugin) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Generating ${(0, node_util.styleText)("bold", plugin.name)}`); if (state.currentConfigs.length === 1) openGroup(`Plugin: ${plugin.name}`); console.log(text); }); context.on("plugin:end", (plugin, { duration, success }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; if (success) state.completedPlugins++; else state.failedPlugins++; const durationStr = formatMsWithColor(duration); const text = getMessage(success ? `${(0, node_util.styleText)("bold", plugin.name)} completed in ${durationStr}` : `${(0, node_util.styleText)("bold", plugin.name)} failed in ${(0, node_util.styleText)("red", (0, _kubb_core_utils.formatMs)(duration))}`); console.log(text); if (state.currentConfigs.length > 1) console.log(" "); if (state.currentConfigs.length === 1) closeGroup(`Plugin: ${plugin.name}`); showProgressStep(); }); context.on("files:processing:start", (files) => { if (logLevel <= _kubb_core.LogLevel.silent) return; state.totalFiles = files.length; state.processedFiles = 0; if (state.currentConfigs.length === 1) openGroup("File Generation"); const text = getMessage(`Writing ${files.length} files`); console.log(text); }); context.on("files:processing:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Files written successfully"); console.log(text); if (state.currentConfigs.length === 1) closeGroup("File Generation"); }); context.on("file:processing:update", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; state.processedFiles++; }); context.on("files:processing:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; showProgressStep(); }); context.on("generation:end", (config) => { const text = getMessage(config.name ? `${(0, node_util.styleText)("blue", "✓")} Generation completed for ${(0, node_util.styleText)("dim", config.name)}` : `${(0, node_util.styleText)("blue", "✓")} Generation completed`); console.log(text); }); context.on("format:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format started"); if (state.currentConfigs.length === 1) openGroup("Formatting"); console.log(text); }); context.on("format:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format completed"); console.log(text); if (state.currentConfigs.length === 1) closeGroup("Formatting"); }); context.on("lint:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint started"); if (state.currentConfigs.length === 1) openGroup("Linting"); console.log(text); }); context.on("lint:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint completed"); console.log(text); if (state.currentConfigs.length === 1) closeGroup("Linting"); }); context.on("hook:start", async ({ id, command, args }) => { const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command; const text = getMessage(`Hook ${(0, node_util.styleText)("dim", commandWithArgs)} started`); if (logLevel > _kubb_core.LogLevel.silent) { if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`); console.log(text); } if (!id) return; try { const result = await (0, tinyexec.x)(command, [...args ?? []], { nodeOptions: { detached: true }, throwOnError: true }); await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [result.stdout.trimEnd()] }); if (logLevel > _kubb_core.LogLevel.silent) console.log(result.stdout.trimEnd()); await context.emit("hook:end", { command, args, id, success: true, error: null }); } catch (err) { const error = err; const stderr = error.output?.stderr ?? ""; const stdout = error.output?.stdout ?? ""; await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [stdout, stderr].filter(Boolean) }); if (stderr) console.error(`::error::${stderr}`); if (stdout) console.log(stdout); const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`); await context.emit("hook:end", { command, args, id, success: false, error: errorMessage }); await context.emit("error", errorMessage); } }); context.on("hook:end", ({ command, args }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command; const text = getMessage(`Hook ${(0, node_util.styleText)("dim", commandWithArgs)} completed`); console.log(text); if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`); }); context.on("generation:summary", (config, { status, hrStart, failedPlugins }) => { const pluginsCount = config.plugins?.length || 0; const successCount = pluginsCount - failedPlugins.size; const duration = (0, _kubb_core_utils.formatHrtime)(hrStart); if (state.currentConfigs.length > 1) console.log(" "); console.log(status === "success" ? `Kubb Summary: ${(0, node_util.styleText)("blue", "✓")} ${`${successCount} successful`}, ${pluginsCount} total, ${(0, node_util.styleText)("green", duration)}` : `Kubb Summary: ${(0, node_util.styleText)("blue", "✓")} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${(0, node_util.styleText)("green", duration)}`); if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${(0, node_util.styleText)("bold", config.name)}` : "Generation"); }); } }); //#endregion //#region src/loggers/plainLogger.ts /** * Plain console adapter for non-TTY environments * Simple console.log output with indentation */ const plainLogger = (0, _kubb_core.defineLogger)({ name: "plain", install(context, options) { const logLevel = options?.logLevel || 3; function getMessage(message) { if (logLevel >= _kubb_core.LogLevel.verbose) return [`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })}]`, message].join(" "); return message; } context.on("info", (message, info) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ "ℹ", message, info ].join(" ")); console.log(text); }); context.on("success", (message, info = "") => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage([ "✓", message, logLevel >= _kubb_core.LogLevel.info ? info : void 0 ].filter(Boolean).join(" ")); console.log(text); }); context.on("warn", (message, info) => { if (logLevel < _kubb_core.LogLevel.warn) return; const text = getMessage([ "⚠", message, logLevel >= _kubb_core.LogLevel.info ? info : void 0 ].filter(Boolean).join(" ")); console.log(text); }); context.on("error", (error) => { const caused = error.cause; const text = getMessage(["✗", error.message].join(" ")); console.log(text); if (logLevel >= _kubb_core.LogLevel.debug && error.stack) { const frames = error.stack.split("\n").slice(1, 4); for (const frame of frames) console.log(getMessage(frame.trim())); if (caused?.stack) { console.log(`└─ caused by ${caused.message}`); const frames = caused.stack.split("\n").slice(1, 4); for (const frame of frames) console.log(getMessage(` ${frame.trim()}`)); } } }); context.on("lifecycle:start", () => { console.log("Kubb CLI 🧩"); }); context.on("config:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration started"); console.log(text); }); context.on("config:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Configuration completed"); console.log(text); }); context.on("generation:start", () => { const text = getMessage("Configuration started"); console.log(text); }); context.on("plugin:start", (plugin) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Generating ${plugin.name}`); console.log(text); }); context.on("plugin:end", (plugin, { duration, success }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const durationStr = (0, _kubb_core_utils.formatMs)(duration); const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`); console.log(text); }); context.on("files:processing:start", (files) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Writing ${files.length} files`); console.log(text); }); context.on("file:processing:update", ({ file, config }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Writing ${(0, node_path.relative)(config.root, file.path)}`); console.log(text); }); context.on("files:processing:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Files written successfully"); console.log(text); }); context.on("generation:end", (config) => { const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed"); console.log(text); }); context.on("format:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format started"); console.log(text); }); context.on("format:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Format completed"); console.log(text); }); context.on("lint:start", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint started"); console.log(text); }); context.on("lint:end", () => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage("Lint completed"); console.log(text); }); context.on("hook:start", async ({ id, command, args }) => { const commandWithArgs = args?.length ? `${command} ${args.join(" ")}` : command; const text = getMessage(`Hook ${commandWithArgs} started`); if (logLevel > _kubb_core.LogLevel.silent) console.log(text); if (!id) return; try { const result = await (0, tinyexec.x)(command, [...args ?? []], { nodeOptions: { detached: true }, throwOnError: true }); await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [result.stdout.trimEnd()] }); if (logLevel > _kubb_core.LogLevel.silent) console.log(result.stdout.trimEnd()); await context.emit("hook:end", { command, args, id, success: true, error: null }); } catch (err) { const error = err; const stderr = error.output?.stderr ?? ""; const stdout = error.output?.stdout ?? ""; await context.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [stdout, stderr].filter(Boolean) }); if (stderr) console.error(stderr); if (stdout) console.log(stdout); const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`); await context.emit("hook:end", { command, args, id, success: false, error: errorMessage }); await context.emit("error", errorMessage); } }); context.on("hook:end", ({ command, args }) => { if (logLevel <= _kubb_core.LogLevel.silent) return; const text = getMessage(`Hook ${args?.length ? `${command} ${args.join(" ")}` : command} completed`); console.log(text); }); context.on("generation:summary", (config, { pluginTimings, status, hrStart, failedPlugins, filesCreated }) => { const summary = getSummary({ failedPlugins, filesCreated, config, status, hrStart, pluginTimings: logLevel >= _kubb_core.LogLevel.verbose ? pluginTimings : void 0 }); console.log("---------------------------"); console.log(summary.join("\n")); console.log("---------------------------"); }); } }); //#endregion //#region src/loggers/utils.ts function detectLogger() { if (isGitHubActions()) return "github-actions"; if (canUseTTY()) return "clack"; return "plain"; } const logMapper = { clack: clackLogger, plain: plainLogger, "github-actions": githubActionsLogger }; async function setupLogger(context, { logLevel }) { const type = detectLogger(); const logger = logMapper[type]; if (!logger) throw new Error(`Unknown adapter type: ${type}`); const cleanup = await logger.install(context, { logLevel }); if (logLevel >= _kubb_core.LogLevel.debug) await fileSystemLogger.install(context, { logLevel }); return cleanup; } //#endregion //#region src/utils/executeHooks.ts async function executeHooks({ hooks, events }) { const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done].filter(Boolean); for (const command of commands) { const [cmd, ...args] = (0, _kubb_core_utils.tokenize)(command); if (!cmd) continue; const hookId = (0, node_crypto.createHash)("sha256").update(command).digest("hex"); await events.emit("hook:start", { id: hookId, command: cmd, args }); await events.onOnce("hook:end", async ({ success, error }) => { if (!success) throw error; await events.emit("success", `${(0, node_util.styleText)("dim", command)} successfully executed`); }); } } //#endregion //#region src/runners/generate.ts async function generate({ input, config: userConfig, events, logLevel }) { const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0); const hrStart = node_process.default.hrtime(); const config = { ...userConfig, root: userConfig.root || node_process.default.cwd(), input: inputPath ? { ...userConfig.input, path: inputPath } : userConfig.input, output: { write: true, barrelType: "named", extension: { ".ts": ".ts" }, format: "prettier", ...userConfig.output } }; await events.emit("generation:start", config); await events.emit("info", config.name ? `Setup generation ${(0, node_util.styleText)("bold", config.name)}` : "Setup generation", inputPath); const { sources, fabric, pluginManager } = await (0, _kubb_core.setup)({ config, events }); await events.emit("info", config.name ? `Build generation ${(0, node_util.styleText)("bold", config.name)}` : "Build generation", inputPath); const { files, failedPlugins, pluginTimings, error } = await (0, _kubb_core.safeBuild)({ config, events }, { pluginManager, fabric, events, sources }); await events.emit("info", "Load summary"); if (failedPlugins.size > 0 || error) { [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean).forEach((err) => { events.emit("error", err); }); await events.emit("generation:end", config, files, sources); await events.emit("generation:summary", config, { failedPlugins, filesCreated: files.length, status: failedPlugins.size > 0 || error ? "failed" : "success", hrStart, pluginTimings: logLevel >= _kubb_core.LogLevel.verbose ? pluginTimings : void 0 }); node_process.default.exit(1); } await events.emit("success", "Generation successfully", inputPath); await events.emit("generation:end", config, files, sources); if (config.output.format) { await events.emit("format:start"); let formatter = config.output.format; if (formatter === "auto") { const detectedFormatter = await (0, _kubb_core_utils.detectFormatter)(); if (!detectedFormatter) await events.emit("warn", "No formatter found (biome, prettier, or oxfmt). Skipping formatting."); else { formatter = detectedFormatter; await events.emit("info", `Auto-detected formatter: ${(0, node_util.styleText)("dim", formatter)}`); } } if (formatter && formatter !== "auto" && formatter in _kubb_core_utils.formatters) { const formatterConfig = _kubb_core_utils.formatters[formatter]; const outputPath = node_path.default.resolve(config.root, config.output.path); try { const hookId = (0, node_crypto.createHash)("sha256").update([config.name, formatter].filter(Boolean).join("-")).digest("hex"); await events.emit("hook:start", { id: hookId, command: formatterConfig.command, args: formatterConfig.args(outputPath) }); await events.onOnce("hook:end", async ({ success, error }) => { if (!success) throw error; await events.emit("success", [ `Formatting with ${(0, node_util.styleText)("dim", formatter)}`, logLevel >= _kubb_core.LogLevel.info ? `on ${(0, node_util.styleText)("dim", outputPath)}` : void 0, "successfully" ].filter(Boolean).join(" ")); }); } catch (caughtError) { const error = new Error(formatterConfig.errorMessage); error.cause = caughtError; await events.emit("error", error); } } await events.emit("format:end"); } if (config.output.lint) { await events.emit("lint:start"); let linter = config.output.lint; if (linter === "auto") { const detectedLinter = await (0, _kubb_core_utils.detectLinter)(); if (!detectedLinter) await events.emit("warn", "No linter found (biome, oxlint, or eslint). Skipping linting."); else { linter = detectedLinter; await events.emit("info", `Auto-detected linter: ${(0, node_util.styleText)("dim", linter)}`); } } if (linter && linter !== "auto" && linter in _kubb_core_utils.linters) { const linterConfig = _kubb_core_utils.linters[linter]; const outputPath = node_path.default.resolve(config.root, config.output.path); try { const hookId = (0, node_crypto.createHash)("sha256").update([config.name, linter].filter(Boolean).join("-")).digest("hex"); await events.emit("hook:start", { id: hookId, command: linterConfig.command, args: linterConfig.args(outputPath) }); await events.onOnce("hook:end", async ({ success, error }) => { if (!success) throw error; await events.emit("success", [ `Linting with ${(0, node_util.styleText)("dim", linter)}`, logLevel >= _kubb_core.LogLevel.info ? `on ${(0, node_util.styleText)("dim", outputPath)}` : void 0, "successfully" ].filter(Boolean).join(" ")); }); } catch (caughtError) { const error = new Error(linterConfig.errorMessage); error.cause = caughtError; await events.emit("error", error); } } await events.emit("lint:end"); } if (config.hooks) { await events.emit("hooks:start"); await executeHooks({ hooks: config.hooks, events }); await events.emit("hooks:end"); } await events.emit("generation:summary", config, { failedPlugins, filesCreated: files.length, status: failedPlugins.size > 0 || error ? "failed" : "success", hrStart, pluginTimings }); } //#endregion //#region src/utils/getCosmiConfig.ts const tsLoader = async (configFile) => { return await (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, { jsx: { runtime: "automatic", importSource: "@kubb/react-fabric" }, sourceMaps: true, interopDefault: true }).import(configFile, { default: true }); }; async function getCosmiConfig(moduleName, config) { let result; const searchPlaces = [ "package.json", `.${moduleName}rc`, `.${moduleName}rc.json`, `.${moduleName}rc.yaml`, `.${moduleName}rc.yml`, `.${moduleName}rc.ts`, `.${moduleName}rc.js`, `.${moduleName}rc.mjs`, `.${moduleName}rc.cjs`, `${moduleName}.config.ts`, `${moduleName}.config.js`, `${moduleName}.config.mjs`, `${moduleName}.config.cjs` ]; const explorer = (0, cosmiconfig.cosmiconfig)(moduleName, { cache: false, searchPlaces: [ ...searchPlaces.map((searchPlace) => { return `.config/${searchPlace}`; }), ...searchPlaces.map((searchPlace) => { return `configs/${searchPlace}`; }), ...searchPlaces ], loaders: { ".ts": tsLoader } }); try { result = config ? await explorer.load(config) : await explorer.search(); } catch (error) { throw new Error("Config failed loading", { cause: error }); } if (result?.isEmpty || !result || !result.config) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config"); return result; } //#endregion //#region src/utils/watcher.ts async func