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

Merge pull request #1251 from hey-api/fix/parser-custom-plugin

fix: add support for custom plugins

authored by

Lubos and committed by
GitHub
6119693e 404690ca

+130 -11
+5
.changeset/fluffy-tomatoes-warn.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix: add support for custom plugins
+38 -9
packages/openapi-ts/src/index.ts
··· 15 15 operationParameterFilterFn, 16 16 operationParameterNameFn, 17 17 } from './openApi/config'; 18 + import type { ClientPlugins } from './plugins'; 18 19 import { defaultPluginConfigs } from './plugins'; 19 - import type { PluginNames } from './plugins/types'; 20 + import type { DefaultPluginConfigsMap, PluginNames } from './plugins/types'; 20 21 import type { Client } from './types/client'; 21 22 import type { ClientConfig, Config, UserConfig } from './types/config'; 22 23 import { CLIENTS } from './types/config'; ··· 164 165 }; 165 166 166 167 const getPluginOrder = ({ 168 + pluginConfigs, 167 169 userPlugins, 168 170 }: { 171 + pluginConfigs: DefaultPluginConfigsMap<ClientPlugins>; 169 172 userPlugins: ReadonlyArray<PluginNames>; 170 173 }): Config['pluginOrder'] => { 171 174 const circularReferenceTracker = new Set<PluginNames>(); ··· 179 182 if (!visitedNodes.has(name)) { 180 183 circularReferenceTracker.add(name); 181 184 182 - for (const dependency of defaultPluginConfigs[name]._dependencies || []) { 185 + const pluginConfig = pluginConfigs[name]; 186 + 187 + if (!pluginConfig) { 188 + throw new Error( 189 + `🚫 unknown plugin dependency "${name}" - do you need to register a custom plugin with this name?`, 190 + ); 191 + } 192 + 193 + for (const dependency of pluginConfig._dependencies || []) { 183 194 dfs(dependency); 184 195 } 185 196 186 - for (const dependency of defaultPluginConfigs[name] 187 - ._optionalDependencies || []) { 197 + for (const dependency of pluginConfig._optionalDependencies || []) { 188 198 if (userPlugins.includes(dependency)) { 189 199 dfs(dependency); 190 200 } ··· 218 228 return plugin; 219 229 } 220 230 221 - // @ts-ignore 231 + // @ts-expect-error 222 232 userPluginsConfig[plugin.name] = plugin; 223 233 return plugin.name; 224 234 }); 225 - const pluginOrder = getPluginOrder({ userPlugins }); 235 + 236 + const pluginOrder = getPluginOrder({ 237 + pluginConfigs: { 238 + ...userPluginsConfig, 239 + ...defaultPluginConfigs, 240 + }, 241 + userPlugins, 242 + }); 226 243 227 244 const plugins = pluginOrder.reduce( 228 245 (result, name) => { 229 - // @ts-ignore 246 + const defaultOptions = defaultPluginConfigs[name]; 247 + const userOptions = userPluginsConfig[name]; 248 + if (userOptions && defaultOptions) { 249 + const nativePluginOption = Object.keys(userOptions).find((key) => 250 + key.startsWith('_'), 251 + ); 252 + if (nativePluginOption) { 253 + throw new Error( 254 + `🚫 cannot register plugin "${userOptions.name}" - attempting to override a native plugin option "${nativePluginOption}"`, 255 + ); 256 + } 257 + } 258 + // @ts-expect-error 230 259 result[name] = { 231 - ...defaultPluginConfigs[name], 232 - ...userPluginsConfig[name], 260 + ...defaultOptions, 261 + ...userOptions, 233 262 }; 234 263 return result; 235 264 },
+2 -1
packages/openapi-ts/src/plugins/types.d.ts
··· 40 40 } 41 41 42 42 interface CommonConfig { 43 - name: PluginNames; 43 + // eslint-disable-next-line @typescript-eslint/ban-types 44 + name: PluginNames | (string & {}); 44 45 output?: string; 45 46 } 46 47
+85 -1
packages/openapi-ts/test/plugins.spec.ts
··· 2 2 import path from 'node:path'; 3 3 import { fileURLToPath } from 'node:url'; 4 4 5 - import { describe, expect, it } from 'vitest'; 5 + import { describe, expect, it, vi } from 'vitest'; 6 6 7 7 import { createClient } from '../'; 8 + import type { PluginConfig } from '../src/plugins/types'; 8 9 import type { UserConfig } from '../src/types/config'; 9 10 import { getFilePaths } from './utils'; 10 11 ··· 221 222 ), 222 223 ); 223 224 }); 225 + }); 226 + }); 227 + 228 + describe('custom plugin', () => { 229 + it('handles a custom plugin', async () => { 230 + const myPlugin: PluginConfig<{ 231 + customOption: boolean; 232 + name: string; 233 + output: string; 234 + }> = { 235 + _dependencies: ['@hey-api/types'], 236 + _handler: vi.fn(), 237 + _handlerLegacy: vi.fn(), 238 + customOption: true, 239 + name: 'my-plugin', 240 + output: 'my-plugin', 241 + }; 242 + 243 + await createClient({ 244 + client: '@hey-api/client-fetch', 245 + experimentalParser: true, 246 + input: path.join(__dirname, 'spec', '3.1.x', 'full.json'), 247 + output: path.join(outputDir, myPlugin.name, 'default'), 248 + // @ts-expect-error 249 + plugins: [myPlugin], 250 + }); 251 + 252 + expect(myPlugin._handler).toHaveBeenCalled(); 253 + expect(myPlugin._handlerLegacy).not.toHaveBeenCalled(); 254 + }); 255 + 256 + it('throws on invalid dependency', async () => { 257 + const myPlugin: PluginConfig<{ 258 + name: string; 259 + output: string; 260 + }> = { 261 + // @ts-expect-error 262 + _dependencies: ['@hey-api/oops'], 263 + _handler: vi.fn(), 264 + _handlerLegacy: vi.fn(), 265 + name: 'my-plugin', 266 + output: 'my-plugin', 267 + }; 268 + 269 + await expect(() => 270 + createClient({ 271 + client: '@hey-api/client-fetch', 272 + experimentalParser: true, 273 + input: path.join(__dirname, 'spec', '3.1.x', 'full.json'), 274 + output: path.join(outputDir, myPlugin.name, 'default'), 275 + // @ts-expect-error 276 + plugins: [myPlugin], 277 + }), 278 + ).rejects.toThrowError(/unknown plugin/g); 279 + 280 + expect(myPlugin._handler).not.toHaveBeenCalled(); 281 + expect(myPlugin._handlerLegacy).not.toHaveBeenCalled(); 282 + }); 283 + 284 + it('throws on native plugin override', async () => { 285 + const myPlugin: PluginConfig<{ 286 + name: string; 287 + output: string; 288 + }> = { 289 + _handler: vi.fn(), 290 + _handlerLegacy: vi.fn(), 291 + name: '@hey-api/types', 292 + output: 'my-plugin', 293 + }; 294 + 295 + await expect(() => 296 + createClient({ 297 + client: '@hey-api/client-fetch', 298 + experimentalParser: true, 299 + input: path.join(__dirname, 'spec', '3.1.x', 'full.json'), 300 + output: path.join(outputDir, myPlugin.name, 'default'), 301 + // @ts-expect-error 302 + plugins: [myPlugin], 303 + }), 304 + ).rejects.toThrowError(/cannot register plugin/g); 305 + 306 + expect(myPlugin._handler).not.toHaveBeenCalled(); 307 + expect(myPlugin._handlerLegacy).not.toHaveBeenCalled(); 224 308 }); 225 309 }); 226 310 }