├── .commitlintrc ├── .editorconfig ├── .eslintrc ├── .github ├── dependabot.yml └── workflows │ ├── check-linked-issues.yml │ ├── ci.yml │ ├── notify-release.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .nvmrc ├── .prettierrc ├── README.md ├── example ├── json-schema-to-typescript-config.json ├── openapi.yml └── output │ ├── json │ └── components.schemas │ │ ├── Address.json │ │ ├── ApiResponse.json │ │ ├── Category.json │ │ ├── Customer.json │ │ ├── DateExample.json │ │ ├── FooBARBaz.json │ │ ├── Order.json │ │ ├── Pet.json │ │ ├── Tag.json │ │ └── User.json │ ├── ts │ ├── Address.ts │ ├── ApiResponse.ts │ ├── Category.ts │ ├── Customer.ts │ ├── DateExample.ts │ ├── FooBARBaz.ts │ ├── Order.ts │ ├── Pet.ts │ ├── Tag.ts │ └── User.ts │ └── types │ ├── Address.d.ts │ ├── ApiResponse.d.ts │ ├── Category.d.ts │ ├── Customer.d.ts │ ├── DateExample.d.ts │ ├── FooBARBaz.d.ts │ ├── Order.d.ts │ ├── Pet.d.ts │ ├── Tag.d.ts │ └── User.d.ts ├── index.ts ├── package-lock.json ├── package.json ├── scripts └── generate-toc.mjs ├── src ├── cli.ts ├── commands │ ├── json2ts.ts │ ├── oas2json.ts │ ├── oas2ts.ts │ └── oas2tson.ts ├── types │ ├── Json2TsOptions.d.ts │ ├── Oas2Tson.d.ts │ └── SchemasMetaData.d.ts └── utils │ ├── do-not-edit-text.ts │ ├── openapi-schema-to-json-schema-wrapper.ts │ ├── paths.ts │ └── read-config-file.ts ├── test ├── fixtures │ ├── openapi.yml │ └── schemas │ │ ├── Address.json │ │ ├── ApiResponse.json │ │ ├── Category.json │ │ ├── Customer.json │ │ ├── DateExample.json │ │ ├── Order.json │ │ ├── Pet.json │ │ ├── Tag.json │ │ └── User.json ├── json2ts.test.ts ├── oas2json.test.ts ├── oas2ts.test.ts └── oas2tson.test.ts ├── tsconfig.json └── tsup.config.ts /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:prettier/recommended" 11 | ], 12 | "ignorePatterns": ["test/temp/**/*", "example/**/*", "dist/**/*"], 13 | "env": { 14 | "node": true, 15 | "es2021": true 16 | }, 17 | "parserOptions": { 18 | "ecmaVersion": 2021, 19 | "sourceType": "module" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/check-linked-issues.yml: -------------------------------------------------------------------------------- 1 | name: Check linked issues 2 | 'on': 3 | pull_request_target: 4 | types: 5 | - opened 6 | - edited 7 | - reopened 8 | - synchronize 9 | jobs: 10 | check_pull_requests: 11 | runs-on: ubuntu-latest 12 | name: Check linked issues 13 | permissions: 14 | issues: read 15 | pull-requests: write 16 | steps: 17 | - uses: nearform-actions/github-action-check-linked-issues@v1 18 | id: check-linked-issues 19 | with: 20 | exclude-branches: release/**, dependabot/** 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Lint and test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version-file: '.nvmrc' 18 | - run: | 19 | npm ci 20 | npm run lint 21 | npm test 22 | npm run build 23 | 24 | automerge: 25 | name: Merge dependabot's PRs 26 | needs: test 27 | runs-on: ubuntu-latest 28 | permissions: 29 | pull-requests: write 30 | contents: write 31 | steps: 32 | - uses: fastify/github-action-merge-dependabot@v3 33 | -------------------------------------------------------------------------------- /.github/workflows/notify-release.yml: -------------------------------------------------------------------------------- 1 | name: Notify release 2 | 'on': 3 | workflow_dispatch: 4 | schedule: 5 | - cron: 30 8 * * * 6 | release: 7 | types: 8 | - published 9 | issues: 10 | types: 11 | - closed 12 | jobs: 13 | setup: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | issues: write 17 | contents: read 18 | steps: 19 | - uses: nearform-actions/github-action-notify-release@v1 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | semver: 7 | description: The semver to use 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | pull_request: 16 | types: [closed] 17 | 18 | jobs: 19 | release: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | issues: write 24 | pull-requests: write 25 | id-token: write 26 | steps: 27 | - uses: nearform-actions/optic-release-automation-action@v4 28 | with: 29 | semver: ${{ github.event.inputs.semver }} 30 | commit-message: 'chore: release {version}' 31 | build-command: npm ci && npm run build 32 | npm-token: ${{ secrets[format('NPM_TOKEN_{0}', github.actor)] || secrets.NPM_TOKEN }} 33 | optic-token: ${{ secrets[format('OPTIC_TOKEN_{0}', github.actor)] || secrets.OPTIC_TOKEN }} 34 | provenance: true 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .eslintcache 3 | 4 | # JetBrains IDEs 5 | .idea 6 | # Visual Studio Code 7 | .vscode 8 | 9 | # OS X 10 | .DS_Store 11 | 12 | # tests 13 | test/temp 14 | .nyc_output 15 | .tap 16 | 17 | # build 18 | dist 19 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | 2 | npm run generate-toc 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Transformer Toolkit 2 | 3 | Effortlessly automate your API design-first development workflow by generating [JSON schemas](https://json-schema.org/) and [TypeScript types](https://www.typescriptlang.org/) from an [OpenAPI specification](https://spec.openapis.org/oas/v3.1.0). 4 | 5 | ## Table of Contents 6 | 7 | * [Installation](#installation) 8 | * [CLI](#cli) 9 | * [Create JSON Schema From OpenAPI Definitions](#create-json-schema-from-openapi-definitions) 10 | * [Usage](#usage) 11 | * [Example](#example) 12 | * [Options](#options) 13 | * [Generate TypeScript types from OpenAPI Defintions](#generate-typescript-types-from-openapi-defintions) 14 | * [Usage](#usage-1) 15 | * [Example](#example-1) 16 | * [Options](#options-1) 17 | * [Generate TypeScript types from JSON schemas](#generate-typescript-types-from-json-schemas) 18 | * [Usage](#usage-2) 19 | * [Example](#example-2) 20 | * [Options](#options-2) 21 | * [Create TypeScript JSON Schema From OpenAPI Definitions](#create-typescript-json-schema-from-openapi-definitions) 22 | * [Usage](#usage-3) 23 | * [Example](#example-3) 24 | * [Options](#options-3) 25 | * [Programmatic Usage](#programmatic-usage) 26 | * [Generate JSON Schemas from OpenAPI](#generate-json-schemas-from-openapi) 27 | * [Generate TypeScript Types from OpenAPI](#generate-typescript-types-from-openapi) 28 | * [Generate TypeScript Types from JSON Schemas](#generate-typescript-types-from-json-schemas-1) 29 | * [Generate TypeScript exported JSON Schemas from OpenAPI](#generate-typescript-exported-json-schemas-from-openapi) 30 | * [Example](#example-4) 31 | * [Additional Configuration](#additional-configuration) 32 | 33 | ## Installation 34 | 35 | You can install the package with npm (or another package manager): 36 | 37 | ```sh 38 | $ npm install openapi-transformer-toolkit 39 | ``` 40 | 41 | If you want to install it globally, you can provide the `-g` flag. 42 | 43 | Alternatively, you can run the CLI using `npx`: 44 | 45 | ```sh 46 | $ npx openapi-transformer-toolkit [command] [options] 47 | ``` 48 | 49 | ## CLI 50 | 51 | For easier usage, the package includes the `openapi-transformer-toolkit` executable you can use from your CLI. 52 | 53 |
54 | 55 | 56 | ### Create JSON Schema From OpenAPI Definitions 57 | 58 | 59 | 60 | Using the `oas2json` command you can create JSON schema records from OpenAPI definitions. 61 | 62 | #### Usage 63 | 64 | ```sh 65 | openapi-transformer-toolkit oas2json [options] 66 | ``` 67 | 68 | #### Example 69 | 70 | ```sh 71 | $ openapi-transformer-toolkit oas2json -i ./openapi.yml -o ./schemas -p paths 72 | ``` 73 | 74 | #### Options 75 | 76 | ``` 77 | -i, --input Specify the path to the OpenAPI file 78 | -o, --output Specify the path to the folder where you wish to output the schemas 79 | -p, --properties Specify the properties/definitions in the OpenAPI file to convert in a comma-separated list (optional) 80 | -h, --help Display help for command 81 | ``` 82 | 83 |
84 | 85 |
86 | 87 | 88 | ### Generate TypeScript types from OpenAPI Defintions 89 | 90 | 91 | 92 | Using the `oas2ts` command you can create TypeScript types from your OpenAPI definitions. 93 | 94 | #### Usage 95 | 96 | ```sh 97 | openapi-transformer-toolkit oas2ts [options] 98 | ``` 99 | 100 | #### Example 101 | 102 | ```sh 103 | $ openapi-transformer-toolkit oas2ts -i ./openapi.yml -o ./types 104 | ``` 105 | 106 | ```sh 107 | $ openapi-transformer-toolkit oas2ts -i ./openapi.yml -o ./types -c ./config.json 108 | ``` 109 | 110 | #### Options 111 | 112 | ``` 113 | -i, --input Path to the OpenAPI file 114 | -o, --output Path to the folder where to output the TypeScript types 115 | -c, --config Path to the JSON/JS config file 116 | -h, --help Display help for command 117 | ``` 118 | 119 | See [Additional Configuration](#additional-configuration) for the `-c, --config` option. 120 | 121 |
122 | 123 |
124 | 125 | 126 | ### Generate TypeScript types from JSON schemas 127 | 128 | 129 | 130 | Using the `json2ts` command you can create TypeScript types from your JSON Schema definitions. 131 | 132 | #### Usage 133 | 134 | ```sh 135 | openapi-transformer-toolkit json2ts [options] 136 | ``` 137 | 138 | #### Example 139 | 140 | ```sh 141 | $ openapi-transformer-toolkit json2ts -i ./schemas -o ./types 142 | ``` 143 | 144 | ```sh 145 | $ openapi-transformer-toolkit json2ts -i ./schemas -o ./types -c ./config.json 146 | ``` 147 | 148 | #### Options 149 | 150 | ``` 151 | -i, --input Path to the JSON schemas folder 152 | -o, --output Path to the folder where to output the TS files 153 | -c, --config Path to the JSON/JS config file 154 | -h, --help Display help for command 155 | ``` 156 | 157 | See [Additional Configuration](#additional-configuration) for the `-c, --config` option. 158 | 159 |
160 | 161 |
162 | 163 | 164 | ### Create TypeScript JSON Schema From OpenAPI Definitions 165 | 166 | 167 | 168 | Using the `oas2tson` command you can create Typescript exported JSON schema records from OpenAPI definitions. 169 | 170 | #### Usage 171 | 172 | ```sh 173 | openapi-transformer-toolkit oas2tson [options] 174 | ``` 175 | 176 | #### Example 177 | 178 | ```sh 179 | $ openapi-transformer-toolkit oas2tson -i ./openapi.yml -o ./schemas -p paths 180 | ``` 181 | 182 | #### Options 183 | 184 | ``` 185 | -i, --input Specify the path to the OpenAPI file 186 | -o, --output Specify the path to the folder where you wish to output the schemas 187 | -p, --properties Specify the properties/definitions in the OpenAPI file to convert in a comma-separated list (optional) 188 | -h, --help Display help for command 189 | ``` 190 | 191 |
192 | 193 | ## Programmatic Usage 194 | 195 | You can also use the package programmatically by importing the necessary functions: 196 | 197 | ```javascript 198 | import { oas2json, oas2ts, json2ts, oas2tson } from 'openapi-transformer-toolkit' 199 | ``` 200 | 201 | ### Generate JSON Schemas from OpenAPI 202 | 203 | To generate JSON schemas from your OpenAPI specification, provide the path to the OpenAPI file and the output directory for the generated schemas: 204 | 205 | ```javascript 206 | const openAPIPath = 'path/to/openapi.yml' 207 | const schemasPath = 'path/to/output/schemas' 208 | const propertiesToConvert = 'paths' 209 | 210 | oas2json(openAPIPath, schemasPath, propertiesToConvert) 211 | ``` 212 | 213 | ### Generate TypeScript Types from OpenAPI 214 | 215 | To generate TypeScript types from the OpenAPI specification, provide the path to the OpenAPI file and the output directory for the TypeScript types. Optionally, the third parameter can contain [configuration options](#additional-configuration) 216 | 217 | ```javascript 218 | const openAPIPath = 'path/to/openapi.yml' 219 | const tsTypesPath = 'path/to/output/types' 220 | // 221 | const options = { 222 | bannerComment: 'Custom banner content' 223 | } 224 | 225 | await oas2ts(openAPIPath, tsTypesPath, options) 226 | ``` 227 | 228 | ### Generate TypeScript Types from JSON Schemas 229 | 230 | To generate TypeScript types from the generated JSON schemas, provide the path to the JSON schema directory and the output directory for the TypeScript types. Optionally, the third parameter can contain [configuration options](#additional-configuration) 231 | 232 | ```javascript 233 | const schemasPath = 'path/to/output/schemas' 234 | const tsTypesPath = 'path/to/output/types' 235 | 236 | await json2ts(schemasPath, tsTypesPath) 237 | ``` 238 | 239 | ### Generate TypeScript exported JSON Schemas from OpenAPI 240 | 241 | To generate TypeScript exported JSON schemas from your OpenAPI specification, provide the path to the OpenAPI file and the output directory for the generated schemas: 242 | 243 | ```javascript 244 | const openAPIPath = 'path/to/openapi.yml' 245 | const schemasPath = 'path/to/output/schemas' 246 | const propertiesToConvert = 'paths' 247 | 248 | oas2tson(openAPIPath, schemasPath, propertiesToConvert) 249 | ``` 250 | 251 | ## Example 252 | 253 | The [example](./example) folder contains an example OpenAPI specification and the generated JSON schemas and TypeScript types. To generate the JSON schemas and TypeScript types from the example OpenAPI specification, run: 254 | 255 | ```sh 256 | $ npm run oas2json 257 | ``` 258 | 259 | and then: 260 | 261 | ```sh 262 | $ npm run oas2ts 263 | ``` 264 | 265 | or: 266 | 267 | ```sh 268 | $ npm run json2ts 269 | ``` 270 | 271 | ```sh 272 | $ npm run oas2json 273 | ``` 274 | 275 | And to generate TypeScript exported JSON schema from example OpenAPI specification, run: 276 | 277 | ```sh 278 | $ npm run oas2tson 279 | ``` 280 | 281 | The generated JSON schemas and TypeScript types will be saved in the output schemas and types folders respectively. 282 | 283 | ## Additional Configuration 284 | 285 | OpenAPI Transformer Toolkit package utilises the [json-schema-to-typescript](https://www.npmjs.com/package/json-schema-to-typescript) package. 286 | 287 | This package allows you to specify [additional options which can be passed to the command when executing](https://www.npmjs.com/package/json-schema-to-typescript#user-content-options), for example to affect the style of output, or change how `additionalProperties` from your API definition is handled. 288 | 289 | To utilise this feature, OpenAPI Transformer Toolkit can read these additional options from a file when being used from a CLI. [An example of this can be found here](https://github.com/nearform/openapi-transformer-toolkit/blob/master/example/json-schema-to-typescript-config.json). 290 | 291 | When using OpenAPI Transformer Toolkit programmatically, these options can optionally be supplied as the third argument to the `oas2ts` and `json2ts` functions. 292 | 293 | [![banner](https://raw.githubusercontent.com/nearform/.github/refs/heads/master/assets/os-banner-green.svg)](https://www.nearform.com/contact/?utm_source=open-source&utm_medium=banner&utm_campaign=os-project-pages) 294 | -------------------------------------------------------------------------------- /example/json-schema-to-typescript-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "additionalProperties": true, 3 | "style": { 4 | "tabWidth": 2, 5 | "singleQuote": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Swagger Petstore - OpenAPI 3.0 4 | description: |- 5 | This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about 6 | Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! 7 | You can now help us improve the API whether it's by making changes to the definition itself or to the code. 8 | That way, with time, we can improve the API in general, and expose some of the new features in OAS3. 9 | 10 | _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_ 11 | 12 | Some useful links: 13 | - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) 14 | - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) 15 | termsOfService: http://swagger.io/terms/ 16 | contact: 17 | email: apiteam@swagger.io 18 | license: 19 | name: Apache 2.0 20 | url: http://www.apache.org/licenses/LICENSE-2.0.html 21 | version: 1.0.11 22 | externalDocs: 23 | description: Find out more about Swagger 24 | url: http://swagger.io 25 | servers: 26 | - url: https://petstore3.swagger.io/api/v3 27 | tags: 28 | - name: pet 29 | description: Everything about your Pets 30 | externalDocs: 31 | description: Find out more 32 | url: http://swagger.io 33 | - name: store 34 | description: Access to Petstore orders 35 | externalDocs: 36 | description: Find out more about our store 37 | url: http://swagger.io 38 | - name: user 39 | description: Operations about user 40 | paths: 41 | /pet: 42 | put: 43 | tags: 44 | - pet 45 | summary: Update an existing pet 46 | description: Update an existing pet by Id 47 | operationId: updatePet 48 | requestBody: 49 | description: Update an existent pet in the store 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '#/components/schemas/Pet' 54 | application/xml: 55 | schema: 56 | $ref: '#/components/schemas/Pet' 57 | application/x-www-form-urlencoded: 58 | schema: 59 | $ref: '#/components/schemas/Pet' 60 | required: true 61 | responses: 62 | '200': 63 | description: Successful operation 64 | content: 65 | application/json: 66 | schema: 67 | $ref: '#/components/schemas/Pet' 68 | application/xml: 69 | schema: 70 | $ref: '#/components/schemas/Pet' 71 | '400': 72 | description: Invalid ID supplied 73 | '404': 74 | description: Pet not found 75 | '405': 76 | description: Validation exception 77 | security: 78 | - petstore_auth: 79 | - write:pets 80 | - read:pets 81 | post: 82 | tags: 83 | - pet 84 | summary: Add a new pet to the store 85 | description: Add a new pet to the store 86 | operationId: addPet 87 | requestBody: 88 | description: Create a new pet in the store 89 | content: 90 | application/json: 91 | schema: 92 | $ref: '#/components/schemas/Pet' 93 | application/xml: 94 | schema: 95 | $ref: '#/components/schemas/Pet' 96 | application/x-www-form-urlencoded: 97 | schema: 98 | $ref: '#/components/schemas/Pet' 99 | required: true 100 | responses: 101 | '200': 102 | description: Successful operation 103 | content: 104 | application/json: 105 | schema: 106 | $ref: '#/components/schemas/Pet' 107 | application/xml: 108 | schema: 109 | $ref: '#/components/schemas/Pet' 110 | '405': 111 | description: Invalid input 112 | security: 113 | - petstore_auth: 114 | - write:pets 115 | - read:pets 116 | /pet/findByStatus: 117 | get: 118 | tags: 119 | - pet 120 | summary: Finds Pets by status 121 | description: Multiple status values can be provided with comma separated strings 122 | operationId: findPetsByStatus 123 | parameters: 124 | - name: status 125 | in: query 126 | description: Status values that need to be considered for filter 127 | required: false 128 | explode: true 129 | schema: 130 | type: string 131 | default: available 132 | enum: 133 | - available 134 | - pending 135 | - sold 136 | responses: 137 | '200': 138 | description: successful operation 139 | content: 140 | application/json: 141 | schema: 142 | type: array 143 | items: 144 | $ref: '#/components/schemas/Pet' 145 | application/xml: 146 | schema: 147 | type: array 148 | items: 149 | $ref: '#/components/schemas/Pet' 150 | '400': 151 | description: Invalid status value 152 | security: 153 | - petstore_auth: 154 | - write:pets 155 | - read:pets 156 | /pet/findByTags: 157 | get: 158 | tags: 159 | - pet 160 | summary: Finds Pets by tags 161 | description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 162 | operationId: findPetsByTags 163 | parameters: 164 | - name: tags 165 | in: query 166 | description: Tags to filter by 167 | required: false 168 | explode: true 169 | schema: 170 | type: array 171 | items: 172 | type: string 173 | responses: 174 | '200': 175 | description: successful operation 176 | content: 177 | application/json: 178 | schema: 179 | type: array 180 | items: 181 | $ref: '#/components/schemas/Pet' 182 | application/xml: 183 | schema: 184 | type: array 185 | items: 186 | $ref: '#/components/schemas/Pet' 187 | '400': 188 | description: Invalid tag value 189 | security: 190 | - petstore_auth: 191 | - write:pets 192 | - read:pets 193 | /pet/{petId}: 194 | get: 195 | tags: 196 | - pet 197 | summary: Find pet by ID 198 | description: Returns a single pet 199 | operationId: getPetById 200 | parameters: 201 | - name: petId 202 | in: path 203 | description: ID of pet to return 204 | required: true 205 | schema: 206 | type: integer 207 | format: int64 208 | responses: 209 | '200': 210 | description: successful operation 211 | content: 212 | application/json: 213 | schema: 214 | $ref: '#/components/schemas/Pet' 215 | application/xml: 216 | schema: 217 | $ref: '#/components/schemas/Pet' 218 | '400': 219 | description: Invalid ID supplied 220 | '404': 221 | description: Pet not found 222 | security: 223 | - api_key: [] 224 | - petstore_auth: 225 | - write:pets 226 | - read:pets 227 | post: 228 | tags: 229 | - pet 230 | summary: Updates a pet in the store with form data 231 | description: '' 232 | operationId: updatePetWithForm 233 | parameters: 234 | - name: petId 235 | in: path 236 | description: ID of pet that needs to be updated 237 | required: true 238 | schema: 239 | type: integer 240 | format: int64 241 | - name: name 242 | in: query 243 | description: Name of pet that needs to be updated 244 | schema: 245 | type: string 246 | - name: status 247 | in: query 248 | description: Status of pet that needs to be updated 249 | schema: 250 | type: string 251 | responses: 252 | '405': 253 | description: Invalid input 254 | security: 255 | - petstore_auth: 256 | - write:pets 257 | - read:pets 258 | delete: 259 | tags: 260 | - pet 261 | summary: Deletes a pet 262 | description: delete a pet 263 | operationId: deletePet 264 | parameters: 265 | - name: api_key 266 | in: header 267 | description: '' 268 | required: false 269 | schema: 270 | type: string 271 | - name: petId 272 | in: path 273 | description: Pet id to delete 274 | required: true 275 | schema: 276 | type: integer 277 | format: int64 278 | responses: 279 | '400': 280 | description: Invalid pet value 281 | security: 282 | - petstore_auth: 283 | - write:pets 284 | - read:pets 285 | /pet/{petId}/uploadImage: 286 | post: 287 | tags: 288 | - pet 289 | summary: uploads an image 290 | description: '' 291 | operationId: uploadFile 292 | parameters: 293 | - name: petId 294 | in: path 295 | description: ID of pet to update 296 | required: true 297 | schema: 298 | type: integer 299 | format: int64 300 | - name: additionalMetadata 301 | in: query 302 | description: Additional Metadata 303 | required: false 304 | schema: 305 | type: string 306 | requestBody: 307 | content: 308 | application/octet-stream: 309 | schema: 310 | type: string 311 | format: binary 312 | responses: 313 | '200': 314 | description: successful operation 315 | content: 316 | application/json: 317 | schema: 318 | $ref: '#/components/schemas/ApiResponse' 319 | security: 320 | - petstore_auth: 321 | - write:pets 322 | - read:pets 323 | /store/inventory: 324 | get: 325 | tags: 326 | - store 327 | summary: Returns pet inventories by status 328 | description: Returns a map of status codes to quantities 329 | operationId: getInventory 330 | responses: 331 | '200': 332 | description: successful operation 333 | content: 334 | application/json: 335 | schema: 336 | type: object 337 | additionalProperties: 338 | type: integer 339 | format: int32 340 | security: 341 | - api_key: [] 342 | /store/order: 343 | post: 344 | tags: 345 | - store 346 | summary: Place an order for a pet 347 | description: Place a new order in the store 348 | operationId: placeOrder 349 | requestBody: 350 | content: 351 | application/json: 352 | schema: 353 | $ref: '#/components/schemas/Order' 354 | application/xml: 355 | schema: 356 | $ref: '#/components/schemas/Order' 357 | application/x-www-form-urlencoded: 358 | schema: 359 | $ref: '#/components/schemas/Order' 360 | responses: 361 | '200': 362 | description: successful operation 363 | content: 364 | application/json: 365 | schema: 366 | $ref: '#/components/schemas/Order' 367 | '405': 368 | description: Invalid input 369 | /store/order/{orderId}: 370 | get: 371 | tags: 372 | - store 373 | summary: Find purchase order by ID 374 | description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. 375 | operationId: getOrderById 376 | parameters: 377 | - name: orderId 378 | in: path 379 | description: ID of order that needs to be fetched 380 | required: true 381 | schema: 382 | type: integer 383 | format: int64 384 | responses: 385 | '200': 386 | description: successful operation 387 | content: 388 | application/json: 389 | schema: 390 | $ref: '#/components/schemas/Order' 391 | application/xml: 392 | schema: 393 | $ref: '#/components/schemas/Order' 394 | '400': 395 | description: Invalid ID supplied 396 | '404': 397 | description: Order not found 398 | delete: 399 | tags: 400 | - store 401 | summary: Delete purchase order by ID 402 | description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors 403 | operationId: deleteOrder 404 | parameters: 405 | - name: orderId 406 | in: path 407 | description: ID of the order that needs to be deleted 408 | required: true 409 | schema: 410 | type: integer 411 | format: int64 412 | responses: 413 | '400': 414 | description: Invalid ID supplied 415 | '404': 416 | description: Order not found 417 | /user: 418 | post: 419 | tags: 420 | - user 421 | summary: Create user 422 | description: This can only be done by the logged in user. 423 | operationId: createUser 424 | requestBody: 425 | description: Created user object 426 | content: 427 | application/json: 428 | schema: 429 | $ref: '#/components/schemas/User' 430 | application/xml: 431 | schema: 432 | $ref: '#/components/schemas/User' 433 | application/x-www-form-urlencoded: 434 | schema: 435 | $ref: '#/components/schemas/User' 436 | responses: 437 | default: 438 | description: successful operation 439 | content: 440 | application/json: 441 | schema: 442 | $ref: '#/components/schemas/User' 443 | application/xml: 444 | schema: 445 | $ref: '#/components/schemas/User' 446 | /user/createWithList: 447 | post: 448 | tags: 449 | - user 450 | summary: Creates list of users with given input array 451 | description: Creates list of users with given input array 452 | operationId: createUsersWithListInput 453 | requestBody: 454 | content: 455 | application/json: 456 | schema: 457 | type: array 458 | items: 459 | $ref: '#/components/schemas/User' 460 | responses: 461 | '200': 462 | description: Successful operation 463 | content: 464 | application/json: 465 | schema: 466 | $ref: '#/components/schemas/User' 467 | application/xml: 468 | schema: 469 | $ref: '#/components/schemas/User' 470 | default: 471 | description: successful operation 472 | /user/login: 473 | get: 474 | tags: 475 | - user 476 | summary: Logs user into the system 477 | description: '' 478 | operationId: loginUser 479 | parameters: 480 | - name: username 481 | in: query 482 | description: The user name for login 483 | required: false 484 | schema: 485 | type: string 486 | - name: password 487 | in: query 488 | description: The password for login in clear text 489 | required: false 490 | schema: 491 | type: string 492 | responses: 493 | '200': 494 | description: successful operation 495 | headers: 496 | X-Rate-Limit: 497 | description: calls per hour allowed by the user 498 | schema: 499 | type: integer 500 | format: int32 501 | X-Expires-After: 502 | description: date in UTC when token expires 503 | schema: 504 | type: string 505 | format: date-time 506 | content: 507 | application/xml: 508 | schema: 509 | type: string 510 | application/json: 511 | schema: 512 | type: string 513 | '400': 514 | description: Invalid username/password supplied 515 | /user/logout: 516 | get: 517 | tags: 518 | - user 519 | summary: Logs out current logged in user session 520 | description: '' 521 | operationId: logoutUser 522 | parameters: [] 523 | responses: 524 | default: 525 | description: successful operation 526 | /user/{username}: 527 | get: 528 | tags: 529 | - user 530 | summary: Get user by user name 531 | description: '' 532 | operationId: getUserByName 533 | parameters: 534 | - name: username 535 | in: path 536 | description: 'The name that needs to be fetched. Use user1 for testing. ' 537 | required: true 538 | schema: 539 | type: string 540 | responses: 541 | '200': 542 | description: successful operation 543 | content: 544 | application/json: 545 | schema: 546 | $ref: '#/components/schemas/User' 547 | application/xml: 548 | schema: 549 | $ref: '#/components/schemas/User' 550 | '400': 551 | description: Invalid username supplied 552 | '404': 553 | description: User not found 554 | put: 555 | tags: 556 | - user 557 | summary: Update user 558 | description: This can only be done by the logged in user. 559 | operationId: updateUser 560 | parameters: 561 | - name: username 562 | in: path 563 | description: name that need to be deleted 564 | required: true 565 | schema: 566 | type: string 567 | requestBody: 568 | description: Update an existent user in the store 569 | content: 570 | application/json: 571 | schema: 572 | $ref: '#/components/schemas/User' 573 | application/xml: 574 | schema: 575 | $ref: '#/components/schemas/User' 576 | application/x-www-form-urlencoded: 577 | schema: 578 | $ref: '#/components/schemas/User' 579 | responses: 580 | default: 581 | description: successful operation 582 | delete: 583 | tags: 584 | - user 585 | summary: Delete user 586 | description: This can only be done by the logged in user. 587 | operationId: deleteUser 588 | parameters: 589 | - name: username 590 | in: path 591 | description: The name that needs to be deleted 592 | required: true 593 | schema: 594 | type: string 595 | responses: 596 | '400': 597 | description: Invalid username supplied 598 | '404': 599 | description: User not found 600 | components: 601 | schemas: 602 | DateExample: 603 | type: string 604 | format: date-time 605 | Order: 606 | type: object 607 | properties: 608 | id: 609 | type: integer 610 | format: int64 611 | example: 10 612 | petId: 613 | type: integer 614 | format: int64 615 | example: 198772 616 | quantity: 617 | type: integer 618 | format: int32 619 | example: 7 620 | shipDate: 621 | type: string 622 | format: date-time #changed 623 | status: 624 | type: string 625 | description: Order Status 626 | example: approved 627 | enum: 628 | - placed 629 | - approved 630 | - delivered 631 | complete: 632 | type: boolean 633 | xml: 634 | name: order 635 | Customer: 636 | type: object 637 | properties: 638 | id: 639 | type: integer 640 | format: int64 641 | example: 100000 642 | username: 643 | type: string 644 | example: fehguy 645 | address: 646 | type: array 647 | xml: 648 | name: addresses 649 | wrapped: true 650 | items: 651 | $ref: '#/components/schemas/Address' 652 | xml: 653 | name: customer 654 | Address: 655 | type: object 656 | properties: 657 | street: 658 | type: string 659 | example: 437 Lytton 660 | city: 661 | type: string 662 | example: Palo Alto 663 | state: 664 | type: string 665 | example: CA 666 | zip: 667 | type: string 668 | example: '94301' 669 | xml: 670 | name: address 671 | Category: 672 | type: object 673 | properties: 674 | id: 675 | type: integer 676 | format: int64 677 | example: 1 678 | name: 679 | type: string 680 | example: Dogs 681 | xml: 682 | name: category 683 | User: 684 | type: object 685 | properties: 686 | id: 687 | type: integer 688 | format: int64 689 | example: 10 690 | username: 691 | type: string 692 | example: theUser 693 | firstName: 694 | type: string 695 | example: John 696 | lastName: 697 | type: string 698 | example: James 699 | email: 700 | type: string 701 | example: john@email.com 702 | password: 703 | type: string 704 | example: '12345' 705 | phone: 706 | type: string 707 | example: '12345' 708 | userStatus: 709 | type: integer 710 | description: User Status 711 | format: int32 712 | example: 1 713 | xml: 714 | name: user 715 | Tag: 716 | type: object 717 | properties: 718 | id: 719 | type: integer 720 | format: int64 721 | name: 722 | type: string 723 | xml: 724 | name: tag 725 | Pet: 726 | required: 727 | - name 728 | - photoUrls 729 | type: object 730 | properties: 731 | id: 732 | type: integer 733 | format: int64 734 | example: 10 735 | name: 736 | type: string 737 | example: doggie 738 | category: 739 | $ref: '#/components/schemas/Category' 740 | photoUrls: 741 | type: array 742 | xml: 743 | wrapped: true 744 | items: 745 | type: string 746 | xml: 747 | name: photoUrl 748 | tags: 749 | type: array 750 | xml: 751 | wrapped: true 752 | items: 753 | $ref: '#/components/schemas/Tag' 754 | status: 755 | type: string 756 | description: pet status in the store 757 | enum: 758 | - available 759 | - pending 760 | - sold 761 | nullableValue: 762 | type: string 763 | nullable: true 764 | description: example nullable value 765 | xml: 766 | name: pet 767 | ApiResponse: 768 | type: object 769 | properties: 770 | code: 771 | type: integer 772 | format: int32 773 | type: 774 | type: string 775 | message: 776 | type: string 777 | FooBARBaz: 778 | $ref: '#/components/schemas/FooBARBaz' 779 | xml: 780 | name: '##default' 781 | FooBARBaz: 782 | type: string 783 | description: this name is valid and should be FooBARBaz everywhere it appears 784 | requestBodies: 785 | Pet: 786 | description: Pet object that needs to be added to the store 787 | content: 788 | application/json: 789 | schema: 790 | $ref: '#/components/schemas/Pet' 791 | application/xml: 792 | schema: 793 | $ref: '#/components/schemas/Pet' 794 | UserArray: 795 | description: List of user object 796 | content: 797 | application/json: 798 | schema: 799 | type: array 800 | items: 801 | $ref: '#/components/schemas/User' 802 | securitySchemes: 803 | petstore_auth: 804 | type: oauth2 805 | flows: 806 | implicit: 807 | authorizationUrl: https://petstore3.swagger.io/oauth/authorize 808 | scopes: 809 | write:pets: modify pets in your account 810 | read:pets: read your pets 811 | api_key: 812 | type: apiKey 813 | name: api_key 814 | in: header 815 | -------------------------------------------------------------------------------- /example/output/json/components.schemas/Address.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "street": { 5 | "type": "string" 6 | }, 7 | "city": { 8 | "type": "string" 9 | }, 10 | "state": { 11 | "type": "string" 12 | }, 13 | "zip": { 14 | "type": "string" 15 | } 16 | }, 17 | "title": "Address", 18 | "$id": "Address.json" 19 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/ApiResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "code": { 5 | "type": "integer", 6 | "format": "int32", 7 | "minimum": -2147483648, 8 | "maximum": 2147483647 9 | }, 10 | "type": { 11 | "type": "string" 12 | }, 13 | "message": { 14 | "type": "string" 15 | }, 16 | "FooBARBaz": { 17 | "$ref": "FooBARBaz.json" 18 | } 19 | }, 20 | "title": "ApiResponse", 21 | "$id": "ApiResponse.json" 22 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/Category.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "name": { 11 | "type": "string" 12 | } 13 | }, 14 | "title": "Category", 15 | "$id": "Category.json" 16 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/Customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "username": { 11 | "type": "string" 12 | }, 13 | "address": { 14 | "type": "array", 15 | "items": { 16 | "$ref": "Address.json" 17 | } 18 | } 19 | }, 20 | "title": "Customer", 21 | "$id": "Customer.json" 22 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/DateExample.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "format": "date-time", 4 | "title": "DateExample", 5 | "$id": "DateExample.json", 6 | "tsType": "Date" 7 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/FooBARBaz.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "description": "this name is valid and should be FooBARBaz everywhere it appears", 4 | "title": "FooBARBaz", 5 | "$id": "FooBARBaz.json" 6 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/Order.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "petId": { 11 | "type": "integer", 12 | "format": "int64", 13 | "minimum": -9223372036854776000, 14 | "maximum": 9223372036854776000 15 | }, 16 | "quantity": { 17 | "type": "integer", 18 | "format": "int32", 19 | "minimum": -2147483648, 20 | "maximum": 2147483647 21 | }, 22 | "shipDate": { 23 | "type": "string", 24 | "format": "date-time" 25 | }, 26 | "status": { 27 | "type": "string", 28 | "description": "Order Status", 29 | "enum": [ 30 | "placed", 31 | "approved", 32 | "delivered" 33 | ] 34 | }, 35 | "complete": { 36 | "type": "boolean" 37 | } 38 | }, 39 | "title": "Order", 40 | "$id": "Order.json" 41 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/Pet.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": [ 3 | "name", 4 | "photoUrls" 5 | ], 6 | "type": "object", 7 | "properties": { 8 | "id": { 9 | "type": "integer", 10 | "format": "int64", 11 | "minimum": -9223372036854776000, 12 | "maximum": 9223372036854776000 13 | }, 14 | "name": { 15 | "type": "string" 16 | }, 17 | "category": { 18 | "$ref": "Category.json" 19 | }, 20 | "photoUrls": { 21 | "type": "array", 22 | "items": { 23 | "type": "string" 24 | } 25 | }, 26 | "tags": { 27 | "type": "array", 28 | "items": { 29 | "$ref": "Tag.json" 30 | } 31 | }, 32 | "status": { 33 | "type": "string", 34 | "description": "pet status in the store", 35 | "enum": [ 36 | "available", 37 | "pending", 38 | "sold" 39 | ] 40 | }, 41 | "nullableValue": { 42 | "type": [ 43 | "string", 44 | "null" 45 | ], 46 | "description": "example nullable value" 47 | } 48 | }, 49 | "title": "Pet", 50 | "$id": "Pet.json" 51 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/Tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "name": { 11 | "type": "string" 12 | } 13 | }, 14 | "title": "Tag", 15 | "$id": "Tag.json" 16 | } -------------------------------------------------------------------------------- /example/output/json/components.schemas/User.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "username": { 11 | "type": "string" 12 | }, 13 | "firstName": { 14 | "type": "string" 15 | }, 16 | "lastName": { 17 | "type": "string" 18 | }, 19 | "email": { 20 | "type": "string" 21 | }, 22 | "password": { 23 | "type": "string" 24 | }, 25 | "phone": { 26 | "type": "string" 27 | }, 28 | "userStatus": { 29 | "type": "integer", 30 | "description": "User Status", 31 | "format": "int32", 32 | "minimum": -2147483648, 33 | "maximum": 2147483647 34 | } 35 | }, 36 | "title": "User", 37 | "$id": "User.json" 38 | } -------------------------------------------------------------------------------- /example/output/ts/Address.ts: -------------------------------------------------------------------------------- 1 | export const Address = { 2 | type: "object", 3 | properties: { 4 | street: { type: "string" }, 5 | city: { type: "string" }, 6 | state: { type: "string" }, 7 | zip: { type: "string" }, 8 | }, 9 | title: "Address", 10 | $id: "Address.json", 11 | } as const; 12 | -------------------------------------------------------------------------------- /example/output/ts/ApiResponse.ts: -------------------------------------------------------------------------------- 1 | export const ApiResponse = { 2 | type: "object", 3 | properties: { 4 | code: { 5 | type: "integer", 6 | format: "int32", 7 | minimum: -2147483648, 8 | maximum: 2147483647, 9 | }, 10 | type: { type: "string" }, 11 | message: { type: "string" }, 12 | FooBARBaz: { 13 | type: "string", 14 | description: 15 | "this name is valid and should be FooBARBaz everywhere it appears", 16 | title: "FooBARBaz", 17 | $id: "FooBARBaz.json", 18 | }, 19 | }, 20 | title: "ApiResponse", 21 | $id: "ApiResponse.json", 22 | } as const; 23 | -------------------------------------------------------------------------------- /example/output/ts/Category.ts: -------------------------------------------------------------------------------- 1 | export const Category = { 2 | type: "object", 3 | properties: { 4 | id: { 5 | type: "integer", 6 | format: "int64", 7 | minimum: -9223372036854776000, 8 | maximum: 9223372036854776000, 9 | }, 10 | name: { type: "string" }, 11 | }, 12 | title: "Category", 13 | $id: "Category.json", 14 | } as const; 15 | -------------------------------------------------------------------------------- /example/output/ts/Customer.ts: -------------------------------------------------------------------------------- 1 | export const Customer = { 2 | type: "object", 3 | properties: { 4 | id: { 5 | type: "integer", 6 | format: "int64", 7 | minimum: -9223372036854776000, 8 | maximum: 9223372036854776000, 9 | }, 10 | username: { type: "string" }, 11 | address: { 12 | type: "array", 13 | items: { 14 | type: "object", 15 | properties: { 16 | street: { type: "string" }, 17 | city: { type: "string" }, 18 | state: { type: "string" }, 19 | zip: { type: "string" }, 20 | }, 21 | title: "Address", 22 | $id: "Address.json", 23 | }, 24 | }, 25 | }, 26 | title: "Customer", 27 | $id: "Customer.json", 28 | } as const; 29 | -------------------------------------------------------------------------------- /example/output/ts/DateExample.ts: -------------------------------------------------------------------------------- 1 | export const DateExample = { 2 | type: "string", 3 | format: "date-time", 4 | title: "DateExample", 5 | $id: "DateExample.json", 6 | tsType: "Date", 7 | } as const; 8 | -------------------------------------------------------------------------------- /example/output/ts/FooBARBaz.ts: -------------------------------------------------------------------------------- 1 | export const FooBARBaz = { 2 | type: "string", 3 | description: 4 | "this name is valid and should be FooBARBaz everywhere it appears", 5 | title: "FooBARBaz", 6 | $id: "FooBARBaz.json", 7 | } as const; 8 | -------------------------------------------------------------------------------- /example/output/ts/Order.ts: -------------------------------------------------------------------------------- 1 | export const Order = { 2 | type: "object", 3 | properties: { 4 | id: { 5 | type: "integer", 6 | format: "int64", 7 | minimum: -9223372036854776000, 8 | maximum: 9223372036854776000, 9 | }, 10 | petId: { 11 | type: "integer", 12 | format: "int64", 13 | minimum: -9223372036854776000, 14 | maximum: 9223372036854776000, 15 | }, 16 | quantity: { 17 | type: "integer", 18 | format: "int32", 19 | minimum: -2147483648, 20 | maximum: 2147483647, 21 | }, 22 | shipDate: { type: "string", format: "date-time" }, 23 | status: { 24 | type: "string", 25 | description: "Order Status", 26 | enum: ["placed", "approved", "delivered"], 27 | }, 28 | complete: { type: "boolean" }, 29 | }, 30 | title: "Order", 31 | $id: "Order.json", 32 | } as const; 33 | -------------------------------------------------------------------------------- /example/output/ts/Pet.ts: -------------------------------------------------------------------------------- 1 | export const Pet = { 2 | required: ["name", "photoUrls"], 3 | type: "object", 4 | properties: { 5 | id: { 6 | type: "integer", 7 | format: "int64", 8 | minimum: -9223372036854776000, 9 | maximum: 9223372036854776000, 10 | }, 11 | name: { type: "string" }, 12 | category: { 13 | type: "object", 14 | properties: { 15 | id: { 16 | type: "integer", 17 | format: "int64", 18 | minimum: -9223372036854776000, 19 | maximum: 9223372036854776000, 20 | }, 21 | name: { type: "string" }, 22 | }, 23 | title: "Category", 24 | $id: "Category.json", 25 | }, 26 | photoUrls: { type: "array", items: { type: "string" } }, 27 | tags: { 28 | type: "array", 29 | items: { 30 | type: "object", 31 | properties: { 32 | id: { 33 | type: "integer", 34 | format: "int64", 35 | minimum: -9223372036854776000, 36 | maximum: 9223372036854776000, 37 | }, 38 | name: { type: "string" }, 39 | }, 40 | title: "Tag", 41 | $id: "Tag.json", 42 | }, 43 | }, 44 | status: { 45 | type: "string", 46 | description: "pet status in the store", 47 | enum: ["available", "pending", "sold"], 48 | }, 49 | nullableValue: { 50 | type: ["string", "null"], 51 | description: "example nullable value", 52 | }, 53 | }, 54 | title: "Pet", 55 | $id: "Pet.json", 56 | } as const; 57 | -------------------------------------------------------------------------------- /example/output/ts/Tag.ts: -------------------------------------------------------------------------------- 1 | export const Tag = { 2 | type: "object", 3 | properties: { 4 | id: { 5 | type: "integer", 6 | format: "int64", 7 | minimum: -9223372036854776000, 8 | maximum: 9223372036854776000, 9 | }, 10 | name: { type: "string" }, 11 | }, 12 | title: "Tag", 13 | $id: "Tag.json", 14 | } as const; 15 | -------------------------------------------------------------------------------- /example/output/ts/User.ts: -------------------------------------------------------------------------------- 1 | export const User = { 2 | type: "object", 3 | properties: { 4 | id: { 5 | type: "integer", 6 | format: "int64", 7 | minimum: -9223372036854776000, 8 | maximum: 9223372036854776000, 9 | }, 10 | username: { type: "string" }, 11 | firstName: { type: "string" }, 12 | lastName: { type: "string" }, 13 | email: { type: "string" }, 14 | password: { type: "string" }, 15 | phone: { type: "string" }, 16 | userStatus: { 17 | type: "integer", 18 | description: "User Status", 19 | format: "int32", 20 | minimum: -2147483648, 21 | maximum: 2147483647, 22 | }, 23 | }, 24 | title: "User", 25 | $id: "User.json", 26 | } as const; 27 | -------------------------------------------------------------------------------- /example/output/types/Address.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export interface Address { 9 | street?: string; 10 | city?: string; 11 | state?: string; 12 | zip?: string; 13 | [k: string]: unknown; 14 | } 15 | -------------------------------------------------------------------------------- /example/output/types/ApiResponse.d.ts: -------------------------------------------------------------------------------- 1 | import { FooBARBaz } from './FooBARBaz'; 2 | 3 | /* eslint-disable */ 4 | /** 5 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 6 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 7 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 8 | */ 9 | 10 | export interface ApiResponse { 11 | code?: number; 12 | type?: string; 13 | message?: string; 14 | FooBARBaz?: FooBARBaz; 15 | [k: string]: unknown; 16 | } 17 | -------------------------------------------------------------------------------- /example/output/types/Category.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export interface Category { 9 | id?: number; 10 | name?: string; 11 | [k: string]: unknown; 12 | } 13 | -------------------------------------------------------------------------------- /example/output/types/Customer.d.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './Address'; 2 | 3 | /* eslint-disable */ 4 | /** 5 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 6 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 7 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 8 | */ 9 | 10 | export interface Customer { 11 | id?: number; 12 | username?: string; 13 | address?: Address[]; 14 | [k: string]: unknown; 15 | } 16 | -------------------------------------------------------------------------------- /example/output/types/DateExample.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export type DateExample = Date; 9 | -------------------------------------------------------------------------------- /example/output/types/FooBARBaz.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | /** 9 | * this name is valid and should be FooBARBaz everywhere it appears 10 | */ 11 | export type FooBARBaz = string; 12 | -------------------------------------------------------------------------------- /example/output/types/Order.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export interface Order { 9 | id?: number; 10 | petId?: number; 11 | quantity?: number; 12 | shipDate?: string; 13 | /** 14 | * Order Status 15 | */ 16 | status?: 'placed' | 'approved' | 'delivered'; 17 | complete?: boolean; 18 | [k: string]: unknown; 19 | } 20 | -------------------------------------------------------------------------------- /example/output/types/Pet.d.ts: -------------------------------------------------------------------------------- 1 | import { Category } from './Category'; 2 | import { Tag } from './Tag'; 3 | 4 | /* eslint-disable */ 5 | /** 6 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 7 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 8 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 9 | */ 10 | 11 | export interface Pet { 12 | id?: number; 13 | name: string; 14 | category?: Category; 15 | photoUrls: string[]; 16 | tags?: Tag[]; 17 | /** 18 | * pet status in the store 19 | */ 20 | status?: 'available' | 'pending' | 'sold'; 21 | /** 22 | * example nullable value 23 | */ 24 | nullableValue?: string | null; 25 | [k: string]: unknown; 26 | } 27 | -------------------------------------------------------------------------------- /example/output/types/Tag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export interface Tag { 9 | id?: number; 10 | name?: string; 11 | [k: string]: unknown; 12 | } 13 | -------------------------------------------------------------------------------- /example/output/types/User.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | export interface User { 9 | id?: number; 10 | username?: string; 11 | firstName?: string; 12 | lastName?: string; 13 | email?: string; 14 | password?: string; 15 | phone?: string; 16 | /** 17 | * User Status 18 | */ 19 | userStatus?: number; 20 | [k: string]: unknown; 21 | } 22 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export { runCommand as json2ts } from './src/commands/json2ts.js' 2 | export { runCommand as oas2json } from './src/commands/oas2json.js' 3 | export { runCommand as oas2ts } from './src/commands/oas2ts.js' 4 | export { runCommand as oas2tson } from './src/commands/oas2tson.js' 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openapi-transformer-toolkit", 3 | "version": "1.5.0", 4 | "description": "Generates schemas and types from OpenAPI specifications", 5 | "main": "dist/esm/index.js", 6 | "type": "module", 7 | "license": "ISC", 8 | "scripts": { 9 | "generate-toc": "node ./scripts/generate-toc.mjs", 10 | "prejson2ts": "npm run build", 11 | "json2ts": "./dist/esm/src/cli.js json2ts -i ./example/output/json/components.schemas -o ./example/output/types -c ./example/json-schema-to-typescript-config.json", 12 | "lint": "eslint .", 13 | "preoas2json": "npm run build", 14 | "oas2json": "./dist/esm/src/cli.js oas2json -i ./example/openapi.yml -o ./example/output/json", 15 | "preoas2ts": "npm run build", 16 | "oas2ts": "./dist/esm/src/cli.js oas2ts -i ./example/openapi.yml -o ./example/output/types -c ./example/json-schema-to-typescript-config.json", 17 | "preoas2tson": "npm run build", 18 | "oas2tson": "./dist/esm/src/cli.js oas2tson -i ./example/openapi.yml -o ./example/output/ts", 19 | "prepare": "husky", 20 | "test": "node --test --loader ts-node/esm test/*.test.ts", 21 | "build": "tsup-node", 22 | "postbuild": "cp ./package.json ./dist/esm" 23 | }, 24 | "bin": { 25 | "openapi-transformer-toolkit": "./dist/esm/src/cli.js" 26 | }, 27 | "files": [ 28 | "dist", 29 | "example" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/nearform/openapi-transformer-toolkit.git" 34 | }, 35 | "author": "", 36 | "bugs": { 37 | "url": "https://github.com/nearform/openapi-transformer-toolkit/issues" 38 | }, 39 | "homepage": "https://github.com/nearform/openapi-transformer-toolkit#readme", 40 | "dependencies": { 41 | "@apidevtools/json-schema-ref-parser": "^12.0.1", 42 | "@openapi-contrib/openapi-schema-to-json-schema": "^5.1.0", 43 | "commander": "^14.0.0", 44 | "desm": "^1.3.1", 45 | "filenamify": "^6.0.0", 46 | "fs-extra": "^11.1.1", 47 | "json-schema-to-typescript": "^15.0.2", 48 | "lodash.get": "^4.4.2", 49 | "lodash.trimstart": "^4.5.1", 50 | "pino": "^9.3.1", 51 | "prettier": "^3.3.3", 52 | "yaml": "^2.4.5" 53 | }, 54 | "devDependencies": { 55 | "@commitlint/cli": "^19.3.0", 56 | "@commitlint/config-conventional": "^19.2.2", 57 | "@types/fs-extra": "^11.0.4", 58 | "@types/lodash": "^4.17.13", 59 | "@types/lodash.get": "^4.4.9", 60 | "@types/lodash.trimstart": "^4.5.9", 61 | "@types/node": "^22.9.0", 62 | "@typescript-eslint/eslint-plugin": "^7.16.1", 63 | "@typescript-eslint/parser": "^7.16.1", 64 | "eslint": "^8.57.0", 65 | "eslint-config-prettier": "^10.0.1", 66 | "eslint-plugin-prettier": "^5.2.1", 67 | "husky": "^9.1.1", 68 | "lint-staged": "^16.0.0", 69 | "remark": "^15.0.1", 70 | "remark-toc": "^9.0.0", 71 | "ts-node": "^10.9.2", 72 | "tsup": "^8.2.0", 73 | "typescript": "^5.5.3" 74 | }, 75 | "lint-staged": { 76 | "*.{js,jsx}": "eslint --cache --fix" 77 | }, 78 | "keywords": [ 79 | "openapi", 80 | "openapi3", 81 | "swagger", 82 | "api", 83 | "json-schema", 84 | "typescript", 85 | "codegen", 86 | "code-generation", 87 | "autogenerate", 88 | "schema", 89 | "api-design", 90 | "api-development", 91 | "rest-api", 92 | "oas", 93 | "json", 94 | "transformer", 95 | "toolkit" 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /scripts/generate-toc.mjs: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from 'fs/promises' 2 | import { remark } from 'remark' 3 | import remarkToc from 'remark-toc' 4 | 5 | const README_PATH = 'README.md' 6 | 7 | const readmeContent = await readFile(README_PATH, 'utf8') 8 | 9 | const file = await remark().use(remarkToc).process(readmeContent) 10 | 11 | await writeFile(README_PATH, String(file)) 12 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Command } from 'commander' 4 | import packageJson from '../package.json' with { type: 'json' } 5 | import { json2ts } from './commands/json2ts.js' 6 | import { oas2json } from './commands/oas2json.js' 7 | import { oas2ts } from './commands/oas2ts.js' 8 | import { oas2tson } from './commands/oas2tson.js' 9 | 10 | const program = new Command() 11 | program 12 | .name('openapi-transformer-toolkit') 13 | .description('Generates schemas and types from OpenAPI specifications') 14 | .version(packageJson.version) 15 | .option('-d, --debug', 'Output debugging information') 16 | 17 | program.addCommand(oas2json) 18 | program.addCommand(json2ts) 19 | program.addCommand(oas2ts) 20 | program.addCommand(oas2tson) 21 | 22 | program.parse() 23 | -------------------------------------------------------------------------------- /src/commands/json2ts.ts: -------------------------------------------------------------------------------- 1 | import { $RefParser } from '@apidevtools/json-schema-ref-parser' 2 | import { Command } from 'commander' 3 | import fs from 'fs-extra' 4 | import { compileFromFile } from 'json-schema-to-typescript' 5 | import path from 'path' 6 | import pino from 'pino' 7 | import { format } from 'prettier' 8 | import { exit } from 'process' 9 | import type { 10 | Json2TsArgs, 11 | Json2TsDefaultOptions, 12 | Json2TsOptions 13 | } from '../types/Json2TsOptions' 14 | import { doNotEditText } from '../utils/do-not-edit-text.js' 15 | import { readConfigFile } from '../utils/read-config-file.js' 16 | 17 | const generateAndWriteTsFile = async ( 18 | schemaPath: string, 19 | tsTypesPath: string, 20 | options: Json2TsOptions 21 | ) => { 22 | const ts = await compileFromFile(schemaPath, options) 23 | 24 | const interfaceName = path.basename(schemaPath, '.json') 25 | 26 | const parser = new $RefParser() 27 | await parser.dereference(schemaPath) 28 | 29 | const imports = Object.values(parser.$refs.values()) 30 | .filter( 31 | refSchema => 32 | refSchema.$id && refSchema.title && refSchema.title !== interfaceName 33 | ) 34 | .map( 35 | refSchema => 36 | `import { ${refSchema.title} } from './${refSchema.$id.replace( 37 | '.json', 38 | '' 39 | )}'` 40 | ) 41 | .join('\n') 42 | 43 | const tsWithImports = `${imports ? `${imports}\n\n` : ''}${ts}` 44 | const tsFormatted = await format(tsWithImports, { 45 | parser: 'typescript', 46 | ...options.style 47 | }) 48 | 49 | const tsFileName = path.basename(schemaPath, '.json') + '.d.ts' 50 | 51 | fs.writeFileSync(path.join(tsTypesPath, tsFileName), tsFormatted) 52 | } 53 | 54 | export const runCommand = async ( 55 | schemasPath: string, 56 | tsTypesPath: string, 57 | customOptions?: Json2TsOptions, 58 | logger = pino() 59 | ) => { 60 | fs.removeSync(tsTypesPath) 61 | fs.ensureDirSync(tsTypesPath) 62 | 63 | let schemaPaths 64 | 65 | try { 66 | schemaPaths = fs.readdirSync(schemasPath) 67 | } catch (e) { 68 | logger.error('❌ Could not find the JSON schemas folder') 69 | exit(1) 70 | } 71 | 72 | const defaultOptions: Json2TsDefaultOptions = { 73 | cwd: schemasPath, 74 | bannerComment: doNotEditText, 75 | declareExternallyReferenced: false 76 | } 77 | 78 | const options = { ...defaultOptions, ...customOptions } 79 | 80 | for (const schemaFileName of schemaPaths) { 81 | const schemaPath = path.join(schemasPath, schemaFileName) 82 | await generateAndWriteTsFile(schemaPath, tsTypesPath, options) 83 | } 84 | 85 | logger.info('✅ TypeScript types generated successfully from JSON schemas') 86 | } 87 | 88 | const main = () => { 89 | const options = json2ts.optsWithGlobals() 90 | const customOptions = options.config ? readConfigFile(options.config) : {} 91 | runCommand(options.input, options.output, customOptions, options.muteLogger) 92 | } 93 | 94 | const json2ts = new Command('json2ts') 95 | 96 | const description = `This command will generate TypeScript types from JSON schemas. 97 | 98 | Examples: 99 | $ openapi-transformer-toolkit json2ts -i ./schemas -o ./types 100 | $ openapi-transformer-toolkit json2ts -i ./schemas -o ./types -c ./config.json 101 | ` 102 | 103 | json2ts 104 | .summary('Creates TypeScript types from JSON schemas') 105 | .description(description) 106 | .requiredOption('-i, --input ', 'Path to the JSON schemas folder') 107 | .requiredOption( 108 | '-o, --output ', 109 | 'Path to the folder where to output the TS files' 110 | ) 111 | .option( 112 | '-c, --config ', 113 | 'Path to the JSON/JS config file with these possible options: https://www.npmjs.com/package/json-schema-to-typescript' 114 | ) 115 | .allowUnknownOption() 116 | .allowExcessArguments(true) 117 | .action(main) 118 | 119 | export { json2ts } 120 | -------------------------------------------------------------------------------- /src/commands/oas2json.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander' 2 | import filenamify from 'filenamify' 3 | import fs from 'fs-extra' 4 | import _get from 'lodash.get' 5 | import _trimStart from 'lodash.trimstart' 6 | import path from 'path' 7 | import pino from 'pino' 8 | import { exit } from 'process' 9 | import YAML from 'yaml' 10 | 11 | import type { JSONSchema4 } from 'json-schema' 12 | 13 | import { fromSchema } from '../utils/openapi-schema-to-json-schema-wrapper.js' 14 | 15 | const COMPONENT_REF_REGEXP = 16 | /#\/components\/(callbacks|examples|headers|links|parameters|requestBodies|responses|schemas|securitySchemes)\/[^"]+/g 17 | const INVALID_URI_CHARS_REGEXP = /[^a-zA-Z0-9\-._~:/?#[\]@!$&'()*+,;=]/g 18 | 19 | export const adaptSchema = ( 20 | generatedSchema: JSONSchema4, 21 | name: string, 22 | filename: string 23 | ) => { 24 | const sanitizedFilename = filename.replace(INVALID_URI_CHARS_REGEXP, '') 25 | delete generatedSchema.$schema 26 | generatedSchema.title = name 27 | generatedSchema.$id = `${sanitizedFilename}.json` 28 | 29 | if (generatedSchema.format?.includes('date')) { 30 | generatedSchema.tsType = 'Date' 31 | } 32 | } 33 | 34 | const processSchema = ( 35 | schema: JSONSchema4, 36 | schemasPath: string, 37 | definitionKeyword: string, 38 | isArray: boolean 39 | ) => { 40 | Object.entries(schema).forEach(([key, value]) => { 41 | // for elements in an array the name would be its index if we were 42 | // to just use its key, so go into the parsed schema and get the 43 | // actual name so the files are more easily identifiable 44 | const name = isArray ? value.name : key 45 | const filename = _trimStart(filenamify(name, { replacement: '-' }), '-') 46 | 47 | adaptSchema(value, name, filename) 48 | 49 | let schemaAsString = JSON.stringify(value, null, 2) 50 | const refs = schemaAsString.match(COMPONENT_REF_REGEXP) 51 | refs?.forEach(ref => { 52 | const refName = ref.split('/').slice(-1) 53 | schemaAsString = schemaAsString.replace(ref, `${refName}.json`) 54 | }) 55 | 56 | const destinationDir = path.join(schemasPath, definitionKeyword) 57 | const destinationPath = path.join(destinationDir, `${filename}.json`) 58 | 59 | fs.ensureDirSync(destinationDir) 60 | fs.writeFileSync(destinationPath, schemaAsString) 61 | }) 62 | } 63 | 64 | export const runCommand = ( 65 | openApiPath: string, 66 | schemasPath: string, 67 | propertiesToExport?: string, 68 | logger = pino() 69 | ) => { 70 | fs.removeSync(schemasPath) 71 | fs.ensureDirSync(schemasPath) 72 | 73 | let openAPIContent 74 | 75 | try { 76 | openAPIContent = fs.readFileSync(openApiPath, 'utf8') 77 | } catch (e) { 78 | logger.error('❌ Could not find the OpenAPI file') 79 | exit(1) 80 | } 81 | 82 | const parsedOpenAPIContent = YAML.parse(openAPIContent) 83 | 84 | const definitionKeywords = [ 85 | ...new Set([ 86 | ...(propertiesToExport?.split(',') || []), 87 | 'components.schemas' 88 | ]) 89 | ] 90 | 91 | try { 92 | const generatedSchema = fromSchema(parsedOpenAPIContent, { 93 | definitionKeywords 94 | }) 95 | 96 | definitionKeywords.forEach(key => { 97 | const schema: JSONSchema4 = _get(generatedSchema, key) 98 | const isArray = Array.isArray(_get(parsedOpenAPIContent, key)) 99 | processSchema(schema, schemasPath, key, isArray) 100 | }) 101 | } catch (error) { 102 | logger.warn('Failed to convert non-object attribute, skipping') 103 | return 104 | } 105 | 106 | logger.info('✅ JSON schemas generated successfully from OpenAPI file') 107 | } 108 | 109 | const main = () => { 110 | const options = oas2json.optsWithGlobals() 111 | runCommand(options.input, options.output, options.properties, options.logger) 112 | } 113 | 114 | const oas2json = new Command('oas2json') 115 | 116 | const description = `This command will generate JSON schemas from an OpenAPI file. 117 | 118 | Examples: 119 | $ openapi-transformer-toolkit oas2json -i ./openapi.yml -o ./schemas 120 | ` 121 | 122 | oas2json 123 | .summary('Create JSON schemas from an OpenAPI file') 124 | .description(description) 125 | .requiredOption('-i, --input ', 'Path to the OpenAPI file') 126 | .requiredOption( 127 | '-o, --output ', 128 | 'Path to the folder where to output the schemas' 129 | ) 130 | .option( 131 | '-p, --properties ', 132 | 'Comma-separated list of properties to convert from the OpenAPI file' 133 | ) 134 | .allowUnknownOption() 135 | .allowExcessArguments(true) 136 | .action(main) 137 | 138 | export { oas2json } 139 | -------------------------------------------------------------------------------- /src/commands/oas2ts.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander' 2 | import fs from 'fs-extra' 3 | import os from 'os' 4 | import path from 'path' 5 | import pino, { Logger } from 'pino' 6 | import type { Json2TsOptions } from '../types/Json2TsOptions' 7 | import { readConfigFile } from '../utils/read-config-file.js' 8 | import { runCommand as runJson2TsCommand } from './json2ts.js' 9 | import { runCommand as runOas2JsonCommand } from './oas2json.js' 10 | 11 | const TEMP_FOLDER = path.join(os.tmpdir(), 'temp-json-schemas') 12 | 13 | const cleanUpTempFolder = (logger: Logger) => 14 | fs.remove(TEMP_FOLDER).catch(error => { 15 | logger.error('❌ Failed to clean up temporary folder:', error.message) 16 | }) 17 | 18 | export const runCommand = async ( 19 | openApiPath: string, 20 | tsTypesPath: string, 21 | customOptions?: Json2TsOptions, 22 | logger: Logger = pino() 23 | ) => { 24 | try { 25 | const silentLogger = pino({ level: 'silent' }) 26 | const schemasDir = path.join(TEMP_FOLDER, 'components.schemas') 27 | runOas2JsonCommand(openApiPath, TEMP_FOLDER, undefined, silentLogger) 28 | 29 | await runJson2TsCommand( 30 | schemasDir, 31 | tsTypesPath, 32 | customOptions, 33 | silentLogger 34 | ) 35 | 36 | logger.info('✅ TypeScript types generated successfully from OpenAPI file') 37 | } catch (error) { 38 | logger.error( 39 | '❌ An error occurred during the process:', 40 | (error as Error).message 41 | ) 42 | } finally { 43 | await cleanUpTempFolder(logger) 44 | } 45 | } 46 | 47 | const main = async () => { 48 | const options = oas2ts.optsWithGlobals() 49 | const customOptions = options.config ? readConfigFile(options.config) : {} 50 | runCommand(options.input, options.output, customOptions, options.logger) 51 | } 52 | 53 | const oas2ts = new Command('oas2ts') 54 | 55 | const description = `This command will generate TypeScript types from an OpenAPI file. 56 | 57 | Examples: 58 | $ openapi-transformer-toolkit oas2ts -i ./openapi.yml -o ./types 59 | $ openapi-transformer-toolkit oas2ts -i ./openapi.yml -o ./types -c ./config.json 60 | ` 61 | 62 | oas2ts 63 | .summary('Create TypeScript types from an OpenAPI file') 64 | .description(description) 65 | .requiredOption('-i, --input ', 'Path to the OpenAPI file') 66 | .requiredOption( 67 | '-o, --output ', 68 | 'Path to the folder where to output the TypeScript types' 69 | ) 70 | .option( 71 | '-c, --config ', 72 | 'Path to the JSON/JS config file with these possible options: https://www.npmjs.com/package/json-schema-to-typescript' 73 | ) 74 | .allowUnknownOption() 75 | .allowExcessArguments(true) 76 | .action(main) 77 | 78 | export { oas2ts } 79 | -------------------------------------------------------------------------------- /src/commands/oas2tson.ts: -------------------------------------------------------------------------------- 1 | import $RefParser, { ParserOptions } from '@apidevtools/json-schema-ref-parser' 2 | import { Command } from 'commander' 3 | import filenamify from 'filenamify' 4 | import fs from 'fs-extra' 5 | import _get from 'lodash.get' 6 | import _trimStart from 'lodash.trimstart' 7 | import path from 'path' 8 | import pino from 'pino' 9 | import { exit } from 'process' 10 | import YAML from 'yaml' 11 | 12 | import os from 'os' 13 | import prettier from 'prettier' 14 | 15 | import type { JSONSchema4 } from 'json-schema' 16 | 17 | import type { Oas2Tson } from '../types/Oas2Tson' 18 | import type SchemasMetaData from '../types/SchemasMetaData' 19 | import { fromSchema } from '../utils/openapi-schema-to-json-schema-wrapper.js' 20 | 21 | const COMPONENT_REF_REGEXP = /#\/components\/schemas\/[^"]+/g 22 | const outputSchemasMetaData: SchemasMetaData[] = [] 23 | 24 | export const adaptSchema = ( 25 | generatedSchema: JSONSchema4, 26 | name: string, 27 | filename: string 28 | ) => { 29 | delete generatedSchema.$schema 30 | generatedSchema.title = name 31 | generatedSchema.$id = `${filename}.json` 32 | } 33 | 34 | const processSchema = ( 35 | schema: JSONSchema4, 36 | schemasPath: string, 37 | definitionKeyword: string, 38 | isArray: boolean 39 | ) => { 40 | Object.entries(schema).forEach(([key, value]) => { 41 | // for elements in an array the name would be its index if we were 42 | // to just use its key, so go into the parsed schema and get the 43 | // actual name so the files are more easily identifiable 44 | const name = isArray ? value.name : key 45 | const filename = _trimStart(filenamify(name, { replacement: '-' }), '-') 46 | 47 | adaptSchema(value, name, filename) 48 | 49 | let schemaAsString = JSON.stringify(value, null, 2) 50 | // N.B. - this obviously only supports refs where the string contains 'components/schemas' 51 | // if we want to support refs in places other than this, we'll need to revisit this 52 | // approach to be more flexible 53 | const refs = schemaAsString.match(COMPONENT_REF_REGEXP) 54 | refs?.forEach(ref => { 55 | const refName = ref.split('/').slice(-1) 56 | schemaAsString = schemaAsString.replace(ref, `${refName}.json`) 57 | }) 58 | 59 | const destinationDir = path.join(schemasPath, 'tempjson') 60 | const destinationPath = path.join(destinationDir, `${filename}.json`) 61 | 62 | outputSchemasMetaData.push({ dir: destinationDir, path: destinationPath }) 63 | 64 | fs.ensureDirSync(destinationDir) 65 | fs.writeFileSync(destinationPath, schemaAsString) 66 | }) 67 | } 68 | 69 | const parserOptions: ParserOptions = { 70 | dereference: { 71 | onDereference: (path: string, value: JSONSchema4) => { 72 | delete value.$id 73 | } 74 | } 75 | } 76 | 77 | const processJSON = async ( 78 | schemasPath: string, 79 | tempdir: string, 80 | excludeDereferencedIds?: boolean 81 | ) => { 82 | fs.ensureDirSync(schemasPath) 83 | for (const currentSchema of outputSchemasMetaData) { 84 | /** 85 | * monitor https://github.com/APIDevTools/json-schema-ref-parser/issues/342 86 | * to check if they accept a flag to exclude Ids and eventually remove the onDereference callback 87 | */ 88 | const dereferencedSchema = await (excludeDereferencedIds 89 | ? $RefParser.dereference(currentSchema.path, parserOptions) 90 | : $RefParser.dereference(currentSchema.path)) 91 | 92 | const fileName = path.parse(currentSchema.path).name 93 | const tsSchema = `export const ${fileName} = ${JSON.stringify( 94 | dereferencedSchema 95 | )} as const` 96 | const formattedSchema = await prettier.format(tsSchema, { 97 | parser: 'typescript' 98 | }) 99 | fs.writeFileSync(path.join(schemasPath, `${fileName}.ts`), formattedSchema) 100 | } 101 | fs.removeSync(path.join(tempdir, 'tempjson')) 102 | } 103 | 104 | export const runCommand = async ( 105 | openApiPath: string, 106 | schemasPath: string, 107 | propertiesToExport?: string, 108 | excludeDereferencedIds?: boolean, 109 | logger = pino() 110 | ) => { 111 | fs.removeSync(schemasPath) 112 | fs.ensureDirSync(schemasPath) 113 | 114 | let openAPIContent 115 | 116 | try { 117 | openAPIContent = fs.readFileSync(openApiPath, 'utf8') 118 | } catch (e) { 119 | logger.error('❌ Could not find the OpenAPI file') 120 | exit(1) 121 | } 122 | 123 | const parsedOpenAPIContent = YAML.parse(openAPIContent) 124 | 125 | const definitionKeywords = [ 126 | ...new Set([ 127 | ...(propertiesToExport?.split(',') || []), 128 | 'components.schemas' 129 | ]) 130 | ] 131 | 132 | try { 133 | const generatedSchema = fromSchema(parsedOpenAPIContent, { 134 | definitionKeywords 135 | }) 136 | 137 | const tempdir = os.tmpdir() 138 | definitionKeywords.forEach(key => { 139 | const schema = _get(generatedSchema, key) 140 | const isArray = Array.isArray(_get(parsedOpenAPIContent, key)) 141 | processSchema(schema, tempdir, key, isArray) 142 | }) 143 | await processJSON(schemasPath, tempdir, excludeDereferencedIds) 144 | } catch (error) { 145 | logger.warn(error, 'Failed to convert non-object attribute, skipping') 146 | return 147 | } 148 | 149 | logger.info('✅ TS schemas generated successfully from OpenAPI file') 150 | } 151 | 152 | const main = () => { 153 | const options = oas2tson.optsWithGlobals() 154 | runCommand( 155 | options.input, 156 | options.output, 157 | options.properties, 158 | options.excludeDereferencedIds, 159 | options.logger 160 | ) 161 | } 162 | 163 | const oas2tson = new Command('oas2tson') 164 | 165 | const description = `This command will generate JSON schemas from an OpenAPI file. 166 | 167 | Examples: 168 | $ openapi-transformer-toolkit oas2tson -i ./openapi.yml -o ./schemas 169 | ` 170 | 171 | oas2tson 172 | .summary('Create JSON schemas from an OpenAPI file') 173 | .description(description) 174 | .requiredOption('-i, --input ', 'Path to the OpenAPI file') 175 | .requiredOption( 176 | '-o, --output ', 177 | 'Path to the folder where to output the schemas' 178 | ) 179 | .option( 180 | '-p, --properties ', 181 | 'Comma-separated list of properties to convert from the OpenAPI file' 182 | ) 183 | .option('--excludeDereferencedIds', 'exclude $id of dereferenced schemas') 184 | .allowUnknownOption() 185 | .allowExcessArguments(true) 186 | .action(main) 187 | 188 | export { oas2tson } 189 | -------------------------------------------------------------------------------- /src/types/Json2TsOptions.d.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'json-schema-to-typescript' 2 | import { Logger } from 'pino' 3 | 4 | export type Json2TsArgs = { 5 | input: string 6 | output: string 7 | config?: string 8 | muteLogger?: Logger 9 | } 10 | 11 | export type Json2TsDefaultOptions = Pick< 12 | Options, 13 | 'cwd' | 'bannerComment' | 'declareExternallyReferenced' 14 | > 15 | 16 | export type Json2TsOptions = Partial 17 | -------------------------------------------------------------------------------- /src/types/Oas2Tson.d.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from 'pino' 2 | 3 | export type Oas2Tson = { 4 | input: string 5 | output: string 6 | properties?: string 7 | excludeDereferencedIds?: boolean 8 | logger?: Logger 9 | } 10 | -------------------------------------------------------------------------------- /src/types/SchemasMetaData.d.ts: -------------------------------------------------------------------------------- 1 | export default interface SchemasMetaData { 2 | dir: string 3 | path: string 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/do-not-edit-text.ts: -------------------------------------------------------------------------------- 1 | export const doNotEditText = `/* eslint-disable */ 2 | /** 3 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 5 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 6 | */ 7 | 8 | ` 9 | -------------------------------------------------------------------------------- /src/utils/openapi-schema-to-json-schema-wrapper.ts: -------------------------------------------------------------------------------- 1 | export { fromSchema } from '@openapi-contrib/openapi-schema-to-json-schema' 2 | -------------------------------------------------------------------------------- /src/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import { join as joinDesm } from 'desm' 2 | import { join, resolve } from 'node:path' 3 | import { cwd } from 'node:process' 4 | 5 | const workingDirectory = cwd() 6 | const packageRoot = joinDesm(import.meta.url, '..', '..') 7 | 8 | const resolvePath = (basePath: string, ...pathParts: string[]): string => 9 | resolve(join(basePath, ...pathParts)) 10 | 11 | export const resolveFromPackageRoot = (...pathParts: string[]) => 12 | resolvePath(packageRoot, ...pathParts) 13 | 14 | export const resolveFromWorkingDirectory = (...pathParts: string[]) => 15 | resolvePath(workingDirectory, ...pathParts) 16 | -------------------------------------------------------------------------------- /src/utils/read-config-file.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import pino from 'pino' 3 | import { exit } from 'process' 4 | import { resolveFromWorkingDirectory } from './paths.js' 5 | 6 | const readConfigFile = (configPath: string) => { 7 | const logger = pino() 8 | const resolvedPath = resolveFromWorkingDirectory(configPath) 9 | 10 | try { 11 | return require(resolvedPath) 12 | } catch (error) { 13 | try { 14 | const fileContents = fs.readFileSync(resolvedPath, 'utf-8') 15 | return JSON.parse(fileContents) 16 | } catch (jsonError) { 17 | logger.error( 18 | '❌ Could not load the config file as a JS module or parse it as JSON. Please check the file content.' 19 | ) 20 | exit(1) 21 | } 22 | } 23 | } 24 | 25 | export { readConfigFile } 26 | -------------------------------------------------------------------------------- /test/fixtures/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Swagger Petstore - OpenAPI 3.0 4 | description: |- 5 | This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about 6 | Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! 7 | You can now help us improve the API whether it's by making changes to the definition itself or to the code. 8 | That way, with time, we can improve the API in general, and expose some of the new features in OAS3. 9 | 10 | _If you're looking for the Swagger 2.0/OAS 2.0 version of Petstore, then click [here](https://editor.swagger.io/?url=https://petstore.swagger.io/v2/swagger.yaml). Alternatively, you can load via the `Edit > Load Petstore OAS 2.0` menu option!_ 11 | 12 | Some useful links: 13 | - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) 14 | - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) 15 | termsOfService: http://swagger.io/terms/ 16 | contact: 17 | email: apiteam@swagger.io 18 | license: 19 | name: Apache 2.0 20 | url: http://www.apache.org/licenses/LICENSE-2.0.html 21 | version: 1.0.11 22 | externalDocs: 23 | description: Find out more about Swagger 24 | url: http://swagger.io 25 | servers: 26 | - url: https://petstore3.swagger.io/api/v3 27 | tags: 28 | - name: pet 29 | description: Everything about your Pets 30 | externalDocs: 31 | description: Find out more 32 | url: http://swagger.io 33 | - name: store 34 | description: Access to Petstore orders 35 | externalDocs: 36 | description: Find out more about our store 37 | url: http://swagger.io 38 | - name: user 39 | description: Operations about user 40 | paths: 41 | /pet: 42 | put: 43 | tags: 44 | - pet 45 | summary: Update an existing pet 46 | description: Update an existing pet by Id 47 | operationId: updatePet 48 | requestBody: 49 | $ref: '#/components/requestBodies/PetBody' 50 | responses: 51 | '200': 52 | description: Successful operation 53 | content: 54 | application/json: 55 | schema: 56 | $ref: '#/components/schemas/Pet' 57 | application/xml: 58 | schema: 59 | $ref: '#/components/schemas/Pet' 60 | '400': 61 | description: Invalid ID supplied 62 | '404': 63 | description: Pet not found 64 | '405': 65 | description: Validation exception 66 | security: 67 | - petstore_auth: 68 | - write:pets 69 | - read:pets 70 | post: 71 | tags: 72 | - pet 73 | summary: Add a new pet to the store 74 | description: Add a new pet to the store 75 | operationId: addPet 76 | requestBody: 77 | $ref: '#/components/requestBodies/PetBody' 78 | responses: 79 | '200': 80 | description: Successful operation 81 | content: 82 | application/json: 83 | schema: 84 | $ref: '#/components/schemas/Pet' 85 | application/xml: 86 | schema: 87 | $ref: '#/components/schemas/Pet' 88 | '405': 89 | description: Invalid input 90 | security: 91 | - petstore_auth: 92 | - write:pets 93 | - read:pets 94 | /pet/findByStatus: 95 | get: 96 | tags: 97 | - pet 98 | summary: Finds Pets by status 99 | description: Multiple status values can be provided with comma separated strings 100 | operationId: findPetsByStatus 101 | parameters: 102 | - name: status 103 | in: query 104 | description: Status values that need to be considered for filter 105 | required: false 106 | explode: true 107 | schema: 108 | type: string 109 | default: available 110 | enum: 111 | - available 112 | - pending 113 | - sold 114 | responses: 115 | '200': 116 | description: successful operation 117 | content: 118 | application/json: 119 | schema: 120 | type: array 121 | items: 122 | $ref: '#/components/schemas/Pet' 123 | application/xml: 124 | schema: 125 | type: array 126 | items: 127 | $ref: '#/components/schemas/Pet' 128 | '400': 129 | description: Invalid status value 130 | security: 131 | - petstore_auth: 132 | - write:pets 133 | - read:pets 134 | /pet/findByTags: 135 | get: 136 | tags: 137 | - pet 138 | summary: Finds Pets by tags 139 | description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 140 | operationId: findPetsByTags 141 | parameters: 142 | - name: tags 143 | in: query 144 | description: Tags to filter by 145 | required: false 146 | explode: true 147 | schema: 148 | type: array 149 | items: 150 | type: string 151 | responses: 152 | '200': 153 | description: successful operation 154 | content: 155 | application/json: 156 | schema: 157 | type: array 158 | items: 159 | $ref: '#/components/schemas/Pet' 160 | application/xml: 161 | schema: 162 | type: array 163 | items: 164 | $ref: '#/components/schemas/Pet' 165 | '400': 166 | description: Invalid tag value 167 | security: 168 | - petstore_auth: 169 | - write:pets 170 | - read:pets 171 | /pet/{petId}: 172 | get: 173 | tags: 174 | - pet 175 | summary: Find pet by ID 176 | description: Returns a single pet 177 | operationId: getPetById 178 | parameters: 179 | - name: petId 180 | in: path 181 | description: ID of pet to return 182 | required: true 183 | schema: 184 | type: integer 185 | format: int64 186 | responses: 187 | '200': 188 | description: successful operation 189 | content: 190 | application/json: 191 | schema: 192 | $ref: '#/components/schemas/Pet' 193 | application/xml: 194 | schema: 195 | $ref: '#/components/schemas/Pet' 196 | '400': 197 | description: Invalid ID supplied 198 | '404': 199 | description: Pet not found 200 | security: 201 | - api_key: [] 202 | - petstore_auth: 203 | - write:pets 204 | - read:pets 205 | post: 206 | tags: 207 | - pet 208 | summary: Updates a pet in the store with form data 209 | description: '' 210 | operationId: updatePetWithForm 211 | parameters: 212 | - name: petId 213 | in: path 214 | description: ID of pet that needs to be updated 215 | required: true 216 | schema: 217 | type: integer 218 | format: int64 219 | - name: name 220 | in: query 221 | description: Name of pet that needs to be updated 222 | schema: 223 | type: string 224 | - name: status 225 | in: query 226 | description: Status of pet that needs to be updated 227 | schema: 228 | type: string 229 | responses: 230 | '405': 231 | description: Invalid input 232 | security: 233 | - petstore_auth: 234 | - write:pets 235 | - read:pets 236 | delete: 237 | tags: 238 | - pet 239 | summary: Deletes a pet 240 | description: delete a pet 241 | operationId: deletePet 242 | parameters: 243 | - name: api_key 244 | in: header 245 | description: '' 246 | required: false 247 | schema: 248 | type: string 249 | - name: petId 250 | in: path 251 | description: Pet id to delete 252 | required: true 253 | schema: 254 | type: integer 255 | format: int64 256 | responses: 257 | '400': 258 | description: Invalid pet value 259 | security: 260 | - petstore_auth: 261 | - write:pets 262 | - read:pets 263 | /pet/{petId}/uploadImage: 264 | post: 265 | tags: 266 | - pet 267 | summary: uploads an image 268 | description: '' 269 | operationId: uploadFile 270 | parameters: 271 | - name: petId 272 | in: path 273 | description: ID of pet to update 274 | required: true 275 | schema: 276 | type: integer 277 | format: int64 278 | - name: additionalMetadata 279 | in: query 280 | description: Additional Metadata 281 | required: false 282 | schema: 283 | type: string 284 | requestBody: 285 | content: 286 | application/octet-stream: 287 | schema: 288 | type: string 289 | format: binary 290 | responses: 291 | '200': 292 | description: successful operation 293 | content: 294 | application/json: 295 | schema: 296 | $ref: '#/components/schemas/ApiResponse' 297 | security: 298 | - petstore_auth: 299 | - write:pets 300 | - read:pets 301 | /store/inventory: 302 | get: 303 | tags: 304 | - store 305 | summary: Returns pet inventories by status 306 | description: Returns a map of status codes to quantities 307 | operationId: getInventory 308 | responses: 309 | '200': 310 | description: successful operation 311 | content: 312 | application/json: 313 | schema: 314 | type: object 315 | additionalProperties: 316 | type: integer 317 | format: int32 318 | security: 319 | - api_key: [] 320 | /store/order: 321 | post: 322 | tags: 323 | - store 324 | summary: Place an order for a pet 325 | description: Place a new order in the store 326 | operationId: placeOrder 327 | requestBody: 328 | content: 329 | application/json: 330 | schema: 331 | $ref: '#/components/schemas/Order' 332 | application/xml: 333 | schema: 334 | $ref: '#/components/schemas/Order' 335 | application/x-www-form-urlencoded: 336 | schema: 337 | $ref: '#/components/schemas/Order' 338 | responses: 339 | '200': 340 | description: successful operation 341 | content: 342 | application/json: 343 | schema: 344 | $ref: '#/components/schemas/Order' 345 | '405': 346 | description: Invalid input 347 | /store/order/{orderId}: 348 | get: 349 | tags: 350 | - store 351 | summary: Find purchase order by ID 352 | description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. 353 | operationId: getOrderById 354 | parameters: 355 | - name: orderId 356 | in: path 357 | description: ID of order that needs to be fetched 358 | required: true 359 | schema: 360 | type: integer 361 | format: int64 362 | responses: 363 | '200': 364 | description: successful operation 365 | content: 366 | application/json: 367 | schema: 368 | $ref: '#/components/schemas/Order' 369 | application/xml: 370 | schema: 371 | $ref: '#/components/schemas/Order' 372 | '400': 373 | description: Invalid ID supplied 374 | '404': 375 | description: Order not found 376 | delete: 377 | tags: 378 | - store 379 | summary: Delete purchase order by ID 380 | description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors 381 | operationId: deleteOrder 382 | parameters: 383 | - name: orderId 384 | in: path 385 | description: ID of the order that needs to be deleted 386 | required: true 387 | schema: 388 | type: integer 389 | format: int64 390 | responses: 391 | '400': 392 | description: Invalid ID supplied 393 | '404': 394 | description: Order not found 395 | /user: 396 | post: 397 | tags: 398 | - user 399 | summary: Create user 400 | description: This can only be done by the logged in user. 401 | operationId: createUser 402 | requestBody: 403 | $ref: '#/components/requestBodies/UserBody' 404 | responses: 405 | default: 406 | description: successful operation 407 | content: 408 | application/json: 409 | schema: 410 | $ref: '#/components/schemas/User' 411 | application/xml: 412 | schema: 413 | $ref: '#/components/schemas/User' 414 | /user/createWithList: 415 | post: 416 | tags: 417 | - user 418 | summary: Creates list of users with given input array 419 | description: Creates list of users with given input array 420 | operationId: createUsersWithListInput 421 | requestBody: 422 | content: 423 | application/json: 424 | schema: 425 | type: array 426 | items: 427 | $ref: '#/components/schemas/User' 428 | responses: 429 | '200': 430 | description: Successful operation 431 | content: 432 | application/json: 433 | schema: 434 | $ref: '#/components/schemas/User' 435 | application/xml: 436 | schema: 437 | $ref: '#/components/schemas/User' 438 | default: 439 | description: successful operation 440 | /user/login: 441 | get: 442 | tags: 443 | - user 444 | summary: Logs user into the system 445 | description: '' 446 | operationId: loginUser 447 | parameters: 448 | - name: username 449 | in: query 450 | description: The user name for login 451 | required: false 452 | schema: 453 | type: string 454 | - name: password 455 | in: query 456 | description: The password for login in clear text 457 | required: false 458 | schema: 459 | type: string 460 | responses: 461 | '200': 462 | description: successful operation 463 | headers: 464 | X-Rate-Limit: 465 | description: calls per hour allowed by the user 466 | schema: 467 | type: integer 468 | format: int32 469 | X-Expires-After: 470 | description: date in UTC when token expires 471 | schema: 472 | type: string 473 | format: date-time 474 | content: 475 | application/xml: 476 | schema: 477 | type: string 478 | application/json: 479 | schema: 480 | type: string 481 | '400': 482 | description: Invalid username/password supplied 483 | /user/logout: 484 | get: 485 | tags: 486 | - user 487 | summary: Logs out current logged in user session 488 | description: '' 489 | operationId: logoutUser 490 | parameters: [] 491 | responses: 492 | default: 493 | description: successful operation 494 | /user/{username}: 495 | get: 496 | tags: 497 | - user 498 | summary: Get user by user name 499 | description: '' 500 | operationId: getUserByName 501 | parameters: 502 | - name: username 503 | in: path 504 | description: 'The name that needs to be fetched. Use user1 for testing. ' 505 | required: true 506 | schema: 507 | type: string 508 | responses: 509 | '200': 510 | description: successful operation 511 | content: 512 | application/json: 513 | schema: 514 | $ref: '#/components/schemas/User' 515 | application/xml: 516 | schema: 517 | $ref: '#/components/schemas/User' 518 | '400': 519 | description: Invalid username supplied 520 | '404': 521 | description: User not found 522 | put: 523 | tags: 524 | - user 525 | summary: Update user 526 | description: This can only be done by the logged in user. 527 | operationId: updateUser 528 | parameters: 529 | - name: username 530 | in: path 531 | description: name that need to be deleted 532 | required: true 533 | schema: 534 | type: string 535 | requestBody: 536 | $ref: '#/components/requestBodies/UserBody' 537 | responses: 538 | default: 539 | description: successful operation 540 | delete: 541 | tags: 542 | - user 543 | summary: Delete user 544 | description: This can only be done by the logged in user. 545 | operationId: deleteUser 546 | parameters: 547 | - name: username 548 | in: path 549 | description: The name that needs to be deleted 550 | required: true 551 | schema: 552 | type: string 553 | responses: 554 | '400': 555 | description: Invalid username supplied 556 | '404': 557 | description: User not found 558 | components: 559 | schemas: 560 | DateExample: 561 | type: string 562 | format: date-time 563 | Order: 564 | type: object 565 | properties: 566 | id: 567 | type: integer 568 | format: int64 569 | example: 10 570 | petId: 571 | type: integer 572 | format: int64 573 | example: 198772 574 | quantity: 575 | type: integer 576 | format: int32 577 | example: 7 578 | shipDate: 579 | type: string 580 | format: date-time 581 | status: 582 | type: string 583 | description: Order Status 584 | example: approved 585 | enum: 586 | - placed 587 | - approved 588 | - delivered 589 | complete: 590 | type: boolean 591 | xml: 592 | name: order 593 | Customer: 594 | type: object 595 | properties: 596 | id: 597 | type: integer 598 | format: int64 599 | example: 100000 600 | username: 601 | type: string 602 | example: fehguy 603 | address: 604 | type: array 605 | xml: 606 | name: addresses 607 | wrapped: true 608 | items: 609 | $ref: '#/components/schemas/Address' 610 | xml: 611 | name: customer 612 | Address: 613 | type: object 614 | properties: 615 | street: 616 | type: string 617 | example: 437 Lytton 618 | city: 619 | type: string 620 | example: Palo Alto 621 | state: 622 | type: string 623 | example: CA 624 | zip: 625 | type: string 626 | example: '94301' 627 | xml: 628 | name: address 629 | Category: 630 | type: object 631 | properties: 632 | id: 633 | type: integer 634 | format: int64 635 | example: 1 636 | name: 637 | type: string 638 | example: Dogs 639 | xml: 640 | name: category 641 | User: 642 | type: object 643 | properties: 644 | id: 645 | type: integer 646 | format: int64 647 | example: 10 648 | username: 649 | type: string 650 | example: theUser 651 | firstName: 652 | type: string 653 | example: John 654 | lastName: 655 | type: string 656 | example: James 657 | email: 658 | type: string 659 | example: john@email.com 660 | password: 661 | type: string 662 | example: '12345' 663 | phone: 664 | type: string 665 | example: '12345' 666 | userStatus: 667 | type: integer 668 | description: User Status 669 | format: int32 670 | example: 1 671 | xml: 672 | name: user 673 | Tag: 674 | type: object 675 | properties: 676 | id: 677 | type: integer 678 | format: int64 679 | name: 680 | type: string 681 | xml: 682 | name: tag 683 | Pet: 684 | required: 685 | - name 686 | - photoUrls 687 | type: object 688 | properties: 689 | id: 690 | type: integer 691 | format: int64 692 | example: 10 693 | name: 694 | type: string 695 | example: doggie 696 | category: 697 | $ref: '#/components/schemas/Category' 698 | photoUrls: 699 | type: array 700 | xml: 701 | wrapped: true 702 | items: 703 | type: string 704 | xml: 705 | name: photoUrl 706 | tags: 707 | type: array 708 | xml: 709 | wrapped: true 710 | items: 711 | $ref: '#/components/schemas/Tag' 712 | status: 713 | type: string 714 | description: pet status in the store 715 | enum: 716 | - available 717 | - pending 718 | - sold 719 | nullableValue: 720 | type: string 721 | nullable: true 722 | description: example nullable value 723 | xml: 724 | name: pet 725 | ApiResponse: 726 | type: object 727 | properties: 728 | code: 729 | type: integer 730 | format: int32 731 | type: 732 | type: string 733 | message: 734 | type: string 735 | fooBARBaz: 736 | $ref: '#/components/schemas/FooBARBaz' 737 | xml: 738 | name: '##default' 739 | FooBARBaz: 740 | type: string 741 | description: should not fail; should generate FooBARBaz.* 742 | requestBodies: 743 | PetBody: 744 | description: A JSON object containing pet information 745 | required: true 746 | content: 747 | application/json: 748 | schema: 749 | $ref: '#/components/schemas/Pet' 750 | application/xml: 751 | schema: 752 | $ref: '#/components/schemas/Pet' 753 | application/x-www-form-urlencoded: 754 | schema: 755 | $ref: '#/components/schemas/Pet' 756 | UserBody: 757 | description: A JSON object containing user information 758 | required: true 759 | content: 760 | application/json: 761 | schema: 762 | $ref: '#/components/schemas/User' 763 | application/xml: 764 | schema: 765 | $ref: '#/components/schemas/User' 766 | application/x-www-form-urlencoded: 767 | schema: 768 | $ref: '#/components/schemas/User' 769 | securitySchemes: 770 | petstore_auth: 771 | type: oauth2 772 | flows: 773 | implicit: 774 | authorizationUrl: https://petstore3.swagger.io/oauth/authorize 775 | scopes: 776 | write:pets: modify pets in your account 777 | read:pets: read your pets 778 | api_key: 779 | type: apiKey 780 | name: api_key 781 | in: header 782 | -------------------------------------------------------------------------------- /test/fixtures/schemas/Address.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "street": { 5 | "type": "string" 6 | }, 7 | "city": { 8 | "type": "string" 9 | }, 10 | "state": { 11 | "type": "string" 12 | }, 13 | "zip": { 14 | "type": "string" 15 | } 16 | }, 17 | "title": "Address", 18 | "$id": "Address.json" 19 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/ApiResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "code": { 5 | "type": "integer", 6 | "format": "int32", 7 | "minimum": -2147483648, 8 | "maximum": 2147483647 9 | }, 10 | "type": { 11 | "type": "string" 12 | }, 13 | "message": { 14 | "type": "string" 15 | } 16 | }, 17 | "title": "ApiResponse", 18 | "$id": "ApiResponse.json" 19 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/Category.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "name": { 11 | "type": "string" 12 | } 13 | }, 14 | "title": "Category", 15 | "$id": "Category.json" 16 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/Customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "username": { 11 | "type": "string" 12 | }, 13 | "address": { 14 | "type": "array", 15 | "items": { 16 | "$ref": "Address.json" 17 | } 18 | } 19 | }, 20 | "title": "Customer", 21 | "$id": "Customer.json" 22 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/DateExample.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "format": "date-time", 4 | "title": "DateExample", 5 | "$id": "DateExample.json", 6 | "tsType": "Date" 7 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/Order.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "petId": { 11 | "type": "integer", 12 | "format": "int64", 13 | "minimum": -9223372036854776000, 14 | "maximum": 9223372036854776000 15 | }, 16 | "quantity": { 17 | "type": "integer", 18 | "format": "int32", 19 | "minimum": -2147483648, 20 | "maximum": 2147483647 21 | }, 22 | "shipDate": { 23 | "type": "string", 24 | "format": "date-time" 25 | }, 26 | "status": { 27 | "type": "string", 28 | "description": "Order Status", 29 | "enum": [ 30 | "placed", 31 | "approved", 32 | "delivered" 33 | ] 34 | }, 35 | "complete": { 36 | "type": "boolean" 37 | } 38 | }, 39 | "title": "Order", 40 | "$id": "Order.json" 41 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/Pet.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": [ 3 | "name", 4 | "photoUrls" 5 | ], 6 | "type": "object", 7 | "properties": { 8 | "id": { 9 | "type": "integer", 10 | "format": "int64", 11 | "minimum": -9223372036854776000, 12 | "maximum": 9223372036854776000 13 | }, 14 | "name": { 15 | "type": "string" 16 | }, 17 | "category": { 18 | "$ref": "Category.json" 19 | }, 20 | "photoUrls": { 21 | "type": "array", 22 | "items": { 23 | "type": "string" 24 | } 25 | }, 26 | "tags": { 27 | "type": "array", 28 | "items": { 29 | "$ref": "Tag.json" 30 | } 31 | }, 32 | "status": { 33 | "type": "string", 34 | "description": "pet status in the store", 35 | "enum": [ 36 | "available", 37 | "pending", 38 | "sold" 39 | ] 40 | } 41 | }, 42 | "title": "Pet", 43 | "$id": "Pet.json" 44 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/Tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "name": { 11 | "type": "string" 12 | } 13 | }, 14 | "title": "Tag", 15 | "$id": "Tag.json" 16 | } -------------------------------------------------------------------------------- /test/fixtures/schemas/User.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "id": { 5 | "type": "integer", 6 | "format": "int64", 7 | "minimum": -9223372036854776000, 8 | "maximum": 9223372036854776000 9 | }, 10 | "username": { 11 | "type": "string" 12 | }, 13 | "firstName": { 14 | "type": "string" 15 | }, 16 | "lastName": { 17 | "type": "string" 18 | }, 19 | "email": { 20 | "type": "string" 21 | }, 22 | "password": { 23 | "type": "string" 24 | }, 25 | "phone": { 26 | "type": "string" 27 | }, 28 | "userStatus": { 29 | "type": "integer", 30 | "description": "User Status", 31 | "format": "int32", 32 | "minimum": -2147483648, 33 | "maximum": 2147483647 34 | } 35 | }, 36 | "title": "User", 37 | "$id": "User.json" 38 | } -------------------------------------------------------------------------------- /test/json2ts.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import { test, TestContext } from 'node:test' 3 | import { runCommand } from '../src/commands/json2ts.js' 4 | import { doNotEditText } from '../src/utils/do-not-edit-text.js' 5 | import { resolveFromPackageRoot } from '../src/utils/paths.js' 6 | 7 | const TEST_DIRECTORY = resolveFromPackageRoot('test', 'temp') 8 | 9 | const inputPath = './test/fixtures/schemas' 10 | const outputPath = './test/temp/types-from-json' 11 | 12 | test('json2ts runCommand', async (t: TestContext) => { 13 | fs.ensureDirSync(TEST_DIRECTORY) 14 | 15 | const customOptions = { 16 | bannerComment: doNotEditText 17 | } 18 | 19 | await runCommand(inputPath, outputPath, customOptions) 20 | 21 | const generatedFiles = fs.readdirSync(outputPath) 22 | 23 | t.assert.deepStrictEqual( 24 | generatedFiles, 25 | [ 26 | 'Address.d.ts', 27 | 'ApiResponse.d.ts', 28 | 'Category.d.ts', 29 | 'Customer.d.ts', 30 | 'DateExample.d.ts', 31 | 'Order.d.ts', 32 | 'Pet.d.ts', 33 | 'Tag.d.ts', 34 | 'User.d.ts' 35 | ], 36 | 'generates the expected TS files' 37 | ) 38 | 39 | const petFile = resolveFromPackageRoot(outputPath, 'Pet.d.ts') 40 | const generatedPetFile = fs.readFileSync(petFile, 'utf-8') 41 | 42 | t.assert.deepStrictEqual( 43 | generatedPetFile, 44 | `import { Category } from "./Category"; 45 | import { Tag } from "./Tag"; 46 | 47 | /* eslint-disable */ 48 | /** 49 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 50 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 51 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 52 | */ 53 | 54 | export interface Pet { 55 | id?: number; 56 | name: string; 57 | category?: Category; 58 | photoUrls: string[]; 59 | tags?: Tag[]; 60 | /** 61 | * pet status in the store 62 | */ 63 | status?: "available" | "pending" | "sold"; 64 | [k: string]: unknown; 65 | } 66 | `, 67 | 'Pet.d.ts is created correctly' 68 | ) 69 | 70 | const customerFile = resolveFromPackageRoot(outputPath, 'Customer.d.ts') 71 | const generatedCustomerFile = fs.readFileSync(customerFile, 'utf-8') 72 | 73 | t.assert.deepStrictEqual( 74 | generatedCustomerFile, 75 | `import { Address } from "./Address"; 76 | 77 | /* eslint-disable */ 78 | /** 79 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 80 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 81 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 82 | */ 83 | 84 | export interface Customer { 85 | id?: number; 86 | username?: string; 87 | address?: Address[]; 88 | [k: string]: unknown; 89 | } 90 | `, 91 | 'Customer.d.ts is created correctly' 92 | ) 93 | }) 94 | -------------------------------------------------------------------------------- /test/oas2json.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import { describe, test, TestContext } from 'node:test' 3 | import { adaptSchema, runCommand } from '../src/commands/oas2json.js' 4 | import { resolveFromPackageRoot } from '../src/utils/paths.js' 5 | 6 | import type { JSONSchema4 } from 'json-schema' 7 | 8 | describe('oas2json', () => { 9 | test('adaptSchema function', async (t: TestContext) => { 10 | const schema: JSONSchema4 = { 11 | $schema: 'http://json-schema.org/draft-07/schema#', 12 | type: 'string', 13 | format: 'date' 14 | } 15 | 16 | adaptSchema(schema, 'TestSchema', 'TestSchema') 17 | 18 | t.assert.deepStrictEqual( 19 | schema, 20 | { 21 | type: 'string', 22 | format: 'date', 23 | title: 'TestSchema', 24 | $id: 'TestSchema.json', 25 | tsType: 'Date' 26 | }, 27 | 'works as expected' 28 | ) 29 | }) 30 | 31 | const TEST_DIRECTORY = resolveFromPackageRoot('test', 'temp') 32 | 33 | describe('runCommand function', () => { 34 | fs.ensureDirSync(TEST_DIRECTORY) 35 | const inputPath = './test/fixtures/openapi.yml' 36 | const outputPath = './test/temp/schemas' 37 | const schemasDir = `${outputPath}/components.schemas` 38 | const pathsDir = `${outputPath}/paths` 39 | 40 | test('should generate JSON schema files from the OpenAPI input', async (t: TestContext) => { 41 | runCommand(inputPath, outputPath, 'paths') 42 | 43 | const generatedSchemas = fs.readdirSync(schemasDir) 44 | t.assert.deepStrictEqual( 45 | generatedSchemas, 46 | [ 47 | 'Address.json', 48 | 'ApiResponse.json', 49 | 'Category.json', 50 | 'Customer.json', 51 | 'DateExample.json', 52 | 'FooBARBaz.json', 53 | 'Order.json', 54 | 'Pet.json', 55 | 'Tag.json', 56 | 'User.json' 57 | ], 58 | 'generates the expected JSON files for schemas' 59 | ) 60 | 61 | const generatedPaths = fs.readdirSync(pathsDir) 62 | t.assert.deepStrictEqual( 63 | generatedPaths, 64 | [ 65 | 'pet-findByStatus.json', 66 | 'pet-findByTags.json', 67 | 'pet-{petId}-uploadImage.json', 68 | 'pet-{petId}.json', 69 | 'pet.json', 70 | 'store-inventory.json', 71 | 'store-order-{orderId}.json', 72 | 'store-order.json', 73 | 'user-createWithList.json', 74 | 'user-login.json', 75 | 'user-logout.json', 76 | 'user-{username}.json', 77 | 'user.json' 78 | ], 79 | 'generates the expected JSON files for paths' 80 | ) 81 | 82 | const petSchema = fs.readJsonSync( 83 | resolveFromPackageRoot(schemasDir, 'Pet.json') 84 | ) 85 | t.assert.deepStrictEqual( 86 | petSchema, 87 | { 88 | required: ['name', 'photoUrls'], 89 | type: 'object', 90 | properties: { 91 | id: { 92 | type: 'integer', 93 | format: 'int64', 94 | minimum: -9223372036854776000, 95 | maximum: 9223372036854776000 96 | }, 97 | name: { 98 | type: 'string' 99 | }, 100 | category: { 101 | $ref: 'Category.json' 102 | }, 103 | photoUrls: { 104 | type: 'array', 105 | items: { 106 | type: 'string' 107 | } 108 | }, 109 | tags: { 110 | type: 'array', 111 | items: { 112 | $ref: 'Tag.json' 113 | } 114 | }, 115 | status: { 116 | type: 'string', 117 | description: 'pet status in the store', 118 | enum: ['available', 'pending', 'sold'] 119 | }, 120 | nullableValue: { 121 | type: ['string', 'null'], 122 | description: 'example nullable value' 123 | } 124 | }, 125 | title: 'Pet', 126 | $id: 'Pet.json' 127 | }, 128 | 'Pet.json schema is created correctly' 129 | ) 130 | 131 | const customerSchema = fs.readJsonSync( 132 | resolveFromPackageRoot(schemasDir, 'Customer.json') 133 | ) 134 | t.assert.deepStrictEqual( 135 | customerSchema, 136 | { 137 | type: 'object', 138 | properties: { 139 | id: { 140 | type: 'integer', 141 | format: 'int64', 142 | minimum: -9223372036854776000, 143 | maximum: 9223372036854776000 144 | }, 145 | username: { 146 | type: 'string' 147 | }, 148 | address: { 149 | type: 'array', 150 | items: { 151 | $ref: 'Address.json' 152 | } 153 | } 154 | }, 155 | title: 'Customer', 156 | $id: 'Customer.json' 157 | }, 158 | 'Customer.json schema is created correctly' 159 | ) 160 | 161 | const petPath = fs.readJsonSync( 162 | resolveFromPackageRoot(pathsDir, 'pet.json') 163 | ) 164 | t.assert.deepStrictEqual( 165 | petPath, 166 | { 167 | put: { 168 | tags: ['pet'], 169 | summary: 'Update an existing pet', 170 | description: 'Update an existing pet by Id', 171 | operationId: 'updatePet', 172 | requestBody: { 173 | $ref: 'PetBody.json' // File generated from #/components/requestBodies/PetBody 174 | }, 175 | responses: { 176 | 200: { 177 | description: 'Successful operation', 178 | content: { 179 | 'application/json': { 180 | schema: { 181 | $ref: 'Pet.json' 182 | } 183 | }, 184 | 'application/xml': { 185 | schema: { 186 | $ref: 'Pet.json' 187 | } 188 | } 189 | } 190 | }, 191 | 400: { 192 | description: 'Invalid ID supplied' 193 | }, 194 | 404: { 195 | description: 'Pet not found' 196 | }, 197 | 405: { 198 | description: 'Validation exception' 199 | } 200 | }, 201 | security: [ 202 | { 203 | petstore_auth: ['write:pets', 'read:pets'] 204 | } 205 | ] 206 | }, 207 | post: { 208 | tags: ['pet'], 209 | summary: 'Add a new pet to the store', 210 | description: 'Add a new pet to the store', 211 | operationId: 'addPet', 212 | requestBody: { 213 | $ref: 'PetBody.json' 214 | }, 215 | responses: { 216 | 200: { 217 | description: 'Successful operation', 218 | content: { 219 | 'application/json': { 220 | schema: { 221 | $ref: 'Pet.json' 222 | } 223 | }, 224 | 'application/xml': { 225 | schema: { 226 | $ref: 'Pet.json' 227 | } 228 | } 229 | } 230 | }, 231 | 405: { 232 | description: 'Invalid input' 233 | } 234 | }, 235 | security: [ 236 | { 237 | petstore_auth: ['write:pets', 'read:pets'] 238 | } 239 | ] 240 | }, 241 | title: '/pet', 242 | $id: 'pet.json' 243 | }, 244 | 'pet.json path is created correctly' 245 | ) 246 | 247 | const petPetIdPath = fs.readJsonSync( 248 | resolveFromPackageRoot(pathsDir, 'pet-{petId}.json') 249 | ) 250 | t.assert.deepStrictEqual( 251 | petPetIdPath, 252 | { 253 | get: { 254 | tags: ['pet'], 255 | summary: 'Find pet by ID', 256 | description: 'Returns a single pet', 257 | operationId: 'getPetById', 258 | parameters: [ 259 | { 260 | name: 'petId', 261 | in: 'path', 262 | description: 'ID of pet to return', 263 | required: true, 264 | schema: { 265 | type: 'integer', 266 | format: 'int64' 267 | } 268 | } 269 | ], 270 | responses: { 271 | 200: { 272 | description: 'successful operation', 273 | content: { 274 | 'application/json': { 275 | schema: { 276 | $ref: 'Pet.json' 277 | } 278 | }, 279 | 'application/xml': { 280 | schema: { 281 | $ref: 'Pet.json' 282 | } 283 | } 284 | } 285 | }, 286 | 400: { 287 | description: 'Invalid ID supplied' 288 | }, 289 | 404: { 290 | description: 'Pet not found' 291 | } 292 | }, 293 | security: [ 294 | { 295 | api_key: [] 296 | }, 297 | { 298 | petstore_auth: ['write:pets', 'read:pets'] 299 | } 300 | ] 301 | }, 302 | post: { 303 | tags: ['pet'], 304 | summary: 'Updates a pet in the store with form data', 305 | description: '', 306 | operationId: 'updatePetWithForm', 307 | parameters: [ 308 | { 309 | name: 'petId', 310 | in: 'path', 311 | description: 'ID of pet that needs to be updated', 312 | required: true, 313 | schema: { 314 | type: 'integer', 315 | format: 'int64' 316 | } 317 | }, 318 | { 319 | name: 'name', 320 | in: 'query', 321 | description: 'Name of pet that needs to be updated', 322 | schema: { 323 | type: 'string' 324 | } 325 | }, 326 | { 327 | name: 'status', 328 | in: 'query', 329 | description: 'Status of pet that needs to be updated', 330 | schema: { 331 | type: 'string' 332 | } 333 | } 334 | ], 335 | responses: { 336 | 405: { 337 | description: 'Invalid input' 338 | } 339 | }, 340 | security: [ 341 | { 342 | petstore_auth: ['write:pets', 'read:pets'] 343 | } 344 | ] 345 | }, 346 | delete: { 347 | tags: ['pet'], 348 | summary: 'Deletes a pet', 349 | description: 'delete a pet', 350 | operationId: 'deletePet', 351 | parameters: [ 352 | { 353 | name: 'api_key', 354 | in: 'header', 355 | description: '', 356 | required: false, 357 | schema: { 358 | type: 'string' 359 | } 360 | }, 361 | { 362 | name: 'petId', 363 | in: 'path', 364 | description: 'Pet id to delete', 365 | required: true, 366 | schema: { 367 | type: 'integer', 368 | format: 'int64' 369 | } 370 | } 371 | ], 372 | responses: { 373 | 400: { 374 | description: 'Invalid pet value' 375 | } 376 | }, 377 | security: [ 378 | { 379 | petstore_auth: ['write:pets', 'read:pets'] 380 | } 381 | ] 382 | }, 383 | title: '/pet/{petId}', 384 | $id: 'pet-petId.json' // Does not contain any invalid URI chars (i.e. curly braces) 385 | }, 386 | 'pet-{petId}.json path is created correctly' 387 | ) 388 | }) 389 | 390 | test('should convert components.schemas automatically', async (t: TestContext) => { 391 | runCommand(inputPath, outputPath, 'components.requestBodies') 392 | 393 | const generatedDirs = fs.readdirSync(outputPath) 394 | t.assert.deepStrictEqual( 395 | generatedDirs, 396 | ['components.requestBodies', 'components.schemas'], 397 | 'generates the expected directories' 398 | ) 399 | 400 | const petReqBody = fs.readJSONSync( 401 | resolveFromPackageRoot( 402 | outputPath, 403 | 'components.requestBodies/PetBody.json' 404 | ) 405 | ) 406 | t.assert.deepStrictEqual( 407 | petReqBody, 408 | { 409 | description: 'A JSON object containing pet information', 410 | required: true, 411 | content: { 412 | 'application/json': { 413 | schema: { 414 | $ref: 'Pet.json' 415 | } 416 | }, 417 | 'application/xml': { 418 | schema: { 419 | $ref: 'Pet.json' 420 | } 421 | }, 422 | 'application/x-www-form-urlencoded': { 423 | schema: { 424 | $ref: 'Pet.json' 425 | } 426 | } 427 | }, 428 | title: 'PetBody', 429 | $id: 'PetBody.json' 430 | }, 431 | 'generates the expected JSON' 432 | ) 433 | }) 434 | 435 | test('should dynamically find the name when converting an array property', async (t: TestContext) => { 436 | runCommand(inputPath, outputPath, 'tags') 437 | 438 | const generatedDirs = fs.readdirSync(outputPath) 439 | t.assert.deepStrictEqual( 440 | generatedDirs, 441 | ['components.schemas', 'tags'], 442 | 'generates the expected directories' 443 | ) 444 | const generatedFiles = fs.readdirSync(`${outputPath}/tags`) 445 | t.assert.deepStrictEqual(generatedFiles, [ 446 | 'pet.json', 447 | 'store.json', 448 | 'user.json' 449 | ]) 450 | 451 | const petTag = fs.readJSONSync( 452 | resolveFromPackageRoot(outputPath, 'tags/pet.json') 453 | ) 454 | t.assert.deepStrictEqual( 455 | petTag, 456 | { 457 | name: 'pet', 458 | description: 'Everything about your Pets', 459 | title: 'pet', 460 | $id: 'pet.json' 461 | }, 462 | 'generates the expected name for array elements' 463 | ) 464 | }) 465 | }) 466 | }) 467 | -------------------------------------------------------------------------------- /test/oas2ts.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import { describe, test, TestContext } from 'node:test' 3 | import { runCommand } from '../src/commands/oas2ts.js' 4 | import { resolveFromPackageRoot } from '../src/utils/paths.js' 5 | 6 | const TEST_DIRECTORY = resolveFromPackageRoot('test', 'temp') 7 | 8 | const inputPath = './test/fixtures/openapi.yml' 9 | const outputPath = './test/temp/types-from-oas' 10 | 11 | describe('oas2ts', () => { 12 | test('runCommand function', async (t: TestContext) => { 13 | fs.ensureDirSync(TEST_DIRECTORY) 14 | 15 | await runCommand(inputPath, outputPath) 16 | 17 | const generatedFiles = fs.readdirSync(outputPath) 18 | t.assert.deepStrictEqual( 19 | generatedFiles, 20 | [ 21 | 'Address.d.ts', 22 | 'ApiResponse.d.ts', 23 | 'Category.d.ts', 24 | 'Customer.d.ts', 25 | 'DateExample.d.ts', 26 | 'FooBARBaz.d.ts', 27 | 'Order.d.ts', 28 | 'Pet.d.ts', 29 | 'Tag.d.ts', 30 | 'User.d.ts' 31 | ], 32 | 'generates the expected TS files' 33 | ) 34 | 35 | const petFile = resolveFromPackageRoot(outputPath, 'Pet.d.ts') 36 | const generatedPetFile = fs.readFileSync(petFile, 'utf-8') 37 | 38 | t.assert.deepStrictEqual( 39 | generatedPetFile, 40 | `import { Category } from "./Category"; 41 | import { Tag } from "./Tag"; 42 | 43 | /* eslint-disable */ 44 | /** 45 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 46 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 47 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 48 | */ 49 | 50 | export interface Pet { 51 | id?: number; 52 | name: string; 53 | category?: Category; 54 | photoUrls: string[]; 55 | tags?: Tag[]; 56 | /** 57 | * pet status in the store 58 | */ 59 | status?: "available" | "pending" | "sold"; 60 | /** 61 | * example nullable value 62 | */ 63 | nullableValue?: string | null; 64 | [k: string]: unknown; 65 | } 66 | `, 67 | 'Pet.d.ts is created correctly' 68 | ) 69 | 70 | const customerFile = resolveFromPackageRoot(outputPath, 'Customer.d.ts') 71 | const generatedCustomerFile = fs.readFileSync(customerFile, 'utf-8') 72 | 73 | t.assert.deepStrictEqual( 74 | generatedCustomerFile, 75 | `import { Address } from "./Address"; 76 | 77 | /* eslint-disable */ 78 | /** 79 | * This file was automatically generated by openapi-transformer-toolkit CLI/methods. 80 | * DO NOT MODIFY IT BY HAND. Instead, modify the source OpenAPI file, 81 | * and run openapi-transformer-toolkit CLI/methods to regenerate this file. 82 | */ 83 | 84 | export interface Customer { 85 | id?: number; 86 | username?: string; 87 | address?: Address[]; 88 | [k: string]: unknown; 89 | } 90 | `, 91 | 'Customer.d.ts is created correctly' 92 | ) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /test/oas2tson.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import { describe, test, TestContext } from 'node:test' 3 | import { runCommand } from '../src/commands/oas2tson.js' 4 | import { resolveFromPackageRoot } from '../src/utils/paths.js' 5 | 6 | const TEST_DIRECTORY = resolveFromPackageRoot('test', 'temp') 7 | 8 | const inputPath = './test/fixtures/openapi.yml' 9 | const outputPath = './test/temp/typescriptobj-from-oas/ts' 10 | 11 | describe('oas2tson', () => { 12 | test('runCommand function', async (t: TestContext) => { 13 | fs.ensureDirSync(TEST_DIRECTORY) 14 | 15 | await runCommand(inputPath, outputPath) 16 | 17 | const generatedFiles = fs.readdirSync(outputPath) 18 | 19 | t.assert.deepStrictEqual( 20 | generatedFiles, 21 | [ 22 | 'Address.ts', 23 | 'ApiResponse.ts', 24 | 'Category.ts', 25 | 'Customer.ts', 26 | 'DateExample.ts', 27 | 'FooBARBaz.ts', 28 | 'Order.ts', 29 | 'Pet.ts', 30 | 'Tag.ts', 31 | 'User.ts' 32 | ], 33 | 'generates the expected TS files' 34 | ) 35 | 36 | const petFile = resolveFromPackageRoot(outputPath, 'Pet.ts') 37 | const generatedPetFile = fs.readFileSync(petFile, 'utf-8') 38 | 39 | t.assert.deepStrictEqual( 40 | generatedPetFile, 41 | `export const Pet = { 42 | required: ["name", "photoUrls"], 43 | type: "object", 44 | properties: { 45 | id: { 46 | type: "integer", 47 | format: "int64", 48 | minimum: -9223372036854776000, 49 | maximum: 9223372036854776000, 50 | }, 51 | name: { type: "string" }, 52 | category: { 53 | type: "object", 54 | properties: { 55 | id: { 56 | type: "integer", 57 | format: "int64", 58 | minimum: -9223372036854776000, 59 | maximum: 9223372036854776000, 60 | }, 61 | name: { type: "string" }, 62 | }, 63 | title: "Category", 64 | $id: "Category.json", 65 | }, 66 | photoUrls: { type: "array", items: { type: "string" } }, 67 | tags: { 68 | type: "array", 69 | items: { 70 | type: "object", 71 | properties: { 72 | id: { 73 | type: "integer", 74 | format: "int64", 75 | minimum: -9223372036854776000, 76 | maximum: 9223372036854776000, 77 | }, 78 | name: { type: "string" }, 79 | }, 80 | title: "Tag", 81 | $id: "Tag.json", 82 | }, 83 | }, 84 | status: { 85 | type: "string", 86 | description: "pet status in the store", 87 | enum: ["available", "pending", "sold"], 88 | }, 89 | nullableValue: { 90 | type: ["string", "null"], 91 | description: "example nullable value", 92 | }, 93 | }, 94 | title: "Pet", 95 | $id: "Pet.json", 96 | } as const; 97 | `, 98 | 'Pet.ts is created correctly' 99 | ) 100 | 101 | const customerFile = resolveFromPackageRoot(outputPath, 'Customer.ts') 102 | const generatedCustomerFile = fs.readFileSync(customerFile, 'utf-8') 103 | 104 | t.assert.deepStrictEqual( 105 | generatedCustomerFile, 106 | `export const Customer = { 107 | type: "object", 108 | properties: { 109 | id: { 110 | type: "integer", 111 | format: "int64", 112 | minimum: -9223372036854776000, 113 | maximum: 9223372036854776000, 114 | }, 115 | username: { type: "string" }, 116 | address: { 117 | type: "array", 118 | items: { 119 | type: "object", 120 | properties: { 121 | street: { type: "string" }, 122 | city: { type: "string" }, 123 | state: { type: "string" }, 124 | zip: { type: "string" }, 125 | }, 126 | title: "Address", 127 | $id: "Address.json", 128 | }, 129 | }, 130 | }, 131 | title: "Customer", 132 | $id: "Customer.json", 133 | } as const; 134 | `, 135 | 'Customer.ts is created correctly' 136 | ) 137 | 138 | const orderFile = resolveFromPackageRoot(outputPath, 'Order.ts') 139 | const generatedOrderFile = fs.readFileSync(orderFile, 'utf-8') 140 | 141 | t.assert.deepStrictEqual( 142 | generatedOrderFile, 143 | `export const Order = { 144 | type: "object", 145 | properties: { 146 | id: { 147 | type: "integer", 148 | format: "int64", 149 | minimum: -9223372036854776000, 150 | maximum: 9223372036854776000, 151 | }, 152 | petId: { 153 | type: "integer", 154 | format: "int64", 155 | minimum: -9223372036854776000, 156 | maximum: 9223372036854776000, 157 | }, 158 | quantity: { 159 | type: "integer", 160 | format: "int32", 161 | minimum: -2147483648, 162 | maximum: 2147483647, 163 | }, 164 | shipDate: { type: "string", format: "date-time" }, 165 | status: { 166 | type: "string", 167 | description: "Order Status", 168 | enum: ["placed", "approved", "delivered"], 169 | }, 170 | complete: { type: "boolean" }, 171 | }, 172 | title: "Order", 173 | $id: "Order.json", 174 | } as const; 175 | `, 176 | 'Order.ts is created correctly' 177 | ) 178 | 179 | const dateExampleFile = resolveFromPackageRoot(outputPath, 'DateExample.ts') 180 | const generatedDateExampleFile = fs.readFileSync(dateExampleFile, 'utf-8') 181 | 182 | t.assert.deepStrictEqual( 183 | generatedDateExampleFile, 184 | `export const DateExample = { 185 | type: "string", 186 | format: "date-time", 187 | title: "DateExample", 188 | $id: "DateExample.json", 189 | } as const; 190 | `, 191 | 'DateExample.ts is created correctly' 192 | ) 193 | }) 194 | 195 | test('runCommand function with excludeDereferencedIds', async (t: TestContext) => { 196 | fs.ensureDirSync(TEST_DIRECTORY) 197 | 198 | await runCommand(inputPath, outputPath, undefined, true) 199 | 200 | const generatedFiles = fs.readdirSync(outputPath) 201 | 202 | t.assert.deepStrictEqual( 203 | generatedFiles, 204 | [ 205 | 'Address.ts', 206 | 'ApiResponse.ts', 207 | 'Category.ts', 208 | 'Customer.ts', 209 | 'DateExample.ts', 210 | 'FooBARBaz.ts', 211 | 'Order.ts', 212 | 'Pet.ts', 213 | 'Tag.ts', 214 | 'User.ts' 215 | ], 216 | 'generates the expected TS files' 217 | ) 218 | 219 | const petFile = resolveFromPackageRoot(outputPath, 'Pet.ts') 220 | const generatedPetFile = fs.readFileSync(petFile, 'utf-8') 221 | 222 | t.assert.deepStrictEqual( 223 | generatedPetFile, 224 | `export const Pet = { 225 | required: ["name", "photoUrls"], 226 | type: "object", 227 | properties: { 228 | id: { 229 | type: "integer", 230 | format: "int64", 231 | minimum: -9223372036854776000, 232 | maximum: 9223372036854776000, 233 | }, 234 | name: { type: "string" }, 235 | category: { 236 | type: "object", 237 | properties: { 238 | id: { 239 | type: "integer", 240 | format: "int64", 241 | minimum: -9223372036854776000, 242 | maximum: 9223372036854776000, 243 | }, 244 | name: { type: "string" }, 245 | }, 246 | title: "Category", 247 | }, 248 | photoUrls: { type: "array", items: { type: "string" } }, 249 | tags: { 250 | type: "array", 251 | items: { 252 | type: "object", 253 | properties: { 254 | id: { 255 | type: "integer", 256 | format: "int64", 257 | minimum: -9223372036854776000, 258 | maximum: 9223372036854776000, 259 | }, 260 | name: { type: "string" }, 261 | }, 262 | title: "Tag", 263 | }, 264 | }, 265 | status: { 266 | type: "string", 267 | description: "pet status in the store", 268 | enum: ["available", "pending", "sold"], 269 | }, 270 | nullableValue: { 271 | type: ["string", "null"], 272 | description: "example nullable value", 273 | }, 274 | }, 275 | title: "Pet", 276 | $id: "Pet.json", 277 | } as const; 278 | `, 279 | 'Pet.ts is created correctly' 280 | ) 281 | 282 | const customerFile = resolveFromPackageRoot(outputPath, 'Customer.ts') 283 | const generatedCustomerFile = fs.readFileSync(customerFile, 'utf-8') 284 | 285 | t.assert.deepStrictEqual( 286 | generatedCustomerFile, 287 | `export const Customer = { 288 | type: "object", 289 | properties: { 290 | id: { 291 | type: "integer", 292 | format: "int64", 293 | minimum: -9223372036854776000, 294 | maximum: 9223372036854776000, 295 | }, 296 | username: { type: "string" }, 297 | address: { 298 | type: "array", 299 | items: { 300 | type: "object", 301 | properties: { 302 | street: { type: "string" }, 303 | city: { type: "string" }, 304 | state: { type: "string" }, 305 | zip: { type: "string" }, 306 | }, 307 | title: "Address", 308 | }, 309 | }, 310 | }, 311 | title: "Customer", 312 | $id: "Customer.json", 313 | } as const; 314 | `, 315 | 'Customer.ts is created correctly' 316 | ) 317 | }) 318 | }) 319 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "esnext", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "resolveJsonModule": true, 13 | "outDir": "dist", 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['index.ts', 'src/**/*.ts'], 5 | dts: true, 6 | splitting: true, 7 | clean: true, 8 | minify: true, 9 | format: ['esm'], 10 | bundle: false, 11 | legacyOutput: true 12 | }) 13 | --------------------------------------------------------------------------------