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

Merge pull request #2852 from hey-api/fix/cli-typescript

fix(cli): move cli script to typescript

authored by Lubos and committed by GitHub 3def2b37 f14f95ad

+5
.changeset/short-phones-count.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(cli): move cli script to typescript
+4 -4
.github/copilot-instructions.md
··· 49 49 # Server starts on http://localhost:5173/ 50 50 51 51 # Run CLI tool directly 52 - node packages/openapi-ts/bin/index.cjs --help 52 + node packages/openapi-ts/dist/run.js --help 53 53 # or after building 54 54 npx @hey-api/openapi-ts --help 55 55 ``` ··· 85 85 86 86 ```bash 87 87 # Test CLI help 88 - node packages/openapi-ts/bin/index.cjs --help 88 + node packages/openapi-ts/dist/run.js --help 89 89 90 90 # Test CLI version 91 - node packages/openapi-ts/bin/index.cjs --version 91 + node packages/openapi-ts/dist/run.js --version 92 92 93 93 # Test basic code generation with a simple OpenAPI spec 94 94 # Create a minimal test spec and generate client code 95 - node packages/openapi-ts/bin/index.cjs -i path/to/spec.json -o ./test-output --plugins "@hey-api/client-fetch" "@hey-api/typescript" 95 + node packages/openapi-ts/dist/run.js -i path/to/spec.json -o ./test-output --plugins "@hey-api/client-fetch" "@hey-api/typescript" 96 96 ``` 97 97 98 98 2. **Example Application Test**:
+4 -4
.github/workflows/ci.yml
··· 37 37 - name: Build packages 38 38 run: pnpm build --filter="@hey-api/**" 39 39 40 - - name: Build examples 41 - if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' 42 - run: pnpm build --filter="@example/**" 43 - 44 40 - name: Check examples generated code 45 41 if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' 46 42 run: pnpm examples:check 43 + 44 + - name: Build examples 45 + if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' 46 + run: pnpm build --filter="@example/**" 47 47 48 48 - name: Run linter 49 49 run: pnpm lint
+1
.gitignore
··· 20 20 .turbo 21 21 22 22 # test files 23 + .gen/ 23 24 test/generated 24 25 25 26 # debug files
+3 -6
.vscode/launch.json
··· 13 13 "request": "launch", 14 14 "name": "openapi-ts", 15 15 "skipFiles": ["<node_internals>/**"], 16 + "cwd": "${workspaceFolder}/dev", 16 17 "runtimeExecutable": "node", 17 - "runtimeArgs": [], 18 - "program": "${workspaceFolder}/packages/openapi-ts/bin/index.cjs", 19 - "args": [ 20 - "-f", 21 - "${workspaceFolder}/packages/openapi-ts-tests/main/test/openapi-ts.config.ts" 22 - ] 18 + "runtimeArgs": ["-r", "ts-node/register/transpile-only"], 19 + "program": "${workspaceFolder}/packages/openapi-ts/src/cli.ts" 23 20 } 24 21 ] 25 22 }
debug-helpers/graph-hotspots.js dev/graph-hotspots.js
debug-helpers/json-to-dot.js dev/json-to-dot.js
+2
package.json
··· 48 48 "@config/vite-base": "workspace:*", 49 49 "@eslint/js": "9.32.0", 50 50 "@hey-api/custom-client": "workspace:*", 51 + "@hey-api/openapi-ts": "workspace:*", 51 52 "@types/node": "22.10.5", 52 53 "@typescript-eslint/eslint-plugin": "8.29.1", 53 54 "@vitest/coverage-v8": "3.1.1", ··· 65 66 "prettier": "3.4.2", 66 67 "rollup": "4.31.0", 67 68 "rollup-plugin-dts": "6.1.1", 69 + "ts-node": "10.9.2", 68 70 "tsdown": "0.15.8", 69 71 "turbo": "2.5.8", 70 72 "typescript": "5.9.3",
+1
packages/openapi-ts-tests/main/.gitignore
··· 16 16 .env 17 17 18 18 # test files 19 + .gen/ 19 20 test/generated 20 21 generated/
-368
packages/openapi-ts-tests/main/test/bin.test.ts
··· 1 - import path from 'node:path'; 2 - 3 - import { sync } from 'cross-spawn'; 4 - import { describe, expect, it } from 'vitest'; 5 - 6 - import { getSpecsPath } from '../../utils'; 7 - 8 - describe('bin', () => { 9 - it('supports required parameters', () => { 10 - const result = sync('node', [ 11 - path.resolve( 12 - __dirname, 13 - '..', 14 - '..', 15 - '..', 16 - 'openapi-ts', 17 - 'bin', 18 - 'index.cjs', 19 - ), 20 - '--input', 21 - path.resolve(getSpecsPath(), 'v3.json'), 22 - '--output', 23 - path.resolve(__dirname, 'generated', 'bin'), 24 - '--client', 25 - '@hey-api/client-fetch', 26 - '--dry-run', 27 - 'true', 28 - ]); 29 - expect(result.error).toBeFalsy(); 30 - expect(result.status).toBe(0); 31 - }); 32 - 33 - it('generates angular client', () => { 34 - const result = sync('node', [ 35 - path.resolve( 36 - __dirname, 37 - '..', 38 - '..', 39 - '..', 40 - 'openapi-ts', 41 - 'bin', 42 - 'index.cjs', 43 - ), 44 - '--input', 45 - path.resolve(getSpecsPath(), 'v3.json'), 46 - '--output', 47 - path.resolve(__dirname, 'generated', 'bin'), 48 - '--client', 49 - 'legacy/angular', 50 - '--dry-run', 51 - 'true', 52 - ]); 53 - expect(result.error).toBeFalsy(); 54 - expect(result.status).toBe(0); 55 - }); 56 - 57 - it('generates axios client', () => { 58 - const result = sync('node', [ 59 - path.resolve( 60 - __dirname, 61 - '..', 62 - '..', 63 - '..', 64 - 'openapi-ts', 65 - 'bin', 66 - 'index.cjs', 67 - ), 68 - '--input', 69 - path.resolve(getSpecsPath(), 'v3.json'), 70 - '--output', 71 - path.resolve(__dirname, 'generated', 'bin'), 72 - '--client', 73 - 'legacy/axios', 74 - '--dry-run', 75 - 'true', 76 - ]); 77 - expect(result.error).toBeFalsy(); 78 - expect(result.status).toBe(0); 79 - }); 80 - 81 - it('generates fetch client', () => { 82 - const result = sync('node', [ 83 - path.resolve( 84 - __dirname, 85 - '..', 86 - '..', 87 - '..', 88 - 'openapi-ts', 89 - 'bin', 90 - 'index.cjs', 91 - ), 92 - '--input', 93 - path.resolve(getSpecsPath(), 'v3.json'), 94 - '--output', 95 - path.resolve(__dirname, 'generated', 'bin'), 96 - '--client', 97 - 'legacy/fetch', 98 - '--dry-run', 99 - 'true', 100 - ]); 101 - expect(result.error).toBeFalsy(); 102 - expect(result.status).toBe(0); 103 - }); 104 - 105 - it('generates node client', () => { 106 - const result = sync('node', [ 107 - path.resolve( 108 - __dirname, 109 - '..', 110 - '..', 111 - '..', 112 - 'openapi-ts', 113 - 'bin', 114 - 'index.cjs', 115 - ), 116 - '--input', 117 - path.resolve(getSpecsPath(), 'v3.json'), 118 - '--output', 119 - path.resolve(__dirname, 'generated', 'bin'), 120 - '--client', 121 - 'legacy/node', 122 - '--dry-run', 123 - 'true', 124 - ]); 125 - expect(result.error).toBeFalsy(); 126 - expect(result.status).toBe(0); 127 - }); 128 - 129 - it('generates xhr client', () => { 130 - const result = sync('node', [ 131 - path.resolve( 132 - __dirname, 133 - '..', 134 - '..', 135 - '..', 136 - 'openapi-ts', 137 - 'bin', 138 - 'index.cjs', 139 - ), 140 - '--input', 141 - path.resolve(getSpecsPath(), 'v3.json'), 142 - '--output', 143 - path.resolve(__dirname, 'generated', 'bin'), 144 - '--client', 145 - 'legacy/xhr', 146 - '--dry-run', 147 - 'true', 148 - ]); 149 - expect(result.error).toBeFalsy(); 150 - expect(result.status).toBe(0); 151 - }); 152 - 153 - it('supports all parameters', () => { 154 - const result = sync('node', [ 155 - path.resolve( 156 - __dirname, 157 - '..', 158 - '..', 159 - '..', 160 - 'openapi-ts', 161 - 'bin', 162 - 'index.cjs', 163 - ), 164 - '--input', 165 - path.resolve(getSpecsPath(), 'v3.json'), 166 - '--output', 167 - path.resolve(__dirname, 'generated', 'bin'), 168 - '--client', 169 - 'legacy/fetch', 170 - '--useOptions', 171 - '--exportCore', 172 - 'true', 173 - '--plugins', 174 - '@hey-api/schemas', 175 - '@hey-api/sdk', 176 - '@hey-api/typescript', 177 - '--dry-run', 178 - 'true', 179 - ]); 180 - expect(result.error).toBeFalsy(); 181 - expect(result.status).toBe(0); 182 - }); 183 - 184 - it('throws error without input', () => { 185 - const result = sync('node', [ 186 - path.resolve( 187 - __dirname, 188 - '..', 189 - '..', 190 - '..', 191 - 'openapi-ts', 192 - 'bin', 193 - 'index.cjs', 194 - ), 195 - '--dry-run', 196 - 'true', 197 - ]); 198 - expect(result.stderr.toString()).toContain('missing input'); 199 - }); 200 - 201 - it('throws error without output', () => { 202 - const result = sync('node', [ 203 - path.resolve( 204 - __dirname, 205 - '..', 206 - '..', 207 - '..', 208 - 'openapi-ts', 209 - 'bin', 210 - 'index.cjs', 211 - ), 212 - '--input', 213 - path.resolve(getSpecsPath(), 'v3.json'), 214 - '--dry-run', 215 - 'true', 216 - ]); 217 - expect(result.stderr.toString()).toContain('missing output'); 218 - }); 219 - 220 - it('throws error with wrong parameters', () => { 221 - const result = sync('node', [ 222 - path.resolve( 223 - __dirname, 224 - '..', 225 - '..', 226 - '..', 227 - 'openapi-ts', 228 - 'bin', 229 - 'index.cjs', 230 - ), 231 - '--input', 232 - path.resolve(getSpecsPath(), 'v3.json'), 233 - '--output', 234 - path.resolve(__dirname, 'generated', 'bin'), 235 - '--unknown', 236 - '--dry-run', 237 - 'true', 238 - ]); 239 - expect(result.stderr.toString()).toContain( 240 - `error: unknown option '--unknown'`, 241 - ); 242 - }); 243 - 244 - it('displays help', () => { 245 - const result = sync('node', [ 246 - path.resolve( 247 - __dirname, 248 - '..', 249 - '..', 250 - '..', 251 - 'openapi-ts', 252 - 'bin', 253 - 'index.cjs', 254 - ), 255 - '--help', 256 - '--dry-run', 257 - 'true', 258 - ]); 259 - expect(result.stdout.toString()).toContain(`Usage: openapi-ts [options]`); 260 - expect(result.stdout.toString()).toContain(`-i, --input <value>`); 261 - expect(result.stdout.toString()).toContain(`-o, --output <value>`); 262 - expect(result.stderr.toString()).toBe(''); 263 - }); 264 - }); 265 - 266 - describe('cli', () => { 267 - it('handles false booleans', () => { 268 - const result = sync('node', [ 269 - path.resolve( 270 - __dirname, 271 - '..', 272 - '..', 273 - '..', 274 - 'openapi-ts', 275 - 'bin', 276 - 'index.cjs', 277 - ), 278 - '--input', 279 - path.resolve(getSpecsPath(), 'v3.json'), 280 - '--output', 281 - path.resolve(__dirname, 'generated', 'bin'), 282 - '--debug', 283 - '--plugins', 284 - '--useOptions', 285 - 'false', 286 - '--dry-run', 287 - 'true', 288 - ]); 289 - const stderr = result.stderr.toString(); 290 - expect(stderr).toMatch(/level: 'debug'/); 291 - expect(stderr).toMatch(/dryRun: true/); 292 - expect(stderr).not.toMatch(/@hey-api\/schemas/); 293 - expect(stderr).not.toMatch(/@hey-api\/sdk/); 294 - expect(stderr).not.toMatch(/@hey-api\/typescript/); 295 - expect(stderr).toMatch(/useOptions: false/); 296 - }); 297 - 298 - it('handles true booleans', () => { 299 - const result = sync('node', [ 300 - path.resolve( 301 - __dirname, 302 - '..', 303 - '..', 304 - '..', 305 - 'openapi-ts', 306 - 'bin', 307 - 'index.cjs', 308 - ), 309 - '--input', 310 - path.resolve(getSpecsPath(), 'v3.json'), 311 - '--output', 312 - path.resolve(__dirname, 'generated', 'bin'), 313 - '--client', 314 - '@hey-api/client-fetch', 315 - '--debug', 316 - '--plugins', 317 - '@hey-api/schemas', 318 - '@hey-api/sdk', 319 - '@hey-api/typescript', 320 - '--useOptions', 321 - 'true', 322 - '--dry-run', 323 - 'true', 324 - ]); 325 - const stderr = result.stderr.toString(); 326 - expect(stderr).toMatch(/level: 'debug'/); 327 - expect(stderr).toMatch(/dryRun: true/); 328 - expect(stderr).toMatch(/@hey-api\/schemas/); 329 - expect(stderr).toMatch(/@hey-api\/sdk/); 330 - expect(stderr).toMatch(/@hey-api\/typescript/); 331 - expect(stderr).toMatch(/useOptions: true/); 332 - }); 333 - 334 - it('handles optional booleans', () => { 335 - const result = sync('node', [ 336 - path.resolve( 337 - __dirname, 338 - '..', 339 - '..', 340 - '..', 341 - 'openapi-ts', 342 - 'bin', 343 - 'index.cjs', 344 - ), 345 - '--input', 346 - path.resolve(getSpecsPath(), 'v3.json'), 347 - '--output', 348 - path.resolve(__dirname, 'generated', 'bin'), 349 - '--client', 350 - '@hey-api/client-fetch', 351 - '--debug', 352 - '--plugins', 353 - '@hey-api/schemas', 354 - '@hey-api/sdk', 355 - '@hey-api/typescript', 356 - '--useOptions', 357 - '--dry-run', 358 - 'true', 359 - ]); 360 - const stderr = result.stderr.toString(); 361 - expect(stderr).toMatch(/level: 'debug'/); 362 - expect(stderr).toMatch(/dryRun: true/); 363 - expect(stderr).toMatch(/@hey-api\/schemas/); 364 - expect(stderr).toMatch(/@hey-api\/sdk/); 365 - expect(stderr).toMatch(/@hey-api\/typescript/); 366 - expect(stderr).toMatch(/useOptions: true/); 367 - }); 368 - });
+23
packages/openapi-ts-tests/main/test/cli.test.ts
··· 1 + import path from 'node:path'; 2 + 3 + import { sync } from 'cross-spawn'; 4 + import { describe, expect, it } from 'vitest'; 5 + 6 + import { getSpecsPath } from '../../utils'; 7 + 8 + const specs = getSpecsPath(); 9 + 10 + describe('bin', () => { 11 + it('openapi-ts works', () => { 12 + const result = sync('openapi-ts', [ 13 + '--input', 14 + path.resolve(specs, 'v3.json'), 15 + '--output', 16 + path.resolve(__dirname, '.gen'), 17 + '--dry-run', 18 + 'true', 19 + ]); 20 + expect(result.error).toBeFalsy(); 21 + expect(result.status).toBe(0); 22 + }); 23 + });
packages/openapi-ts-tests/main/test/hey-api.ts dev/hey-api.ts
+5 -5
packages/openapi-ts-tests/main/test/openapi-ts.config.ts dev/openapi-ts.config.ts
··· 6 6 // @ts-ignore 7 7 import { defineConfig, utils } from '@hey-api/openapi-ts'; 8 8 9 - import { getSpecsPath } from '../../utils'; 10 9 // @ts-ignore 11 - import { myClientPlugin } from './custom/client/plugin'; 10 + import { myClientPlugin } from '../packages/openapi-ts-tests/main/test/custom/client/plugin'; 11 + import { getSpecsPath } from '../packages/openapi-ts-tests/utils'; 12 12 13 13 // @ts-ignore 14 14 // eslint-disable-next-line arrow-body-style ··· 96 96 // importFileExtension: '.ts', 97 97 // // indexFile: false, 98 98 // // lint: 'eslint', 99 - // path: path.resolve(__dirname, 'generated', 'sample'), 99 + // path: path.resolve(__dirname, '.gen'), 100 100 // tsConfigPath: path.resolve( 101 101 // __dirname, 102 102 // 'tsconfig', 103 103 // 'tsconfig.nodenext.json', 104 104 // ), 105 105 // }, 106 - path.resolve(__dirname, 'generated', 'sample'), 106 + '.gen', 107 107 ], 108 108 parser: { 109 109 filters: { ··· 478 478 // // level: 'debug', 479 479 // path: './logs', 480 480 // }, 481 - // output: path.resolve(__dirname, 'generated', 'sample'), 481 + // output: '.gen', 482 482 // }, 483 483 ]; 484 484 });
-148
packages/openapi-ts/bin/index.cjs
··· 1 - #!/usr/bin/env node 2 - 3 - 'use strict'; 4 - 5 - const path = require('path'); 6 - 7 - const { program } = require('commander'); 8 - const pkg = require('../package.json'); 9 - 10 - const params = program 11 - .name(Object.keys(pkg.bin)[0]) 12 - .usage('[options]') 13 - .version(pkg.version) 14 - .option( 15 - '-c, --client <value>', 16 - 'HTTP client to generate [@hey-api/client-axios, @hey-api/client-fetch, @hey-api/client-next, @hey-api/client-nuxt, legacy/angular, legacy/axios, legacy/fetch, legacy/node, legacy/xhr]', 17 - ) 18 - .option('-d, --debug', 'Set log level to debug') 19 - .option('--dry-run [value]', 'Skip writing files to disk?') 20 - .option( 21 - '-e, --experimental-parser [value]', 22 - 'Opt-in to the experimental parser?', 23 - ) 24 - .option('-f, --file [value]', 'Path to the config file') 25 - .option( 26 - '-i, --input <value>', 27 - 'OpenAPI specification (path, url, or string content)', 28 - ) 29 - .option('-l, --logs [value]', 'Logs folder') 30 - .option('-o, --output <value>', 'Output folder') 31 - .option('-p, --plugins [value...]', "List of plugins you'd like to use") 32 - .option( 33 - '--base [value]', 34 - 'DEPRECATED. Manually set base in OpenAPI config instead of inferring from server value', 35 - ) 36 - .option('-s, --silent', 'Set log level to silent') 37 - .option( 38 - '--no-log-file', 39 - 'Disable writing a log file. Works like --silent but without suppressing console output', 40 - ) 41 - .option( 42 - '-w, --watch [value]', 43 - 'Regenerate the client when the input file changes?', 44 - ) 45 - .option('--exportCore [value]', 'DEPRECATED. Write core files to disk') 46 - .option('--name <value>', 'DEPRECATED. Custom client class name') 47 - .option('--request <value>', 'DEPRECATED. Path to custom request file') 48 - .option( 49 - '--useOptions [value]', 50 - 'DEPRECATED. Use options instead of arguments?', 51 - ) 52 - .parse(process.argv) 53 - .opts(); 54 - 55 - const stringToBoolean = (value) => { 56 - if (value === 'true') { 57 - return true; 58 - } 59 - if (value === 'false') { 60 - return false; 61 - } 62 - return value; 63 - }; 64 - 65 - const processParams = (obj, booleanKeys) => { 66 - for (const key of booleanKeys) { 67 - const value = obj[key]; 68 - if (typeof value === 'string') { 69 - const parsedValue = stringToBoolean(value); 70 - delete obj[key]; 71 - obj[key] = parsedValue; 72 - } 73 - } 74 - if (obj.file) { 75 - obj.configFile = obj.file; 76 - } 77 - return obj; 78 - }; 79 - 80 - async function start() { 81 - let userConfig; 82 - 83 - try { 84 - const { createClient } = require( 85 - path.resolve(__dirname, '../dist/index.cjs'), 86 - ); 87 - 88 - userConfig = processParams(params, [ 89 - 'dryRun', 90 - 'logFile', 91 - 'experimentalParser', 92 - 'exportCore', 93 - 'useOptions', 94 - ]); 95 - 96 - if (params.plugins === true) { 97 - userConfig.plugins = []; 98 - } else if (params.plugins) { 99 - userConfig.plugins = params.plugins; 100 - } else if (userConfig.client) { 101 - userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; 102 - } 103 - 104 - if (userConfig.client) { 105 - userConfig.plugins.push(userConfig.client); 106 - delete userConfig.client; 107 - } 108 - 109 - userConfig.logs = userConfig.logs 110 - ? { 111 - path: userConfig.logs, 112 - } 113 - : {}; 114 - 115 - if (userConfig.debug || stringToBoolean(process.env.DEBUG)) { 116 - userConfig.logs.level = 'debug'; 117 - } else if (userConfig.silent) { 118 - userConfig.logs.level = 'silent'; 119 - } 120 - 121 - userConfig.logs.file = userConfig.logFile; 122 - delete userConfig.logFile; 123 - 124 - if (typeof params.watch === 'string') { 125 - userConfig.watch = Number.parseInt(params.watch, 10); 126 - } 127 - 128 - if (!Object.keys(userConfig.logs).length) { 129 - delete userConfig.logs; 130 - } 131 - 132 - const context = await createClient(userConfig); 133 - if ( 134 - !context[0] || 135 - !context[0].config || 136 - !context[0].config.input || 137 - !context[0].config.input.some( 138 - (input) => input.watch && input.watch.enabled, 139 - ) 140 - ) { 141 - process.exit(0); 142 - } 143 - } catch { 144 - process.exit(1); 145 - } 146 - } 147 - 148 - start();
+3
packages/openapi-ts/bin/run.cmd
··· 1 + @echo off 2 + node "%~dp0\run.js" %* 3 +
+18
packages/openapi-ts/bin/run.js
··· 1 + #!/usr/bin/env node 2 + import { spawnSync } from 'node:child_process'; 3 + import fs from 'node:fs'; 4 + import path from 'node:path'; 5 + import { fileURLToPath } from 'node:url'; 6 + 7 + const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 + const target = path.join(__dirname, '..', 'dist', 'run.js'); 9 + 10 + if (!fs.existsSync(target)) { 11 + console.error('openapi-ts not built (expect dist/run.js)'); 12 + process.exit(1); 13 + } 14 + 15 + const res = spawnSync(process.execPath, [target, ...process.argv.slice(2)], { 16 + stdio: 'inherit', 17 + }); 18 + process.exit(res.status ?? 0);
+1 -1
packages/openapi-ts/package.json
··· 66 66 "./package.json": "./package.json" 67 67 }, 68 68 "bin": { 69 - "openapi-ts": "bin/index.cjs" 69 + "openapi-ts": "./bin/run.js" 70 70 }, 71 71 "files": [ 72 72 "bin",
+292
packages/openapi-ts/src/__tests__/cli.test.ts
··· 1 + import type { Mock } from 'vitest'; 2 + import { beforeEach, describe, expect, it, vi } from 'vitest'; 3 + 4 + import { runCli } from '~/cli'; 5 + import { createClient } from '~/index'; 6 + 7 + vi.mock('~/index', () => { 8 + const result: Awaited<ReturnType<typeof createClient>> = []; 9 + return { 10 + createClient: vi.fn().mockResolvedValue(result), 11 + }; 12 + }); 13 + const spyExit = vi 14 + .spyOn(process, 'exit') 15 + .mockImplementation(() => ({}) as never); 16 + 17 + const spy = createClient as Mock; 18 + 19 + describe('cli', () => { 20 + beforeEach(() => { 21 + spy.mockClear(); 22 + spyExit.mockClear(); 23 + }); 24 + 25 + it('with default options', async () => { 26 + const originalArgv = process.argv.slice(); 27 + try { 28 + process.argv = [String(process.argv[0]), String(process.argv[1])]; 29 + await runCli(); 30 + } finally { 31 + process.argv = originalArgv; 32 + } 33 + expect(spy).toHaveBeenCalledWith({ 34 + logs: { 35 + file: true, 36 + }, 37 + }); 38 + }); 39 + 40 + it('with minimal options', async () => { 41 + const originalArgv = process.argv.slice(); 42 + try { 43 + process.argv = [ 44 + String(process.argv[0]), 45 + String(process.argv[1]), 46 + '--input', 47 + 'foo.json', 48 + '--output', 49 + 'bar', 50 + ]; 51 + await runCli(); 52 + } finally { 53 + process.argv = originalArgv; 54 + } 55 + expect(spy).toHaveBeenCalledWith({ 56 + input: 'foo.json', 57 + logs: { 58 + file: true, 59 + }, 60 + output: 'bar', 61 + }); 62 + }); 63 + 64 + it('with no plugins', async () => { 65 + const originalArgv = process.argv.slice(); 66 + try { 67 + process.argv = [ 68 + String(process.argv[0]), 69 + String(process.argv[1]), 70 + '--plugins', 71 + ]; 72 + await runCli(); 73 + } finally { 74 + process.argv = originalArgv; 75 + } 76 + expect(spy).toHaveBeenCalledWith({ 77 + logs: { 78 + file: true, 79 + }, 80 + plugins: [], 81 + }); 82 + }); 83 + 84 + it('with plugins', async () => { 85 + const originalArgv = process.argv.slice(); 86 + try { 87 + process.argv = [ 88 + String(process.argv[0]), 89 + String(process.argv[1]), 90 + '--plugins', 91 + 'foo', 92 + ]; 93 + await runCli(); 94 + } finally { 95 + process.argv = originalArgv; 96 + } 97 + expect(spy).toHaveBeenCalledWith({ 98 + logs: { 99 + file: true, 100 + }, 101 + plugins: ['foo'], 102 + }); 103 + }); 104 + 105 + it('with default plugins', async () => { 106 + const originalArgv = process.argv.slice(); 107 + try { 108 + process.argv = [ 109 + String(process.argv[0]), 110 + String(process.argv[1]), 111 + '--client', 112 + 'foo', 113 + ]; 114 + await runCli(); 115 + } finally { 116 + process.argv = originalArgv; 117 + } 118 + expect(spy).toHaveBeenCalledWith({ 119 + logs: { 120 + file: true, 121 + }, 122 + plugins: ['@hey-api/typescript', '@hey-api/sdk', 'foo'], 123 + }); 124 + }); 125 + 126 + describe('logs', () => { 127 + it('debug', async () => { 128 + const originalArgv = process.argv.slice(); 129 + try { 130 + process.argv = [ 131 + String(process.argv[0]), 132 + String(process.argv[1]), 133 + '--debug', 134 + ]; 135 + await runCli(); 136 + } finally { 137 + process.argv = originalArgv; 138 + } 139 + expect(spy).toHaveBeenCalledWith({ 140 + logs: { 141 + file: true, 142 + level: 'debug', 143 + }, 144 + }); 145 + }); 146 + 147 + it('silent', async () => { 148 + const originalArgv = process.argv.slice(); 149 + try { 150 + process.argv = [ 151 + String(process.argv[0]), 152 + String(process.argv[1]), 153 + '--silent', 154 + ]; 155 + await runCli(); 156 + } finally { 157 + process.argv = originalArgv; 158 + } 159 + expect(spy).toHaveBeenCalledWith({ 160 + logs: { 161 + file: true, 162 + level: 'silent', 163 + }, 164 + }); 165 + }); 166 + 167 + it('no log file', async () => { 168 + const originalArgv = process.argv.slice(); 169 + try { 170 + process.argv = [ 171 + String(process.argv[0]), 172 + String(process.argv[1]), 173 + '--no-log-file', 174 + ]; 175 + await runCli(); 176 + } finally { 177 + process.argv = originalArgv; 178 + } 179 + expect(spy).toHaveBeenCalledWith({ 180 + logs: { 181 + file: false, 182 + }, 183 + }); 184 + }); 185 + }); 186 + 187 + it('with all options', async () => { 188 + const originalArgv = process.argv.slice(); 189 + try { 190 + process.argv = [ 191 + String(process.argv[0]), 192 + String(process.argv[1]), 193 + '--client', 194 + 'foo', 195 + '--dry-run', 196 + 'true', 197 + '--experimental-parser', 198 + 'true', 199 + '--file', 200 + 'bar', 201 + '--input', 202 + 'baz', 203 + '--logs', 204 + 'qux', 205 + '--output', 206 + 'quux', 207 + '--plugins', 208 + '--watch', 209 + '--useOptions', 210 + '--exportCore', 211 + ]; 212 + await runCli(); 213 + } finally { 214 + process.argv = originalArgv; 215 + } 216 + expect(spy).toHaveBeenCalledWith({ 217 + configFile: 'bar', 218 + dryRun: true, 219 + experimentalParser: true, 220 + exportCore: true, 221 + input: 'baz', 222 + logs: { 223 + file: true, 224 + path: 'qux', 225 + }, 226 + output: 'quux', 227 + plugins: ['foo'], 228 + useOptions: true, 229 + watch: true, 230 + }); 231 + }); 232 + 233 + it('with false booleans', async () => { 234 + const originalArgv = process.argv.slice(); 235 + try { 236 + process.argv = [ 237 + String(process.argv[0]), 238 + String(process.argv[1]), 239 + '--useOptions', 240 + 'false', 241 + ]; 242 + await runCli(); 243 + } finally { 244 + process.argv = originalArgv; 245 + } 246 + expect(spy).toHaveBeenCalledWith({ 247 + logs: { 248 + file: true, 249 + }, 250 + useOptions: false, 251 + }); 252 + }); 253 + 254 + it('exits when not in watch mode', async () => { 255 + const originalArgv = process.argv.slice(); 256 + try { 257 + process.argv = [String(process.argv[0]), String(process.argv[1])]; 258 + await runCli(); 259 + } finally { 260 + process.argv = originalArgv; 261 + } 262 + expect(spyExit).toHaveBeenCalledWith(0); 263 + }); 264 + 265 + it('does not exit in watch mode', async () => { 266 + spy.mockResolvedValueOnce([ 267 + { 268 + config: { input: [{ watch: { enabled: true } }] }, 269 + }, 270 + ]); 271 + const originalArgv = process.argv.slice(); 272 + try { 273 + process.argv = [String(process.argv[0]), String(process.argv[1])]; 274 + await runCli(); 275 + } finally { 276 + process.argv = originalArgv; 277 + } 278 + expect(spyExit).not.toHaveBeenCalled(); 279 + }); 280 + 281 + it('exits with error code on error', async () => { 282 + spy.mockRejectedValueOnce('Some error'); 283 + const originalArgv = process.argv.slice(); 284 + try { 285 + process.argv = [String(process.argv[0]), String(process.argv[1])]; 286 + await runCli(); 287 + } finally { 288 + process.argv = originalArgv; 289 + } 290 + expect(spyExit).toHaveBeenCalledWith(1); 291 + }); 292 + });
+144
packages/openapi-ts/src/cli.ts
··· 1 + import type { OptionValues } from 'commander'; 2 + import { Command } from 'commander'; 3 + 4 + import { createClient } from '~/index'; 5 + 6 + import pkg from '../package.json' assert { type: 'json' }; 7 + 8 + const stringToBoolean = ( 9 + value: string | undefined, 10 + ): boolean | string | undefined => { 11 + if (value === 'true') return true; 12 + if (value === 'false') return false; 13 + return value; 14 + }; 15 + 16 + const processParams = ( 17 + obj: OptionValues, 18 + booleanKeys: ReadonlyArray<string>, 19 + ): OptionValues => { 20 + for (const key of booleanKeys) { 21 + const value = obj[key]; 22 + if (typeof value === 'string') { 23 + const parsedValue = stringToBoolean(value); 24 + delete obj[key]; 25 + obj[key] = parsedValue; 26 + } 27 + } 28 + return obj; 29 + }; 30 + 31 + export const runCli = async (): Promise<void> => { 32 + const params = new Command() 33 + .name(Object.keys(pkg.bin)[0]!) 34 + .usage('[options]') 35 + .version(pkg.version) 36 + .option( 37 + '-c, --client <value>', 38 + 'HTTP client to generate [@hey-api/client-axios, @hey-api/client-fetch, @hey-api/client-next, @hey-api/client-nuxt, legacy/angular, legacy/axios, legacy/fetch, legacy/node, legacy/xhr]', 39 + ) 40 + .option('-d, --debug', 'Set log level to debug') 41 + .option('--dry-run [value]', 'Skip writing files to disk?') 42 + .option( 43 + '-e, --experimental-parser [value]', 44 + 'Opt-in to the experimental parser?', 45 + ) 46 + .option('-f, --file [value]', 'Path to the config file') 47 + .option( 48 + '-i, --input <value>', 49 + 'OpenAPI specification (path, url, or string content)', 50 + ) 51 + .option('-l, --logs [value]', 'Logs folder') 52 + .option('-o, --output <value>', 'Output folder') 53 + .option('-p, --plugins [value...]', "List of plugins you'd like to use") 54 + .option( 55 + '--base [value]', 56 + 'DEPRECATED. Manually set base in OpenAPI config instead of inferring from server value', 57 + ) 58 + .option('-s, --silent', 'Set log level to silent') 59 + .option( 60 + '--no-log-file', 61 + 'Disable writing a log file. Works like --silent but without suppressing console output', 62 + ) 63 + .option( 64 + '-w, --watch [value]', 65 + 'Regenerate the client when the input file changes?', 66 + ) 67 + .option('--exportCore [value]', 'DEPRECATED. Write core files to disk') 68 + .option('--name <value>', 'DEPRECATED. Custom client class name') 69 + .option('--request <value>', 'DEPRECATED. Path to custom request file') 70 + .option( 71 + '--useOptions [value]', 72 + 'DEPRECATED. Use options instead of arguments?', 73 + ) 74 + .parse(process.argv) 75 + .opts(); 76 + 77 + let userConfig: Record<string, unknown>; 78 + 79 + try { 80 + userConfig = processParams(params, [ 81 + 'dryRun', 82 + 'experimentalParser', 83 + 'exportCore', 84 + 'logFile', 85 + 'useOptions', 86 + ]); 87 + 88 + if (userConfig.file) { 89 + userConfig.configFile = userConfig.file; 90 + delete userConfig.file; 91 + } 92 + 93 + if (params.plugins === true) { 94 + userConfig.plugins = []; 95 + } else if (params.plugins) { 96 + userConfig.plugins = params.plugins; 97 + } else if (userConfig.client) { 98 + userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; 99 + } 100 + 101 + if (userConfig.client) { 102 + (userConfig.plugins as Array<string>).push(userConfig.client as string); 103 + delete userConfig.client; 104 + } 105 + 106 + userConfig.logs = userConfig.logs 107 + ? { 108 + path: userConfig.logs, 109 + } 110 + : {}; 111 + 112 + if (userConfig.debug || stringToBoolean(process.env.DEBUG)) { 113 + (userConfig.logs as Record<string, unknown>).level = 'debug'; 114 + delete userConfig.debug; 115 + } else if (userConfig.silent) { 116 + (userConfig.logs as Record<string, unknown>).level = 'silent'; 117 + delete userConfig.silent; 118 + } 119 + 120 + (userConfig.logs as Record<string, unknown>).file = userConfig.logFile; 121 + delete userConfig.logFile; 122 + 123 + if (typeof params.watch === 'string') { 124 + userConfig.watch = Number.parseInt(params.watch, 10); 125 + } 126 + 127 + if (!Object.keys(userConfig.logs as Record<string, unknown>).length) { 128 + delete userConfig.logs; 129 + } 130 + 131 + const context = await createClient( 132 + userConfig as unknown as Required<Parameters<typeof createClient>>[0], 133 + ); 134 + if ( 135 + !context[0]?.config.input.some( 136 + (input) => input.watch && input.watch.enabled, 137 + ) 138 + ) { 139 + process.exit(0); 140 + } 141 + } catch { 142 + process.exit(1); 143 + } 144 + };
+133
packages/openapi-ts/src/generate.ts
··· 1 + import { checkNodeVersion } from '~/config/engine'; 2 + import type { Configs } from '~/config/init'; 3 + import { initConfigs } from '~/config/init'; 4 + import { getLogs } from '~/config/logs'; 5 + import { createClient as pCreateClient } from '~/createClient'; 6 + import { 7 + ConfigValidationError, 8 + JobError, 9 + logCrashReport, 10 + openGitHubIssueWithCrashReport, 11 + printCrashReport, 12 + shouldReportCrash, 13 + } from '~/error'; 14 + import type { IR } from '~/ir/types'; 15 + import type { Client } from '~/types/client'; 16 + import type { UserConfig } from '~/types/config'; 17 + import type { LazyOrAsync, MaybeArray } from '~/types/utils'; 18 + import { printCliIntro } from '~/utils/cli'; 19 + import { registerHandlebarTemplates } from '~/utils/handlebars'; 20 + import { Logger } from '~/utils/logger'; 21 + 22 + /** 23 + * Generate a client from the provided configuration. 24 + * 25 + * @param userConfig User provided {@link UserConfig} configuration(s). 26 + */ 27 + export const createClient = async ( 28 + userConfig?: LazyOrAsync<MaybeArray<UserConfig>>, 29 + logger = new Logger(), 30 + ): Promise<ReadonlyArray<Client | IR.Context>> => { 31 + const resolvedConfig = 32 + typeof userConfig === 'function' ? await userConfig() : userConfig; 33 + const userConfigs = resolvedConfig 34 + ? resolvedConfig instanceof Array 35 + ? resolvedConfig 36 + : [resolvedConfig] 37 + : []; 38 + 39 + let rawLogs = userConfigs.find( 40 + (config) => getLogs(config).level !== 'silent', 41 + )?.logs; 42 + if (typeof rawLogs === 'string') { 43 + rawLogs = getLogs({ logs: rawLogs }); 44 + } 45 + 46 + let configs: Configs | undefined; 47 + 48 + try { 49 + checkNodeVersion(); 50 + 51 + const eventCreateClient = logger.timeEvent('createClient'); 52 + 53 + const eventConfig = logger.timeEvent('config'); 54 + configs = await initConfigs({ logger, userConfigs }); 55 + const printIntro = configs.results.some( 56 + (result) => result.config.logs.level !== 'silent', 57 + ); 58 + if (printIntro) { 59 + printCliIntro(); 60 + } 61 + eventConfig.timeEnd(); 62 + 63 + const allConfigErrors = configs.results.flatMap((result) => 64 + result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), 65 + ); 66 + if (allConfigErrors.length) { 67 + throw new ConfigValidationError(allConfigErrors); 68 + } 69 + 70 + const eventHandlebars = logger.timeEvent('handlebars'); 71 + const templates = registerHandlebarTemplates(); 72 + eventHandlebars.timeEnd(); 73 + 74 + const clients = await Promise.all( 75 + configs.results.map(async (result) => { 76 + try { 77 + return await pCreateClient({ 78 + config: result.config, 79 + dependencies: configs!.dependencies, 80 + jobIndex: result.jobIndex, 81 + logger, 82 + templates, 83 + }); 84 + } catch (error) { 85 + throw new JobError('', { 86 + error, 87 + jobIndex: result.jobIndex, 88 + }); 89 + } 90 + }), 91 + ); 92 + const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< 93 + Client | IR.Context 94 + >; 95 + 96 + eventCreateClient.timeEnd(); 97 + 98 + const printLogs = configs.results.some( 99 + (result) => result.config.logs.level === 'debug', 100 + ); 101 + logger.report(printLogs); 102 + 103 + return result; 104 + } catch (error) { 105 + const results = configs?.results ?? []; 106 + 107 + const logs = 108 + results.find((result) => result.config.logs.level !== 'silent')?.config 109 + .logs ?? 110 + results[0]?.config.logs ?? 111 + rawLogs; 112 + const dryRun = 113 + results.some((result) => result.config.dryRun) ?? 114 + userConfigs.some((config) => config.dryRun) ?? 115 + false; 116 + const logPath = 117 + logs?.file && !dryRun 118 + ? logCrashReport(error, logs.path ?? '') 119 + : undefined; 120 + if (!logs || logs.level !== 'silent') { 121 + printCrashReport({ error, logPath }); 122 + const isInteractive = 123 + results.some((result) => result.config.interactive) ?? 124 + userConfigs.some((config) => config.interactive) ?? 125 + false; 126 + if (await shouldReportCrash({ error, isInteractive })) { 127 + await openGitHubIssueWithCrashReport(error); 128 + } 129 + } 130 + 131 + throw error; 132 + } 133 + };
+2 -1
packages/openapi-ts/src/generate/legacy/output.ts
··· 65 65 if ( 66 66 !isLegacyClient(config) && 67 67 'bundle' in clientPlugin.config && 68 - clientPlugin.config.bundle 68 + clientPlugin.config.bundle && 69 + !config.dryRun 69 70 ) { 70 71 const meta: ProjectRenderMeta = { 71 72 importFileExtension: config.output.importFileExtension,
+5 -1
packages/openapi-ts/src/generate/output.ts
··· 21 21 }; 22 22 23 23 const client = getClientPlugin(context.config); 24 - if ('bundle' in client.config && client.config.bundle) { 24 + if ( 25 + 'bundle' in client.config && 26 + client.config.bundle && 27 + !context.config.dryRun 28 + ) { 25 29 // not proud of this one 26 30 // @ts-expect-error 27 31 context.config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({
+1 -130
packages/openapi-ts/src/index.ts
··· 35 35 // @ts-expect-error 36 36 import colorSupport from 'color-support'; 37 37 38 - import { checkNodeVersion } from '~/config/engine'; 39 - import type { Configs } from '~/config/init'; 40 - import { initConfigs } from '~/config/init'; 41 - import { getLogs } from '~/config/logs'; 42 - import { createClient as pCreateClient } from '~/createClient'; 43 - import { 44 - ConfigValidationError, 45 - JobError, 46 - logCrashReport, 47 - openGitHubIssueWithCrashReport, 48 - printCrashReport, 49 - shouldReportCrash, 50 - } from '~/error'; 51 - import type { IR } from '~/ir/types'; 52 - import type { Client } from '~/types/client'; 53 38 import type { UserConfig } from '~/types/config'; 54 39 import type { LazyOrAsync, MaybeArray } from '~/types/utils'; 55 - import { printCliIntro } from '~/utils/cli'; 56 - import { registerHandlebarTemplates } from '~/utils/handlebars'; 57 - import { Logger } from '~/utils/logger'; 58 40 59 41 colors.enabled = colorSupport().hasBasic; 60 42 61 - /** 62 - * Generate a client from the provided configuration. 63 - * 64 - * @param userConfig User provided {@link UserConfig} configuration(s). 65 - */ 66 - export const createClient = async ( 67 - userConfig?: LazyOrAsync<MaybeArray<UserConfig>>, 68 - logger = new Logger(), 69 - ): Promise<ReadonlyArray<Client | IR.Context>> => { 70 - const resolvedConfig = 71 - typeof userConfig === 'function' ? await userConfig() : userConfig; 72 - const userConfigs = resolvedConfig 73 - ? resolvedConfig instanceof Array 74 - ? resolvedConfig 75 - : [resolvedConfig] 76 - : []; 77 - 78 - let rawLogs = userConfigs.find( 79 - (config) => getLogs(config).level !== 'silent', 80 - )?.logs; 81 - if (typeof rawLogs === 'string') { 82 - rawLogs = getLogs({ logs: rawLogs }); 83 - } 84 - 85 - let configs: Configs | undefined; 86 - 87 - try { 88 - checkNodeVersion(); 89 - 90 - const eventCreateClient = logger.timeEvent('createClient'); 91 - 92 - const eventConfig = logger.timeEvent('config'); 93 - configs = await initConfigs({ logger, userConfigs }); 94 - const printIntro = configs.results.some( 95 - (result) => result.config.logs.level !== 'silent', 96 - ); 97 - if (printIntro) { 98 - printCliIntro(); 99 - } 100 - eventConfig.timeEnd(); 101 - 102 - const allConfigErrors = configs.results.flatMap((result) => 103 - result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), 104 - ); 105 - if (allConfigErrors.length) { 106 - throw new ConfigValidationError(allConfigErrors); 107 - } 108 - 109 - const eventHandlebars = logger.timeEvent('handlebars'); 110 - const templates = registerHandlebarTemplates(); 111 - eventHandlebars.timeEnd(); 112 - 113 - const clients = await Promise.all( 114 - configs.results.map(async (result) => { 115 - try { 116 - return await pCreateClient({ 117 - config: result.config, 118 - dependencies: configs!.dependencies, 119 - jobIndex: result.jobIndex, 120 - logger, 121 - templates, 122 - }); 123 - } catch (error) { 124 - throw new JobError('', { 125 - error, 126 - jobIndex: result.jobIndex, 127 - }); 128 - } 129 - }), 130 - ); 131 - const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< 132 - Client | IR.Context 133 - >; 134 - 135 - eventCreateClient.timeEnd(); 136 - 137 - const printLogs = configs.results.some( 138 - (result) => result.config.logs.level === 'debug', 139 - ); 140 - logger.report(printLogs); 141 - 142 - return result; 143 - } catch (error) { 144 - const results = configs?.results ?? []; 145 - 146 - const logs = 147 - results.find((result) => result.config.logs.level !== 'silent')?.config 148 - .logs ?? 149 - results[0]?.config.logs ?? 150 - rawLogs; 151 - const dryRun = 152 - results.some((result) => result.config.dryRun) ?? 153 - userConfigs.some((config) => config.dryRun) ?? 154 - false; 155 - const logPath = 156 - logs?.file && !dryRun 157 - ? logCrashReport(error, logs.path ?? '') 158 - : undefined; 159 - if (!logs || logs.level !== 'silent') { 160 - printCrashReport({ error, logPath }); 161 - const isInteractive = 162 - results.some((result) => result.config.interactive) ?? 163 - userConfigs.some((config) => config.interactive) ?? 164 - false; 165 - if (await shouldReportCrash({ error, isInteractive })) { 166 - await openGitHubIssueWithCrashReport(error); 167 - } 168 - } 169 - 170 - throw error; 171 - } 172 - }; 43 + export { createClient } from './generate'; 173 44 174 45 /** 175 46 * Type helper for openapi-ts.config.ts, returns {@link MaybeArray<UserConfig>} object(s)
+1 -1
packages/openapi-ts/src/ir/context.ts
··· 14 14 15 15 import type { IR } from './types'; 16 16 17 - export class IRContext<Spec extends Record<string, any> = any> { 17 + export class Context<Spec extends Record<string, any> = any> { 18 18 /** 19 19 * Configuration for parsing and generating the output. This 20 20 * is a mix of user-provided and default values.
+1 -1
packages/openapi-ts/src/ir/types.d.ts
··· 4 4 ServerObject, 5 5 } from '~/openApi/3.1.x/types/spec'; 6 6 7 - import type { IRContext } from './context'; 7 + import type { Context as IRContext } from './context'; 8 8 import type { IRMediaType } from './mediaType'; 9 9 10 10 interface IRBodyObject {
+2 -2
packages/openapi-ts/src/openApi/index.ts
··· 1 1 import { satisfies } from '~/config/utils/package'; 2 - import { IRContext } from '~/ir/context'; 2 + import { Context } from '~/ir/context'; 3 3 import type { IR } from '~/ir/types'; 4 4 import { parseV2_0_X } from '~/openApi/2.0.x'; 5 5 import { parseV3_0_X } from '~/openApi/3.0.x'; ··· 74 74 logger: Logger; 75 75 spec: unknown; 76 76 }): IR.Context | undefined => { 77 - const context = new IRContext({ 77 + const context = new Context({ 78 78 config, 79 79 dependencies, 80 80 logger,
+2 -2
packages/openapi-ts/src/openApi/shared/utils/graph.ts
··· 503 503 504 504 eventBuildGraph.timeEnd(); 505 505 506 - // functions creating data for debug scripts located in `debug-helpers/` 506 + // functions creating data for debug scripts located in `dev/` 507 507 // const { maxChildren, maxDepth, totalNodes } = debugTools.graph.analyzeStructure(graph); 508 508 // const nodesForViz = debugTools.graph.exportForVisualization(graph); 509 - // fs.writeFileSync('debug-helpers/graph.json', JSON.stringify(nodesForViz, null, 2)); 509 + // fs.writeFileSync('dev/graph.json', JSON.stringify(nodesForViz, null, 2)); 510 510 511 511 return { graph }; 512 512 };
+5
packages/openapi-ts/src/run.ts
··· 1 + #!/usr/bin/env node 2 + 3 + import { runCli } from '~/cli'; 4 + 5 + runCli();
+1 -1
packages/openapi-ts/tsdown.config.ts
··· 29 29 }, 30 30 clean: true, 31 31 dts: true, 32 - entry: ['src/index.ts', 'src/internal.ts'], 32 + entry: ['./src/{index,internal,run}.ts'], 33 33 format: ['cjs', 'esm'], 34 34 minify: !options.watch, 35 35 onSuccess: async () => {
+11 -5
pnpm-lock.yaml
··· 37 37 '@hey-api/custom-client': 38 38 specifier: workspace:* 39 39 version: link:packages/custom-client 40 + '@hey-api/openapi-ts': 41 + specifier: workspace:* 42 + version: link:packages/openapi-ts 40 43 '@types/node': 41 44 specifier: 22.10.5 42 45 version: 22.10.5 ··· 88 91 rollup-plugin-dts: 89 92 specifier: 6.1.1 90 93 version: 6.1.1(rollup@4.31.0)(typescript@5.9.3) 94 + ts-node: 95 + specifier: 10.9.2 96 + version: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) 91 97 tsdown: 92 98 specifier: 0.15.8 93 99 version: 0.15.8(@arethetypeswrong/core@0.18.2)(typescript@5.9.3) ··· 170 176 devDependencies: 171 177 '@angular-devkit/build-angular': 172 178 specifier: 19.2.0 173 - version: 19.2.0(3d70bfaaf57777265a4e229c41aeb4c6) 179 + version: 19.2.0(a969acb6c0970aa981df1cb7672d7c2e) 174 180 '@angular/cli': 175 181 specifier: 19.2.0 176 182 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) ··· 264 270 devDependencies: 265 271 '@angular-devkit/build-angular': 266 272 specifier: 19.2.0 267 - version: 19.2.0(a969acb6c0970aa981df1cb7672d7c2e) 273 + version: 19.2.0(3d70bfaaf57777265a4e229c41aeb4c6) 268 274 '@angular/cli': 269 275 specifier: 19.2.0 270 276 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) ··· 23183 23189 eslint: 9.17.0(jiti@2.6.1) 23184 23190 eslint-import-resolver-node: 0.3.9 23185 23191 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23186 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)) 23192 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23187 23193 eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.6.1)) 23188 23194 eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.6.1)) 23189 23195 eslint-plugin-react-hooks: 5.2.0(eslint@9.17.0(jiti@2.6.1)) ··· 23221 23227 tinyglobby: 0.2.14 23222 23228 unrs-resolver: 1.11.1 23223 23229 optionalDependencies: 23224 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)) 23230 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)) 23225 23231 transitivePeerDependencies: 23226 23232 - supports-color 23227 23233 ··· 23236 23242 transitivePeerDependencies: 23237 23243 - supports-color 23238 23244 23239 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.6.1)): 23245 + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)))(eslint@9.17.0(jiti@2.6.1)): 23240 23246 dependencies: 23241 23247 '@rtsao/scc': 1.1.0 23242 23248 array-includes: 3.1.9
-7
pnpm-workspace.yaml
··· 3 3 - examples/**/* 4 4 - packages/**/* 5 5 6 - onlyBuiltDependencies: 7 - - esbuild 8 - - lmdb 9 - - msgpackr-extract 10 - - sharp 11 - - unrs-resolver 12 - 13 6 patchedDependencies: 14 7 vitepress: patches/vitepress.patch
+13 -4
scripts/examples-generate.sh
··· 10 10 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" 11 11 12 12 echo "⏳ Generating client code for all examples..." 13 + 13 14 # Find all examples with openapi-ts script and generate code in parallel 14 15 # Concurrency control: adjust this number depending on CI machine resources 15 16 CONCURRENCY=${CONCURRENCY:-4} ··· 44 45 echo "Generating: $example_name" 45 46 set -e 46 47 cd "$dir" 47 - echo "-> Running pnpm openapi-ts" 48 - pnpm openapi-ts 48 + echo "-> Running openapi-ts" 49 + pnpm run openapi-ts 49 50 50 51 # Format generated files in this example only to keep the step fast 51 52 if command -v pnpm >/dev/null 2>&1 && pnpm -w -s --version >/dev/null 2>&1; then ··· 76 77 echo "✅ $name succeeded" 77 78 else 78 79 name=$(cat "$tmpdir/$pid.name" 2>/dev/null || echo "$pid") 79 - log=$(cat "$tmpdir/$pid.log" 2>/dev/null || echo "(no log)") 80 - echo "❌ $name failed — see log: $tmpdir/$pid.log" 80 + # Read the metadata file which contains the path to the real log 81 + logpath=$(cat "$tmpdir/$pid.log" 2>/dev/null || echo "") 82 + if [ -n "$logpath" ] && [ -f "$logpath" ]; then 83 + echo "❌ $name failed — showing full log ($logpath):" 84 + echo "---- full log start ----" 85 + cat "$logpath" || true 86 + echo "---- full log end ----" 87 + else 88 + echo "❌ $name failed — no log found (metadata: $tmpdir/$pid.log)" 89 + fi 81 90 failed=1 82 91 fi 83 92 done
+1 -1
scripts/publish-preview-packages.sh
··· 1 - #!/bin/bash 1 + #!/usr/bin/env bash 2 2 3 3 result=$(pnpx turbo run build --affected --dry-run=json) 4 4