├── test ├── nopath │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── path │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── simple │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── longdesc │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── method │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── info │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── nomethod │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── server │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── security │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── security3 │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── server3 │ ├── options.yaml │ ├── output.yaml │ └── input.yaml ├── multiop │ ├── options.yaml │ ├── output.yaml │ └── input.yaml └── test.js ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── package.json ├── .gitignore ├── LICENSE ├── README.md ├── openapi-extract.js ├── lib └── AList.js ├── index.js └── .eslintrc.json /test/nopath/options.yaml: -------------------------------------------------------------------------------- 1 | path: /info.1.json 2 | -------------------------------------------------------------------------------- /test/path/options.yaml: -------------------------------------------------------------------------------- 1 | path: /info.0.json 2 | -------------------------------------------------------------------------------- /test/simple/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | -------------------------------------------------------------------------------- /test/longdesc/options.yaml: -------------------------------------------------------------------------------- 1 | info: true 2 | openai: true 3 | -------------------------------------------------------------------------------- /test/method/options.yaml: -------------------------------------------------------------------------------- 1 | path: /info.0.json 2 | method: get 3 | -------------------------------------------------------------------------------- /test/info/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | info: true 3 | -------------------------------------------------------------------------------- /test/nomethod/options.yaml: -------------------------------------------------------------------------------- 1 | path: /info.0.json 2 | method: post 3 | -------------------------------------------------------------------------------- /test/server/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | server: true 3 | -------------------------------------------------------------------------------- /test/security/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | security: true 3 | -------------------------------------------------------------------------------- /test/security3/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | security: true 3 | -------------------------------------------------------------------------------- /test/server3/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: getLatest 2 | server: true 3 | -------------------------------------------------------------------------------- /test/multiop/options.yaml: -------------------------------------------------------------------------------- 1 | operationid: 2 | - getLatest 3 | - getComic 4 | -------------------------------------------------------------------------------- /test/nopath/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.1.json: {} 7 | -------------------------------------------------------------------------------- /test/nomethod/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | post: {} 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: yargs 10 | versions: 11 | - "> 14.0.0" 12 | - dependency-name: yaml 13 | versions: 14 | - 1.10.2 15 | - dependency-name: mocha 16 | versions: 17 | - 8.3.0 18 | - 8.3.1 19 | -------------------------------------------------------------------------------- /test/longdesc/output.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: API 4 | version: 1.0.0 5 | description: "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" 6 | paths: {} 7 | -------------------------------------------------------------------------------- /test/longdesc/input.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: API 4 | version: 1.0.0 5 | description: 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 This should get truncated... 6 | paths: {} 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | # Author: @MikeRalphson 4 | # Issue : n/a 5 | # Desc : This workflow runs a simple lint and test for a node.js project 6 | 7 | # run this on push to any branch and creation of pull-requests 8 | on: [push, pull_request] 9 | 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 # checkout repo content 15 | - uses: actions/setup-node@v2 # setup Node.js 16 | with: 17 | node-version: '18.x' 18 | - name: Audit package-lock.json 19 | run: npx package-lock-audit ./package-lock.json 20 | - name: Install deps 21 | run: npm i 22 | - name: Run lint 23 | run: npm run lint 24 | - name: Run tests 25 | run: npm run test 26 | -------------------------------------------------------------------------------- /test/path/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | "200": 13 | description: OK 14 | schema: 15 | $ref: "#/definitions/comic" 16 | definitions: 17 | comic: 18 | properties: 19 | alt: 20 | type: string 21 | day: 22 | type: string 23 | img: 24 | type: string 25 | link: 26 | type: string 27 | month: 28 | type: string 29 | news: 30 | type: string 31 | num: 32 | type: number 33 | safe_title: 34 | type: string 35 | title: 36 | type: string 37 | transcript: 38 | type: string 39 | year: 40 | type: string 41 | type: object 42 | -------------------------------------------------------------------------------- /test/method/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | "200": 13 | description: OK 14 | schema: 15 | $ref: "#/definitions/comic" 16 | definitions: 17 | comic: 18 | properties: 19 | alt: 20 | type: string 21 | day: 22 | type: string 23 | img: 24 | type: string 25 | link: 26 | type: string 27 | month: 28 | type: string 29 | news: 30 | type: string 31 | num: 32 | type: number 33 | safe_title: 34 | type: string 35 | title: 36 | type: string 37 | transcript: 38 | type: string 39 | year: 40 | type: string 41 | type: object 42 | -------------------------------------------------------------------------------- /test/simple/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/comic' 16 | definitions: 17 | comic: 18 | properties: 19 | alt: 20 | type: string 21 | day: 22 | type: string 23 | img: 24 | type: string 25 | link: 26 | type: string 27 | month: 28 | type: string 29 | news: 30 | type: string 31 | num: 32 | type: number 33 | safe_title: 34 | type: string 35 | title: 36 | type: string 37 | transcript: 38 | type: string 39 | year: 40 | type: string 41 | type: object 42 | -------------------------------------------------------------------------------- /test/security/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | "200": 13 | description: OK 14 | schema: 15 | $ref: "#/definitions/comic" 16 | securityDefinitions: {} 17 | definitions: 18 | comic: 19 | properties: 20 | alt: 21 | type: string 22 | day: 23 | type: string 24 | img: 25 | type: string 26 | link: 27 | type: string 28 | month: 29 | type: string 30 | news: 31 | type: string 32 | num: 33 | type: number 34 | safe_title: 35 | type: string 36 | title: 37 | type: string 38 | transcript: 39 | type: string 40 | year: 41 | type: string 42 | type: object 43 | -------------------------------------------------------------------------------- /test/server/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | host: xkcd.com 6 | schemes: 7 | - http 8 | basePath: / 9 | paths: 10 | /info.0.json: 11 | get: 12 | operationId: getLatest 13 | description: | 14 | Fetch current comic and metadata. 15 | responses: 16 | "200": 17 | description: OK 18 | schema: 19 | $ref: "#/definitions/comic" 20 | definitions: 21 | comic: 22 | properties: 23 | alt: 24 | type: string 25 | day: 26 | type: string 27 | img: 28 | type: string 29 | link: 30 | type: string 31 | month: 32 | type: string 33 | news: 34 | type: string 35 | num: 36 | type: number 37 | safe_title: 38 | type: string 39 | title: 40 | type: string 41 | transcript: 42 | type: string 43 | year: 44 | type: string 45 | type: object 46 | -------------------------------------------------------------------------------- /test/security3/output.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | "200": 13 | description: OK 14 | content: 15 | "*/*": 16 | schema: 17 | $ref: "#/components/schemas/comic" 18 | components: 19 | schemas: 20 | comic: 21 | properties: 22 | alt: 23 | type: string 24 | day: 25 | type: string 26 | img: 27 | type: string 28 | link: 29 | type: string 30 | month: 31 | type: string 32 | news: 33 | type: string 34 | num: 35 | type: number 36 | safe_title: 37 | type: string 38 | title: 39 | type: string 40 | transcript: 41 | type: string 42 | year: 43 | type: string 44 | type: object 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openapi-extract", 3 | "version": "1.3.0", 4 | "description": "extract paths or operations from OpenAPI/Swagger definitions", 5 | "main": "index.js", 6 | "bin": "openapi-extract.js", 7 | "scripts": { 8 | "lint": "npx eslint *.js lib/*.js test/*.js", 9 | "test": "npx mocha" 10 | }, 11 | "author": "Mike Ralphson", 12 | "license": "BSD-3-Clause", 13 | "dependencies": { 14 | "mkdirp": "^2.1.6", 15 | "reftools": "^1.1.1", 16 | "rimraf": "^4.4.1", 17 | "yaml": "^2.2.1", 18 | "yargs": "^17.7.1" 19 | }, 20 | "devDependencies": { 21 | "mocha": "10.2" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/mermade/openapi-extract.git" 26 | }, 27 | "keywords": [ 28 | "openapi", 29 | "openapi3", 30 | "swagger", 31 | "asyncapi", 32 | "extract", 33 | "microservice" 34 | ], 35 | "bugs": { 36 | "url": "https://github.com/mermade/openapi-extract/issues" 37 | }, 38 | "homepage": "https://github.com/mermade/openapi-extract#readme" 39 | } 40 | -------------------------------------------------------------------------------- /test/server3/output.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | servers: 6 | - url: http://xkcd.com/ 7 | paths: 8 | /info.0.json: 9 | get: 10 | operationId: getLatest 11 | description: | 12 | Fetch current comic and metadata. 13 | responses: 14 | "200": 15 | description: OK 16 | content: 17 | "*/*": 18 | schema: 19 | $ref: "#/components/schemas/comic" 20 | components: 21 | schemas: 22 | comic: 23 | properties: 24 | alt: 25 | type: string 26 | day: 27 | type: string 28 | img: 29 | type: string 30 | link: 31 | type: string 32 | month: 33 | type: string 34 | news: 35 | type: string 36 | num: 37 | type: number 38 | safe_title: 39 | type: string 40 | title: 41 | type: string 42 | transcript: 43 | type: string 44 | year: 45 | type: string 46 | type: object 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | shard/ 3 | *.swp 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | -------------------------------------------------------------------------------- /test/multiop/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | title: XKCD 4 | version: 1.0.0 5 | paths: 6 | /info.0.json: 7 | get: 8 | operationId: getLatest 9 | description: | 10 | Fetch current comic and metadata. 11 | responses: 12 | "200": 13 | description: OK 14 | schema: 15 | $ref: "#/definitions/comic" 16 | "/{comicId}/info.0.json": 17 | get: 18 | operationId: getComic 19 | description: | 20 | Fetch comics and metadata by comic id. 21 | parameters: 22 | - in: path 23 | name: comicId 24 | required: true 25 | type: number 26 | responses: 27 | "200": 28 | description: OK 29 | schema: 30 | $ref: "#/definitions/comic" 31 | definitions: 32 | comic: 33 | properties: 34 | alt: 35 | type: string 36 | day: 37 | type: string 38 | img: 39 | type: string 40 | link: 41 | type: string 42 | month: 43 | type: string 44 | news: 45 | type: string 46 | num: 47 | type: number 48 | safe_title: 49 | type: string 50 | title: 51 | type: string 52 | transcript: 53 | type: string 54 | year: 55 | type: string 56 | type: object 57 | -------------------------------------------------------------------------------- /test/info/output.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: Webcomic of romance, sarcasm, math, and language. 4 | title: XKCD 5 | version: 1.0.0 6 | x-apisguru-categories: 7 | - media 8 | x-logo: 9 | url: http://imgs.xkcd.com/static/terrible_small_logo.png 10 | x-origin: 11 | - format: swagger 12 | url: https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml 13 | version: "2.0" 14 | x-providerName: xkcd.com 15 | x-tags: 16 | - humor 17 | - comics 18 | x-unofficialSpec: true 19 | paths: 20 | /info.0.json: 21 | get: 22 | operationId: getLatest 23 | description: | 24 | Fetch current comic and metadata. 25 | responses: 26 | "200": 27 | description: OK 28 | schema: 29 | $ref: "#/definitions/comic" 30 | definitions: 31 | comic: 32 | properties: 33 | alt: 34 | type: string 35 | day: 36 | type: string 37 | img: 38 | type: string 39 | link: 40 | type: string 41 | month: 42 | type: string 43 | news: 44 | type: string 45 | num: 46 | type: number 47 | safe_title: 48 | type: string 49 | title: 50 | type: string 51 | transcript: 52 | type: string 53 | year: 54 | type: string 55 | type: object 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Mermade Software 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const assert = require('assert'); 6 | const yaml = require('yaml'); 7 | 8 | const extract = require('../index.js'); 9 | 10 | const doPrivate = (!process.env.SKIP_PRIVATE); 11 | 12 | const tests = fs.readdirSync(__dirname).filter(file => { 13 | return fs.statSync(path.join(__dirname, file)).isDirectory() && (!file.startsWith('_') || doPrivate); 14 | }); 15 | 16 | describe('Extract tests', () => { 17 | tests.forEach((test) => { 18 | describe(test, () => { 19 | it('should match expected output', (done) => { 20 | let options = {}; 21 | try { 22 | options = yaml.parse(fs.readFileSync(path.join(__dirname, test, 'options.yaml'),'utf8'), {schema:'core'}); 23 | } 24 | catch (ex) {} 25 | 26 | const input = yaml.parse(fs.readFileSync(path.join(__dirname, test, 'input.yaml'),'utf8'), {schema:'core'}); 27 | let readOutput = false; 28 | let output = {}; 29 | try { 30 | output = yaml.parse(fs.readFileSync(path.join(__dirname, test, 'output.yaml'),'utf8'), {schema:'core'}); 31 | readOutput = true; 32 | } 33 | catch (ex) {} 34 | 35 | const result = extract.extract(input, options); 36 | if (!readOutput) { 37 | output = result; 38 | fs.writeFileSync(path.join(__dirname, test, 'output.yaml'), yaml.stringify(result), 'utf8'); 39 | } 40 | 41 | assert.deepStrictEqual(result, output); 42 | return done(); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/info/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/method/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/multiop/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/nomethod/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/nopath/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/path/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/security/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/server/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/simple/input.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | schemes: 3 | - http 4 | host: xkcd.com 5 | basePath: / 6 | info: 7 | description: 'Webcomic of romance, sarcasm, math, and language.' 8 | title: XKCD 9 | version: 1.0.0 10 | x-apisguru-categories: 11 | - media 12 | x-logo: 13 | url: 'http://imgs.xkcd.com/static/terrible_small_logo.png' 14 | x-origin: 15 | - format: swagger 16 | url: 'https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml' 17 | version: '2.0' 18 | x-providerName: xkcd.com 19 | x-tags: 20 | - humor 21 | - comics 22 | x-unofficialSpec: true 23 | externalDocs: 24 | url: 'https://xkcd.com/json.html' 25 | securityDefinitions: {} 26 | paths: 27 | /info.0.json: 28 | get: 29 | operationId: getLatest 30 | description: | 31 | Fetch current comic and metadata. 32 | responses: 33 | '200': 34 | description: OK 35 | schema: 36 | $ref: '#/definitions/comic' 37 | '/{comicId}/info.0.json': 38 | get: 39 | operationId: getComic 40 | description: | 41 | Fetch comics and metadata by comic id. 42 | parameters: 43 | - in: path 44 | name: comicId 45 | required: true 46 | type: number 47 | responses: 48 | '200': 49 | description: OK 50 | schema: 51 | $ref: '#/definitions/comic' 52 | definitions: 53 | comic: 54 | properties: 55 | alt: 56 | type: string 57 | day: 58 | type: string 59 | img: 60 | type: string 61 | link: 62 | type: string 63 | month: 64 | type: string 65 | news: 66 | type: string 67 | num: 68 | type: number 69 | safe_title: 70 | type: string 71 | title: 72 | type: string 73 | transcript: 74 | type: string 75 | year: 76 | type: string 77 | type: object 78 | -------------------------------------------------------------------------------- /test/security3/input.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | description: Webcomic of romance, sarcasm, math, and language. 4 | title: XKCD 5 | version: 1.0.0 6 | x-apisguru-categories: 7 | - media 8 | x-logo: 9 | url: http://imgs.xkcd.com/static/terrible_small_logo.png 10 | x-origin: 11 | - format: swagger 12 | url: https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml 13 | version: "2.0" 14 | x-providerName: xkcd.com 15 | x-tags: 16 | - humor 17 | - comics 18 | x-unofficialSpec: true 19 | externalDocs: 20 | url: https://xkcd.com/json.html 21 | paths: 22 | /info.0.json: 23 | get: 24 | operationId: getLatest 25 | description: | 26 | Fetch current comic and metadata. 27 | responses: 28 | "200": 29 | description: OK 30 | content: 31 | "*/*": 32 | schema: 33 | $ref: "#/components/schemas/comic" 34 | "/{comicId}/info.0.json": 35 | get: 36 | operationId: getComic 37 | description: | 38 | Fetch comics and metadata by comic id. 39 | parameters: 40 | - in: path 41 | name: comicId 42 | required: true 43 | schema: 44 | type: number 45 | responses: 46 | "200": 47 | description: OK 48 | content: 49 | "*/*": 50 | schema: 51 | $ref: "#/components/schemas/comic" 52 | servers: 53 | - url: http://xkcd.com/ 54 | components: 55 | schemas: 56 | comic: 57 | properties: 58 | alt: 59 | type: string 60 | day: 61 | type: string 62 | img: 63 | type: string 64 | link: 65 | type: string 66 | month: 67 | type: string 68 | news: 69 | type: string 70 | num: 71 | type: number 72 | safe_title: 73 | type: string 74 | title: 75 | type: string 76 | transcript: 77 | type: string 78 | year: 79 | type: string 80 | type: object 81 | -------------------------------------------------------------------------------- /test/server3/input.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | description: Webcomic of romance, sarcasm, math, and language. 4 | title: XKCD 5 | version: 1.0.0 6 | x-apisguru-categories: 7 | - media 8 | x-logo: 9 | url: http://imgs.xkcd.com/static/terrible_small_logo.png 10 | x-origin: 11 | - format: swagger 12 | url: https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml 13 | version: "2.0" 14 | x-providerName: xkcd.com 15 | x-tags: 16 | - humor 17 | - comics 18 | x-unofficialSpec: true 19 | externalDocs: 20 | url: https://xkcd.com/json.html 21 | paths: 22 | /info.0.json: 23 | get: 24 | operationId: getLatest 25 | description: | 26 | Fetch current comic and metadata. 27 | responses: 28 | "200": 29 | description: OK 30 | content: 31 | "*/*": 32 | schema: 33 | $ref: "#/components/schemas/comic" 34 | "/{comicId}/info.0.json": 35 | get: 36 | operationId: getComic 37 | description: | 38 | Fetch comics and metadata by comic id. 39 | parameters: 40 | - in: path 41 | name: comicId 42 | required: true 43 | schema: 44 | type: number 45 | responses: 46 | "200": 47 | description: OK 48 | content: 49 | "*/*": 50 | schema: 51 | $ref: "#/components/schemas/comic" 52 | servers: 53 | - url: http://xkcd.com/ 54 | components: 55 | schemas: 56 | comic: 57 | properties: 58 | alt: 59 | type: string 60 | day: 61 | type: string 62 | img: 63 | type: string 64 | link: 65 | type: string 66 | month: 67 | type: string 68 | news: 69 | type: string 70 | num: 71 | type: number 72 | safe_title: 73 | type: string 74 | title: 75 | type: string 76 | transcript: 77 | type: string 78 | year: 79 | type: string 80 | type: object 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openapi-extract 2 | 3 | ![ci](https://github.com/Mermade/openapi-extract/workflows/ci/badge.svg) 4 | 5 | Extract paths, operations, parameters, schemas etc from OpenAPI/Swagger definitions. 6 | 7 | Works with OpenAPI/Swagger 2.0 and 3.x definitions. 8 | 9 | ``` 10 | Usage: openapi-extract [options] {infile} [{outfile}] 11 | 12 | Options: 13 | -h, --help Show help [boolean] 14 | --version Show version number [boolean] 15 | --openai make the definition OpenAI compliant [boolean] 16 | --server include server information [boolean] 17 | --shard shard the input to an output directory [string] 18 | -p, --path the path to extract [string] 19 | -o, --operationid the operationIds to extract [array] 20 | -m, --method the method to extract for the given path [string] 21 | -i, --info copy full info object, otherwise minimal [boolean] 22 | -d, --removeDocs remove all externalDocs properties [boolean] 23 | -r, --removeExamples remove all example/examples properties [boolean] 24 | -x, --removeExtensions remove all x- extension properties [boolean] 25 | -s, --security include security information [boolean] 26 | -v, --verbose increase verbosity [boolean] 27 | ``` 28 | 29 | or 30 | 31 | ```javascript 32 | const openapiExtractor = require('openapi-extract'); 33 | const options = {}; 34 | // options.path = '...'; 35 | // options.method = '...'; 36 | // options.operationid = ['...']; 37 | const res = openapiExtractor.extract(obj, options); 38 | 39 | const map = openapiExtractor.shard(obj, options); 40 | ``` 41 | 42 | The `options` object takes the same values as the CLI, for these keys and default values: 43 | 44 | * path = '' 45 | * method = '' 46 | * info = false 47 | * openai = false 48 | * removeDocs = false 49 | * removeExamples = false 50 | * removeExtensions = false 51 | * server = false 52 | * security = false 53 | * operationid = [] 54 | 55 | ## OpenAI compliant mode 56 | 57 | This option turns on the following rules: 58 | 59 | 1. The `description` properties must have a maximum length of 300 characters 60 | -------------------------------------------------------------------------------- /openapi-extract.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const fs = require('node:fs'); 6 | const path = require('node:path'); 7 | 8 | const yaml = require('yaml'); 9 | const rimraf = require('rimraf'); 10 | const mkdirp = require('mkdirp'); 11 | 12 | const extractor = require('./index.js'); 13 | 14 | let argv = require('yargs') 15 | .usage('Usage: openapi-extract [options] {infile} [{outfile}]') 16 | .demand(1) 17 | .strict() 18 | .help('h') 19 | .alias('h', 'help') 20 | .version() 21 | .string('path') 22 | .alias('p','path') 23 | .describe('path','the path to extract') 24 | .boolean('openai') 25 | .describe('openai','make definition OpenAI compliant') 26 | .array('operationid') 27 | .alias('o','operationid') 28 | .describe('operationid','the operationIds to extract') 29 | .string('method') 30 | .alias('m','method') 31 | .describe('method','the method to extract for the given path') 32 | .boolean('info') 33 | .alias('i','info') 34 | .describe('info','copy full info object, otherwise minimal') 35 | .boolean('server') 36 | .describe('server','include server information') 37 | .boolean('security') 38 | .alias('s','security') 39 | .describe('security','include security information') 40 | .boolean('removeDocs') 41 | .alias('d','removeDocs') 42 | .describe('removeDocs','remove all externalDocs properties') 43 | .boolean('removeExamples') 44 | .alias('r','removeExamples') 45 | .describe('removeExamples','remove all example/examples properties') 46 | .boolean('removeExtensions') 47 | .alias('x','removeExtensions') 48 | .describe('removeExtensions','remove all x- extension properties') 49 | .string('shard') 50 | .describe('shard','shard the input to an output directory') 51 | .boolean('verbose') 52 | .alias('v','verbose') 53 | .describe('verbose','increase verbosity') 54 | .argv; 55 | 56 | async function main() { 57 | let s = fs.readFileSync(argv._[0],'utf8'); 58 | let obj = yaml.parse(s); 59 | let filename = 'openapi.yaml'; 60 | if (obj.asyncapi) filename = 'asyncapi.yaml'; 61 | if (argv.shard) { 62 | const map = extractor.shard(obj, argv); 63 | await rimraf(argv.shard, { preserveRoot: false }); 64 | for (let [key, value] of map) { 65 | process.stderr.write('.'); 66 | try { 67 | await mkdirp(path.resolve(argv.shard,key)); 68 | fs.writeFileSync(path.resolve(argv.shard,key,filename),yaml.stringify(value),'utf8'); 69 | } 70 | catch (ex) { 71 | process.stderr.write(`\n${ex.message}\n`); 72 | } 73 | } 74 | process.stderr.write('\n'); 75 | } 76 | else { 77 | let res = extractor.extract(obj, argv); 78 | if (argv._[0].indexOf('.json')>=0) { 79 | s = JSON.stringify(res,null,2); 80 | } 81 | else { 82 | s = yaml.stringify(res); 83 | } 84 | if (argv._.length>1) { 85 | fs.writeFileSync(argv._[1],s,'utf8'); 86 | } 87 | else { 88 | console.log(s); 89 | } 90 | } 91 | } 92 | 93 | main(); 94 | -------------------------------------------------------------------------------- /lib/AList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const root = Symbol('reftools-AList-root'); 4 | 5 | function always(key,value) { 6 | return true; 7 | } 8 | 9 | /** 10 | * A graph adjacency list of object identities, implemented on top of a Map 11 | * https://en.wikipedia.org/wiki/Adjacency_list 12 | */ 13 | class AList extends Map { 14 | 15 | constructor(obj, parent = root) { 16 | super(); 17 | this.ingest(obj,parent); 18 | } 19 | 20 | ingest(obj, parent = root, property = '') { 21 | const kv = { key:property,value:parent }; 22 | if (this.has(obj)) { 23 | this.get(obj).push(kv); 24 | } 25 | else { 26 | this.set(obj, [ kv ]); 27 | for (let p in obj) { 28 | if (obj[p] !== null && typeof obj[p] === 'object' && obj.hasOwnProperty(p)) { 29 | this.ingest(obj[p], obj, p); 30 | } 31 | } 32 | } 33 | } 34 | 35 | invert() { 36 | if (this.inverse) return this.inverse; 37 | const result = new AList(); 38 | for (let [key,value] of this) { 39 | if (Array.isArray(value)) { 40 | for (let parent of value) { 41 | if (result.has(parent.value)) { 42 | result.get(parent.value).push({ key:parent.key, value:key }); 43 | } 44 | else { 45 | result.set(parent.value, [ { key:parent.key, value:key } ]); 46 | } 47 | } 48 | } 49 | } 50 | this.inverse = result; 51 | result.inverse = this; 52 | return result; 53 | } 54 | 55 | findUpwards(obj, property, validator = always) { 56 | const parents = this.get(obj); 57 | const effParents = parents || [ { key: '', value: root } ]; 58 | if (obj && obj.hasOwnProperty(property) && validator(property,obj,effParents[0].value,this)) { 59 | return obj; 60 | } 61 | if (Array.isArray(parents)) { 62 | for (let parent of parents) { 63 | let result = this.findUpwards(parent.value, property, validator); 64 | if ((typeof result !== 'undefined') && validator(property,result,parent.value,this)) return result; 65 | } 66 | } 67 | } 68 | 69 | findDownwards(obj, property, validator) { 70 | if (!this.inverse) this.inverse = this.invert(); 71 | return this.inverse.findUpwards(obj, property, validator); 72 | } 73 | 74 | findProperty(property, validator = always) { 75 | for (let [key,value] of this) { 76 | if (key && key.hasOwnProperty(property) && validator(property,key,value,this)) { 77 | return key; 78 | } 79 | } 80 | } 81 | 82 | findAll(obj) { 83 | return this.get(obj); 84 | } 85 | 86 | findFirst(obj) { 87 | const parents = this.get(obj); 88 | if (parents && parents.length) return parents[0]; 89 | } 90 | 91 | getPath(obj, parent = null, route = []) { 92 | if (this.has(obj)) { 93 | const parents = this.get(obj); 94 | if (Array.isArray(parents) && parents.length) { 95 | const prefParent = parent ? parent : parents[0]; 96 | route.push(prefParent.key); 97 | this.getPath(prefParent.value, null, route); 98 | } 99 | } 100 | return route; 101 | } 102 | 103 | getDepth(obj, parent = null, route = []) { 104 | return this.getPath(obj,parent,route).length; 105 | } 106 | 107 | getReference(obj, parent = null, route = []) { 108 | const path = this.getPath(obj,parent,route); 109 | for (let p in path) { 110 | path[p] = path[p].split('~').join('~0').split('/').join('~1'); 111 | } 112 | return '#'+path.reverse().join('/'); 113 | } 114 | 115 | setAnnotation(obj, parent, value, key = 'data', stop = false) { 116 | if (key === 'key' || key === 'value') return; // reserved for our use 117 | if (this.has(obj)) { 118 | const parents = this.get(obj); 119 | const target = parents.find(function(e,i,a){ 120 | return (e.value === parent); 121 | }); 122 | if (target) { 123 | return target[key] = value; 124 | } 125 | } 126 | if (!stop) return this.setAnnotation(parent, obj, value, key, true); 127 | } 128 | 129 | getParent(obj) { 130 | const parents = this.get(obj); 131 | if (parents && parents.length) return parents[0].value; 132 | } 133 | 134 | getGrandParent(obj) { 135 | const parents = this.get(obj); 136 | if (parents && parents.length) return this.getParent(parents[0].value); 137 | } 138 | 139 | delete(obj) { 140 | const parent = this.getParent(obj); 141 | for (let prop in parent) { 142 | if (parent.hasOwnProperty(prop) && parent[prop] === obj) { 143 | delete parent[prop]; 144 | } 145 | } 146 | } 147 | 148 | static deleteProperty(obj, property) { 149 | for (let prop in obj) { 150 | if (obj.hasOwnProperty(prop) && prop === property) { 151 | delete obj[prop]; 152 | } 153 | } 154 | } 155 | 156 | static deletePrefix(obj, prefix) { 157 | for (let prop in obj) { 158 | if (obj.hasOwnProperty(prop) && prop.startsWith(prefix)) { 159 | delete obj[prop]; 160 | } 161 | } 162 | } 163 | 164 | } 165 | 166 | module.exports = { AList, root }; 167 | 168 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const recurse = require('reftools/lib/recurse.js').recurse; 4 | const clone = require('reftools/lib/clone.js').clone; 5 | const jptr = require('reftools/lib/jptr.js').jptr; 6 | const { AList } = require('./lib/AList.js'); 7 | 8 | // TODO add a memoized cache 9 | 10 | function clean(obj, property) { 11 | if (obj && (!Array.isArray(obj[property])) && (typeof obj[property] === 'object') && (!Object.keys(obj[property]).length)) { 12 | if (property !== 'paths') delete obj[property]; 13 | } 14 | return obj; 15 | } 16 | 17 | function deref(target, src, obj) { 18 | let changes = 1; 19 | let seen = {}; 20 | while (changes) { 21 | changes = 0; 22 | recurse(target,{},function(t,key,state){ 23 | if ((key === '$ref') && (typeof t[key] === 'string') && !seen[state.path]) { 24 | if (t[key].startsWith('#')) { 25 | changes++; 26 | jptr(src,t[key],jptr(obj,t[key])); 27 | } 28 | seen[state.path] = true; 29 | } 30 | }); 31 | } 32 | return target; 33 | } 34 | 35 | function extract(obj, options) { 36 | 37 | const defaults = {}; 38 | defaults.info = false; 39 | defaults.removeDocs = false; 40 | defaults.removeExamples = false; 41 | defaults.removeExtensions = false; 42 | defaults.server = false; 43 | defaults.security = false; 44 | defaults.openai = false; 45 | defaults.operationid = []; 46 | options = Object.assign({},defaults,options); 47 | 48 | if (!Array.isArray(options.operationid)) { 49 | options.operationid = [options.operationid]; 50 | } 51 | 52 | let src = {}; 53 | if (obj.openapi) { 54 | src.openapi = obj.openapi; 55 | } 56 | else if (obj.swagger) { 57 | src.swagger = obj.swagger; 58 | } 59 | else { 60 | src.asyncapi = obj.asyncapi; 61 | } 62 | if (options.info) { 63 | src.info = obj.info; 64 | } 65 | else { 66 | src.info = { title: obj.info.title, version: obj.info.version }; 67 | } 68 | if (options.server) { 69 | if (obj.openapi || obj.asyncapi) { 70 | src.servers = obj.servers; 71 | } 72 | else { 73 | src.host = obj.host; 74 | src.schemes = obj.schemes; 75 | src.basePath = obj.basePath; 76 | } 77 | } 78 | src.paths = {}; 79 | if (src.openapi) { 80 | src.components = {}; 81 | if (options.security) { 82 | if (obj.security) src.security = obj.security; 83 | if (obj.securitySchemes) src.securitySchemes = obj.securitySchemes; 84 | if (obj.components && obj.components.securitySchemes) { 85 | src.components.securitySchemes = obj.components.securitySchemes; 86 | } 87 | } 88 | src.components.parameters = {}; 89 | src.components.responses = {}; 90 | src.components.headers = {}; 91 | src.components.schemas = {}; 92 | } 93 | else { 94 | if (options.security) { 95 | if (obj.securityDefinitions) src.securityDefinitions = obj.securityDefinitions; 96 | if (obj.security) src.security = obj.security; 97 | } 98 | src.parameters = {}; 99 | src.responses = {}; 100 | src.headers = {}; 101 | src.definitions = {}; 102 | } 103 | let paths = {}; 104 | 105 | if (options.operationid.length) { 106 | for (let id of options.operationid) { 107 | for (let p in obj.paths) { 108 | for (let o in obj.paths[p]) { 109 | let op = obj.paths[p][o]; 110 | if (op.operationId && op.operationId === id) { 111 | if (!paths[p]) paths[p] = {}; 112 | paths[p][o] = clone(op); 113 | deref(paths[p][o],src,obj); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | else { 120 | paths = {}; 121 | if (options.path) paths[options.path] = {}; 122 | if (options.method) paths[options.path][options.method] = {}; 123 | if (options.method && obj.paths[options.path][options.method]) { 124 | paths[options.path][options.method] = clone(obj.paths[options.path][options.method]); 125 | deref(paths[options.path][options.method],src,obj); 126 | } 127 | else if (options.path) { 128 | for (let o in obj.paths[options.path]) { 129 | if ((o !== 'description') && (o !== 'summary') && 130 | (!o.startsWith('x-'))) { 131 | if (!options.method || options.method === o) { 132 | paths[options.path][o] = clone(obj.paths[options.path][o]); 133 | deref(paths[options.path][o],src,obj); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | if (paths) src.paths = paths; 141 | for (let p in paths) { 142 | if (obj.paths[p] && obj.paths[p].parameters) { 143 | src.paths[p].parameters = clone(obj.paths[p].parameters); 144 | } 145 | } 146 | 147 | if (options.server && (Object.keys(paths).length === 1) && 148 | (Object.keys(Object.values(paths)[0]) === 1)) { 149 | const op = Object.values(Object.values(paths)[0])[0]; 150 | if (op.schemes) { 151 | src.schemes = op.schemes; 152 | delete op.schemes; 153 | } 154 | if (op.servers) { 155 | src.servers = op.servers; 156 | delete op.servers; 157 | } 158 | } 159 | 160 | deref(src.definitions,src,obj); 161 | deref(src.headers,src,obj); 162 | deref(src.responses,src,obj); 163 | deref(src.parameters,src,obj); 164 | deref(src.components,src,obj); 165 | 166 | if (options.openai) { 167 | recurse(src,{},function(obj,key,state){ 168 | if (obj && key === 'description' && obj.description.length > 300) { 169 | state.parent[state.pkey].description = obj.description.substring(0,300); 170 | } 171 | }); 172 | } 173 | 174 | clean(src,'paths'); 175 | clean(src,'definitions'); 176 | clean(src,'headers'); 177 | clean(src,'responses'); 178 | clean(src,'parameters'); 179 | clean(src.components,'parameters'); 180 | clean(src.components,'responses'); 181 | clean(src.components,'headers'); 182 | clean(src.components,'schemas'); 183 | clean(src,'components'); 184 | 185 | const al = new AList(src); 186 | if (options.removeExamples) { 187 | for (let [value,parents] of al) { 188 | AList.deleteProperty(value, 'example'); 189 | AList.deleteProperty(value, 'examples'); 190 | } 191 | } 192 | 193 | if (options.removeExtensions) { 194 | for (let [value,parents] of al) { 195 | AList.deletePrefix(value, 'x-'); 196 | } 197 | } 198 | 199 | if (options.removeDocs) { 200 | for (let [value,parents] of al) { 201 | AList.deleteProperty(value, 'externalDocs'); 202 | } 203 | } 204 | 205 | return src; 206 | } 207 | 208 | function shard(obj, options) { 209 | const results = new Map(); 210 | if (!options.operationid) options.operationid = []; 211 | if (typeof options.operationid === 'string') { 212 | options.operationid = [ options.operationid ]; 213 | } 214 | for (let path in obj.paths) { 215 | if (!options.path || options.path === path) { 216 | for (let method in obj.paths[path]) { 217 | if (!options.method || options.method === method) { 218 | if (!obj.paths[path][method].operationId || options.operationid.length === 0 || (options.operationid && options.operationid.indexOf(obj.paths[path][method].operationId) >= 0)) { 219 | let output = extract(obj, Object.assign({},options,{ path, method, operationid: obj.paths[path][method].operationId })); 220 | if (output.paths && Object.keys(output.paths).length > 0) { 221 | let key = obj.paths[path][method].operationId; 222 | if (!key) key = method+'-'+path; 223 | key = key.split('/').join('-').split('--').join('-').split('{').join('').split('}').join(''); 224 | results.set(key, output); 225 | 226 | } 227 | } 228 | } 229 | } 230 | } 231 | } 232 | return results; 233 | } 234 | 235 | module.exports = { 236 | extract, 237 | shard 238 | }; 239 | 240 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "extends": "eslint:recommended", 11 | "rules": { 12 | "accessor-pairs": "error", 13 | "array-bracket-spacing": [ 14 | "off", 15 | "never" 16 | ], 17 | "array-callback-return": "error", 18 | "arrow-body-style": "off", 19 | "arrow-parens": [ 20 | "off", 21 | "as-needed" 22 | ], 23 | "arrow-spacing": [ 24 | "off", 25 | { 26 | "after": true, 27 | "before": true 28 | } 29 | ], 30 | "block-scoped-var": "off", 31 | "block-spacing": "off", 32 | "brace-style": [ 33 | "off", 34 | "stroustrup" 35 | ], 36 | "callback-return": "off", 37 | "camelcase": "off", 38 | "class-methods-use-this": "error", 39 | "comma-dangle": "error", 40 | "comma-spacing": "off", 41 | "comma-style": [ 42 | "error", 43 | "last" 44 | ], 45 | "complexity": "off", 46 | "computed-property-spacing": [ 47 | "error", 48 | "never" 49 | ], 50 | "consistent-return": "off", 51 | "consistent-this": "error", 52 | "curly": "off", 53 | "default-case": "error", 54 | "dot-location": [ 55 | "off", 56 | "property" 57 | ], 58 | "dot-notation": [ 59 | "error", 60 | { 61 | "allowKeywords": true 62 | } 63 | ], 64 | "eol-last": "error", 65 | "eqeqeq": "off", 66 | "func-call-spacing": "error", 67 | "func-name-matching": "off", 68 | "func-names": "off", 69 | "func-style": [ 70 | "error", 71 | "declaration" 72 | ], 73 | "generator-star-spacing": "off", 74 | "global-require": "off", 75 | "guard-for-in": "off", 76 | "handle-callback-err": "off", 77 | "id-blacklist": "error", 78 | "id-length": "off", 79 | "id-match": "error", 80 | "indent": "off", 81 | "init-declarations": "off", 82 | "jsx-quotes": "error", 83 | "key-spacing": "off", 84 | "keyword-spacing": ["error", { "after": true }], 85 | "line-comment-position": "off", 86 | "linebreak-style": [ 87 | "error", 88 | "unix" 89 | ], 90 | "lines-around-comment": "off", 91 | "lines-around-directive": "off", 92 | "max-depth": "off", 93 | "max-len": "off", 94 | "max-lines": "off", 95 | "max-nested-callbacks": "error", 96 | "max-params": "off", 97 | "max-statements": "off", 98 | "max-statements-per-line": "off", 99 | "multiline-ternary": [ 100 | "off", 101 | "never" 102 | ], 103 | "new-parens": "error", 104 | "newline-after-var": "off", 105 | "newline-before-return": "off", 106 | "newline-per-chained-call": "off", 107 | "no-alert": "error", 108 | "no-array-constructor": "error", 109 | "no-bitwise": "off", 110 | "no-caller": "error", 111 | "no-catch-shadow": "off", 112 | "no-confusing-arrow": "error", 113 | "no-console": "off", 114 | "no-continue": "off", 115 | "no-div-regex": "error", 116 | "no-duplicate-imports": "error", 117 | "no-else-return": "off", 118 | "no-empty": [ 119 | "warn", 120 | { 121 | "allowEmptyCatch": true 122 | } 123 | ], 124 | "no-empty-function": "off", 125 | "no-eq-null": "error", 126 | "no-eval": "error", 127 | "no-extend-native": "off", 128 | "no-extra-bind": "error", 129 | "no-extra-label": "error", 130 | "no-extra-parens": "off", 131 | "no-floating-decimal": "error", 132 | "no-global-assign": "error", 133 | "no-implicit-globals": "error", 134 | "no-implied-eval": "error", 135 | "no-inline-comments": "off", 136 | "no-inner-declarations": [ 137 | "error", 138 | "functions" 139 | ], 140 | "no-invalid-this": "off", 141 | "no-iterator": "error", 142 | "no-label-var": "error", 143 | "no-labels": "error", 144 | "no-lone-blocks": "error", 145 | "no-lonely-if": "off", 146 | "no-loop-func": "off", 147 | "no-magic-numbers": "off", 148 | "no-mixed-operators": "error", 149 | "no-mixed-requires": "error", 150 | "no-multi-spaces": "off", 151 | "no-multi-str": "error", 152 | "no-multiple-empty-lines": "error", 153 | "no-negated-condition": "error", 154 | "no-nested-ternary": "off", 155 | "no-new": "error", 156 | "no-new-func": "error", 157 | "no-new-object": "error", 158 | "no-new-require": "error", 159 | "no-new-wrappers": "error", 160 | "no-octal-escape": "error", 161 | "no-param-reassign": "off", 162 | "no-path-concat": "error", 163 | "no-plusplus": "off", 164 | "no-process-env": "off", 165 | "no-process-exit": "off", 166 | "no-proto": "error", 167 | "no-prototype-builtins": "off", 168 | "no-restricted-globals": "error", 169 | "no-restricted-imports": "error", 170 | "no-restricted-modules": "error", 171 | "no-restricted-properties": "error", 172 | "no-restricted-syntax": "error", 173 | "no-return-assign": "off", 174 | "no-script-url": "error", 175 | "no-self-compare": "error", 176 | "no-sequences": "error", 177 | "no-shadow": "off", 178 | "no-shadow-restricted-names": "error", 179 | "no-spaced-func": "error", 180 | "no-sync": "off", 181 | "no-tabs": "off", 182 | "no-template-curly-in-string": "error", 183 | "no-ternary": "off", 184 | "no-throw-literal": "error", 185 | "no-trailing-spaces": "error", 186 | "no-undef-init": "error", 187 | "no-undefined": "warn", 188 | "no-underscore-dangle": "off", 189 | "no-unmodified-loop-condition": "error", 190 | "no-unneeded-ternary": [ 191 | "error", 192 | { 193 | "defaultAssignment": true 194 | } 195 | ], 196 | "no-unsafe-negation": "error", 197 | "no-unused-expressions": "error", 198 | "no-unused-vars": "off", 199 | "no-use-before-define": "off", 200 | "no-useless-call": "error", 201 | "no-useless-computed-key": "error", 202 | "no-useless-concat": "off", 203 | "no-useless-constructor": "error", 204 | "no-useless-escape": "off", 205 | "no-useless-rename": "error", 206 | "no-var": "off", 207 | "no-void": "error", 208 | "no-warning-comments": "off", 209 | "no-whitespace-before-property": "error", 210 | "no-with": "error", 211 | "object-curly-newline": "off", 212 | "object-curly-spacing": "off", 213 | "object-property-newline": [ 214 | "off", 215 | { 216 | "allowMultiplePropertiesPerLine": true 217 | } 218 | ], 219 | "object-shorthand": "off", 220 | "one-var": "off", 221 | "one-var-declaration-per-line": "error", 222 | "operator-assignment": "off", 223 | "operator-linebreak": "off", 224 | "padded-blocks": "off", 225 | "prefer-arrow-callback": "off", 226 | "prefer-const": "off", 227 | "prefer-numeric-literals": "error", 228 | "prefer-reflect": "off", 229 | "prefer-rest-params": "error", 230 | "prefer-spread": "error", 231 | "prefer-template": "off", 232 | "quote-props": "off", 233 | "quotes": "off", 234 | "radix": "error", 235 | "require-jsdoc": "off", 236 | "require-yield": "off", 237 | "rest-spread-spacing": "error", 238 | "semi": "off", 239 | "semi-spacing": "off", 240 | "sort-imports": "error", 241 | "sort-keys": "off", 242 | "sort-vars": "off", 243 | "space-before-blocks": "off", 244 | "space-before-function-paren": "off", 245 | "space-in-parens": [ 246 | "error", 247 | "never" 248 | ], 249 | "space-infix-ops": "off", 250 | "space-unary-ops": "error", 251 | "spaced-comment": "off", 252 | "strict": "error", 253 | "symbol-description": "error", 254 | "template-curly-spacing": "error", 255 | "unicode-bom": [ 256 | "error", 257 | "never" 258 | ], 259 | "valid-jsdoc": "off", 260 | "vars-on-top": "off", 261 | "wrap-iife": "error", 262 | "wrap-regex": "off", 263 | "yield-star-spacing": "error", 264 | "yoda": [ 265 | "error", 266 | "never" 267 | ] 268 | } 269 | } 270 | --------------------------------------------------------------------------------