@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
JavaScript
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