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.

Merge pull request #3438 from hey-api/copilot/fix-output-header-config

authored by

Lubos and committed by
GitHub
14f2894c 2faab853

+150 -6
+5
.changeset/famous-pianos-attack.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **output**: fix: apply `output.header` to bundled files
+47
packages/openapi-python/src/generate/__tests__/client.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 + /** 4 + * Replicates the outputHeaderToPrefix logic from generate/client.ts for testing. 5 + */ 6 + function outputHeaderToPrefix(header: unknown): string { 7 + if (header == null || typeof header === 'function') return ''; 8 + const lines = 9 + typeof header === 'string' 10 + ? header.split(/\r?\n/) 11 + : (header as string[]).flatMap((line) => line.split(/\r?\n/)); 12 + const content = lines.join('\n'); 13 + return content ? `${content}\n\n` : ''; 14 + } 15 + 16 + describe('outputHeaderToPrefix logic', () => { 17 + it('returns default comment for string header', () => { 18 + const result = outputHeaderToPrefix('# This file is auto-generated by @hey-api/openapi-python'); 19 + expect(result).toBe('# This file is auto-generated by @hey-api/openapi-python\n\n'); 20 + }); 21 + 22 + it('returns joined lines for array header', () => { 23 + const result = outputHeaderToPrefix([ 24 + '# This file is auto-generated by @hey-api/openapi-python', 25 + '# type: ignore', 26 + ]); 27 + expect(result).toBe( 28 + '# This file is auto-generated by @hey-api/openapi-python\n# type: ignore\n\n', 29 + ); 30 + }); 31 + 32 + it('returns empty string for null header', () => { 33 + expect(outputHeaderToPrefix(null)).toBe(''); 34 + }); 35 + 36 + it('returns empty string for undefined header', () => { 37 + expect(outputHeaderToPrefix(undefined)).toBe(''); 38 + }); 39 + 40 + it('returns empty string for function header', () => { 41 + expect(outputHeaderToPrefix(() => '# dynamic')).toBe(''); 42 + }); 43 + 44 + it('handles string with embedded newlines', () => { 45 + const result = outputHeaderToPrefix('# line1\n# line2'); 46 + expect(result).toBe('# line1\n# line2\n\n'); 47 + }); 48 + }); 49 + 3 50 describe('isDevMode logic', () => { 4 51 const scenarios: ReadonlyArray<{ 5 52 description: string;
+24 -3
packages/openapi-python/src/generate/client.ts
··· 3 3 import { fileURLToPath } from 'node:url'; 4 4 5 5 import type { IProject, ProjectRenderMeta } from '@hey-api/codegen-core'; 6 - import type { DefinePlugin } from '@hey-api/shared'; 6 + import type { DefinePlugin, OutputHeader } from '@hey-api/shared'; 7 7 import { ensureDirSync } from '@hey-api/shared'; 8 8 9 9 import type { Config } from '../config/types'; ··· 54 54 } 55 55 56 56 /** 57 + * Converts an {@link OutputHeader} value to a string prefix for file content. 58 + * Returns an empty string when the header is null, undefined, or a function 59 + * (functions require a render context which is not available for bundled files). 60 + */ 61 + function outputHeaderToPrefix(header: OutputHeader): string { 62 + if (header == null || typeof header === 'function') return ''; 63 + const lines = 64 + typeof header === 'string' 65 + ? header.split(/\r?\n/) 66 + : header.flatMap((line) => line.split(/\r?\n/)); 67 + const content = lines.join('\n'); 68 + return content ? `${content}\n\n` : ''; 69 + } 70 + 71 + /** 57 72 * Returns absolute path to the client folder. This is hard-coded for now. 58 73 */ 59 74 export function clientFolderAbsolutePath(config: Config): string { ··· 114 129 115 130 function replaceImports({ 116 131 filePath, 132 + header, 117 133 isDevMode, 118 134 meta, 119 135 renamed, 120 136 }: { 121 137 filePath: string; 138 + header?: string; 122 139 isDevMode?: boolean; 123 140 meta: ProjectRenderMeta; 124 141 renamed: Map<string, string>; ··· 148 165 return replacedMatch; 149 166 }); 150 167 151 - const header = '# This file is auto-generated by @hey-api/openapi-python\n\n'; 168 + const fileHeader = header ?? ''; 152 169 153 - content = `${header}${content}`; 170 + content = `${fileHeader}${content}`; 154 171 155 172 fs.writeFileSync(filePath, content, 'utf8'); 156 173 } ··· 159 176 * Creates a `client` folder containing the same modules as the client package. 160 177 */ 161 178 export function generateClientBundle({ 179 + header, 162 180 meta, 163 181 outputPath, 164 182 plugin, 165 183 project, 166 184 }: { 185 + header?: OutputHeader; 167 186 meta: ProjectRenderMeta; 168 187 outputPath: string; 169 188 plugin: DefinePlugin<Client.Config & { name: string }>['Config']; ··· 171 190 }): Map<string, string> | undefined { 172 191 const renamed = new Map<string, string>(); 173 192 const devMode = isDevMode(); 193 + const headerPrefix = outputHeaderToPrefix(header); 174 194 175 195 // copy Hey API clients to output 176 196 const isHeyApiClientPlugin = plugin.name.startsWith('@hey-api/client-'); ··· 222 242 for (const file of clientFiles) { 223 243 replaceImports({ 224 244 filePath: path.resolve(clientOutputPath, file), 245 + header: headerPrefix, 225 246 isDevMode: devMode, 226 247 meta, 227 248 renamed,
+1
packages/openapi-python/src/generate/output.ts
··· 24 24 // not proud of this one 25 25 // @ts-expect-error 26 26 config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({ 27 + header: config.output.header, 27 28 meta: { 28 29 importFileExtension: config.output.importFileExtension, 29 30 },
+47
packages/openapi-ts/src/generate/__tests__/client.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 + /** 4 + * Replicates the outputHeaderToPrefix logic from generate/client.ts for testing. 5 + */ 6 + function outputHeaderToPrefix(header: unknown): string { 7 + if (header == null || typeof header === 'function') return ''; 8 + const lines = 9 + typeof header === 'string' 10 + ? header.split(/\r?\n/) 11 + : (header as string[]).flatMap((line) => line.split(/\r?\n/)); 12 + const content = lines.join('\n'); 13 + return content ? `${content}\n\n` : ''; 14 + } 15 + 16 + describe('outputHeaderToPrefix logic', () => { 17 + it('returns default comment for string header', () => { 18 + const result = outputHeaderToPrefix('// This file is auto-generated by @hey-api/openapi-ts'); 19 + expect(result).toBe('// This file is auto-generated by @hey-api/openapi-ts\n\n'); 20 + }); 21 + 22 + it('returns joined lines for array header', () => { 23 + const result = outputHeaderToPrefix([ 24 + '// This file is auto-generated by @hey-api/openapi-ts', 25 + '// @ts-nocheck', 26 + ]); 27 + expect(result).toBe( 28 + '// This file is auto-generated by @hey-api/openapi-ts\n// @ts-nocheck\n\n', 29 + ); 30 + }); 31 + 32 + it('returns empty string for null header', () => { 33 + expect(outputHeaderToPrefix(null)).toBe(''); 34 + }); 35 + 36 + it('returns empty string for undefined header', () => { 37 + expect(outputHeaderToPrefix(undefined)).toBe(''); 38 + }); 39 + 40 + it('returns empty string for function header', () => { 41 + expect(outputHeaderToPrefix(() => '// dynamic')).toBe(''); 42 + }); 43 + 44 + it('handles string with embedded newlines', () => { 45 + const result = outputHeaderToPrefix('// line1\n// line2'); 46 + expect(result).toBe('// line1\n// line2\n\n'); 47 + }); 48 + }); 49 + 3 50 describe('isDevMode logic', () => { 4 51 const scenarios: ReadonlyArray<{ 5 52 description: string;
+25 -3
packages/openapi-ts/src/generate/client.ts
··· 3 3 import { fileURLToPath } from 'node:url'; 4 4 5 5 import type { IProject, ProjectRenderMeta } from '@hey-api/codegen-core'; 6 - import type { DefinePlugin } from '@hey-api/shared'; 6 + import type { DefinePlugin, OutputHeader } from '@hey-api/shared'; 7 7 import { ensureDirSync } from '@hey-api/shared'; 8 8 9 9 import type { Config } from '../config/types'; ··· 54 54 } 55 55 56 56 /** 57 + * Converts an {@link OutputHeader} value to a string prefix for file content. 58 + * Returns an empty string when the header is null, undefined, or a function 59 + * (functions require a render context which is not available for bundled files). 60 + */ 61 + function outputHeaderToPrefix(header: OutputHeader): string { 62 + if (header == null || typeof header === 'function') return ''; 63 + const lines = 64 + typeof header === 'string' 65 + ? header.split(/\r?\n/) 66 + : header.flatMap((line) => line.split(/\r?\n/)); 67 + const content = lines.join('\n'); 68 + return content ? `${content}\n\n` : ''; 69 + } 70 + 71 + /** 57 72 * Returns absolute path to the client folder. This is hard-coded for now. 58 73 */ 59 74 export function clientFolderAbsolutePath(config: Config): string { ··· 114 129 115 130 function replaceImports({ 116 131 filePath, 132 + header, 117 133 isDevMode, 118 134 meta, 119 135 renamed, 120 136 }: { 121 137 filePath: string; 138 + header?: string; 122 139 isDevMode?: boolean; 123 140 meta: ProjectRenderMeta; 124 141 renamed: Map<string, string>; ··· 148 165 return replacedMatch; 149 166 }); 150 167 151 - const header = '// This file is auto-generated by @hey-api/openapi-ts\n\n'; 168 + const fileHeader = header ?? ''; 152 169 153 - content = `${header}${content}`; 170 + content = `${fileHeader}${content}`; 154 171 155 172 fs.writeFileSync(filePath, content, 'utf8'); 156 173 } ··· 159 176 * Creates a `client` folder containing the same modules as the client package. 160 177 */ 161 178 export function generateClientBundle({ 179 + header, 162 180 meta, 163 181 outputPath, 164 182 plugin, 165 183 project, 166 184 }: { 185 + header?: OutputHeader; 167 186 meta: ProjectRenderMeta; 168 187 outputPath: string; 169 188 plugin: DefinePlugin<Client.Config & { name: string }>['Config']; ··· 171 190 }): Map<string, string> | undefined { 172 191 const renamed = new Map<string, string>(); 173 192 const devMode = isDevMode(); 193 + const headerPrefix = outputHeaderToPrefix(header); 174 194 175 195 // copy Hey API clients to output 176 196 const isHeyApiClientPlugin = plugin.name.startsWith('@hey-api/client-'); ··· 211 231 for (const file of coreFiles) { 212 232 replaceImports({ 213 233 filePath: path.resolve(coreOutputPath, file), 234 + header: headerPrefix, 214 235 isDevMode: devMode, 215 236 meta, 216 237 renamed, ··· 221 242 for (const file of clientFiles) { 222 243 replaceImports({ 223 244 filePath: path.resolve(clientOutputPath, file), 245 + header: headerPrefix, 224 246 isDevMode: devMode, 225 247 meta, 226 248 renamed,
+1
packages/openapi-ts/src/generate/output.ts
··· 24 24 // not proud of this one 25 25 // @ts-expect-error 26 26 config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({ 27 + header: config.output.header, 27 28 meta: { 28 29 importFileExtension: config.output.importFileExtension, 29 30 },