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.

fix: add output.module option

Lubos e4eea23a 67351ad5

+198 -213
+6
.changeset/tangy-cities-carry.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + "@hey-api/shared": patch 4 + --- 5 + 6 + **output**: add `module` option
+1 -1
docs/openapi-ts/clients/ofetch.md
··· 144 144 The `ofetch` client supports two complementary options: 145 145 146 146 - built-in Hey API interceptors exposed via `client.interceptors` 147 - - native `ofetch` hooks passed through config (e.g. `onRequest`) 147 + - native `ofetch` hooks passed through config (e.g., `onRequest`) 148 148 149 149 ### Example: Request interceptor 150 150
+62 -59
docs/openapi-ts/configuration/output.md
··· 36 36 37 37 You can learn more about complex use cases in the [Advanced](/openapi-ts/configuration#advanced) section. 38 38 39 - ## File Name 39 + ## File 40 + 41 + Control how files are named and annotated in the generated output. 42 + 43 + ### File Name 40 44 41 45 You can customize the naming and casing pattern for files using the `fileName` option. 42 46 ··· 108 112 109 113 ::: 110 114 111 - ## Module Extension 115 + ### File Header 112 116 113 - You can customize the extension used for TypeScript modules. 117 + The generated output includes a notice in every file warning that any modifications will be lost when the files are regenerated. You can customize or disable this notice using the `header` option. 114 118 115 119 ::: code-group 116 120 117 - ```js [default] 121 + ```js [example] 122 + /* eslint-disable */ 123 + // This file is auto-generated by @hey-api/openapi-ts 124 + 125 + /** ... */ 126 + ``` 127 + 128 + <!-- prettier-ignore-start --> 129 + ```js [config] 118 130 export default { 119 131 input: 'hey-api/backend', // sign up at app.heyapi.dev 120 132 output: { 121 - importFileExtension: undefined, // [!code ++] 133 + header: (ctx) => [ // [!code ++] 134 + '/* eslint-disable */', // [!code ++] 135 + ...ctx.defaultValue, // [!code ++] 136 + ], // [!code ++] 122 137 path: 'src/client', 123 138 }, 124 139 }; 125 140 ``` 141 + <!-- prettier-ignore-end --> 126 142 127 - ```js [disabled] 143 + ::: 144 + 145 + ## Module 146 + 147 + Control how module specifiers are generated in the output. 148 + 149 + ### Module Extension 150 + 151 + Set `module.extension` to define the file extension used in import specifiers. This is useful when targeting environments that require fully specified imports (e.g., Node ESM or certain bundlers). 152 + 153 + ::: code-group 154 + 155 + ```js [example] 156 + import foo from './foo.js'; 157 + import bar from './bar.js'; 158 + ``` 159 + 160 + ```js [config] 128 161 export default { 129 162 input: 'hey-api/backend', // sign up at app.heyapi.dev 130 163 output: { 131 - importFileExtension: null, // [!code ++] 164 + module: { 165 + extension: '.js', // [!code ++] 166 + }, 132 167 path: 'src/client', 133 168 }, 134 169 }; 135 170 ``` 136 171 137 - ```js [js] 138 - export default { 139 - input: 'hey-api/backend', // sign up at app.heyapi.dev 140 - output: { 141 - importFileExtension: '.js', // [!code ++] 142 - path: 'src/client', 143 - }, 144 - }; 172 + ::: 173 + 174 + ### Module Path 175 + 176 + Use `module.resolve` for full control over how module specifiers are generated. This lets you override specific modules or redirect them to custom locations (e.g., CDNs or internal aliases). 177 + 178 + ::: code-group 179 + 180 + ```js [example] 181 + import * as z from 'https://esm.sh/zod'; 145 182 ``` 146 183 147 - ```js [ts] 184 + <!-- prettier-ignore-start --> 185 + ```js [config] 148 186 export default { 149 187 input: 'hey-api/backend', // sign up at app.heyapi.dev 150 188 output: { 151 - importFileExtension: '.ts', // [!code ++] 189 + module: { 190 + resolve(path) { // [!code ++] 191 + if (path === 'zod') { // [!code ++] 192 + return 'https://esm.sh/zod'; // [!code ++] 193 + } // [!code ++] 194 + }, // [!code ++] 195 + }, 152 196 path: 'src/client', 153 197 }, 154 198 }; 155 199 ``` 200 + <!-- prettier-ignore-end --> 156 201 157 202 ::: 158 - 159 - By default, we don't add a file extension and let the runtime resolve it. 160 - 161 - ```js 162 - import foo from './foo'; 163 - ``` 164 - 165 - If we detect a [TSConfig file](#tsconfig-path) with `moduleResolution` option set to `nodenext`, we default the extension to `.js`. 166 - 167 - ```js 168 - import foo from './foo.js'; 169 - ``` 170 203 171 204 ## Source 172 205 ··· 333 366 export type ChatCompletion = string; 334 367 335 368 export type ChatCompletion_N2 = number; 336 - ``` 337 - 338 - ::: 339 - 340 - ## File Header 341 - 342 - The generated output includes a notice in every file warning that any modifications will be lost when the files are regenerated. You can customize or disable this notice using the `header` option. 343 - 344 - ::: code-group 345 - 346 - <!-- prettier-ignore-start --> 347 - ```js [config] 348 - export default { 349 - input: 'hey-api/backend', // sign up at app.heyapi.dev 350 - output: { 351 - header: [ 352 - '/* eslint-disable */', // [!code ++] 353 - '// This file is auto-generated by @hey-api/openapi-ts', // [!code ++] 354 - ], 355 - path: 'src/client', 356 - }, 357 - }; 358 - ``` 359 - <!-- prettier-ignore-end --> 360 - 361 - ```ts [example] 362 - /* eslint-disable */ 363 - // This file is auto-generated by @hey-api/openapi-ts 364 - 365 - /** ... */ 366 369 ``` 367 370 368 371 :::
+1 -1
docs/openapi-ts/migrating.md
··· 422 422 423 423 ### Bundle `@hey-api/client-*` plugins 424 424 425 - In previous releases, you had to install a separate client package to generate a fully working output, e.g. `npm install @hey-api/client-fetch`. This created a few challenges: getting started was slower, upgrading was sometimes painful, and bundling too. Beginning with v0.73.0, all Hey API clients are bundled by default and don't require installing any additional dependencies. You can remove any installed client packages and re-run `@hey-api/openapi-ts`. 425 + In previous releases, you had to install a separate client package to generate a fully working output, e.g., `npm install @hey-api/client-fetch`. This created a few challenges: getting started was slower, upgrading was sometimes painful, and bundling too. Beginning with v0.73.0, all Hey API clients are bundled by default and don't require installing any additional dependencies. You can remove any installed client packages and re-run `@hey-api/openapi-ts`. 426 426 427 427 ```sh 428 428 npm uninstall @hey-api/client-fetch
+1 -1
docs/openapi-ts/plugins/concepts/resolvers.md
··· 130 130 131 131 ### Replace default base 132 132 133 - You might want to replace the default base schema, e.g. `v.object()`. 133 + You might want to replace the default base schema, e.g., `v.object()`. 134 134 135 135 ```js 136 136 export const vUser = v.object({
+3 -3
docs/openapi-ts/plugins/pinia-colada.md
··· 60 60 61 61 ## Queries 62 62 63 - Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Query`, e.g. `getPetByIdQuery()`. 63 + Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Query`, e.g., `getPetByIdQuery()`. 64 64 65 65 ::: code-group 66 66 ··· 191 191 192 192 ::: 193 193 194 - Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g. `getPetByIdQueryKey()`. 194 + Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g., `getPetByIdQueryKey()`. 195 195 196 196 ::: code-group 197 197 ··· 223 223 224 224 ## Mutations 225 225 226 - Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g. `addPetMutation()`. 226 + Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g., `addPetMutation()`. 227 227 228 228 ::: code-group 229 229
+5 -5
docs/openapi-ts/plugins/tanstack-query.md
··· 115 115 116 116 ## Queries 117 117 118 - Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Options`, e.g. `getPetByIdOptions()`. 118 + Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Options`, e.g., `getPetByIdOptions()`. 119 119 120 120 ::: code-group 121 121 ··· 281 281 282 282 ::: 283 283 284 - Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g. `getPetByIdQueryKey()`. 284 + Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g., `getPetByIdQueryKey()`. 285 285 286 286 ::: code-group 287 287 ··· 313 313 314 314 ## Infinite Queries 315 315 316 - Infinite queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations) if we detect a [pagination](/openapi-ts/configuration/parser#pagination) parameter. The generated infinite query functions follow the naming convention of SDK functions and by default append `InfiniteOptions`, e.g. `getFooInfiniteOptions()`. 316 + Infinite queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations) if we detect a [pagination](/openapi-ts/configuration/parser#pagination) parameter. The generated infinite query functions follow the naming convention of SDK functions and by default append `InfiniteOptions`, e.g., `getFooInfiniteOptions()`. 317 317 318 318 ::: code-group 319 319 ··· 483 483 484 484 ::: 485 485 486 - Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `InfiniteQueryKey`, e.g. `getPetByIdInfiniteQueryKey()`. 486 + Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `InfiniteQueryKey`, e.g., `getPetByIdInfiniteQueryKey()`. 487 487 488 488 ::: code-group 489 489 ··· 515 515 516 516 ## Mutations 517 517 518 - Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g. `addPetMutation()`. 518 + Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g., `addPetMutation()`. 519 519 520 520 ::: code-group 521 521
+1 -1
docs/openapi-ts/plugins/transformers.md
··· 21 21 22 22 Transformers handle only the most common scenarios. Some of the known limitations are: 23 23 24 - - union types are not transformed (e.g. if you have multiple possible response shapes) 24 + - union types are not transformed (e.g., if you have multiple possible response shapes) 25 25 - only types defined through `$ref` are transformed 26 26 - error responses are not transformed 27 27
+3 -2
packages/openapi-python/src/config/output/config.ts
··· 24 24 name: '{{name}}', 25 25 suffix: '_gen', 26 26 }, 27 + module: {}, 27 28 path: '', 28 29 postProcess: [], 29 30 preferExportAll: false, ··· 48 49 }, 49 50 value: userOutput, 50 51 }) as Output; 51 - if (output.importFileExtension && !output.importFileExtension.startsWith('.')) { 52 - output.importFileExtension = `.${output.importFileExtension}`; 52 + if (output.module.extension && !output.module.extension.startsWith('.')) { 53 + output.module.extension = `.${output.module.extension}`; 53 54 } 54 55 output.postProcess = normalizePostProcess(userOutput.postProcess); 55 56 output.source = resolveSource(output);
+2 -21
packages/openapi-python/src/config/output/types.ts
··· 1 1 import type { BaseOutput, BaseUserOutput, UserPostProcessor } from '@hey-api/shared'; 2 - import type { AnyString } from '@hey-api/types'; 3 2 4 3 import type { PostProcessorPreset } from './postprocess'; 5 4 6 - type ImportFileExtensions = '.py'; 7 - 8 - export type UserOutput = BaseUserOutput & { 9 - /** 10 - * If specified, this will be the file extension used when importing 11 - * other modules. By default, we don't add a file extension and let the 12 - * runtime resolve it. If you're using moduleResolution `nodenext` or 13 - * `node16`, we default to `.js`. 14 - * 15 - * @default undefined 16 - */ 17 - importFileExtension?: ImportFileExtensions | AnyString | null; 5 + export type UserOutput = BaseUserOutput<'.py'> & { 18 6 /** 19 7 * Post-processing commands to run on the output folder, executed in order. 20 8 * ··· 35 23 preferExportAll?: boolean; 36 24 }; 37 25 38 - export type Output = BaseOutput & { 39 - /** 40 - * If specified, this will be the file extension used when importing 41 - * other modules. By default, we don't add a file extension and let the 42 - * runtime resolve it. If you're using moduleResolution `nodenext` or 43 - * `node16`, we default to `.js`. 44 - */ 45 - importFileExtension: ImportFileExtensions | AnyString | null | undefined; 26 + export type Output = BaseOutput<'.py'> & { 46 27 /** 47 28 * Whether `export * from 'module'` should be used when possible 48 29 * instead of named exports.
+1 -2
packages/openapi-python/src/createClient.ts
··· 131 131 const result = typeof header === 'function' ? header({ ...ctx, defaultValue }) : header; 132 132 return result === undefined ? defaultValue : result; 133 133 }, 134 + module: config.output.module, 134 135 preferExportAll: config.output.preferExportAll, 135 - preferFileExtension: config.output.importFileExtension || undefined, 136 - resolveModuleName: config.output.resolveModuleName, 137 136 }), 138 137 ], 139 138 root: config.output.path,
+11 -20
packages/openapi-python/src/py-dsl/utils/render.ts
··· 1 1 import type { RenderContext, Renderer } from '@hey-api/codegen-core'; 2 - import type { ResolveModuleName } from '@hey-api/shared'; 2 + import type { BaseOutput } from '@hey-api/shared'; 3 3 import type { MaybeArray, MaybeFunc } from '@hey-api/types'; 4 4 5 5 import { py } from '../../py-compiler'; ··· 36 36 */ 37 37 private _header?: HeaderArg; 38 38 /** 39 - * Whether `export * from 'module'` should be used when possible instead of named exports. 40 - * 41 - * @private 42 - */ 43 - private _preferExportAll: boolean; 44 - /** 45 - * Controls whether imports/exports include a file extension (e.g., '.ts' or '.js'). 39 + * Options for module specifier resolution. 46 40 * 47 41 * @private 48 42 */ 49 - private _preferFileExtension: string; 43 + private _module?: Partial<BaseOutput>['module']; 50 44 /** 51 - * Optional function to transform module specifiers. 45 + * Whether `export * from 'module'` should be used when possible instead of named exports. 52 46 * 53 47 * @private 54 48 */ 55 - private _resolveModuleName?: ResolveModuleName; 49 + private _preferExportAll: boolean; 56 50 57 51 constructor( 58 - args: { 52 + args: Pick<Partial<BaseOutput>, 'module'> & { 59 53 header?: HeaderArg; 60 54 preferExportAll?: boolean; 61 - preferFileExtension?: string; 62 - resolveModuleName?: ResolveModuleName; 63 55 } = {}, 64 56 ) { 65 57 this._header = args.header; 58 + this._module = args.module; 66 59 this._preferExportAll = args.preferExportAll ?? false; 67 - this._preferFileExtension = args.preferFileExtension ?? ''; 68 - this._resolveModuleName = args.resolveModuleName; 69 60 } 70 61 71 62 render(ctx: RenderContext<PyDsl>): string { ··· 200 191 const sortKey = moduleSortKey({ 201 192 file: ctx.file, 202 193 fromFile: exp.from, 203 - preferFileExtension: this._preferFileExtension, 194 + preferFileExtension: this._module?.extension || '', 204 195 root: ctx.project.root, 205 196 }); 206 - const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2]; 197 + const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2]; 207 198 const [groupIndex] = sortKey; 208 199 209 200 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map()); ··· 266 257 const sortKey = moduleSortKey({ 267 258 file: ctx.file, 268 259 fromFile: imp.from, 269 - preferFileExtension: this._preferFileExtension, 260 + preferFileExtension: this._module?.extension || '', 270 261 root: ctx.project.root, 271 262 }); 272 - const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2]; 263 + const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2]; 273 264 const [groupIndex] = sortKey; 274 265 275 266 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map());
+17 -15
packages/openapi-ts/src/config/output/__tests__/config.test.ts
··· 18 18 }); 19 19 20 20 describe('module resolution detection', () => { 21 - it('should set importFileExtension when moduleResolution is NodeNext', () => { 21 + it('should set module.extension when moduleResolution is NodeNext', () => { 22 22 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 23 23 fs.writeFileSync( 24 24 tsconfigPath, ··· 36 36 }, 37 37 }); 38 38 39 - expect(output.importFileExtension).toBe('.js'); 39 + expect(output.module.extension).toBe('.js'); 40 40 }); 41 41 42 - it('should set importFileExtension when moduleResolution is Node16', () => { 42 + it('should set module.extension when moduleResolution is Node16', () => { 43 43 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 44 44 fs.writeFileSync( 45 45 tsconfigPath, ··· 57 57 }, 58 58 }); 59 59 60 - expect(output.importFileExtension).toBe('.js'); 60 + expect(output.module.extension).toBe('.js'); 61 61 }); 62 62 63 - it('should set importFileExtension when module is NodeNext (implicit moduleResolution)', () => { 63 + it('should set module.extension when module is NodeNext (implicit moduleResolution)', () => { 64 64 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 65 65 fs.writeFileSync( 66 66 tsconfigPath, ··· 78 78 }, 79 79 }); 80 80 81 - expect(output.importFileExtension).toBe('.js'); 81 + expect(output.module.extension).toBe('.js'); 82 82 }); 83 83 84 - it('should set importFileExtension when module is Node16 (implicit moduleResolution)', () => { 84 + it('should set module.extension when module is Node16 (implicit moduleResolution)', () => { 85 85 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 86 86 fs.writeFileSync( 87 87 tsconfigPath, ··· 99 99 }, 100 100 }); 101 101 102 - expect(output.importFileExtension).toBe('.js'); 102 + expect(output.module.extension).toBe('.js'); 103 103 }); 104 104 105 - it('should not set importFileExtension for other module types', () => { 105 + it('should not set module.extension for other module types', () => { 106 106 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 107 107 fs.writeFileSync( 108 108 tsconfigPath, ··· 120 120 }, 121 121 }); 122 122 123 - expect(output.importFileExtension).toBeUndefined(); 123 + expect(output.module.extension).toBeUndefined(); 124 124 }); 125 125 126 - it('should not override explicit importFileExtension setting', () => { 126 + it('should not override explicit module.extension setting', () => { 127 127 const tsconfigPath = path.join(tmpDir, 'tsconfig.json'); 128 128 fs.writeFileSync( 129 129 tsconfigPath, ··· 136 136 137 137 const output = getOutput({ 138 138 output: { 139 - importFileExtension: '.ts', 139 + module: { 140 + extension: '.ts', 141 + }, 140 142 path: tmpDir, 141 143 tsConfigPath: tsconfigPath, 142 144 }, 143 145 }); 144 146 145 - expect(output.importFileExtension).toBe('.ts'); 147 + expect(output.module.extension).toBe('.ts'); 146 148 }); 147 149 148 150 it('should work when both module and moduleResolution are set', () => { ··· 164 166 }, 165 167 }); 166 168 167 - expect(output.importFileExtension).toBe('.js'); 169 + expect(output.module.extension).toBe('.js'); 168 170 }); 169 171 170 172 it('should handle missing tsconfig gracefully', () => { ··· 174 176 }, 175 177 }); 176 178 177 - expect(output.importFileExtension).toBeUndefined(); 179 + expect(output.module.extension).toBeUndefined(); 178 180 }); 179 181 }); 180 182 });
+19 -4
packages/openapi-ts/src/config/output/config.ts
··· 37 37 }, 38 38 format: null, 39 39 lint: null, 40 + module: {}, 40 41 path: '', 41 42 postProcess: [], 42 43 preferExportAll: false, ··· 57 58 }, 58 59 value: fields.fileName, 59 60 }), 61 + module: valueToObject({ 62 + defaultValue: { 63 + extension: fields.importFileExtension, 64 + resolve: fields.resolveModuleName, 65 + }, 66 + mappers: { 67 + object: (moduleFields) => ({ 68 + ...moduleFields, 69 + extension: fields.importFileExtension ?? moduleFields.extension, 70 + resolve: fields.resolveModuleName ?? moduleFields.resolve, 71 + }), 72 + }, 73 + value: fields.module, 74 + }), 60 75 }), 61 76 }, 62 77 value: userOutput, 63 78 }) as Output; 64 79 output.tsConfig = loadTsConfig(findTsConfigPath(__dirname, output.tsConfigPath)); 65 80 if ( 66 - output.importFileExtension === undefined && 81 + output.module.extension === undefined && 67 82 (output.tsConfig?.compilerOptions?.moduleResolution === 'nodenext' || 68 83 output.tsConfig?.compilerOptions?.moduleResolution === 'NodeNext' || 69 84 output.tsConfig?.compilerOptions?.moduleResolution === 'node16' || ··· 73 88 output.tsConfig?.compilerOptions?.module === 'node16' || 74 89 output.tsConfig?.compilerOptions?.module === 'Node16') 75 90 ) { 76 - output.importFileExtension = '.js'; 91 + output.module.extension = '.js'; 77 92 } 78 - if (output.importFileExtension && !output.importFileExtension.startsWith('.')) { 79 - output.importFileExtension = `.${output.importFileExtension}`; 93 + if (output.module.extension && !output.module.extension.startsWith('.')) { 94 + output.module.extension = `.${output.module.extension}`; 80 95 } 81 96 output.postProcess = normalizePostProcess(userOutput.postProcess ?? legacyPostProcess); 82 97 output.source = resolveSource(output);
+4 -12
packages/openapi-ts/src/config/output/types.ts
··· 4 4 5 5 import type { Formatters, Linters, PostProcessorPreset } from './postprocess'; 6 6 7 - type ImportFileExtensions = '.js' | '.ts'; 8 - 9 - export type UserOutput = BaseUserOutput & { 7 + export type UserOutput = BaseUserOutput<'.js' | '.ts'> & { 10 8 /** 11 9 * Which formatter to use to process output folder? 12 10 * ··· 21 19 * `node16`, we default to `.js`. 22 20 * 23 21 * @default undefined 22 + * @deprecated Use `module.extension` instead. 24 23 */ 25 - importFileExtension?: ImportFileExtensions | AnyString | null; 24 + importFileExtension?: '.js' | '.ts' | AnyString | null; 26 25 /** 27 26 * Which linter to use to process output folder? 28 27 * ··· 60 59 tsConfigPath?: AnyString | null; 61 60 }; 62 61 63 - export type Output = BaseOutput & { 62 + export type Output = BaseOutput<'.js' | '.ts'> & { 64 63 /** 65 64 * Which formatter to use to process output folder? 66 65 */ 67 66 format: Formatters | null; 68 - /** 69 - * If specified, this will be the file extension used when importing 70 - * other modules. By default, we don't add a file extension and let the 71 - * runtime resolve it. If you're using moduleResolution `nodenext` or 72 - * `node16`, we default to `.js`. 73 - */ 74 - importFileExtension: ImportFileExtensions | AnyString | null | undefined; 75 67 /** 76 68 * Which linter to use to process output folder? 77 69 */
+1 -2
packages/openapi-ts/src/createClient.ts
··· 131 131 const result = typeof header === 'function' ? header({ ...ctx, defaultValue }) : header; 132 132 return result === undefined ? defaultValue : result; 133 133 }, 134 + module: config.output.module, 134 135 preferExportAll: config.output.preferExportAll, 135 - preferFileExtension: config.output.importFileExtension || undefined, 136 - resolveModuleName: config.output.resolveModuleName, 137 136 }), 138 137 ], 139 138 root: config.output.path,
+9 -12
packages/openapi-ts/src/generate/client.ts
··· 2 2 import path from 'node:path'; 3 3 import { fileURLToPath } from 'node:url'; 4 4 5 - import type { IProject, ProjectRenderMeta } from '@hey-api/codegen-core'; 6 - import type { DefinePlugin, OutputHeader } from '@hey-api/shared'; 5 + import type { IProject } from '@hey-api/codegen-core'; 6 + import type { BaseOutput, DefinePlugin, OutputHeader } from '@hey-api/shared'; 7 7 import { ensureDirSync, isEnvironment, outputHeaderToPrefix } from '@hey-api/shared'; 8 8 9 9 import type { Config } from '../config/types'; ··· 100 100 function replaceImports({ 101 101 filePath, 102 102 header, 103 - meta, 103 + module, 104 104 renamed, 105 - }: { 105 + }: Pick<BaseOutput, 'module'> & { 106 106 filePath: string; 107 107 header?: string; 108 - meta: ProjectRenderMeta; 109 108 renamed: Map<string, string>; 110 109 }): void { 111 110 let content = fs.readFileSync(filePath, 'utf8'); ··· 124 123 const fileName = path.basename(importPath, extension); 125 124 const importDir = path.dirname(importPath); 126 125 const replacedName = 127 - (renamed.get(fileName) ?? fileName) + 128 - (meta.importFileExtension ? meta.importFileExtension : extension); 126 + (renamed.get(fileName) ?? fileName) + (module.extension ? module.extension : extension); 129 127 const replacedMatch = 130 128 match.slice(0, importIndex) + 131 129 [importDir, replacedName].filter(Boolean).join('/') + ··· 145 143 */ 146 144 export function generateClientBundle({ 147 145 header, 148 - meta, 146 + module, 149 147 outputPath, 150 148 plugin, 151 149 project, 152 - }: { 150 + }: Pick<BaseOutput, 'module'> & { 153 151 header?: OutputHeader; 154 - meta: ProjectRenderMeta; 155 152 outputPath: string; 156 153 plugin: DefinePlugin<Client.Config & { name: string }>['Config']; 157 154 project: IProject; ··· 203 200 replaceImports({ 204 201 filePath: path.resolve(coreOutputPath, file), 205 202 header: headerPrefix, 206 - meta, 203 + module, 207 204 renamed, 208 205 }); 209 206 } ··· 213 210 replaceImports({ 214 211 filePath: path.resolve(clientOutputPath, file), 215 212 header: headerPrefix, 216 - meta, 213 + module, 217 214 renamed, 218 215 }); 219 216 }
+1 -3
packages/openapi-ts/src/generate/output.ts
··· 25 25 // @ts-expect-error 26 26 config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({ 27 27 header: config.output.header, 28 - meta: { 29 - importFileExtension: config.output.importFileExtension, 30 - }, 28 + module: config.output.module, 31 29 outputPath, 32 30 // @ts-expect-error 33 31 plugin: client,
+11 -20
packages/openapi-ts/src/ts-dsl/utils/render.ts
··· 1 1 import type { RenderContext, Renderer } from '@hey-api/codegen-core'; 2 - import type { ResolveModuleName } from '@hey-api/shared'; 2 + import type { BaseOutput } from '@hey-api/shared'; 3 3 import type { MaybeArray, MaybeFunc } from '@hey-api/types'; 4 4 import ts from 'typescript'; 5 5 ··· 37 37 */ 38 38 private _header?: HeaderArg; 39 39 /** 40 - * Whether `export * from 'module'` should be used when possible instead of named exports. 41 - * 42 - * @private 43 - */ 44 - private _preferExportAll: boolean; 45 - /** 46 - * Controls whether imports/exports include a file extension (e.g., '.ts' or '.js'). 40 + * Options for module specifier resolution. 47 41 * 48 42 * @private 49 43 */ 50 - private _preferFileExtension: string; 44 + private _module?: Partial<BaseOutput>['module']; 51 45 /** 52 - * Optional function to transform module specifiers. 46 + * Whether `export * from 'module'` should be used when possible instead of named exports. 53 47 * 54 48 * @private 55 49 */ 56 - private _resolveModuleName?: ResolveModuleName; 50 + private _preferExportAll: boolean; 57 51 58 52 constructor( 59 - args: { 53 + args: Pick<Partial<BaseOutput>, 'module'> & { 60 54 header?: HeaderArg; 61 55 preferExportAll?: boolean; 62 - preferFileExtension?: string; 63 - resolveModuleName?: ResolveModuleName; 64 56 } = {}, 65 57 ) { 66 58 this._header = args.header; 59 + this._module = args.module; 67 60 this._preferExportAll = args.preferExportAll ?? false; 68 - this._preferFileExtension = args.preferFileExtension ?? ''; 69 - this._resolveModuleName = args.resolveModuleName; 70 61 } 71 62 72 63 render(ctx: RenderContext<TsDsl>): string { ··· 195 186 const sortKey = moduleSortKey({ 196 187 file: ctx.file, 197 188 fromFile: exp.from, 198 - preferFileExtension: this._preferFileExtension, 189 + preferFileExtension: this._module?.extension || '', 199 190 root: ctx.project.root, 200 191 }); 201 - const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2]; 192 + const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2]; 202 193 const [groupIndex] = sortKey; 203 194 204 195 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map()); ··· 261 252 const sortKey = moduleSortKey({ 262 253 file: ctx.file, 263 254 fromFile: imp.from, 264 - preferFileExtension: this._preferFileExtension, 255 + preferFileExtension: this._module?.extension || '', 265 256 root: ctx.project.root, 266 257 }); 267 - const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2]; 258 + const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2]; 268 259 const [groupIndex] = sortKey; 269 260 270 261 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map());
+38 -28
packages/shared/src/config/shared.ts
··· 1 1 import type { NameConflictResolver, RenderContext, Symbol } from '@hey-api/codegen-core'; 2 - import type { MaybeArray } from '@hey-api/types'; 2 + import type { AnyString, MaybeArray } from '@hey-api/types'; 3 3 4 4 import type { Plugin } from '../plugins/types'; 5 5 import type { Logs } from '../types/logs'; ··· 82 82 /** 83 83 * Base output shape all packages must satisfy. 84 84 */ 85 - export interface BaseUserOutput { 85 + export interface BaseUserOutput<TModuleExtension extends string = string> { 86 86 /** 87 87 * Defines casing of the output fields. By default, we preserve `input` 88 88 * values as data transforms incur a performance penalty at runtime. ··· 156 156 */ 157 157 indexFile?: boolean; 158 158 /** 159 + * Options for module specifier resolution. 160 + */ 161 + module?: { 162 + /** 163 + * If specified, this will be the extension used when importing other 164 + * modules. By default, we don't add an extension unless we detect that 165 + * you're using a module resolution strategy that requires one. 166 + * 167 + * @default undefined 168 + */ 169 + extension?: TModuleExtension | AnyString | null; 170 + /** 171 + * Function to transform module specifiers. 172 + * 173 + * @default undefined 174 + */ 175 + resolve?: ResolveModuleFn; 176 + }; 177 + /** 159 178 * Optional name conflict resolver to customize how naming conflicts 160 179 * are handled. 161 180 */ ··· 168 187 * Optional function to transform module specifiers. 169 188 * 170 189 * @default undefined 190 + * @deprecated use `module.resolve` instead 171 191 */ 172 - resolveModuleName?: ResolveModuleName; 192 + resolveModuleName?: ResolveModuleFn; 173 193 /** 174 194 * Configuration for generating a copy of the input source used to produce this output. 175 195 * ··· 185 205 /** 186 206 * Base output shape all packages must satisfy. 187 207 */ 188 - export interface BaseOutput { 208 + export interface BaseOutput<TModuleExtension extends string = string> { 189 209 /** 190 210 * Defines casing of the output fields. By default, we preserve `input` 191 211 * values as data transforms incur a performance penalty at runtime. ··· 198 218 * input, or package version changes. 199 219 */ 200 220 clean: boolean; 201 - /** 202 - * Whether to generate an entry file that re-exports symbols for convenient imports. 203 - */ 221 + /** Whether to generate an entry file that re-exports symbols for convenient imports. */ 204 222 entryFile: boolean; 205 223 /** 206 224 * Optional function to transform file names before they are used. ··· 221 239 */ 222 240 suffix: string | null; 223 241 }; 224 - /** 225 - * Text to include at the top of every generated file. 226 - */ 242 + /** Text to include at the top of every generated file. */ 227 243 header: OutputHeader; 228 244 /** 229 245 * Whether to generate an entry file that re-exports symbols for convenient imports. ··· 231 247 * @deprecated use `entryFile` instead 232 248 */ 233 249 indexFile: boolean; 234 - /** 235 - * Optional name conflict resolver to customize how naming conflicts 236 - * are handled. 237 - */ 250 + /** Options for module specifier resolution. */ 251 + module: { 252 + /** The extension used when importing other modules. */ 253 + extension: TModuleExtension | AnyString | null; 254 + /** Function to transform module specifiers. */ 255 + resolve: ResolveModuleFn | undefined; 256 + }; 257 + /** Name conflict resolver to customize how naming conflicts are handled. */ 238 258 nameConflictResolver: NameConflictResolver | undefined; 239 - /** 240 - * The absolute path to the output folder. 241 - */ 259 + /** The absolute path to the output folder. */ 242 260 path: string; 243 - /** 244 - * Post-processing commands to run on the output folder, executed in order. 245 - */ 261 + /** Post-processing commands to run on the output folder, executed in order. */ 246 262 postProcess: ReadonlyArray<PostProcessor>; 247 - /** 248 - * Optional function to transform module specifiers. 249 - */ 250 - resolveModuleName: ResolveModuleName | undefined; 251 - /** 252 - * Configuration for generating a copy of the input source used to produce this output. 253 - */ 263 + /** Configuration for generating a copy of the input source used to produce this output. */ 254 264 source: SourceConfig; 255 265 } 256 266 ··· 350 360 /** 351 361 * Function to transform module specifiers. 352 362 */ 353 - export type ResolveModuleName = (moduleName: string, ctx: RenderContext) => string | undefined; 363 + export type ResolveModuleFn = (path: string, ctx: RenderContext) => string | undefined;
+1 -1
packages/shared/src/index.ts
··· 23 23 FeatureToggle, 24 24 IndexExportOption, 25 25 NamingOptions, 26 - ResolveModuleName, 26 + ResolveModuleFn, 27 27 UserCommentsOption, 28 28 UserIndexExportOption, 29 29 } from './config/shared';