UNPKG

@hey-api/openapi-ts

Version:

🌀 OpenAPI to TypeScript codegen. Production-grade SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.

257 lines (256 loc) • 10.7 kB
import { _ as postProcessors, c as TypeScriptRenderer, g as getTypedConfig, h as getClientPlugin, i as generateClientBundle, t as resolveJobs } from "./init-D3VuY80Z.mjs"; import { Logger, Logger as Logger$1, Project } from "@hey-api/codegen-core"; import { ConfigValidationError, Context, InputError, IntentContext, JobError, OperationPath as OperationPath$1, OperationStrategy as OperationStrategy$1, applyNaming, buildGraph, checkNodeVersion, compileInputPath, defaultPaginationKeywords, definePluginConfig as definePluginConfig$1, getInputError, getLogs, getSpec, logCrashReport, logInputPaths, openGitHubIssueWithCrashReport, parseOpenApiSpec, patchOpenApiSpec, postprocessOutput, printCliIntro, printCrashReport, shouldReportCrash, utils } from "@hey-api/shared"; import colors from "ansi-colors"; import colorSupport from "color-support"; import path from "node:path"; import { fileURLToPath } from "node:url"; import fs from "node:fs"; import { $RefParser, ResolverError } from "@hey-api/json-schema-ref-parser"; import { format } from "@lukeed/ms"; import fsPromises from "node:fs/promises"; //#region src/generate/output.ts async function generateOutput(context) { const outputPath = path.resolve(context.config.output.path); if (context.config.output.clean) { if (fs.existsSync(outputPath)) fs.rmSync(outputPath, { force: true, recursive: true }); } const config = getTypedConfig(context); const client = getClientPlugin(config); if ("bundle" in client.config && client.config.bundle && !config.dryRun) config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({ header: config.output.header, module: config.output.module, outputPath, plugin: client, project: context.gen }); for (const plugin of context.registerPlugins()) await plugin.run(); context.gen.plan(); const ctx = new IntentContext(context.spec); for (const intent of context.intents) await intent.run(ctx); let fileCount = 0; const writes = []; for (const file of context.gen.render()) { const filePath = path.resolve(outputPath, file.path); const dir = path.dirname(filePath); if (!context.config.dryRun) writes.push(fsPromises.mkdir(dir, { recursive: true }).then(() => fsPromises.writeFile(filePath, file.content, { encoding: "utf8" }))); fileCount++; } await Promise.all(writes); const { source } = context.config.output; if (source.enabled) { const sourcePath = source.path === null ? void 0 : path.resolve(outputPath, source.path); if (!context.config.dryRun && sourcePath && sourcePath !== outputPath) await fsPromises.mkdir(sourcePath, { recursive: true }); const serialized = await source.serialize(context.spec); if (!context.config.dryRun && sourcePath) { await fsPromises.writeFile(path.resolve(sourcePath, `${source.fileName}.${source.extension}`), serialized, { encoding: "utf8" }); fileCount++; } if (source.callback) await source.callback(serialized); } return { fileCount }; } //#endregion //#region src/createClient.ts async function createClient$1({ config, dependencies, jobIndex, logger, watches: _watches }) { const watches = _watches || Array.from({ length: config.input.length }, () => ({ headers: new Headers() })); const jobStart = Date.now(); const inputPaths = config.input.map((input) => compileInputPath(input)); if (config.logs.level !== "silent" && !_watches) logInputPaths(inputPaths, jobIndex); const getSpecData = async (input, index) => { const eventSpec = logger.timeEvent("spec"); const { arrayBuffer, error, resolvedInput, response } = await getSpec({ fetchOptions: input.fetch, inputPath: inputPaths[index].path, timeout: input.watch.timeout, watch: watches[index] }); eventSpec.timeEnd(); if (error && !_watches) { const text = await response.text().catch(() => ""); const message = `Request failed with status ${response.status}: ${text || response.statusText}`; if (response.status >= 400 && response.status < 500) { const statusText = response.statusText || "Unknown"; const originalError = new Error(message); originalError.source = String(inputPaths[index].path); throw new InputError(`Input request failed: ${response.status} ${statusText}`, originalError); } throw new Error(message); } return { arrayBuffer, resolvedInput }; }; const specData = (await Promise.all(config.input.map((input, index) => getSpecData(input, index)))).filter((data) => data.arrayBuffer || data.resolvedInput); let context; if (specData.length) { const refParser = new $RefParser(); let data; try { data = specData.length > 1 ? await refParser.bundleMany({ arrayBuffer: specData.map((data) => data.arrayBuffer), pathOrUrlOrSchemas: [], resolvedInputs: specData.map((data) => data.resolvedInput) }) : await refParser.bundle({ arrayBuffer: specData[0].arrayBuffer, pathOrUrlOrSchema: void 0, resolvedInput: specData[0].resolvedInput }); } catch (err) { if (err instanceof ResolverError && err.ioErrorCode === "ENOENT") throw new InputError("Input file not found", err); throw err; } if (config.logs.level !== "silent" && _watches) { console.clear(); logInputPaths(inputPaths, jobIndex); } const eventInputPatch = logger.timeEvent("input.patch"); await patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); eventInputPatch.timeEnd(); const eventParser = logger.timeEvent("parser"); const header = config.output.header; context = new Context({ config, dependencies, logger, project: new Project({ defaultFileName: "index", fileName: (base) => { const name = applyNaming(base, config.output.fileName); const { suffix } = config.output.fileName; if (!suffix) return name; return name === "index" || name.endsWith(suffix) ? name : `${name}${suffix}`; }, nameConflictResolvers: config.output.nameConflictResolver ? { typescript: config.output.nameConflictResolver } : void 0, renderers: [new TypeScriptRenderer({ header: (ctx) => { const defaultValue = ["// This file is auto-generated by @hey-api/openapi-ts"]; const result = typeof header === "function" ? header({ ...ctx, defaultValue }) : header; return result === void 0 ? defaultValue : result; }, module: config.output.module, preferExportAll: config.output.preferExportAll })], root: config.output.path }), spec: data }); parseOpenApiSpec(context); context.graph = buildGraph(context.ir, logger).graph; eventParser.timeEnd(); const eventGenerator = logger.timeEvent("generator"); const { fileCount } = await generateOutput(context); eventGenerator.timeEnd(); const totalMs = Date.now() - jobStart; const eventPostprocess = logger.timeEvent("postprocess"); if (!config.dryRun) { const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); postprocessOutput(config.output, postProcessors, jobPrefix); if (config.logs.level !== "silent") { const outputPath = process.env.INIT_CWD ? `./${path.relative(process.env.INIT_CWD, config.output.path)}` : config.output.path; console.log(`${jobPrefix}${colors.green("✅ Done!")} Your output is in ${colors.cyanBright(outputPath)} ${colors.gray(`(${fileCount} ${fileCount === 1 ? "file" : "files"} in ${format(totalMs)})`)}`); } } eventPostprocess.timeEnd(); } const watchedInput = config.input.find((input, index) => input.watch.enabled && typeof inputPaths[index].path === "string"); if (watchedInput) setTimeout(() => { createClient$1({ config, dependencies, jobIndex, logger, watches }); }, watchedInput.watch.interval); return context; } //#endregion //#region src/generate.ts const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** * Generate a client from the provided configuration. * * @param userConfig User provided {@link UserConfig} configuration(s). */ async function createClient(userConfig, logger = new Logger()) { const resolvedConfig = typeof userConfig === "function" ? await userConfig() : userConfig; const userConfigs = resolvedConfig ? resolvedConfig instanceof Array ? resolvedConfig : [resolvedConfig] : []; let rawLogs = userConfigs.find((config) => getLogs(config.logs).level !== "silent")?.logs; if (typeof rawLogs === "string") rawLogs = getLogs(rawLogs); let jobs = []; try { checkNodeVersion(); const eventCreateClient = logger.timeEvent("createClient"); const eventConfig = logger.timeEvent("config"); const resolved = await resolveJobs({ logger, userConfigs }); const dependencies = resolved.dependencies; jobs = resolved.jobs; if (jobs.some((job) => job.config.logs.level !== "silent")) printCliIntro(__dirname); eventConfig.timeEnd(); const configErrors = jobs.flatMap((job) => job.errors.map((error) => ({ error, jobIndex: job.index }))); if (configErrors.length) throw new ConfigValidationError(configErrors); const contexts = (await Promise.all(jobs.map(async (job) => { try { return await createClient$1({ config: job.config, dependencies, jobIndex: job.index, logger }); } catch (error) { if (error instanceof Error) throw new JobError("", { error, jobIndex: job.index }); } }))).filter((ctx) => ctx !== void 0); eventCreateClient.timeEnd(); logger.report(jobs.some((job) => job.config.logs.level === "debug")); return contexts; } catch (error) { const logs = jobs.find((job) => job.config.logs.level !== "silent")?.config.logs ?? jobs[0]?.config.logs ?? rawLogs; const dryRun = jobs.some((job) => job.config.dryRun) ?? userConfigs.some((config) => config.dryRun) ?? false; const inputError = getInputError(error); const normalizedError = inputError ?? error; const logPath = logs?.file && !dryRun ? logCrashReport(normalizedError, logs.path ?? "") : void 0; if (!logs || logs.level !== "silent") { printCrashReport({ error, logPath }); if (await shouldReportCrash({ error: normalizedError, isInteractive: jobs.some((job) => job.config.interactive) ?? userConfigs.some((config) => config.interactive) ?? false })) await openGitHubIssueWithCrashReport(error, __dirname); } if (inputError) return []; throw error; } } //#endregion //#region src/index.ts colors.enabled = colorSupport().hasBasic; async function defineConfig(config) { return typeof config === "function" ? await config() : config; } //#endregion export { defineConfig as a, createClient as c, defaultPaginationKeywords as i, OperationPath$1 as n, definePluginConfig$1 as o, OperationStrategy$1 as r, utils as s, Logger$1 as t }; //# sourceMappingURL=src-BeNy9O9X.mjs.map