diff --git a/.changeset/short-phones-count.md b/.changeset/short-phones-count.md new file mode 100644 index 0000000000..ccfad67ac1 --- /dev/null +++ b/.changeset/short-phones-count.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix(cli): move cli script to typescript diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1f599d34cd..61dac952f0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -49,7 +49,7 @@ pnpm --filter @example/openapi-ts-fetch dev # Server starts on http://localhost:5173/ # Run CLI tool directly -node packages/openapi-ts/bin/index.cjs --help +node packages/openapi-ts/dist/run.js --help # or after building npx @hey-api/openapi-ts --help ``` @@ -85,14 +85,14 @@ After making changes, ALWAYS validate with these scenarios: ```bash # Test CLI help - node packages/openapi-ts/bin/index.cjs --help + node packages/openapi-ts/dist/run.js --help # Test CLI version - node packages/openapi-ts/bin/index.cjs --version + node packages/openapi-ts/dist/run.js --version # Test basic code generation with a simple OpenAPI spec # Create a minimal test spec and generate client code - node packages/openapi-ts/bin/index.cjs -i path/to/spec.json -o ./test-output --plugins "@hey-api/client-fetch" "@hey-api/typescript" + node packages/openapi-ts/dist/run.js -i path/to/spec.json -o ./test-output --plugins "@hey-api/client-fetch" "@hey-api/typescript" ``` 2. **Example Application Test**: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20d66513b8..7868fa9431 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,14 +37,14 @@ jobs: - name: Build packages run: pnpm build --filter="@hey-api/**" - - name: Build examples - if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' - run: pnpm build --filter="@example/**" - - name: Check examples generated code if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' run: pnpm examples:check + - name: Build examples + if: matrix.node-version == '24.10.0' && matrix.os == 'ubuntu-latest' + run: pnpm build --filter="@example/**" + - name: Run linter run: pnpm lint diff --git a/.gitignore b/.gitignore index fc255aa4db..998eb7375d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ coverage .turbo # test files +.gen/ test/generated # debug files diff --git a/.vscode/launch.json b/.vscode/launch.json index e78b8710b9..550fe0f2e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,13 +13,10 @@ "request": "launch", "name": "openapi-ts", "skipFiles": ["/**"], + "cwd": "${workspaceFolder}/dev", "runtimeExecutable": "node", - "runtimeArgs": [], - "program": "${workspaceFolder}/packages/openapi-ts/bin/index.cjs", - "args": [ - "-f", - "${workspaceFolder}/packages/openapi-ts-tests/main/test/openapi-ts.config.ts" - ] + "runtimeArgs": ["-r", "ts-node/register/transpile-only"], + "program": "${workspaceFolder}/packages/openapi-ts/src/cli.ts" } ] } diff --git a/debug-helpers/graph-hotspots.js b/dev/graph-hotspots.js similarity index 100% rename from debug-helpers/graph-hotspots.js rename to dev/graph-hotspots.js diff --git a/packages/openapi-ts-tests/main/test/hey-api.ts b/dev/hey-api.ts similarity index 100% rename from packages/openapi-ts-tests/main/test/hey-api.ts rename to dev/hey-api.ts diff --git a/debug-helpers/json-to-dot.js b/dev/json-to-dot.js similarity index 100% rename from debug-helpers/json-to-dot.js rename to dev/json-to-dot.js diff --git a/packages/openapi-ts-tests/main/test/openapi-ts.config.ts b/dev/openapi-ts.config.ts similarity index 98% rename from packages/openapi-ts-tests/main/test/openapi-ts.config.ts rename to dev/openapi-ts.config.ts index 234a4e57af..40387011d2 100644 --- a/packages/openapi-ts-tests/main/test/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -6,9 +6,9 @@ import { customClientPlugin } from '@hey-api/custom-client/plugin'; // @ts-ignore import { defineConfig, utils } from '@hey-api/openapi-ts'; -import { getSpecsPath } from '../../utils'; // @ts-ignore -import { myClientPlugin } from './custom/client/plugin'; +import { myClientPlugin } from '../packages/openapi-ts-tests/main/test/custom/client/plugin'; +import { getSpecsPath } from '../packages/openapi-ts-tests/utils'; // @ts-ignore // eslint-disable-next-line arrow-body-style @@ -96,14 +96,14 @@ export default defineConfig(() => { // importFileExtension: '.ts', // // indexFile: false, // // lint: 'eslint', - // path: path.resolve(__dirname, 'generated', 'sample'), + // path: path.resolve(__dirname, '.gen'), // tsConfigPath: path.resolve( // __dirname, // 'tsconfig', // 'tsconfig.nodenext.json', // ), // }, - path.resolve(__dirname, 'generated', 'sample'), + '.gen', ], parser: { filters: { @@ -478,7 +478,7 @@ export default defineConfig(() => { // // level: 'debug', // path: './logs', // }, - // output: path.resolve(__dirname, 'generated', 'sample'), + // output: '.gen', // }, ]; }); diff --git a/package.json b/package.json index ada45878ad..3b333423f4 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@config/vite-base": "workspace:*", "@eslint/js": "9.32.0", "@hey-api/custom-client": "workspace:*", + "@hey-api/openapi-ts": "workspace:*", "@types/node": "22.10.5", "@typescript-eslint/eslint-plugin": "8.29.1", "@vitest/coverage-v8": "3.1.1", @@ -65,6 +66,7 @@ "prettier": "3.4.2", "rollup": "4.31.0", "rollup-plugin-dts": "6.1.1", + "ts-node": "10.9.2", "tsdown": "0.15.8", "turbo": "2.5.8", "typescript": "5.9.3", diff --git a/packages/openapi-ts-tests/main/.gitignore b/packages/openapi-ts-tests/main/.gitignore index a84df3b4d5..f75222cd2c 100644 --- a/packages/openapi-ts-tests/main/.gitignore +++ b/packages/openapi-ts-tests/main/.gitignore @@ -16,5 +16,6 @@ coverage .env # test files +.gen/ test/generated generated/ diff --git a/packages/openapi-ts-tests/main/test/bin.test.ts b/packages/openapi-ts-tests/main/test/bin.test.ts deleted file mode 100755 index 4d6a848411..0000000000 --- a/packages/openapi-ts-tests/main/test/bin.test.ts +++ /dev/null @@ -1,368 +0,0 @@ -import path from 'node:path'; - -import { sync } from 'cross-spawn'; -import { describe, expect, it } from 'vitest'; - -import { getSpecsPath } from '../../utils'; - -describe('bin', () => { - it('supports required parameters', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - '@hey-api/client-fetch', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('generates angular client', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/angular', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('generates axios client', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/axios', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('generates fetch client', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/fetch', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('generates node client', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/node', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('generates xhr client', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/xhr', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('supports all parameters', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - 'legacy/fetch', - '--useOptions', - '--exportCore', - 'true', - '--plugins', - '@hey-api/schemas', - '@hey-api/sdk', - '@hey-api/typescript', - '--dry-run', - 'true', - ]); - expect(result.error).toBeFalsy(); - expect(result.status).toBe(0); - }); - - it('throws error without input', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--dry-run', - 'true', - ]); - expect(result.stderr.toString()).toContain('missing input'); - }); - - it('throws error without output', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--dry-run', - 'true', - ]); - expect(result.stderr.toString()).toContain('missing output'); - }); - - it('throws error with wrong parameters', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--unknown', - '--dry-run', - 'true', - ]); - expect(result.stderr.toString()).toContain( - `error: unknown option '--unknown'`, - ); - }); - - it('displays help', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--help', - '--dry-run', - 'true', - ]); - expect(result.stdout.toString()).toContain(`Usage: openapi-ts [options]`); - expect(result.stdout.toString()).toContain(`-i, --input `); - expect(result.stdout.toString()).toContain(`-o, --output `); - expect(result.stderr.toString()).toBe(''); - }); -}); - -describe('cli', () => { - it('handles false booleans', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--debug', - '--plugins', - '--useOptions', - 'false', - '--dry-run', - 'true', - ]); - const stderr = result.stderr.toString(); - expect(stderr).toMatch(/level: 'debug'/); - expect(stderr).toMatch(/dryRun: true/); - expect(stderr).not.toMatch(/@hey-api\/schemas/); - expect(stderr).not.toMatch(/@hey-api\/sdk/); - expect(stderr).not.toMatch(/@hey-api\/typescript/); - expect(stderr).toMatch(/useOptions: false/); - }); - - it('handles true booleans', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - '@hey-api/client-fetch', - '--debug', - '--plugins', - '@hey-api/schemas', - '@hey-api/sdk', - '@hey-api/typescript', - '--useOptions', - 'true', - '--dry-run', - 'true', - ]); - const stderr = result.stderr.toString(); - expect(stderr).toMatch(/level: 'debug'/); - expect(stderr).toMatch(/dryRun: true/); - expect(stderr).toMatch(/@hey-api\/schemas/); - expect(stderr).toMatch(/@hey-api\/sdk/); - expect(stderr).toMatch(/@hey-api\/typescript/); - expect(stderr).toMatch(/useOptions: true/); - }); - - it('handles optional booleans', () => { - const result = sync('node', [ - path.resolve( - __dirname, - '..', - '..', - '..', - 'openapi-ts', - 'bin', - 'index.cjs', - ), - '--input', - path.resolve(getSpecsPath(), 'v3.json'), - '--output', - path.resolve(__dirname, 'generated', 'bin'), - '--client', - '@hey-api/client-fetch', - '--debug', - '--plugins', - '@hey-api/schemas', - '@hey-api/sdk', - '@hey-api/typescript', - '--useOptions', - '--dry-run', - 'true', - ]); - const stderr = result.stderr.toString(); - expect(stderr).toMatch(/level: 'debug'/); - expect(stderr).toMatch(/dryRun: true/); - expect(stderr).toMatch(/@hey-api\/schemas/); - expect(stderr).toMatch(/@hey-api\/sdk/); - expect(stderr).toMatch(/@hey-api\/typescript/); - expect(stderr).toMatch(/useOptions: true/); - }); -}); diff --git a/packages/openapi-ts-tests/main/test/cli.test.ts b/packages/openapi-ts-tests/main/test/cli.test.ts new file mode 100755 index 0000000000..6d35d90cb2 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/cli.test.ts @@ -0,0 +1,23 @@ +import path from 'node:path'; + +import { sync } from 'cross-spawn'; +import { describe, expect, it } from 'vitest'; + +import { getSpecsPath } from '../../utils'; + +const specs = getSpecsPath(); + +describe('bin', () => { + it('openapi-ts works', () => { + const result = sync('openapi-ts', [ + '--input', + path.resolve(specs, 'v3.json'), + '--output', + path.resolve(__dirname, '.gen'), + '--dry-run', + 'true', + ]); + expect(result.error).toBeFalsy(); + expect(result.status).toBe(0); + }); +}); diff --git a/packages/openapi-ts/bin/index.cjs b/packages/openapi-ts/bin/index.cjs deleted file mode 100755 index 754de8d751..0000000000 --- a/packages/openapi-ts/bin/index.cjs +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const path = require('path'); - -const { program } = require('commander'); -const pkg = require('../package.json'); - -const params = program - .name(Object.keys(pkg.bin)[0]) - .usage('[options]') - .version(pkg.version) - .option( - '-c, --client ', - '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]', - ) - .option('-d, --debug', 'Set log level to debug') - .option('--dry-run [value]', 'Skip writing files to disk?') - .option( - '-e, --experimental-parser [value]', - 'Opt-in to the experimental parser?', - ) - .option('-f, --file [value]', 'Path to the config file') - .option( - '-i, --input ', - 'OpenAPI specification (path, url, or string content)', - ) - .option('-l, --logs [value]', 'Logs folder') - .option('-o, --output ', 'Output folder') - .option('-p, --plugins [value...]', "List of plugins you'd like to use") - .option( - '--base [value]', - 'DEPRECATED. Manually set base in OpenAPI config instead of inferring from server value', - ) - .option('-s, --silent', 'Set log level to silent') - .option( - '--no-log-file', - 'Disable writing a log file. Works like --silent but without suppressing console output', - ) - .option( - '-w, --watch [value]', - 'Regenerate the client when the input file changes?', - ) - .option('--exportCore [value]', 'DEPRECATED. Write core files to disk') - .option('--name ', 'DEPRECATED. Custom client class name') - .option('--request ', 'DEPRECATED. Path to custom request file') - .option( - '--useOptions [value]', - 'DEPRECATED. Use options instead of arguments?', - ) - .parse(process.argv) - .opts(); - -const stringToBoolean = (value) => { - if (value === 'true') { - return true; - } - if (value === 'false') { - return false; - } - return value; -}; - -const processParams = (obj, booleanKeys) => { - for (const key of booleanKeys) { - const value = obj[key]; - if (typeof value === 'string') { - const parsedValue = stringToBoolean(value); - delete obj[key]; - obj[key] = parsedValue; - } - } - if (obj.file) { - obj.configFile = obj.file; - } - return obj; -}; - -async function start() { - let userConfig; - - try { - const { createClient } = require( - path.resolve(__dirname, '../dist/index.cjs'), - ); - - userConfig = processParams(params, [ - 'dryRun', - 'logFile', - 'experimentalParser', - 'exportCore', - 'useOptions', - ]); - - if (params.plugins === true) { - userConfig.plugins = []; - } else if (params.plugins) { - userConfig.plugins = params.plugins; - } else if (userConfig.client) { - userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; - } - - if (userConfig.client) { - userConfig.plugins.push(userConfig.client); - delete userConfig.client; - } - - userConfig.logs = userConfig.logs - ? { - path: userConfig.logs, - } - : {}; - - if (userConfig.debug || stringToBoolean(process.env.DEBUG)) { - userConfig.logs.level = 'debug'; - } else if (userConfig.silent) { - userConfig.logs.level = 'silent'; - } - - userConfig.logs.file = userConfig.logFile; - delete userConfig.logFile; - - if (typeof params.watch === 'string') { - userConfig.watch = Number.parseInt(params.watch, 10); - } - - if (!Object.keys(userConfig.logs).length) { - delete userConfig.logs; - } - - const context = await createClient(userConfig); - if ( - !context[0] || - !context[0].config || - !context[0].config.input || - !context[0].config.input.some( - (input) => input.watch && input.watch.enabled, - ) - ) { - process.exit(0); - } - } catch { - process.exit(1); - } -} - -start(); diff --git a/packages/openapi-ts/bin/run.cmd b/packages/openapi-ts/bin/run.cmd new file mode 100644 index 0000000000..d6abd96c4f --- /dev/null +++ b/packages/openapi-ts/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off +node "%~dp0\run.js" %* + diff --git a/packages/openapi-ts/bin/run.js b/packages/openapi-ts/bin/run.js new file mode 100755 index 0000000000..0e00c62255 --- /dev/null +++ b/packages/openapi-ts/bin/run.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const target = path.join(__dirname, '..', 'dist', 'run.js'); + +if (!fs.existsSync(target)) { + console.error('openapi-ts not built (expect dist/run.js)'); + process.exit(1); +} + +const res = spawnSync(process.execPath, [target, ...process.argv.slice(2)], { + stdio: 'inherit', +}); +process.exit(res.status ?? 0); diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index f3648aa857..b3ba3c61ea 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -66,7 +66,7 @@ "./package.json": "./package.json" }, "bin": { - "openapi-ts": "bin/index.cjs" + "openapi-ts": "./bin/run.js" }, "files": [ "bin", diff --git a/packages/openapi-ts/src/__tests__/cli.test.ts b/packages/openapi-ts/src/__tests__/cli.test.ts new file mode 100755 index 0000000000..e93d475f19 --- /dev/null +++ b/packages/openapi-ts/src/__tests__/cli.test.ts @@ -0,0 +1,292 @@ +import type { Mock } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { runCli } from '~/cli'; +import { createClient } from '~/index'; + +vi.mock('~/index', () => { + const result: Awaited> = []; + return { + createClient: vi.fn().mockResolvedValue(result), + }; +}); +const spyExit = vi + .spyOn(process, 'exit') + .mockImplementation(() => ({}) as never); + +const spy = createClient as Mock; + +describe('cli', () => { + beforeEach(() => { + spy.mockClear(); + spyExit.mockClear(); + }); + + it('with default options', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [String(process.argv[0]), String(process.argv[1])]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + }, + }); + }); + + it('with minimal options', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--input', + 'foo.json', + '--output', + 'bar', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + input: 'foo.json', + logs: { + file: true, + }, + output: 'bar', + }); + }); + + it('with no plugins', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--plugins', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + }, + plugins: [], + }); + }); + + it('with plugins', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--plugins', + 'foo', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + }, + plugins: ['foo'], + }); + }); + + it('with default plugins', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--client', + 'foo', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + }, + plugins: ['@hey-api/typescript', '@hey-api/sdk', 'foo'], + }); + }); + + describe('logs', () => { + it('debug', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--debug', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + level: 'debug', + }, + }); + }); + + it('silent', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--silent', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + level: 'silent', + }, + }); + }); + + it('no log file', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--no-log-file', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: false, + }, + }); + }); + }); + + it('with all options', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--client', + 'foo', + '--dry-run', + 'true', + '--experimental-parser', + 'true', + '--file', + 'bar', + '--input', + 'baz', + '--logs', + 'qux', + '--output', + 'quux', + '--plugins', + '--watch', + '--useOptions', + '--exportCore', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + configFile: 'bar', + dryRun: true, + experimentalParser: true, + exportCore: true, + input: 'baz', + logs: { + file: true, + path: 'qux', + }, + output: 'quux', + plugins: ['foo'], + useOptions: true, + watch: true, + }); + }); + + it('with false booleans', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [ + String(process.argv[0]), + String(process.argv[1]), + '--useOptions', + 'false', + ]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spy).toHaveBeenCalledWith({ + logs: { + file: true, + }, + useOptions: false, + }); + }); + + it('exits when not in watch mode', async () => { + const originalArgv = process.argv.slice(); + try { + process.argv = [String(process.argv[0]), String(process.argv[1])]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spyExit).toHaveBeenCalledWith(0); + }); + + it('does not exit in watch mode', async () => { + spy.mockResolvedValueOnce([ + { + config: { input: [{ watch: { enabled: true } }] }, + }, + ]); + const originalArgv = process.argv.slice(); + try { + process.argv = [String(process.argv[0]), String(process.argv[1])]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spyExit).not.toHaveBeenCalled(); + }); + + it('exits with error code on error', async () => { + spy.mockRejectedValueOnce('Some error'); + const originalArgv = process.argv.slice(); + try { + process.argv = [String(process.argv[0]), String(process.argv[1])]; + await runCli(); + } finally { + process.argv = originalArgv; + } + expect(spyExit).toHaveBeenCalledWith(1); + }); +}); diff --git a/packages/openapi-ts/src/cli.ts b/packages/openapi-ts/src/cli.ts new file mode 100644 index 0000000000..5dd796e601 --- /dev/null +++ b/packages/openapi-ts/src/cli.ts @@ -0,0 +1,144 @@ +import type { OptionValues } from 'commander'; +import { Command } from 'commander'; + +import { createClient } from '~/index'; + +import pkg from '../package.json' assert { type: 'json' }; + +const stringToBoolean = ( + value: string | undefined, +): boolean | string | undefined => { + if (value === 'true') return true; + if (value === 'false') return false; + return value; +}; + +const processParams = ( + obj: OptionValues, + booleanKeys: ReadonlyArray, +): OptionValues => { + for (const key of booleanKeys) { + const value = obj[key]; + if (typeof value === 'string') { + const parsedValue = stringToBoolean(value); + delete obj[key]; + obj[key] = parsedValue; + } + } + return obj; +}; + +export const runCli = async (): Promise => { + const params = new Command() + .name(Object.keys(pkg.bin)[0]!) + .usage('[options]') + .version(pkg.version) + .option( + '-c, --client ', + '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]', + ) + .option('-d, --debug', 'Set log level to debug') + .option('--dry-run [value]', 'Skip writing files to disk?') + .option( + '-e, --experimental-parser [value]', + 'Opt-in to the experimental parser?', + ) + .option('-f, --file [value]', 'Path to the config file') + .option( + '-i, --input ', + 'OpenAPI specification (path, url, or string content)', + ) + .option('-l, --logs [value]', 'Logs folder') + .option('-o, --output ', 'Output folder') + .option('-p, --plugins [value...]', "List of plugins you'd like to use") + .option( + '--base [value]', + 'DEPRECATED. Manually set base in OpenAPI config instead of inferring from server value', + ) + .option('-s, --silent', 'Set log level to silent') + .option( + '--no-log-file', + 'Disable writing a log file. Works like --silent but without suppressing console output', + ) + .option( + '-w, --watch [value]', + 'Regenerate the client when the input file changes?', + ) + .option('--exportCore [value]', 'DEPRECATED. Write core files to disk') + .option('--name ', 'DEPRECATED. Custom client class name') + .option('--request ', 'DEPRECATED. Path to custom request file') + .option( + '--useOptions [value]', + 'DEPRECATED. Use options instead of arguments?', + ) + .parse(process.argv) + .opts(); + + let userConfig: Record; + + try { + userConfig = processParams(params, [ + 'dryRun', + 'experimentalParser', + 'exportCore', + 'logFile', + 'useOptions', + ]); + + if (userConfig.file) { + userConfig.configFile = userConfig.file; + delete userConfig.file; + } + + if (params.plugins === true) { + userConfig.plugins = []; + } else if (params.plugins) { + userConfig.plugins = params.plugins; + } else if (userConfig.client) { + userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; + } + + if (userConfig.client) { + (userConfig.plugins as Array).push(userConfig.client as string); + delete userConfig.client; + } + + userConfig.logs = userConfig.logs + ? { + path: userConfig.logs, + } + : {}; + + if (userConfig.debug || stringToBoolean(process.env.DEBUG)) { + (userConfig.logs as Record).level = 'debug'; + delete userConfig.debug; + } else if (userConfig.silent) { + (userConfig.logs as Record).level = 'silent'; + delete userConfig.silent; + } + + (userConfig.logs as Record).file = userConfig.logFile; + delete userConfig.logFile; + + if (typeof params.watch === 'string') { + userConfig.watch = Number.parseInt(params.watch, 10); + } + + if (!Object.keys(userConfig.logs as Record).length) { + delete userConfig.logs; + } + + const context = await createClient( + userConfig as unknown as Required>[0], + ); + if ( + !context[0]?.config.input.some( + (input) => input.watch && input.watch.enabled, + ) + ) { + process.exit(0); + } + } catch { + process.exit(1); + } +}; diff --git a/packages/openapi-ts/src/generate.ts b/packages/openapi-ts/src/generate.ts new file mode 100644 index 0000000000..28d48bdc27 --- /dev/null +++ b/packages/openapi-ts/src/generate.ts @@ -0,0 +1,133 @@ +import { checkNodeVersion } from '~/config/engine'; +import type { Configs } from '~/config/init'; +import { initConfigs } from '~/config/init'; +import { getLogs } from '~/config/logs'; +import { createClient as pCreateClient } from '~/createClient'; +import { + ConfigValidationError, + JobError, + logCrashReport, + openGitHubIssueWithCrashReport, + printCrashReport, + shouldReportCrash, +} from '~/error'; +import type { IR } from '~/ir/types'; +import type { Client } from '~/types/client'; +import type { UserConfig } from '~/types/config'; +import type { LazyOrAsync, MaybeArray } from '~/types/utils'; +import { printCliIntro } from '~/utils/cli'; +import { registerHandlebarTemplates } from '~/utils/handlebars'; +import { Logger } from '~/utils/logger'; + +/** + * Generate a client from the provided configuration. + * + * @param userConfig User provided {@link UserConfig} configuration(s). + */ +export const createClient = async ( + userConfig?: LazyOrAsync>, + logger = new Logger(), +): Promise> => { + const resolvedConfig = + typeof userConfig === 'function' ? await userConfig() : userConfig; + const userConfigs = resolvedConfig + ? resolvedConfig instanceof Array + ? resolvedConfig + : [resolvedConfig] + : []; + + let rawLogs = userConfigs.find( + (config) => getLogs(config).level !== 'silent', + )?.logs; + if (typeof rawLogs === 'string') { + rawLogs = getLogs({ logs: rawLogs }); + } + + let configs: Configs | undefined; + + try { + checkNodeVersion(); + + const eventCreateClient = logger.timeEvent('createClient'); + + const eventConfig = logger.timeEvent('config'); + configs = await initConfigs({ logger, userConfigs }); + const printIntro = configs.results.some( + (result) => result.config.logs.level !== 'silent', + ); + if (printIntro) { + printCliIntro(); + } + eventConfig.timeEnd(); + + const allConfigErrors = configs.results.flatMap((result) => + result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), + ); + if (allConfigErrors.length) { + throw new ConfigValidationError(allConfigErrors); + } + + const eventHandlebars = logger.timeEvent('handlebars'); + const templates = registerHandlebarTemplates(); + eventHandlebars.timeEnd(); + + const clients = await Promise.all( + configs.results.map(async (result) => { + try { + return await pCreateClient({ + config: result.config, + dependencies: configs!.dependencies, + jobIndex: result.jobIndex, + logger, + templates, + }); + } catch (error) { + throw new JobError('', { + error, + jobIndex: result.jobIndex, + }); + } + }), + ); + const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< + Client | IR.Context + >; + + eventCreateClient.timeEnd(); + + const printLogs = configs.results.some( + (result) => result.config.logs.level === 'debug', + ); + logger.report(printLogs); + + return result; + } catch (error) { + const results = configs?.results ?? []; + + const logs = + results.find((result) => result.config.logs.level !== 'silent')?.config + .logs ?? + results[0]?.config.logs ?? + rawLogs; + const dryRun = + results.some((result) => result.config.dryRun) ?? + userConfigs.some((config) => config.dryRun) ?? + false; + const logPath = + logs?.file && !dryRun + ? logCrashReport(error, logs.path ?? '') + : undefined; + if (!logs || logs.level !== 'silent') { + printCrashReport({ error, logPath }); + const isInteractive = + results.some((result) => result.config.interactive) ?? + userConfigs.some((config) => config.interactive) ?? + false; + if (await shouldReportCrash({ error, isInteractive })) { + await openGitHubIssueWithCrashReport(error); + } + } + + throw error; + } +}; diff --git a/packages/openapi-ts/src/generate/legacy/output.ts b/packages/openapi-ts/src/generate/legacy/output.ts index 888abd1fac..38d4a56b81 100644 --- a/packages/openapi-ts/src/generate/legacy/output.ts +++ b/packages/openapi-ts/src/generate/legacy/output.ts @@ -65,7 +65,8 @@ export const generateLegacyOutput = async ({ if ( !isLegacyClient(config) && 'bundle' in clientPlugin.config && - clientPlugin.config.bundle + clientPlugin.config.bundle && + !config.dryRun ) { const meta: ProjectRenderMeta = { importFileExtension: config.output.importFileExtension, diff --git a/packages/openapi-ts/src/generate/output.ts b/packages/openapi-ts/src/generate/output.ts index 6022833ebd..658103c867 100644 --- a/packages/openapi-ts/src/generate/output.ts +++ b/packages/openapi-ts/src/generate/output.ts @@ -21,7 +21,11 @@ export const generateOutput = async ({ context }: { context: IR.Context }) => { }; const client = getClientPlugin(context.config); - if ('bundle' in client.config && client.config.bundle) { + if ( + 'bundle' in client.config && + client.config.bundle && + !context.config.dryRun + ) { // not proud of this one // @ts-expect-error context.config._FRAGILE_CLIENT_BUNDLE_RENAMED = generateClientBundle({ diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 953ec5aab4..81cde15a28 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -35,141 +35,12 @@ import colors from 'ansi-colors'; // @ts-expect-error import colorSupport from 'color-support'; -import { checkNodeVersion } from '~/config/engine'; -import type { Configs } from '~/config/init'; -import { initConfigs } from '~/config/init'; -import { getLogs } from '~/config/logs'; -import { createClient as pCreateClient } from '~/createClient'; -import { - ConfigValidationError, - JobError, - logCrashReport, - openGitHubIssueWithCrashReport, - printCrashReport, - shouldReportCrash, -} from '~/error'; -import type { IR } from '~/ir/types'; -import type { Client } from '~/types/client'; import type { UserConfig } from '~/types/config'; import type { LazyOrAsync, MaybeArray } from '~/types/utils'; -import { printCliIntro } from '~/utils/cli'; -import { registerHandlebarTemplates } from '~/utils/handlebars'; -import { Logger } from '~/utils/logger'; colors.enabled = colorSupport().hasBasic; -/** - * Generate a client from the provided configuration. - * - * @param userConfig User provided {@link UserConfig} configuration(s). - */ -export const createClient = async ( - userConfig?: LazyOrAsync>, - logger = new Logger(), -): Promise> => { - const resolvedConfig = - typeof userConfig === 'function' ? await userConfig() : userConfig; - const userConfigs = resolvedConfig - ? resolvedConfig instanceof Array - ? resolvedConfig - : [resolvedConfig] - : []; - - let rawLogs = userConfigs.find( - (config) => getLogs(config).level !== 'silent', - )?.logs; - if (typeof rawLogs === 'string') { - rawLogs = getLogs({ logs: rawLogs }); - } - - let configs: Configs | undefined; - - try { - checkNodeVersion(); - - const eventCreateClient = logger.timeEvent('createClient'); - - const eventConfig = logger.timeEvent('config'); - configs = await initConfigs({ logger, userConfigs }); - const printIntro = configs.results.some( - (result) => result.config.logs.level !== 'silent', - ); - if (printIntro) { - printCliIntro(); - } - eventConfig.timeEnd(); - - const allConfigErrors = configs.results.flatMap((result) => - result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), - ); - if (allConfigErrors.length) { - throw new ConfigValidationError(allConfigErrors); - } - - const eventHandlebars = logger.timeEvent('handlebars'); - const templates = registerHandlebarTemplates(); - eventHandlebars.timeEnd(); - - const clients = await Promise.all( - configs.results.map(async (result) => { - try { - return await pCreateClient({ - config: result.config, - dependencies: configs!.dependencies, - jobIndex: result.jobIndex, - logger, - templates, - }); - } catch (error) { - throw new JobError('', { - error, - jobIndex: result.jobIndex, - }); - } - }), - ); - const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< - Client | IR.Context - >; - - eventCreateClient.timeEnd(); - - const printLogs = configs.results.some( - (result) => result.config.logs.level === 'debug', - ); - logger.report(printLogs); - - return result; - } catch (error) { - const results = configs?.results ?? []; - - const logs = - results.find((result) => result.config.logs.level !== 'silent')?.config - .logs ?? - results[0]?.config.logs ?? - rawLogs; - const dryRun = - results.some((result) => result.config.dryRun) ?? - userConfigs.some((config) => config.dryRun) ?? - false; - const logPath = - logs?.file && !dryRun - ? logCrashReport(error, logs.path ?? '') - : undefined; - if (!logs || logs.level !== 'silent') { - printCrashReport({ error, logPath }); - const isInteractive = - results.some((result) => result.config.interactive) ?? - userConfigs.some((config) => config.interactive) ?? - false; - if (await shouldReportCrash({ error, isInteractive })) { - await openGitHubIssueWithCrashReport(error); - } - } - - throw error; - } -}; +export { createClient } from './generate'; /** * Type helper for openapi-ts.config.ts, returns {@link MaybeArray} object(s) diff --git a/packages/openapi-ts/src/ir/context.ts b/packages/openapi-ts/src/ir/context.ts index e2e9a140ba..ea55c5f0d0 100644 --- a/packages/openapi-ts/src/ir/context.ts +++ b/packages/openapi-ts/src/ir/context.ts @@ -14,7 +14,7 @@ import { resolveRef } from '~/utils/ref'; import type { IR } from './types'; -export class IRContext = any> { +export class Context = any> { /** * Configuration for parsing and generating the output. This * is a mix of user-provided and default values. diff --git a/packages/openapi-ts/src/ir/types.d.ts b/packages/openapi-ts/src/ir/types.d.ts index af521f54d9..2a4416d987 100644 --- a/packages/openapi-ts/src/ir/types.d.ts +++ b/packages/openapi-ts/src/ir/types.d.ts @@ -4,7 +4,7 @@ import type { ServerObject, } from '~/openApi/3.1.x/types/spec'; -import type { IRContext } from './context'; +import type { Context as IRContext } from './context'; import type { IRMediaType } from './mediaType'; interface IRBodyObject { diff --git a/packages/openapi-ts/src/openApi/index.ts b/packages/openapi-ts/src/openApi/index.ts index f27cb4de2c..7b44ff7934 100644 --- a/packages/openapi-ts/src/openApi/index.ts +++ b/packages/openapi-ts/src/openApi/index.ts @@ -1,5 +1,5 @@ import { satisfies } from '~/config/utils/package'; -import { IRContext } from '~/ir/context'; +import { Context } from '~/ir/context'; import type { IR } from '~/ir/types'; import { parseV2_0_X } from '~/openApi/2.0.x'; import { parseV3_0_X } from '~/openApi/3.0.x'; @@ -74,7 +74,7 @@ export const parseOpenApiSpec = ({ logger: Logger; spec: unknown; }): IR.Context | undefined => { - const context = new IRContext({ + const context = new Context({ config, dependencies, logger, diff --git a/packages/openapi-ts/src/openApi/shared/utils/graph.ts b/packages/openapi-ts/src/openApi/shared/utils/graph.ts index 15c379a84d..d82efbf2b2 100644 --- a/packages/openapi-ts/src/openApi/shared/utils/graph.ts +++ b/packages/openapi-ts/src/openApi/shared/utils/graph.ts @@ -503,10 +503,10 @@ export const buildGraph = ( eventBuildGraph.timeEnd(); - // functions creating data for debug scripts located in `debug-helpers/` + // functions creating data for debug scripts located in `dev/` // const { maxChildren, maxDepth, totalNodes } = debugTools.graph.analyzeStructure(graph); // const nodesForViz = debugTools.graph.exportForVisualization(graph); - // fs.writeFileSync('debug-helpers/graph.json', JSON.stringify(nodesForViz, null, 2)); + // fs.writeFileSync('dev/graph.json', JSON.stringify(nodesForViz, null, 2)); return { graph }; }; diff --git a/packages/openapi-ts/src/run.ts b/packages/openapi-ts/src/run.ts new file mode 100644 index 0000000000..addde85a1a --- /dev/null +++ b/packages/openapi-ts/src/run.ts @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { runCli } from '~/cli'; + +runCli(); diff --git a/packages/openapi-ts/tsdown.config.ts b/packages/openapi-ts/tsdown.config.ts index 9ba7d7bd58..7fe486c528 100644 --- a/packages/openapi-ts/tsdown.config.ts +++ b/packages/openapi-ts/tsdown.config.ts @@ -29,7 +29,7 @@ export default defineConfig((options) => ({ }, clean: true, dts: true, - entry: ['src/index.ts', 'src/internal.ts'], + entry: ['./src/{index,internal,run}.ts'], format: ['cjs', 'esm'], minify: !options.watch, onSuccess: async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b50ff0d18..a012ad70be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,9 @@ importers: '@hey-api/custom-client': specifier: workspace:* version: link:packages/custom-client + '@hey-api/openapi-ts': + specifier: workspace:* + version: link:packages/openapi-ts '@types/node': specifier: 22.10.5 version: 22.10.5 @@ -88,6 +91,9 @@ importers: rollup-plugin-dts: specifier: 6.1.1 version: 6.1.1(rollup@4.31.0)(typescript@5.9.3) + ts-node: + specifier: 10.9.2 + version: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) tsdown: specifier: 0.15.8 version: 0.15.8(@arethetypeswrong/core@0.18.2)(typescript@5.9.3) @@ -170,7 +176,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(3d70bfaaf57777265a4e229c41aeb4c6) + version: 19.2.0(a969acb6c0970aa981df1cb7672d7c2e) '@angular/cli': specifier: 19.2.0 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) @@ -264,7 +270,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(a969acb6c0970aa981df1cb7672d7c2e) + version: 19.2.0(3d70bfaaf57777265a4e229c41aeb4c6) '@angular/cli': specifier: 19.2.0 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) @@ -23183,7 +23189,7 @@ snapshots: eslint: 9.17.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 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-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)) + 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)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.17.0(jiti@2.6.1)) @@ -23221,7 +23227,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - 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)) + 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)) transitivePeerDependencies: - supports-color @@ -23236,7 +23242,7 @@ snapshots: transitivePeerDependencies: - supports-color - 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)): + 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)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f48bf2a714..02d09d6f45 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,12 +3,5 @@ packages: - examples/**/* - packages/**/* -onlyBuiltDependencies: - - esbuild - - lmdb - - msgpackr-extract - - sharp - - unrs-resolver - patchedDependencies: vitepress: patches/vitepress.patch diff --git a/scripts/examples-generate.sh b/scripts/examples-generate.sh index 55cda530e8..fb70327372 100755 --- a/scripts/examples-generate.sh +++ b/scripts/examples-generate.sh @@ -10,6 +10,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" echo "⏳ Generating client code for all examples..." + # Find all examples with openapi-ts script and generate code in parallel # Concurrency control: adjust this number depending on CI machine resources CONCURRENCY=${CONCURRENCY:-4} @@ -44,8 +45,8 @@ for dir in "$ROOT_DIR"/examples/*/; do echo "Generating: $example_name" set -e cd "$dir" - echo "-> Running pnpm openapi-ts" - pnpm openapi-ts + echo "-> Running openapi-ts" + pnpm run openapi-ts # Format generated files in this example only to keep the step fast if command -v pnpm >/dev/null 2>&1 && pnpm -w -s --version >/dev/null 2>&1; then @@ -76,8 +77,16 @@ for pid in $PIDS; do echo "✅ $name succeeded" else name=$(cat "$tmpdir/$pid.name" 2>/dev/null || echo "$pid") - log=$(cat "$tmpdir/$pid.log" 2>/dev/null || echo "(no log)") - echo "❌ $name failed — see log: $tmpdir/$pid.log" + # Read the metadata file which contains the path to the real log + logpath=$(cat "$tmpdir/$pid.log" 2>/dev/null || echo "") + if [ -n "$logpath" ] && [ -f "$logpath" ]; then + echo "❌ $name failed — showing full log ($logpath):" + echo "---- full log start ----" + cat "$logpath" || true + echo "---- full log end ----" + else + echo "❌ $name failed — no log found (metadata: $tmpdir/$pid.log)" + fi failed=1 fi done diff --git a/scripts/publish-preview-packages.sh b/scripts/publish-preview-packages.sh index da35bc365f..099deb36ac 100755 --- a/scripts/publish-preview-packages.sh +++ b/scripts/publish-preview-packages.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash result=$(pnpx turbo run build --affected --dry-run=json)