fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at f3a264b2c5d7af06bb44fa0350ef613bde3aff87 182 lines 5.7 kB view raw
1import path from 'node:path'; 2 3import { type Logger, Project } from '@hey-api/codegen-core'; 4import { $RefParser } from '@hey-api/json-schema-ref-parser'; 5import { 6 applyNaming, 7 buildGraph, 8 compileInputPath, 9 Context, 10 getSpec, 11 type Input, 12 logInputPaths, 13 type OpenApi, 14 parseOpenApiSpec, 15 patchOpenApiSpec, 16 postprocessOutput, 17 type WatchValues, 18} from '@hey-api/shared'; 19import colors from 'ansi-colors'; 20 21import { postProcessors } from './config/output/postprocess'; 22import type { Config } from './config/types'; 23import { generateOutput } from './generate/output'; 24import { PythonRenderer } from './py-dsl'; 25 26export async function createClient({ 27 config, 28 dependencies, 29 jobIndex, 30 logger, 31 watches: _watches, 32}: { 33 config: Config; 34 dependencies: Record<string, string>; 35 jobIndex: number; 36 logger: Logger; 37 /** 38 * Always undefined on the first run, defined on subsequent runs. 39 */ 40 watches?: ReadonlyArray<WatchValues>; 41}): Promise<Context | undefined> { 42 const watches: ReadonlyArray<WatchValues> = 43 _watches || 44 Array.from({ length: config.input.length }, () => ({ 45 headers: new Headers(), 46 })); 47 48 const inputPaths = config.input.map((input) => compileInputPath(input)); 49 50 // on first run, print the message as soon as possible 51 if (config.logs.level !== 'silent' && !_watches) { 52 logInputPaths(inputPaths, jobIndex); 53 } 54 55 const getSpecData = async (input: Input, index: number) => { 56 const eventSpec = logger.timeEvent('spec'); 57 const { arrayBuffer, error, resolvedInput, response } = await getSpec({ 58 fetchOptions: input.fetch, 59 inputPath: inputPaths[index]!.path, 60 timeout: input.watch.timeout, 61 watch: watches[index]!, 62 }); 63 eventSpec.timeEnd(); 64 65 // throw on first run if there's an error to preserve user experience 66 // if in watch mode, subsequent errors won't throw to gracefully handle 67 // cases where server might be reloading 68 if (error && !_watches) { 69 throw new Error(`Request failed with status ${response.status}: ${response.statusText}`); 70 } 71 72 return { arrayBuffer, resolvedInput }; 73 }; 74 const specData = ( 75 await Promise.all(config.input.map((input, index) => getSpecData(input, index))) 76 ).filter((data) => data.arrayBuffer || data.resolvedInput); 77 78 let context: Context | undefined; 79 80 if (specData.length) { 81 const refParser = new $RefParser(); 82 const data = 83 specData.length > 1 84 ? await refParser.bundleMany({ 85 arrayBuffer: specData.map((data) => data.arrayBuffer!), 86 pathOrUrlOrSchemas: [], 87 resolvedInputs: specData.map((data) => data.resolvedInput!), 88 }) 89 : await refParser.bundle({ 90 arrayBuffer: specData[0]!.arrayBuffer, 91 pathOrUrlOrSchema: undefined, 92 resolvedInput: specData[0]!.resolvedInput!, 93 }); 94 95 // on subsequent runs in watch mode, print the message only if we know we're 96 // generating the output 97 if (config.logs.level !== 'silent' && _watches) { 98 console.clear(); 99 logInputPaths(inputPaths, jobIndex); 100 } 101 102 const eventInputPatch = logger.timeEvent('input.patch'); 103 await patchOpenApiSpec({ patchOptions: config.parser.patch, spec: data }); 104 eventInputPatch.timeEnd(); 105 106 const eventParser = logger.timeEvent('parser'); 107 // TODO: allow overriding via config 108 const project = new Project({ 109 defaultFileName: '__init__', 110 fileName: (base) => { 111 const name = applyNaming(base, config.output.fileName); 112 const { suffix } = config.output.fileName; 113 if (!suffix) { 114 return name; 115 } 116 return name === '__init__' || name.endsWith(suffix) ? name : `${name}${suffix}`; 117 }, 118 nameConflictResolvers: config.output.nameConflictResolver 119 ? { 120 typescript: config.output.nameConflictResolver, 121 } 122 : undefined, 123 renderers: [ 124 new PythonRenderer({ 125 header: config.output.header, 126 preferExportAll: config.output.preferExportAll, 127 preferFileExtension: config.output.importFileExtension || undefined, 128 resolveModuleName: config.output.resolveModuleName, 129 }), 130 ], 131 root: config.output.path, 132 }); 133 context = new Context<OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X, Config>({ 134 config, 135 dependencies, 136 logger, 137 project, 138 spec: data as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X, 139 }); 140 parseOpenApiSpec(context); 141 context.graph = buildGraph(context.ir, logger).graph; 142 eventParser.timeEnd(); 143 144 const eventGenerator = logger.timeEvent('generator'); 145 await generateOutput(context); 146 eventGenerator.timeEnd(); 147 148 const eventPostprocess = logger.timeEvent('postprocess'); 149 if (!config.dryRun) { 150 const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 151 postprocessOutput(config.output, postProcessors, jobPrefix); 152 153 if (config.logs.level !== 'silent') { 154 const outputPath = process.env.INIT_CWD 155 ? `./${path.relative(process.env.INIT_CWD, config.output.path)}` 156 : config.output.path; 157 console.log( 158 `${jobPrefix}${colors.green('✅ Done!')} Your output is in ${colors.cyanBright(outputPath)}`, 159 ); 160 } 161 } 162 eventPostprocess.timeEnd(); 163 } 164 165 const watchedInput = config.input.find( 166 (input, index) => input.watch.enabled && typeof inputPaths[index]!.path === 'string', 167 ); 168 169 if (watchedInput) { 170 setTimeout(() => { 171 createClient({ 172 config, 173 dependencies, 174 jobIndex, 175 logger, 176 watches, 177 }); 178 }, watchedInput.watch.interval); 179 } 180 181 return context; 182}