@kubb/cli
Version: 
Command-line interface for Kubb, enabling easy generation of TypeScript, React-Query, Zod, and other code from OpenAPI specifications.
271 lines (264 loc) • 8.82 kB
JavaScript
import path from "node:path";
import * as process$2 from "node:process";
import process$1 from "node:process";
import { safeBuild, setup } from "@kubb/core";
import { LogMapper, createLogger, randomCliColour } from "@kubb/core/logger";
import pc from "picocolors";
import { Presets, SingleBar } from "cli-progress";
import { execa } from "execa";
import { parseArgsStringToArgv } from "string-argv";
import { Writable } from "node:stream";
//#region src/utils/Writables.ts
var ConsolaWritable = class extends Writable {
	consola;
	command;
	constructor(consola, command, opts) {
		super(opts);
		this.command = command;
		this.consola = consola;
	}
	_write(chunk, _encoding, callback) {
		process$2.stdout.write(`${pc.dim(chunk?.toString())}`);
		callback();
	}
};
//#endregion
//#region src/utils/executeHooks.ts
async function executeHooks({ hooks, logger }) {
	const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done].filter(Boolean);
	for (const command of commands) {
		const consolaWritable = new ConsolaWritable(logger.consola, command);
		const [cmd, ..._args] = [...parseArgsStringToArgv(command)];
		if (!cmd) continue;
		logger?.emit("start", `Executing hook ${logger.logLevel !== LogMapper.silent ? pc.dim(command) : ""}`);
		await execa(cmd, _args, {
			detached: true,
			stdout: logger?.logLevel === LogMapper.silent ? void 0 : ["pipe", consolaWritable],
			stripFinalNewline: true
		});
		logger?.emit("success", `Executed hook ${logger.logLevel !== LogMapper.silent ? pc.dim(command) : ""}`);
	}
	logger?.emit("success", "Executed hooks");
}
//#endregion
//#region src/utils/getErrorCauses.ts
function getErrorCauses(errors) {
	return errors.reduce((prev, error) => {
		const causedError = error?.cause;
		if (causedError) {
			prev = [...prev, ...getErrorCauses([causedError])];
			return prev;
		}
		prev = [...prev, error];
		return prev;
	}, []).filter(Boolean);
}
//#endregion
//#region src/utils/parseHrtimeToSeconds.ts
function parseHrtimeToSeconds(hrtime) {
	return (hrtime[0] + hrtime[1] / 1e9).toFixed(3);
}
//#endregion
//#region src/utils/getSummary.ts
function getSummary({ pluginManager, filesCreated, status, hrStart, config }) {
	const logs = /* @__PURE__ */ new Set();
	const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(hrStart));
	const buildStartPlugins = pluginManager.executed.filter((item) => item.hookName === "buildStart" && item.plugin.name !== "core").map((item) => item.plugin.name);
	const buildEndPlugins = pluginManager.executed.filter((item) => item.hookName === "buildEnd" && item.plugin.name !== "core").map((item) => item.plugin.name);
	const failedPlugins = config.plugins?.filter((plugin) => !buildEndPlugins.includes(plugin.name))?.map((plugin) => plugin.name);
	const pluginsCount = config.plugins?.length || 0;
	const meta = {
		plugins: status === "success" ? `${pc.green(`${buildStartPlugins.length} successful`)}, ${pluginsCount} total` : `${pc.red(`${failedPlugins?.length ?? 1} failed`)}, ${pluginsCount} total`,
		pluginsFailed: status === "failed" ? failedPlugins?.map((name) => randomCliColour(name))?.join(", ") : void 0,
		filesCreated,
		time: `${pc.yellow(`${elapsedSeconds}s`)}`,
		output: path.isAbsolute(config.root) ? path.resolve(config.root, config.output.path) : config.root
	};
	logs.add([
		[`${pc.bold("Plugins:")}        ${meta.plugins}`, true],
		[`${pc.dim("Failed:")}          ${meta.pluginsFailed || "none"}`, !!meta.pluginsFailed],
		[`${pc.bold("Generated:")}      ${meta.filesCreated} files in ${meta.time}`, true],
		[`${pc.bold("Output:")}         ${meta.output}`, true]
	].map((item) => {
		if (item.at(1)) return item.at(0);
	}).filter(Boolean).join("\n"));
	return [...logs];
}
//#endregion
//#region src/runners/generate.ts
async function generate({ input, config, progressCache, args }) {
	const hrStart = process$1.hrtime();
	const logger = createLogger({
		logLevel: LogMapper[args.logLevel] || 3,
		name: config.name
	});
	const { root = process$1.cwd(),...userConfig } = config;
	const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
	if (logger.logLevel !== LogMapper.debug) {
		logger.on("progress_start", ({ id, size, message = "" }) => {
			logger.consola?.pauseLogs();
			const payload = {
				id,
				message
			};
			const progressBar = new SingleBar({
				format: "{percentage}% {bar} {value}/{total} | {message}",
				barsize: 30,
				clearOnComplete: true,
				emptyOnZero: true
			}, Presets.shades_grey);
			if (!progressCache.has(id)) {
				progressCache.set(id, progressBar);
				progressBar.start(size, 1, payload);
			}
		});
		logger.on("progress_stop", ({ id }) => {
			progressCache.get(id)?.stop();
			logger.consola?.resumeLogs();
		});
		logger.on("progressed", ({ id, message = "" }) => {
			const payload = {
				id,
				message
			};
			progressCache.get(id)?.increment(1, payload);
		});
	}
	const definedConfig = {
		root,
		...userConfig,
		input: inputPath ? {
			...userConfig.input,
			path: inputPath
		} : userConfig.input,
		output: {
			write: true,
			barrelType: "named",
			extension: { ".ts": ".ts" },
			format: "prettier",
			...userConfig.output
		}
	};
	const { fabric, pluginManager } = await setup({
		config: definedConfig,
		logger
	});
	logger.emit("start", `Building ${logger.logLevel !== LogMapper.silent ? pc.dim(inputPath) : ""}`);
	const { files, error } = await safeBuild({
		config: definedConfig,
		logger
	}, {
		pluginManager,
		fabric
	});
	if (logger.logLevel === LogMapper.debug) {
		logger.consola?.start("Writing logs");
		const logFiles = await logger.writeLogs();
		logger.consola?.success(`Written logs: \n${logFiles.join("\n")}`);
	}
	const summary = getSummary({
		filesCreated: files.length,
		pluginManager,
		config: definedConfig,
		status: error ? "failed" : "success",
		hrStart
	});
	if (error && logger.consola) {
		logger.consola?.resumeLogs();
		logger.consola.error(`Build failed ${logger.logLevel !== LogMapper.silent ? pc.dim(inputPath) : ""}`);
		logger.consola.box({
			title: `${config.name || ""}`,
			message: summary.join(""),
			style: {
				padding: 2,
				borderColor: "red",
				borderStyle: "rounded"
			}
		});
		const errors = getErrorCauses([error]);
		if (logger.consola && errors.length && logger.logLevel === LogMapper.debug) errors.forEach((err) => {
			logger.consola?.error(err);
		});
		logger.consola?.error(error);
		process$1.exit(1);
	}
	if (config.output.format === "prettier") {
		logger?.emit("start", `Formatting with ${config.output.format}`);
		try {
			await execa("prettier", [
				"--ignore-unknown",
				"--write",
				path.resolve(definedConfig.root, definedConfig.output.path)
			]);
		} catch (e) {
			logger.consola?.warn("Prettier not found");
			logger.consola?.error(e);
		}
		logger?.emit("success", `Formatted with ${config.output.format}`);
	}
	if (config.output.format === "biome") {
		logger?.emit("start", `Formatting with ${config.output.format}`);
		try {
			await execa("biome", [
				"format",
				"--write",
				path.resolve(definedConfig.root, definedConfig.output.path)
			]);
		} catch (e) {
			logger.consola?.warn("Biome not found");
			logger.consola?.error(e);
		}
		logger?.emit("success", `Formatted with ${config.output.format}`);
	}
	if (config.output.lint === "eslint") {
		logger?.emit("start", `Linting with ${config.output.format}`);
		try {
			await execa("eslint", [path.resolve(definedConfig.root, definedConfig.output.path), "--fix"]);
		} catch (e) {
			logger.consola?.warn("Eslint not found");
			logger.consola?.error(e);
		}
		logger?.emit("success", `Linted with ${config.output.format}`);
	}
	if (config.output.lint === "biome") {
		logger?.emit("start", `Linting with ${config.output.format}`);
		try {
			await execa("biome", [
				"lint",
				"--fix",
				path.resolve(definedConfig.root, definedConfig.output.path)
			]);
		} catch (e) {
			logger.consola?.warn("Biome not found");
			logger.consola?.error(e);
		}
		logger?.emit("success", `Linted with ${config.output.format}`);
	}
	if (config.output.lint === "oxlint") {
		logger?.emit("start", `Linting with ${config.output.format}`);
		try {
			await execa("oxlint", ["--fix", path.resolve(definedConfig.root, definedConfig.output.path)]);
		} catch (e) {
			logger.consola?.warn("Oxlint not found");
			logger.consola?.error(e);
		}
		logger?.emit("success", `Linted with ${config.output.format}`);
	}
	if (config.hooks) await executeHooks({
		hooks: config.hooks,
		logger
	});
	logger.consola?.log(`⚡Build completed ${logger.logLevel !== LogMapper.silent ? pc.dim(inputPath) : ""}`);
	logger.consola?.box({
		title: `${config.name || ""}`,
		message: summary.join(""),
		style: {
			padding: 2,
			borderColor: "green",
			borderStyle: "rounded"
		}
	});
}
//#endregion
export { generate };
//# sourceMappingURL=generate-ChOKW4hA.js.map