UNPKG

@kubb/core

Version:

Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.

616 lines (601 loc) 19.7 kB
import { t as __name } from "./chunk-iVr_oF3V.js"; import { a as getElapsedMs, c as getMode, d as PromiseManager, f as Queue, i as formatMs, n as URLPath, o as AsyncEventEmitter, p as BuildError, s as PluginManager, t as getBarrelFiles } from "./getBarrelFiles-DqGGy0LT.js"; import { a as exists, i as readSync, n as getRelativePath, o as clean, r as read, t as write } from "./fs--foJTbRT.js"; import "./transformers-CqOjKLQD.js"; import mod from "node:module"; import path, { resolve } from "node:path"; import { createFabric } from "@kubb/react-fabric"; import { typescriptParser } from "@kubb/react-fabric/parsers"; import { fsPlugin } from "@kubb/react-fabric/plugins"; import process$1, { version } from "node:process"; import os from "node:os"; import { fileURLToPath, pathToFileURL } from "node:url"; import fs, { promises } from "node:fs"; import { coerce, satisfies } from "semver"; //#region src/BaseGenerator.ts /** * Abstract class that contains the building blocks for plugins to create their own Generator * @link idea based on https://github.com/colinhacks/zod/blob/master/src/types.ts#L137 */ var BaseGenerator = class { #options = {}; #context = {}; constructor(options, context) { if (context) this.#context = context; if (options) this.#options = options; return this; } get options() { return this.#options; } get context() { return this.#context; } set options(options) { this.#options = { ...this.#options, ...options }; } }; //#endregion //#region src/config.ts /** * Helper for defining a Kubb configuration. * * Accepts either: * - A config object or array of configs * - A function returning the config(s), optionally async, * receiving the CLI options as argument * * @example * export default defineConfig(({ logLevel }) => ({ * root: 'src', * plugins: [myPlugin()], * })) */ function defineConfig(config) { return config; } /** * Type guard to check if a given config has an `input.path`. */ function isInputPath(config) { return typeof config?.input === "object" && config.input !== null && "path" in config.input; } //#endregion //#region package.json var version$1 = "4.18.0"; //#endregion //#region src/utils/diagnostics.ts /** * Get diagnostic information for debugging */ function getDiagnosticInfo() { return { nodeVersion: version, KubbVersion: version$1, platform: process.platform, arch: process.arch, cwd: process.cwd() }; } //#endregion //#region src/build.ts async function setup(options) { const { config: userConfig, events = new AsyncEventEmitter() } = options; const diagnosticInfo = getDiagnosticInfo(); if (Array.isArray(userConfig.input)) await events.emit("warn", "This feature is still under development — use with caution"); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [ "Configuration:", ` • Name: ${userConfig.name || "unnamed"}`, ` • Root: ${userConfig.root || process.cwd()}`, ` • Output: ${userConfig.output?.path || "not specified"}`, ` • Plugins: ${userConfig.plugins?.length || 0}`, "Output Settings:", ` • Write: ${userConfig.output?.write !== false ? "enabled" : "disabled"}`, ` • Formater: ${userConfig.output?.format || "none"}`, ` • Linter: ${userConfig.output?.lint || "none"}`, "Environment:", Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n") ] }); try { if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) { await exists(userConfig.input.path); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`✓ Input file validated: ${userConfig.input.path}`] }); } } catch (caughtError) { if (isInputPath(userConfig)) { const error = caughtError; throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error }); } } const definedConfig = { root: userConfig.root || process.cwd(), ...userConfig, output: { write: true, barrelType: "named", extension: { ".ts": ".ts" }, defaultBanner: "simple", ...userConfig.output }, plugins: userConfig.plugins }; if (definedConfig.output.clean) { await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`] }); await clean(definedConfig.output.path); } const fabric = createFabric(); fabric.use(fsPlugin, { dryRun: !definedConfig.output.write }); fabric.use(typescriptParser); fabric.context.on("files:processing:start", (files) => { events.emit("files:processing:start", files); events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`Writing ${files.length} files...`] }); }); fabric.context.on("file:processing:update", async (params) => { const { file, source } = params; await events.emit("file:processing:update", { ...params, config: definedConfig, source }); if (source) await write(file.path, source, { sanity: false }); }); fabric.context.on("files:processing:end", async (files) => { await events.emit("files:processing:end", files); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`✓ File write process completed for ${files.length} files`] }); }); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [ "✓ Fabric initialized", ` • File writing: ${definedConfig.output.write ? "enabled" : "disabled (dry-run)"}`, ` • Barrel type: ${definedConfig.output.barrelType || "none"}` ] }); return { events, fabric, pluginManager: new PluginManager(definedConfig, { fabric, events, concurrency: 15 }) }; } async function build(options, overrides) { const { fabric, files, pluginManager, failedPlugins, pluginTimings, error } = await safeBuild(options, overrides); if (error) throw error; if (failedPlugins.size > 0) { const errors = [...failedPlugins].map(({ error: error$1 }) => error$1); throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors }); } return { failedPlugins, fabric, files, pluginManager, pluginTimings, error: void 0 }; } async function safeBuild(options, overrides) { const { fabric, pluginManager, events } = overrides ? overrides : await setup(options); const failedPlugins = /* @__PURE__ */ new Set(); const pluginTimings = /* @__PURE__ */ new Map(); const config = pluginManager.config; try { for (const plugin of pluginManager.plugins) { const context = pluginManager.getContext(plugin); const hrStart = process.hrtime(); const installer = plugin.install.bind(context); try { const timestamp = /* @__PURE__ */ new Date(); await events.emit("plugin:start", plugin); await events.emit("debug", { date: timestamp, logs: ["Installing plugin...", ` • Plugin Key: [${plugin.key.join(", ")}]`] }); await installer(context); const duration = getElapsedMs(hrStart); pluginTimings.set(plugin.name, duration); await events.emit("plugin:end", plugin, { duration, success: true }); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`✓ Plugin installed successfully (${formatMs(duration)})`] }); } catch (caughtError) { const error = caughtError; const errorTimestamp = /* @__PURE__ */ new Date(); const duration = getElapsedMs(hrStart); await events.emit("plugin:end", plugin, { duration, success: false, error }); await events.emit("debug", { date: errorTimestamp, logs: [ "✗ Plugin installation failed", ` • Plugin Key: ${JSON.stringify(plugin.key)}`, ` • Error: ${error.constructor.name} - ${error.message}`, " • Stack Trace:", error.stack || "No stack trace available" ] }); failedPlugins.add({ plugin, error }); } } if (config.output.barrelType) { const rootPath = resolve(resolve(config.root), config.output.path, "index.ts"); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [ "Generating barrel file", ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}` ] }); const barrelFiles = fabric.files.filter((file) => { return file.sources.some((source) => source.isIndexable); }); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`Found ${barrelFiles.length} indexable files for barrel export`] }); const pluginKeyMap = /* @__PURE__ */ new Map(); for (const plugin of pluginManager.plugins) pluginKeyMap.set(JSON.stringify(plugin.key), plugin); const rootFile = { path: rootPath, baseName: "index.ts", exports: barrelFiles.flatMap((file) => { const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly); return file.sources?.map((source) => { if (!file.path || !source.isIndexable) return; const meta = file.meta; const pluginOptions = (meta?.pluginKey ? pluginKeyMap.get(JSON.stringify(meta.pluginKey)) : void 0)?.options; if (!pluginOptions || pluginOptions?.output?.barrelType === false) return; return { name: config.output.barrelType === "all" ? void 0 : [source.name], path: getRelativePath(rootPath, file.path), isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly }; }).filter(Boolean); }).filter(Boolean), sources: [], imports: [], meta: {} }; await fabric.upsertFile(rootFile); await events.emit("debug", { date: /* @__PURE__ */ new Date(), logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`] }); } const files = [...fabric.files]; await fabric.write({ extension: config.output.extension }); return { failedPlugins, fabric, files, pluginManager, pluginTimings }; } catch (error) { return { failedPlugins, fabric, files: [], pluginManager, pluginTimings, error }; } } //#endregion //#region src/defineLogger.ts function defineLogger(logger) { return { ...logger }; } //#endregion //#region src/definePlugin.ts /** * Wraps a plugin builder to make the options parameter optional. */ function definePlugin(build$1) { return (options) => build$1(options ?? {}); } //#endregion //#region ../../node_modules/.pnpm/p-limit@4.0.0/node_modules/p-limit/index.js function pLimit(concurrency) { if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up"); const queue = new Queue(); let activeCount = 0; const next = () => { activeCount--; if (queue.size > 0) queue.dequeue()(); }; const run = async (fn, resolve$1, args) => { activeCount++; const result = (async () => fn(...args))(); resolve$1(result); try { await result; } catch {} next(); }; const enqueue = (fn, resolve$1, args) => { queue.enqueue(run.bind(void 0, fn, resolve$1, args)); (async () => { await Promise.resolve(); if (activeCount < concurrency && queue.size > 0) queue.dequeue()(); })(); }; const generator = (fn, ...args) => new Promise((resolve$1) => { enqueue(fn, resolve$1, args); }); Object.defineProperties(generator, { activeCount: { get: () => activeCount }, pendingCount: { get: () => queue.size }, clearQueue: { value: () => { queue.clear(); } } }); return generator; } //#endregion //#region ../../node_modules/.pnpm/p-locate@6.0.0/node_modules/p-locate/index.js var EndError = class extends Error { constructor(value) { super(); this.value = value; } }; const testElement = async (element, tester) => tester(await element); const finder = async (element) => { const values = await Promise.all(element); if (values[1] === true) throw new EndError(values[0]); return false; }; async function pLocate(iterable, tester, { concurrency = Number.POSITIVE_INFINITY, preserveOrder = true } = {}) { const limit = pLimit(concurrency); const items = [...iterable].map((element) => [element, limit(testElement, element, tester)]); const checkLimit = pLimit(preserveOrder ? 1 : Number.POSITIVE_INFINITY); try { await Promise.all(items.map((element) => checkLimit(finder, element))); } catch (error) { if (error instanceof EndError) return error.value; throw error; } } //#endregion //#region ../../node_modules/.pnpm/locate-path@7.2.0/node_modules/locate-path/index.js const typeMappings = { directory: "isDirectory", file: "isFile" }; function checkType(type) { if (Object.hasOwnProperty.call(typeMappings, type)) return; throw new Error(`Invalid type specified: ${type}`); } const matchType = (type, stat) => stat[typeMappings[type]](); const toPath$1 = /* @__PURE__ */ __name((urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath, "toPath"); async function locatePath(paths, { cwd = process$1.cwd(), type = "file", allowSymlinks = true, concurrency, preserveOrder } = {}) { checkType(type); cwd = toPath$1(cwd); const statFunction = allowSymlinks ? promises.stat : promises.lstat; return pLocate(paths, async (path_) => { try { return matchType(type, await statFunction(path.resolve(cwd, path_))); } catch { return false; } }, { concurrency, preserveOrder }); } function locatePathSync(paths, { cwd = process$1.cwd(), type = "file", allowSymlinks = true } = {}) { checkType(type); cwd = toPath$1(cwd); const statFunction = allowSymlinks ? fs.statSync : fs.lstatSync; for (const path_ of paths) try { const stat = statFunction(path.resolve(cwd, path_), { throwIfNoEntry: false }); if (!stat) continue; if (matchType(type, stat)) return path_; } catch {} } //#endregion //#region ../../node_modules/.pnpm/unicorn-magic@0.1.0/node_modules/unicorn-magic/node.js function toPath(urlOrPath) { return urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath; } //#endregion //#region ../../node_modules/.pnpm/find-up@7.0.0/node_modules/find-up/index.js const findUpStop = Symbol("findUpStop"); async function findUpMultiple(name, options = {}) { let directory = path.resolve(toPath(options.cwd) ?? ""); const { root } = path.parse(directory); const stopAt = path.resolve(directory, toPath(options.stopAt ?? root)); const limit = options.limit ?? Number.POSITIVE_INFINITY; const paths = [name].flat(); const runMatcher = async (locateOptions) => { if (typeof name !== "function") return locatePath(paths, locateOptions); const foundPath = await name(locateOptions.cwd); if (typeof foundPath === "string") return locatePath([foundPath], locateOptions); return foundPath; }; const matches = []; while (true) { const foundPath = await runMatcher({ ...options, cwd: directory }); if (foundPath === findUpStop) break; if (foundPath) matches.push(path.resolve(directory, foundPath)); if (directory === stopAt || matches.length >= limit) break; directory = path.dirname(directory); } return matches; } function findUpMultipleSync(name, options = {}) { let directory = path.resolve(toPath(options.cwd) ?? ""); const { root } = path.parse(directory); const stopAt = path.resolve(directory, toPath(options.stopAt) ?? root); const limit = options.limit ?? Number.POSITIVE_INFINITY; const paths = [name].flat(); const runMatcher = (locateOptions) => { if (typeof name !== "function") return locatePathSync(paths, locateOptions); const foundPath = name(locateOptions.cwd); if (typeof foundPath === "string") return locatePathSync([foundPath], locateOptions); return foundPath; }; const matches = []; while (true) { const foundPath = runMatcher({ ...options, cwd: directory }); if (foundPath === findUpStop) break; if (foundPath) matches.push(path.resolve(directory, foundPath)); if (directory === stopAt || matches.length >= limit) break; directory = path.dirname(directory); } return matches; } async function findUp(name, options = {}) { return (await findUpMultiple(name, { ...options, limit: 1 }))[0]; } function findUpSync(name, options = {}) { return findUpMultipleSync(name, { ...options, limit: 1 })[0]; } //#endregion //#region src/PackageManager.ts var PackageManager = class PackageManager { static #cache = {}; #cwd; #SLASHES = new Set(["/", "\\"]); constructor(workspace) { if (workspace) this.#cwd = workspace; return this; } set workspace(workspace) { this.#cwd = workspace; } get workspace() { return this.#cwd; } normalizeDirectory(directory) { if (!this.#SLASHES.has(directory[directory.length - 1])) return `${directory}/`; return directory; } getLocation(path$1) { let location = path$1; if (this.#cwd) location = mod.createRequire(this.normalizeDirectory(this.#cwd)).resolve(path$1); return location; } async import(path$1) { try { let location = this.getLocation(path$1); if (os.platform() === "win32") location = pathToFileURL(location).href; const module = await import(location); return module?.default ?? module; } catch (error) { console.error(error); return; } } async getPackageJSON() { const pkgPath = await findUp(["package.json"], { cwd: this.#cwd }); if (!pkgPath) return; const json = await read(pkgPath); return JSON.parse(json); } getPackageJSONSync() { const pkgPath = findUpSync(["package.json"], { cwd: this.#cwd }); if (!pkgPath) return; const json = readSync(pkgPath); return JSON.parse(json); } static setVersion(dependency, version$2) { PackageManager.#cache[dependency] = version$2; } #match(packageJSON, dependency) { const dependencies = { ...packageJSON["dependencies"] || {}, ...packageJSON["devDependencies"] || {} }; if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency]; const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency)); return matchedDependency ? dependencies[matchedDependency] : void 0; } async getVersion(dependency) { if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency]; const packageJSON = await this.getPackageJSON(); if (!packageJSON) return; return this.#match(packageJSON, dependency); } getVersionSync(dependency) { if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency]; const packageJSON = this.getPackageJSONSync(); if (!packageJSON) return; return this.#match(packageJSON, dependency); } async isValid(dependency, version$2) { const packageVersion = await this.getVersion(dependency); if (!packageVersion) return false; if (packageVersion === version$2) return true; const semVer = coerce(packageVersion); if (!semVer) throw new Error(`${packageVersion} is not valid`); return satisfies(semVer, version$2); } isValidSync(dependency, version$2) { const packageVersion = this.getVersionSync(dependency); if (!packageVersion) return false; if (version$2 === "next" && packageVersion === version$2) return true; const semVer = coerce(packageVersion); if (!semVer) return false; return satisfies(semVer, version$2); } }; //#endregion //#region src/types.ts const LogLevel = { silent: Number.NEGATIVE_INFINITY, error: 0, warn: 1, info: 3, verbose: 4, debug: 5 }; //#endregion export { BaseGenerator, LogLevel, PackageManager, PluginManager, PromiseManager, build, build as default, defineConfig, defineLogger, definePlugin, getBarrelFiles, getMode, isInputPath, safeBuild, setup }; //# sourceMappingURL=index.js.map