kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: improve openapi-to-ts cli and docs
rodzic
125088dcd5
commit
f04739e098
|
@ -13,7 +13,7 @@
|
|||
"@agentic/weather": "workspace:*",
|
||||
"@ai-sdk/openai": "catalog:",
|
||||
"ai": "catalog:",
|
||||
"exit-hook": "^4.0.0",
|
||||
"exit-hook": "catalog:",
|
||||
"openai": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/* eslint-disable no-template-curly-in-string */
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { assert } from '@agentic/core'
|
||||
|
||||
import { generateTSFromOpenAPI } from '../src'
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// TODO: Add proper CLI handling
|
||||
async function main() {
|
||||
const pathToOpenApiSpec =
|
||||
process.argv[2] ??
|
||||
path.join(dirname, '..', 'fixtures', 'openapi', '3.0', 'notion.json')
|
||||
assert(pathToOpenApiSpec, 'Missing path to OpenAPI spec')
|
||||
|
||||
await generateTSFromOpenAPI(pathToOpenApiSpec)
|
||||
}
|
||||
|
||||
await main()
|
|
@ -0,0 +1,72 @@
|
|||
import { cli } from 'cleye'
|
||||
import { gracefulExit } from 'exit-hook'
|
||||
|
||||
import { generateTSFromOpenAPI } from '../src'
|
||||
|
||||
async function main() {
|
||||
const args = cli(
|
||||
{
|
||||
name: 'openapi-to-ts',
|
||||
parameters: ['<openapi file path>'],
|
||||
flags: {
|
||||
debug: {
|
||||
type: Boolean,
|
||||
description: 'Enables verbose debug logging',
|
||||
alias: 'v',
|
||||
default: false
|
||||
},
|
||||
outputDir: {
|
||||
type: String,
|
||||
description: 'Path to the output directory (defaults to cwd)',
|
||||
alias: 'o'
|
||||
},
|
||||
dryRun: {
|
||||
type: Boolean,
|
||||
description: 'Disables all side effects',
|
||||
default: false
|
||||
},
|
||||
noPrettier: {
|
||||
type: Boolean,
|
||||
description: 'Disables prettier formatting',
|
||||
default: false
|
||||
},
|
||||
noEslint: {
|
||||
type: Boolean,
|
||||
description: 'Disables eslint formatting',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
process.argv
|
||||
)
|
||||
|
||||
const openapiFilePath = args._[2]!
|
||||
|
||||
if (!openapiFilePath) {
|
||||
console.error('Missing required argument: <openapi file path>\n')
|
||||
args.showHelp()
|
||||
gracefulExit(1)
|
||||
return
|
||||
}
|
||||
|
||||
const output = await generateTSFromOpenAPI({
|
||||
openapiFilePath,
|
||||
outputDir: args.flags.outputDir || process.cwd(),
|
||||
dryRun: args.flags.dryRun,
|
||||
prettier: !args.flags.noPrettier,
|
||||
eslint: !args.flags.noEslint
|
||||
})
|
||||
|
||||
if (args.flags.dryRun) {
|
||||
console.log(output)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
gracefulExit(0)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
gracefulExit(1)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@agentic/openapi-to-ts",
|
||||
"version": "0.1.0",
|
||||
"description": "TODO",
|
||||
"description": "Generate an Agentic TypeScript client from an OpenAPI spec.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
@ -13,7 +13,7 @@
|
|||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"bin": {
|
||||
"openapi-to-ts": "./dist/generate-from-openapi.js"
|
||||
"openapi-to-ts": "./dist/openapi-to-ts.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
@ -30,15 +30,16 @@
|
|||
"@agentic/core": "workspace:*",
|
||||
"@apidevtools/swagger-parser": "^10.1.1",
|
||||
"camelcase": "^8.0.0",
|
||||
"cleye": "catalog:",
|
||||
"decamelize": "^6.0.0",
|
||||
"execa": "^9.5.2",
|
||||
"exit-hook": "catalog:",
|
||||
"json-schema-to-zod": "^2.6.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>TODO.</em>
|
||||
<em>Generate an Agentic TypeScript client from an OpenAPI spec.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
@ -19,15 +19,36 @@
|
|||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## Example Usage
|
||||
|
||||
```sh
|
||||
# local development
|
||||
tsx bin/openapi-to-ts.ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
|
||||
# published version
|
||||
npx @agentic/openapi-to-ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
|
||||
# npm install -g version
|
||||
npm install -g @agentic/openapi-to-ts
|
||||
openapi-to-ts fixtures/openapi/3.0/notion.json -o fixtures/generated
|
||||
```
|
||||
|
||||
Most OpenAPI specs should be supported, but I've made no attempt to maximize robustness. This tool is meant to generate Agentic TS clients which are 99% of the way there, but some tweaking may be necessary post-generation.
|
||||
|
||||
Some things you may want to tweak:
|
||||
|
||||
- simplifying the zod parameters accepted by `@aiFunction` input schemas since LLMs tend to do better with a few, key parameters
|
||||
- removing unused API endpoints & associated types that you don't want to expose to LLMs
|
||||
|
||||
## TODO
|
||||
|
||||
- convert to https://github.com/readmeio/oas
|
||||
- convert openapi parsing & utils to https://github.com/readmeio/oas
|
||||
- support filters
|
||||
- // match only the schema named `foo` and `GET` operation for the `/api/v1/foo` path
|
||||
- include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$',
|
||||
- [ ] Convert HTML in descriptions to markdown.
|
||||
- [ ] Properly format multiline function comments.
|
||||
- [ ] Debug stripe schema issue.
|
||||
- [ ] Convert HTML in descriptions to markdown
|
||||
- [ ] Properly format multiline function comments
|
||||
- [ ] Debug stripe schema issue
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable no-template-curly-in-string */
|
||||
import * as fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import type { IJsonSchema, OpenAPIV3 } from 'openapi-types'
|
||||
import { assert } from '@agentic/core'
|
||||
|
@ -16,7 +15,6 @@ import {
|
|||
dereferenceFull,
|
||||
getAndResolve,
|
||||
getComponentDisplayName,
|
||||
getComponentName,
|
||||
getDescription,
|
||||
getOperationParamsName,
|
||||
getOperationResponseName,
|
||||
|
@ -26,7 +24,6 @@ import {
|
|||
prettify
|
||||
} from './utils'
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const jsonContentType = 'application/json'
|
||||
const multipartFormData = 'multipart/form-data'
|
||||
|
||||
|
@ -41,7 +38,21 @@ const httpMethods = [
|
|||
'trace'
|
||||
] as const
|
||||
|
||||
export async function generateTSFromOpenAPI(openapiFilePath: string) {
|
||||
export type GenerateTSFromOpenAPIOptions = {
|
||||
openapiFilePath: string
|
||||
outputDir: string
|
||||
dryRun?: boolean
|
||||
prettier?: boolean
|
||||
eslint?: boolean
|
||||
}
|
||||
|
||||
export async function generateTSFromOpenAPI({
|
||||
openapiFilePath,
|
||||
outputDir,
|
||||
dryRun = false,
|
||||
prettier = true,
|
||||
eslint = true
|
||||
}: GenerateTSFromOpenAPIOptions): Promise<string> {
|
||||
const parser = new SwaggerParser()
|
||||
const spec = (await parser.bundle(openapiFilePath)) as OpenAPIV3.Document
|
||||
// | OpenAPIV3_1.Document
|
||||
|
@ -68,12 +79,8 @@ export async function generateTSFromOpenAPI(openapiFilePath: string) {
|
|||
const nameUpperCase = nameSnakeCase.toUpperCase()
|
||||
const clientName = `${name}Client`
|
||||
const namespaceName = nameLowerCase
|
||||
// const destFolder = path.join('packages', nameKebabCase)
|
||||
// const destFolderSrc = path.join(destFolder, 'src')
|
||||
|
||||
const destFolder = path.join(dirname, '..', 'fixtures', 'generated')
|
||||
const destFileClient = path.join(destFolder, `${nameKebabCase}-client.ts`)
|
||||
|
||||
const destFileClient = path.join(outputDir, `${nameKebabCase}-client.ts`)
|
||||
const apiBaseUrl = spec.servers?.[0]?.url
|
||||
|
||||
const securitySchemes = spec.components?.securitySchemes
|
||||
|
@ -543,14 +550,14 @@ export async function generateTSFromOpenAPI(openapiFilePath: string) {
|
|||
componentSchemas[type] = schema
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\ncomponents',
|
||||
JSON.stringify(
|
||||
sortedComponents.map((ref) => getComponentName(ref)),
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
// console.log(
|
||||
// '\ncomponents',
|
||||
// JSON.stringify(
|
||||
// sortedComponents.map((ref) => getComponentName(ref)),
|
||||
// null,
|
||||
// 2
|
||||
// )
|
||||
// )
|
||||
|
||||
// console.log(
|
||||
// '\nmodels',
|
||||
|
@ -603,9 +610,10 @@ import { z } from 'zod'`.trim()
|
|||
.join('\n\n')
|
||||
|
||||
const description = getDescription(spec.info?.description)
|
||||
const prettifyImpl = prettier ? prettify : (code: string) => code
|
||||
|
||||
const output = (
|
||||
await prettify(
|
||||
await prettifyImpl(
|
||||
[
|
||||
outputTypes,
|
||||
`
|
||||
|
@ -659,8 +667,16 @@ export class ${clientName} extends AIFunctionsProvider {
|
|||
.replaceAll(/z\s*\.object\({}\)\s*\.merge\(([^)]*)\)/gm, '$1')
|
||||
.replaceAll(/\/\*\*(\S.*\S)\*\//g, '/** $1 */')
|
||||
|
||||
console.log(output)
|
||||
await fs.mkdir(destFolder, { recursive: true })
|
||||
if (dryRun) {
|
||||
return output
|
||||
}
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true })
|
||||
await fs.writeFile(destFileClient, output)
|
||||
await execa('npx', ['eslint', '--fix', '--no-ignore', destFileClient])
|
||||
|
||||
if (eslint) {
|
||||
await execa('npx', ['eslint', '--fix', '--no-ignore', destFileClient])
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/**
|
||||
* This file is forked from: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-jsonschema-parameters
|
||||
*
|
||||
* Several fixes have been applied.
|
||||
*
|
||||
* The original code is licensed under the MIT license.
|
||||
*/
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export default defineConfig([
|
|||
dts: true
|
||||
},
|
||||
{
|
||||
entry: ['bin/generate-from-openapi.ts'],
|
||||
entry: ['bin/openapi-to-ts.ts'],
|
||||
outDir: 'dist',
|
||||
target: 'node18',
|
||||
platform: 'node',
|
||||
|
|
|
@ -39,12 +39,18 @@ catalogs:
|
|||
ai:
|
||||
specifier: ^4.1.61
|
||||
version: 4.1.61
|
||||
cleye:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
dedent:
|
||||
specifier: ^1.5.3
|
||||
version: 1.5.3
|
||||
delay:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
exit-hook:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
fast-xml-parser:
|
||||
specifier: ^5.0.9
|
||||
version: 5.0.9
|
||||
|
@ -189,7 +195,7 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 4.1.61(react@18.3.1)(zod@3.24.2)
|
||||
exit-hook:
|
||||
specifier: ^4.0.0
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.0
|
||||
openai:
|
||||
specifier: 'catalog:'
|
||||
|
@ -891,12 +897,18 @@ importers:
|
|||
camelcase:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
cleye:
|
||||
specifier: 'catalog:'
|
||||
version: 1.3.4
|
||||
decamelize:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
execa:
|
||||
specifier: ^9.5.2
|
||||
version: 9.5.2
|
||||
exit-hook:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.0
|
||||
json-schema-to-zod:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0
|
||||
|
@ -910,9 +922,6 @@ importers:
|
|||
'@agentic/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
ky:
|
||||
specifier: 'catalog:'
|
||||
version: 1.7.5
|
||||
|
||||
packages/people-data-labs:
|
||||
dependencies:
|
||||
|
@ -3873,6 +3882,9 @@ packages:
|
|||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
cleye@1.3.4:
|
||||
resolution: {integrity: sha512-Rd6M8ecBDtdYdPR22h6gG37lPqqJ3hSOaplaGwuGYey9xKmEElOvTgupqfyLSlISshroRpVhYjDtW3vwNUNBaQ==}
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
||||
engines: {node: '>=18'}
|
||||
|
@ -6494,6 +6506,9 @@ packages:
|
|||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
terminal-columns@1.4.1:
|
||||
resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==}
|
||||
|
||||
text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
|
||||
|
@ -6671,6 +6686,9 @@ packages:
|
|||
resolution: {integrity: sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
type-flag@3.0.0:
|
||||
resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -10214,6 +10232,11 @@ snapshots:
|
|||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
|
||||
cleye@1.3.4:
|
||||
dependencies:
|
||||
terminal-columns: 1.4.1
|
||||
type-flag: 3.0.0
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
dependencies:
|
||||
restore-cursor: 5.1.0
|
||||
|
@ -13109,6 +13132,8 @@ snapshots:
|
|||
mkdirp: 1.0.4
|
||||
yallist: 4.0.0
|
||||
|
||||
terminal-columns@1.4.1: {}
|
||||
|
||||
text-table@0.2.0: {}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
|
@ -13266,6 +13291,8 @@ snapshots:
|
|||
|
||||
type-fest@4.37.0: {}
|
||||
|
||||
type-flag@3.0.0: {}
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
|
|
|
@ -7,8 +7,10 @@ catalog:
|
|||
zod: ^3.24.2
|
||||
zod-validation-error: ^3.4.0
|
||||
openai-zod-to-json-schema: ^1.0.3
|
||||
cleye: ^1.3.4
|
||||
dedent: ^1.5.3
|
||||
delay: ^6.0.0
|
||||
exit-hook: ^4.0.0
|
||||
jsonrepair: ^3.9.0
|
||||
jsrsasign: ^10.9.0
|
||||
mathjs: ^13.0.3
|
||||
|
|
Ładowanie…
Reference in New Issue