oapiex 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/bin/cli.mjs +12 -2
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OAPIEX
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/oapiex)
|
|
4
|
+
[](https://www.npmjs.com/package/oapiex)
|
|
5
|
+
[](https://github.com/toneflix/oapiex/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/toneflix/oapiex/actions/workflows/docs.yml)
|
|
7
|
+
[](https://github.com/toneflix/oapiex/actions/workflows/test.yml)
|
|
8
|
+
[](https://github.com/toneflix/oapiex/actions/workflows/publish.yml)
|
|
9
9
|
|
|
10
10
|
OAPIE is a CLI and TypeScript library for extracting API operation data from documentation sites and converting it into raw extracted payloads or OpenAPI-like documents.
|
|
11
11
|
|
|
12
12
|
It currently focuses on ReadMe-powered API docs and saved HTML pages, with room to expand to additional documentation platforms.
|
|
13
13
|
|
|
14
|
-
- npm: https://www.npmjs.com/package/
|
|
15
|
-
- docs: https://toneflix.github.io/
|
|
14
|
+
- npm: https://www.npmjs.com/package/oapiex
|
|
15
|
+
- docs: https://toneflix.github.io/oapiex
|
|
16
16
|
- repository: https://github.com/toneflix/oapi
|
|
17
17
|
|
|
18
18
|
## Features
|
|
@@ -28,34 +28,34 @@ It currently focuses on ReadMe-powered API docs and saved HTML pages, with room
|
|
|
28
28
|
|
|
29
29
|
### CLI
|
|
30
30
|
|
|
31
|
-
Install globally if you want the `
|
|
31
|
+
Install globally if you want the `oapiex` command available everywhere:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
pnpm add -g
|
|
34
|
+
pnpm add -g oapiex
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npm i -g
|
|
38
|
+
npm i -g oapiex
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
yarn global add
|
|
42
|
+
yarn global add oapiex
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
### Library
|
|
46
46
|
|
|
47
|
-
Install locally if you want to use
|
|
47
|
+
Install locally if you want to use OAPIEX from your own scripts or tooling:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
pnpm add
|
|
50
|
+
pnpm add oapiex
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npm i
|
|
54
|
+
npm i oapiex
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
yarn add
|
|
58
|
+
yarn add oapiex
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
## CLI Quick Start
|
|
@@ -88,7 +88,7 @@ oapie init
|
|
|
88
88
|
Extract a single operation:
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
|
-
import { Application, extractReadmeOperationFromHtml } from '
|
|
91
|
+
import { Application, extractReadmeOperationFromHtml } from 'oapiex';
|
|
92
92
|
|
|
93
93
|
const app = new Application({
|
|
94
94
|
browser: 'puppeteer',
|
|
@@ -112,7 +112,7 @@ import {
|
|
|
112
112
|
Application,
|
|
113
113
|
createOpenApiDocumentFromReadmeOperations,
|
|
114
114
|
extractReadmeOperationFromHtml,
|
|
115
|
-
} from '
|
|
115
|
+
} from 'oapiex';
|
|
116
116
|
|
|
117
117
|
const app = new Application({ browser: 'puppeteer' });
|
|
118
118
|
const html = await app.loadHtmlSource(
|
|
@@ -132,16 +132,16 @@ console.log(document.paths);
|
|
|
132
132
|
|
|
133
133
|
## Configuration
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
OAPIEX looks for one of these files in the current working directory:
|
|
136
136
|
|
|
137
|
-
- `
|
|
138
|
-
- `
|
|
139
|
-
- `
|
|
137
|
+
- `oapiex.config.ts`
|
|
138
|
+
- `oapiex.config.js`
|
|
139
|
+
- `oapiex.config.cjs`
|
|
140
140
|
|
|
141
141
|
Example:
|
|
142
142
|
|
|
143
143
|
```ts
|
|
144
|
-
import { defineConfig } from '
|
|
144
|
+
import { defineConfig } from 'oapiex';
|
|
145
145
|
|
|
146
146
|
export default defineConfig({
|
|
147
147
|
outputFormat: 'json',
|
|
@@ -181,11 +181,11 @@ Full documentation is available at https://oapi-extractor.toneflix.net.
|
|
|
181
181
|
|
|
182
182
|
Useful sections:
|
|
183
183
|
|
|
184
|
-
- Getting started: https://toneflix.github.io/
|
|
185
|
-
- CLI reference: https://toneflix.github.io/
|
|
186
|
-
- Programmatic usage: https://toneflix.github.io/
|
|
187
|
-
- Configuration: https://toneflix.github.io/
|
|
188
|
-
- Roadmap: https://toneflix.github.io/
|
|
184
|
+
- Getting started: https://toneflix.github.io/oapiex/guide/getting-started
|
|
185
|
+
- CLI reference: https://toneflix.github.io/oapiex/reference/cli
|
|
186
|
+
- Programmatic usage: https://toneflix.github.io/oapiex/reference/programmatic-usage
|
|
187
|
+
- Configuration: https://toneflix.github.io/oapiex/reference/configuration
|
|
188
|
+
- Roadmap: https://toneflix.github.io/oapiex/project/roadmap
|
|
189
189
|
|
|
190
190
|
## Roadmap Highlights
|
|
191
191
|
|
|
@@ -197,7 +197,7 @@ Current future-looking areas include:
|
|
|
197
197
|
- support for Apidog documentation pages
|
|
198
198
|
- support for Postman documentation pages
|
|
199
199
|
|
|
200
|
-
See the full roadmap at https://toneflix.github.io/
|
|
200
|
+
See the full roadmap at https://toneflix.github.io/oapiex/project/roadmap.
|
|
201
201
|
|
|
202
202
|
## Development
|
|
203
203
|
|
package/bin/cli.mjs
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
import{JSDOM as e}from"jsdom";import{Window as t}from"happy-dom";import n from"axios";import{Logger as r}from"@h3ravel/shared";import i from"node:path";import a,{readFile as o}from"node:fs/promises";import{Kernel as s}from"@h3ravel/musket";import{pathToFileURL as c}from"node:url";const l={outputFormat:`pretty`,outputShape:`raw`,requestTimeout:5e4,maxRedirects:5,userAgent:`Mozilla/5.0 (X11; Linux x64) AppleWebKit/537.36 (KHTML, like Gecko) OpenApiExtractor/1.0.0`,retryCount:3,retryDelay:1e3,browser:`puppeteer`,happyDom:{enableJavaScriptEvaluation:!0,suppressInsecureJavaScriptEnvironmentWarning:!0},puppeteer:{headless:!0,args:[`--no-sandbox`,`--disable-setuid-sandbox`]}};let u=l;const d=()=>globalThis.__oapieBrowserSession,ee=async(e=u)=>{let t=d();if(t?.browser===e.browser)return t;t&&await f();let n={browser:e.browser,closers:[]};return e.browser===`puppeteer`&&(n.puppeteerBrowser=await(await import(`puppeteer`)).launch({headless:e.puppeteer?.headless??!0,args:e.puppeteer?.args??[`--no-sandbox`,`--disable-setuid-sandbox`]})),globalThis.__oapieBrowserSession=n,n},f=async()=>{let e=d();if(e){globalThis.__oapieBrowserSession=void 0;for(let t of e.closers.reverse())await t();e.puppeteerBrowser&&await e.puppeteerBrowser.close()}},p=(e,t)=>{let n=d();return!n||n.browser!==e?!1:(n.closers.push(t),!0)},m=e=>{let t={...l,...e,happyDom:{...l.happyDom,...e.happyDom}};return u=t,t},te=async(r,i=u,a=!1)=>{let{data:o}=i.browser===`puppeteer`?{data:``}:await n.get(r,{timeout:i.requestTimeout,maxRedirects:i.maxRedirects,headers:{"User-Agent":i.userAgent,Accept:`text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8`}});if(i.browser===`axios`)return o;if(i.browser===`happy-dom`){let e=new t({url:r,innerWidth:1024,innerHeight:768,settings:i.happyDom});e.document.write(o),await e.happyDOM.waitUntilComplete();let n=e.document.documentElement.outerHTML;if(!n)throw await e.happyDOM.close(),Error(`Unable to extract HTML from remote source: ${r}`);return p(`happy-dom`,()=>e.happyDOM.close())||await e.happyDOM.close(),n}else if(i.browser===`jsdom`){let t;try{({window:t}=new e(o,{url:r,contentType:`text/html`,runScripts:`dangerously`,includeNodeLocations:!0}));let n=t.document.documentElement.outerHTML;if(!n)throw Error(`Unable to extract HTML from remote source: ${r}`);let i=t;return p(`jsdom`,()=>i.close())||t.close(),t=void 0,n}finally{t&&t.close()}}else if(i.browser===`puppeteer`){let e=d(),t=e?.browser===`puppeteer`?e.puppeteerBrowser:void 0,o=!1,s;try{t||(t=await(await import(`puppeteer`)).launch({headless:i.puppeteer?.headless??!0,args:i.puppeteer?.args??[`--no-sandbox`,`--disable-setuid-sandbox`]}),o=!0),s=await t.newPage(),await s.setUserAgent({userAgent:i.userAgent}),await s.setRequestInterception(!0),s.on(`request`,e=>{let t=e.resourceType();if([`image`,`font`,`media`,`stylesheet`].includes(t))return void e.abort();e.continue()});try{await s.goto(r,{waitUntil:`domcontentloaded`,timeout:i.requestTimeout})}catch(e){if(!s||!await g(s))throw e}await ne(s,i.requestTimeout,a),await h(s,i.requestTimeout);let e=await s.content();if(!e)throw Error(`Unable to extract HTML from remote source: ${r}`);if(!e.includes(`id="ssr-props"`)){let{data:t}=await n.get(r,{timeout:i.requestTimeout,maxRedirects:i.maxRedirects,headers:{"User-Agent":i.userAgent,Accept:`text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8`}});e=_(e,t)}return e}finally{s&&!s.isClosed()&&await s.close(),o&&t&&await t.close()}}else throw Error(`Unsupported browser specified in configuration: ${u.browser}`)},ne=async(e,t,n=!1)=>{try{n&&await e.waitForSelector(`.hub-sidebar-content .rm-Sidebar-link`),await e.waitForSelector(`[data-testid="http-method"], article#content, script#ssr-props`,{timeout:t})}catch{}},h=async(e,t)=>{try{await e.waitForFunction(()=>{let e=!!document.querySelector(`[data-testid="http-method"]`),t=!!document.querySelector(`form[name="Parameters"]`),n=!!document.querySelector(`.rm-PlaygroundRequest`),r=!!document.querySelector(`.rm-PlaygroundResponse`),i=!!document.querySelector(`script#ssr-props`);return e?t||n||r||i:!1},{timeout:t})}catch{}try{await e.waitForSelector(`.rm-PlaygroundRequest, .rm-PlaygroundResponse, form[name="Parameters"], script#ssr-props`,{timeout:Math.min(t,5e3)})}catch{}try{await e.waitForNetworkIdle?.({idleTime:500,timeout:Math.min(t,5e3)})}catch{}},g=async e=>!!await e.$(`[data-testid="http-method"], article#content, script#ssr-props`),_=(e,t)=>{if(e.includes(`id="ssr-props"`))return e;let n=v(t);return n?e.includes(`</body>`)?e.replace(`</body>`,`${n}</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}</html>`):`${e}${n}`:e},v=e=>e.match(/<script id="ssr-props"[^>]*>[\s\S]*?<\/script>/i)?.[0]??null,y=e=>typeof e==`object`&&!!e&&!Array.isArray(e),re=e=>{let t=e.trim();if(!/^(?:\{|\[)/.test(t))return null;try{return JSON.parse(t)}catch{let e=ie(t);try{return JSON.parse(e)}catch{return null}}},ie=e=>{let t=ae(e);return`${t}${se(t)}`},ae=e=>{let t=``,n=!1,r=!1,i=``;for(let a=0;a<e.length;a+=1){let o=e[a];if(n){if(t+=o,r){r=!1;continue}if(o===`\\`){r=!0;continue}o===`"`&&(n=!1,i=`"`);continue}if(o===`"`){let r=e.slice(a);/^"(?:\\.|[^"\\])*"\s*:/.test(r)&&oe(i,t)&&(t+=`,`),t+=o,n=!0;continue}t+=o,/\s/.test(o)||(i=o)}return t},oe=(e,t)=>{if(!e||![`"`,`}`,`]`,`e`,`l`,`0`,`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`].includes(e))return!1;let n=t.trimEnd();return!n.endsWith(`,`)&&!n.endsWith(`{`)},se=e=>{let t=[],n=!1,r=!1;for(let i of e){if(r){r=!1;continue}if(i===`\\`){r=!0;continue}if(i===`"`){n=!n;continue}if(!n){if(i===`{`){t.push(`}`);continue}if(i===`[`){t.push(`]`);continue}(i===`}`||i===`]`)&&t[t.length-1]===i&&t.pop()}}return t.reverse().join(``)},ce=e=>{let n=new t;n.document.write(e);let{document:r}=n,i=r.querySelector(`article#content`)??r.body,a=i.querySelector(`form[name="Parameters"]`),o=r.querySelector(`.rm-PlaygroundRequest`),s=r.querySelector(`.rm-PlaygroundResponse`),c=j(o),l=me(c[0]??null),u=M(s);return b({method:Z(i.querySelector(`[data-testid="http-method"]`))?.toUpperCase()??null,url:Z(i.querySelector(`[data-testid="serverurl"]`)),description:A(i),sidebarLinks:N(r.querySelector(`.rm-Sidebar.hub-sidebar-content`)),requestParams:P(a),requestCodeSnippets:c,requestExample:c[0]?.body??null,requestExampleNormalized:l,responseSchemas:F(i),responseBodies:u,responseExample:u[0]?.body??null,responseExampleRaw:u[0]?.rawBody??null},x(r))},b=(e,t)=>t?{method:e.method??t.method,url:e.url??t.url,description:e.description??t.description,sidebarLinks:e.sidebarLinks.length>0?e.sidebarLinks:t.sidebarLinks,requestParams:e.requestParams.length>0?e.requestParams:t.requestParams,requestCodeSnippets:e.requestCodeSnippets.length>0?e.requestCodeSnippets:t.requestCodeSnippets,requestExample:e.requestExample??t.requestExample,requestExampleNormalized:e.requestExampleNormalized??t.requestExampleNormalized,responseSchemas:e.responseSchemas.length>0?e.responseSchemas:t.responseSchemas,responseBodies:e.responseBodies.length>0?e.responseBodies:t.responseBodies,responseExample:e.responseExample??t.responseExample,responseExampleRaw:e.responseExampleRaw??t.responseExampleRaw}:e,x=e=>{let t=e.querySelector(`script#ssr-props`)?.textContent?.trim();if(!t)return null;let n;try{n=JSON.parse(t)}catch{return null}let r=y(n)&&y(n.document)?n.document.api:null;if(!y(r))return null;let i=typeof r.method==`string`?r.method.toUpperCase():null,a=typeof r.path==`string`?r.path:null,o=y(r.schema)?r.schema:null,s=C(o,a,i?.toLowerCase()??null),c=Array.isArray(o?.servers)&&y(o.servers[0])&&typeof o.servers[0].url==`string`?o.servers[0].url:null;if(!s&&!i&&!a)return null;let l=O(s?.responses??{});return{method:i,url:w(c,a),description:s?.description??null,sidebarLinks:[],requestParams:[...S(s?.parameters),...T(s?.requestBody)],requestCodeSnippets:[],requestExample:null,requestExampleNormalized:null,responseSchemas:D(s?.responses??{}),responseBodies:l,responseExample:l[0]?.body??null,responseExampleRaw:l[0]?.rawBody??null}},S=e=>e?e.map(e=>({name:e.name,in:e.in,path:[e.name],type:e.schema?.type??null,required:e.required??!1,defaultValue:e.schema?.default==null?e.example==null?null:String(e.example):String(e.schema.default),description:e.description??e.schema?.description??null})):[],C=(e,t,n)=>{if(!e||!t||!n||!y(e.paths))return null;let r=e.paths[t];return!y(r)||!y(r[n])?null:r[n]},w=(e,t)=>!e||!t?null:`${e.replace(/\/$/,``)}${t.startsWith(`/`)?t:`/${t}`}`,T=e=>{let t=e?.content?.[`application/json`]?.schema;return t?E(t):[]},E=(e,t=[])=>{if(!e.properties)return[];let n=[];for(let[r,i]of Object.entries(e.properties)){let a=[...t,r],o=e.required?.includes(r)??!1;if(i.type===`object`&&i.properties){n.push(...E(i,a).map(e=>({...e,required:e.path.length===a.length+1?o&&e.required:e.required})));continue}n.push({name:r,in:`body`,path:a,type:i.type??null,required:o,defaultValue:i.default==null?null:String(i.default),description:i.description??null})}return n},D=e=>Object.entries(e).map(([e,t])=>({statusCode:e,description:t.description??e})),O=e=>{let t=[];for(let[n,r]of Object.entries(e))for(let[e,i]of Object.entries(r.content??{})){let a=k(i);if(a==null)continue;let o=typeof a==`string`?a:JSON.stringify(a,null,2),s=K(o,e);t.push({format:s.format,contentType:e,statusCode:n,label:r.description??n,body:s.body,rawBody:o})}return t},k=e=>{if(e.example!==void 0)return e.example;let t=e.examples;if(t&&y(t)){for(let e of Object.values(t))if(y(e)&&`value`in e)return e.value??null}return e.schema?.example===void 0?null:e.schema.example},A=e=>{let t=e.querySelector(`header`);if(!t)return null;let n=Array.from(t.querySelectorAll(`[data-testid="RDMD"]`));return Z(n[n.length-1]??null)},j=e=>{if(!e)return[];let t=R(e);return I(e).map(e=>({label:t,body:e}))},M=e=>{if(!e)return[];let t=I(e),n=V(e),r=H(e);return t.map((e,t)=>{let i=r[t]??r[0]??null,a=n[t]??n[0]??null,o=K(e,a);return{format:o.format,contentType:a,statusCode:i?.match(/\b\d{3}\b/)?.[0]??null,label:i,body:o.body,rawBody:e}})},N=e=>e?Array.from(e.querySelectorAll(`.rm-Sidebar-link`)).map(e=>{let t=e.closest(`.rm-Sidebar-section`),n=Z(e.querySelector(`[data-testid="http-method"]`))?.toUpperCase()??null;return{section:Z(t?.querySelector(`.rm-Sidebar-heading`)??null),label:B(e,n),href:e.getAttribute(`href`),method:n,active:e.classList.contains(`active`)||e.getAttribute(`aria-current`)===`page`,subpage:e.classList.contains(`subpage`)}}).filter(e=>e.label.length>0):[],P=e=>e?Array.from(e.querySelectorAll(`label`)).map(t=>{let n=Z(t),r=le(t,e),i=U(r,t);return{name:n??``,in:W(r,t,e),path:ue(t,n),type:de(r,i),required:fe(r,i),defaultValue:z(i),description:pe(r)}}).filter(e=>e.name.length>0):[],F=e=>{let t=e.querySelector(`#response-schemas`)?.nextElementSibling;return!t||!t.classList.contains(`rm-APIResponseSchemaPicker`)?[]:Array.from(t.querySelectorAll(`button`)).map(e=>{let t=Z(e),n=t?.match(/\b\d{3}\b/),r=Array.from(e.querySelectorAll(`[data-testid="RDMD"]`));return{statusCode:n?.[0]??null,description:Z(r[0]??null)??t}}).filter(e=>e.statusCode!==null)},I=e=>Array.from(e.querySelectorAll(`.CodeSnippet`)).map(e=>L(e)).filter(e=>!!e),L=e=>{if(!e)return null;let t=Array.from(e.querySelectorAll(`.CodeMirror-code pre.CodeMirror-line`));return t.length===0?null:t.map(e=>e.textContent?.replace(/\u00a0/g,` `)??``).join(`
|
|
2
|
-
`).trimEnd()||null},
|
|
1
|
+
import{JSDOM as e}from"jsdom";import{Window as t}from"happy-dom";import n from"axios";import{Logger as r}from"@h3ravel/shared";import i from"node:path";import a,{readFile as o}from"node:fs/promises";import{Command as s,Kernel as c}from"@h3ravel/musket";import{fileURLToPath as l}from"url";import u from"prettier";import{pathToFileURL as d}from"node:url";const f=[`axios`,`happy-dom`,`jsdom`,`puppeteer`],p={outputFormat:`pretty`,outputShape:`raw`,requestTimeout:5e4,maxRedirects:5,userAgent:`Mozilla/5.0 (X11; Linux x64) AppleWebKit/537.36 (KHTML, like Gecko) OpenApiExtractor/1.0.0`,retryCount:3,retryDelay:1e3,browser:`puppeteer`,happyDom:{enableJavaScriptEvaluation:!0,suppressInsecureJavaScriptEnvironmentWarning:!0},puppeteer:{headless:!0,args:[`--no-sandbox`,`--disable-setuid-sandbox`]}};let m=p;const h=()=>globalThis.__oapieBrowserSession,g=async(e=m)=>{let t=h();if(t?.browser===e.browser)return t;t&&await _();let n={browser:e.browser,closers:[]};return e.browser===`puppeteer`&&(n.puppeteerBrowser=await(await import(`puppeteer`)).launch({headless:e.puppeteer?.headless??!0,args:e.puppeteer?.args??[`--no-sandbox`,`--disable-setuid-sandbox`]})),globalThis.__oapieBrowserSession=n,n},_=async()=>{let e=h();if(e){globalThis.__oapieBrowserSession=void 0;for(let t of e.closers.reverse())await t();e.puppeteerBrowser&&await e.puppeteerBrowser.close()}},v=(e,t)=>{let n=h();return!n||n.browser!==e?!1:(n.closers.push(t),!0)},y=e=>{let t={...p,...e,happyDom:{...p.happyDom,...e.happyDom}};return m=t,t},ee=e=>f.includes(e),te=async(r,i=m,a=!1)=>{let{data:o}=i.browser===`puppeteer`?{data:``}:await n.get(r,{timeout:i.requestTimeout,maxRedirects:i.maxRedirects,headers:{"User-Agent":i.userAgent,Accept:`text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8`}});if(i.browser===`axios`)return o;if(i.browser===`happy-dom`){let e=new t({url:r,innerWidth:1024,innerHeight:768,settings:i.happyDom});e.document.write(o),await e.happyDOM.waitUntilComplete();let n=e.document.documentElement.outerHTML;if(!n)throw await e.happyDOM.close(),Error(`Unable to extract HTML from remote source: ${r}`);return v(`happy-dom`,()=>e.happyDOM.close())||await e.happyDOM.close(),n}else if(i.browser===`jsdom`){let t;try{({window:t}=new e(o,{url:r,contentType:`text/html`,runScripts:`dangerously`,includeNodeLocations:!0}));let n=t.document.documentElement.outerHTML;if(!n)throw Error(`Unable to extract HTML from remote source: ${r}`);let i=t;return v(`jsdom`,()=>i.close())||t.close(),t=void 0,n}finally{t&&t.close()}}else if(i.browser===`puppeteer`){let e=h(),t=e?.browser===`puppeteer`?e.puppeteerBrowser:void 0,o=!1,s;try{t||(t=await(await import(`puppeteer`)).launch({headless:i.puppeteer?.headless??!0,args:i.puppeteer?.args??[`--no-sandbox`,`--disable-setuid-sandbox`]}),o=!0),s=await t.newPage(),await s.setUserAgent({userAgent:i.userAgent}),await s.setRequestInterception(!0),s.on(`request`,e=>{let t=e.resourceType();if([`image`,`font`,`media`,`stylesheet`].includes(t))return void e.abort();e.continue()});try{await s.goto(r,{waitUntil:`domcontentloaded`,timeout:i.requestTimeout})}catch(e){if(!s||!await b(s))throw e}await ne(s,i.requestTimeout,a),await re(s,i.requestTimeout);let e=await s.content();if(!e)throw Error(`Unable to extract HTML from remote source: ${r}`);if(!e.includes(`id="ssr-props"`)){let{data:t}=await n.get(r,{timeout:i.requestTimeout,maxRedirects:i.maxRedirects,headers:{"User-Agent":i.userAgent,Accept:`text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8`}});e=ie(e,t)}return e}finally{s&&!s.isClosed()&&await s.close(),o&&t&&await t.close()}}else throw Error(`Unsupported browser specified in configuration: ${m.browser}`)},ne=async(e,t,n=!1)=>{try{n&&await e.waitForSelector(`.hub-sidebar-content .rm-Sidebar-link`),await e.waitForSelector(`[data-testid="http-method"], article#content, script#ssr-props`,{timeout:t})}catch{}},re=async(e,t)=>{try{await e.waitForFunction(()=>{let e=!!document.querySelector(`[data-testid="http-method"]`),t=!!document.querySelector(`form[name="Parameters"]`),n=!!document.querySelector(`.rm-PlaygroundRequest`),r=!!document.querySelector(`.rm-PlaygroundResponse`),i=!!document.querySelector(`script#ssr-props`);return e?t||n||r||i:!1},{timeout:t})}catch{}try{await e.waitForSelector(`.rm-PlaygroundRequest, .rm-PlaygroundResponse, form[name="Parameters"], script#ssr-props`,{timeout:Math.min(t,5e3)})}catch{}try{await e.waitForNetworkIdle?.({idleTime:500,timeout:Math.min(t,5e3)})}catch{}},b=async e=>!!await e.$(`[data-testid="http-method"], article#content, script#ssr-props`),ie=(e,t)=>{if(e.includes(`id="ssr-props"`))return e;let n=ae(t);return n?e.includes(`</body>`)?e.replace(`</body>`,`${n}</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}</html>`):`${e}${n}`:e},ae=e=>e.match(/<script id="ssr-props"[^>]*>[\s\S]*?<\/script>/i)?.[0]??null,x=e=>typeof e==`object`&&!!e&&!Array.isArray(e),S=e=>{let t=e.trim();if(!/^(?:\{|\[)/.test(t))return null;try{return JSON.parse(t)}catch{let e=oe(t);try{return JSON.parse(e)}catch{return null}}},oe=e=>{let t=se(e);return`${t}${le(t)}`},se=e=>{let t=``,n=!1,r=!1,i=``;for(let a=0;a<e.length;a+=1){let o=e[a];if(n){if(t+=o,r){r=!1;continue}if(o===`\\`){r=!0;continue}o===`"`&&(n=!1,i=`"`);continue}if(o===`"`){let r=e.slice(a);/^"(?:\\.|[^"\\])*"\s*:/.test(r)&&ce(i,t)&&(t+=`,`),t+=o,n=!0;continue}t+=o,/\s/.test(o)||(i=o)}return t},ce=(e,t)=>{if(!e||![`"`,`}`,`]`,`e`,`l`,`0`,`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`].includes(e))return!1;let n=t.trimEnd();return!n.endsWith(`,`)&&!n.endsWith(`{`)},le=e=>{let t=[],n=!1,r=!1;for(let i of e){if(r){r=!1;continue}if(i===`\\`){r=!0;continue}if(i===`"`){n=!n;continue}if(!n){if(i===`{`){t.push(`}`);continue}if(i===`[`){t.push(`]`);continue}(i===`}`||i===`]`)&&t[t.length-1]===i&&t.pop()}}return t.reverse().join(``)},C=e=>{let n=new t;n.document.write(e);let{document:r}=n,i=r.querySelector(`article#content`)??r.body,a=i.querySelector(`form[name="Parameters"]`),o=r.querySelector(`.rm-PlaygroundRequest`),s=r.querySelector(`.rm-PlaygroundResponse`),c=O(o),l=Ee(c[0]??null),u=k(s);return ue({method:W(i.querySelector(`[data-testid="http-method"]`))?.toUpperCase()??null,url:W(i.querySelector(`[data-testid="serverurl"]`)),description:D(i),sidebarLinks:A(r.querySelector(`.rm-Sidebar.hub-sidebar-content`)),requestParams:j(a),requestCodeSnippets:c,requestExample:c[0]?.body??null,requestExampleNormalized:l,responseSchemas:M(i),responseBodies:u,responseExample:u[0]?.body??null,responseExampleRaw:u[0]?.rawBody??null},de(r))},ue=(e,t)=>t?{method:e.method??t.method,url:e.url??t.url,description:e.description??t.description,sidebarLinks:e.sidebarLinks.length>0?e.sidebarLinks:t.sidebarLinks,requestParams:e.requestParams.length>0?e.requestParams:t.requestParams,requestCodeSnippets:e.requestCodeSnippets.length>0?e.requestCodeSnippets:t.requestCodeSnippets,requestExample:e.requestExample??t.requestExample,requestExampleNormalized:e.requestExampleNormalized??t.requestExampleNormalized,responseSchemas:e.responseSchemas.length>0?e.responseSchemas:t.responseSchemas,responseBodies:e.responseBodies.length>0?e.responseBodies:t.responseBodies,responseExample:e.responseExample??t.responseExample,responseExampleRaw:e.responseExampleRaw??t.responseExampleRaw}:e,de=e=>{let t=e.querySelector(`script#ssr-props`)?.textContent?.trim();if(!t)return null;let n;try{n=JSON.parse(t)}catch{return null}let r=x(n)&&x(n.document)?n.document.api:null;if(!x(r))return null;let i=typeof r.method==`string`?r.method.toUpperCase():null,a=typeof r.path==`string`?r.path:null,o=x(r.schema)?r.schema:null,s=pe(o,a,i?.toLowerCase()??null),c=Array.isArray(o?.servers)&&x(o.servers[0])&&typeof o.servers[0].url==`string`?o.servers[0].url:null;if(!s&&!i&&!a)return null;let l=T(s?.responses??{});return{method:i,url:me(c,a),description:s?.description??null,sidebarLinks:[],requestParams:[...fe(s?.parameters),...he(s?.requestBody)],requestCodeSnippets:[],requestExample:null,requestExampleNormalized:null,responseSchemas:ge(s?.responses??{}),responseBodies:l,responseExample:l[0]?.body??null,responseExampleRaw:l[0]?.rawBody??null}},fe=e=>e?e.map(e=>({name:e.name,in:e.in,path:[e.name],type:e.schema?.type??null,required:e.required??!1,defaultValue:e.schema?.default==null?e.example==null?null:String(e.example):String(e.schema.default),description:e.description??e.schema?.description??null})):[],pe=(e,t,n)=>{if(!e||!t||!n||!x(e.paths))return null;let r=e.paths[t];return!x(r)||!x(r[n])?null:r[n]},me=(e,t)=>!e||!t?null:`${e.replace(/\/$/,``)}${t.startsWith(`/`)?t:`/${t}`}`,he=e=>{let t=e?.content?.[`application/json`]?.schema;return t?w(t):[]},w=(e,t=[])=>{if(!e.properties)return[];let n=[];for(let[r,i]of Object.entries(e.properties)){let a=[...t,r],o=e.required?.includes(r)??!1;if(i.type===`object`&&i.properties){n.push(...w(i,a).map(e=>({...e,required:e.path.length===a.length+1?o&&e.required:e.required})));continue}n.push({name:r,in:`body`,path:a,type:i.type??null,required:o,defaultValue:i.default==null?null:String(i.default),description:i.description??null})}return n},ge=e=>Object.entries(e).map(([e,t])=>({statusCode:e,description:t.description??e})),T=e=>{let t=[];for(let[n,r]of Object.entries(e))for(let[e,i]of Object.entries(r.content??{})){let a=E(i);if(a==null)continue;let o=typeof a==`string`?a:JSON.stringify(a,null,2),s=z(o,e);t.push({format:s.format,contentType:e,statusCode:n,label:r.description??n,body:s.body,rawBody:o})}return t},E=e=>{if(e.example!==void 0)return e.example;let t=e.examples;if(t&&x(t)){for(let e of Object.values(t))if(x(e)&&`value`in e)return e.value??null}return e.schema?.example===void 0?null:e.schema.example},D=e=>{let t=e.querySelector(`header`);if(!t)return null;let n=Array.from(t.querySelectorAll(`[data-testid="RDMD"]`));return W(n[n.length-1]??null)},O=e=>{if(!e)return[];let t=F(e);return N(e).map(e=>({label:t,body:e}))},k=e=>{if(!e)return[];let t=N(e),n=_e(e),r=ve(e);return t.map((e,t)=>{let i=r[t]??r[0]??null,a=n[t]??n[0]??null,o=z(e,a);return{format:o.format,contentType:a,statusCode:i?.match(/\b\d{3}\b/)?.[0]??null,label:i,body:o.body,rawBody:e}})},A=e=>e?Array.from(e.querySelectorAll(`.rm-Sidebar-link`)).map(e=>{let t=e.closest(`.rm-Sidebar-section`),n=W(e.querySelector(`[data-testid="http-method"]`))?.toUpperCase()??null;return{section:W(t?.querySelector(`.rm-Sidebar-heading`)??null),label:L(e,n),href:e.getAttribute(`href`),method:n,active:e.classList.contains(`active`)||e.getAttribute(`aria-current`)===`page`,subpage:e.classList.contains(`subpage`)}}).filter(e=>e.label.length>0):[],j=e=>e?Array.from(e.querySelectorAll(`label`)).map(t=>{let n=W(t),r=ye(t,e),i=be(r,t);return{name:n??``,in:xe(r,t,e),path:Se(t,n),type:Ce(r,i),required:we(r,i),defaultValue:I(i),description:Te(r)}}).filter(e=>e.name.length>0):[],M=e=>{let t=e.querySelector(`#response-schemas`)?.nextElementSibling;return!t||!t.classList.contains(`rm-APIResponseSchemaPicker`)?[]:Array.from(t.querySelectorAll(`button`)).map(e=>{let t=W(e),n=t?.match(/\b\d{3}\b/),r=Array.from(e.querySelectorAll(`[data-testid="RDMD"]`));return{statusCode:n?.[0]??null,description:W(r[0]??null)??t}}).filter(e=>e.statusCode!==null)},N=e=>Array.from(e.querySelectorAll(`.CodeSnippet`)).map(e=>P(e)).filter(e=>!!e),P=e=>{if(!e)return null;let t=Array.from(e.querySelectorAll(`.CodeMirror-code pre.CodeMirror-line`));return t.length===0?null:t.map(e=>e.textContent?.replace(/\u00a0/g,` `)??``).join(`
|
|
2
|
+
`).trimEnd()||null},F=e=>{let t=e.querySelector(`header`);if(!t)return null;let n=Array.from(t.querySelectorAll(`button`)).map(e=>K(e)).filter(e=>!!e);return n.find(e=>e.toLowerCase()!==`examples`)??n[0]??null},I=e=>e&&e.getAttribute(`value`)?.trim()||null,L=(e,t)=>G(e.querySelectorAll(`span`)).filter(e=>!t||e.toUpperCase()!==t).sort((e,t)=>t.length-e.length)[0]??W(e)??``,_e=e=>G(e.querySelectorAll(`div`)).filter(e=>/^[\w.+-]+\/[\w.+-]+$/i.test(e)),ve=e=>Array.from(e.querySelectorAll(`button, [role="button"]`)).map(e=>K(e)).filter(e=>!!e).filter(e=>/\b\d{3}\b/.test(e)),ye=(e,t)=>{let n=e.getAttribute(`for`),r=e;for(;r;){let e=n?r.querySelector(`#${U(n)}`):null,i=r.querySelector(`input, textarea, select`);if((e||i)&&r!==t)return r;r=r.parentElement}return e},be=(e,t)=>{let n=t.getAttribute(`for`),r=n?e.querySelector(`#${U(n)}`):null,i=e.querySelector(`input, textarea, select`);return r??i},xe=(e,t,n)=>{let r=R(`${t.getAttribute(`for`)?.toLowerCase()??``} ${e.closest(`[id]`)?.getAttribute(`id`)?.toLowerCase()??``}`);if(r)return r;let i=e;for(;i&&i!==n;){let e=i.previousElementSibling;for(;e;){if(e.tagName===`HEADER`){let t=R(W(e)??``);if(t)return t}e=e.previousElementSibling}i=i.parentElement}return null},R=e=>{let t=e.trim().toLowerCase();return t.includes(`query params`)||t.includes(`query-`)?`query`:t.includes(`headers`)||t.includes(`header-`)?`header`:t.includes(`body params`)||t.includes(`request body`)||t.includes(`body-`)?`body`:t.includes(`path params`)||t.includes(`path-`)?`path`:t.includes(`cookie`)||t.includes(`cookie-`)?`cookie`:null},Se=(e,t)=>{let n=e.getAttribute(`for`)??``,r=n.includes(`_`)?n.slice(n.indexOf(`_`)+1):``;return r.includes(`.`)?r.split(`.`).map(e=>e.trim()).filter(e=>e.length>0):t?[t]:[]},Ce=(e,t)=>{let n=t?.getAttribute(`type`)?.toLowerCase();if(n===`text`)return`string`;if(n)return n;let r=Array.from(e.querySelectorAll(`[class]`)).find(e=>(e.getAttribute(`class`)??``).split(/\s+/).some(e=>e.startsWith(`field-`)))?.getAttribute(`class`)?.split(/\s+/).find(e=>e.startsWith(`field-`));return r?r.replace(/^field-/,``):null},we=(e,t)=>t?.hasAttribute(`required`)?!0:G(e.querySelectorAll(`*`)).some(e=>e.toLowerCase()===`required`),Te=e=>W(e.querySelector(`[id$="__description"]`)||(Array.from(e.querySelectorAll(`[data-testid="RDMD"]`))[0]??null)),z=(e,t)=>{let n=e.trim();if(t?.toLowerCase().includes(`json`)||/^(?:\{|\[)/.test(n)){let t=S(n);return t===null?{format:`text`,body:e}:{format:`json`,body:t}}return{format:`text`,body:e}},Ee=e=>e?De(e)??Oe(e)??{sourceLabel:e.label,method:null,url:null,headers:{},bodyFormat:null,body:null,rawBody:null}:null,De=e=>{if(!e.body.startsWith(`curl `))return null;let t=e.body.match(/--request\s+([A-Z]+)/)?.[1]??null,n=e.body.match(/--url\s+(\S+)/)?.[1]??null,r=Object.fromEntries(Array.from(e.body.matchAll(/--header\s+'([^:]+):\s*([^']+)'/g)).map(e=>[e[1].trim(),e[2].trim()])),i=e.body.match(/--data\s+'([\s\S]*)'$/)?.[1]?.trim()??null,a=i?z(i,r[`content-type`]??r[`Content-Type`]??null):null;return{sourceLabel:e.label,method:t,url:n,headers:r,bodyFormat:a?.format??null,body:a?.body??null,rawBody:i}},Oe=e=>{let t=e.body.match(/fetch\(\s*(["'])(.*?)\1\s*,\s*\{([\s\S]*)\}\s*\)/);if(!t)return null;let[,,n,r]=t,i=B(r,`method`)?.toUpperCase()??null,a=ke(r),o=Ae(r),s=a[`content-type`]??a[`Content-Type`]??null,c=o?je(o,s):null;return{sourceLabel:e.label,method:i,url:n,headers:a,bodyFormat:c?.format??null,body:c?.body??null,rawBody:o}},ke=e=>{let t=B(e,`headers`);if(!t)return{};let n=H(t);return x(n)?Object.fromEntries(Object.entries(n).map(([e,t])=>[e,String(t)])):{}},Ae=e=>B(e,`body`),je=(e,t)=>{let n=H(e);return n!==null&&(t?.toLowerCase().includes(`json`)||/^[[{]/.test(e.trim()))?{format:`json`,body:n}:z(e,t)},B=(e,t)=>{let n=e.match(RegExp(`\\b${t}\\s*:`,`m`));if(!n||n.index===void 0)return null;let r=n.index+n[0].length;for(;/\s/.test(e[r]??``);)r+=1;if(e.startsWith(`JSON.stringify`,r)){let t=e.indexOf(`(`,r);return t===-1?null:V(e,t,`(`,`)`)?.slice(1,-1).trim()??null}if(e[r]===`{`||e[r]===`[`){let t=e[r]===`{`?`}`:`]`;return V(e,r,e[r],t)?.trim()??null}return e[r]===`"`||e[r]===`'`?Me(e,r):(e.slice(r).match(/^([^,\n]+)/)?.[1])?.trim()??null},V=(e,t,n,r)=>{let i=0,a=null,o=!1;for(let s=t;s<e.length;s+=1){let c=e[s];if(a){if(o){o=!1;continue}if(c===`\\`){o=!0;continue}c===a&&(a=null);continue}if(c===`"`||c===`'`){a=c;continue}if(c===n){i+=1;continue}if(c===r&&(--i,i===0))return e.slice(t,s+1)}return null},Me=(e,t)=>{let n=e[t],r=``,i=!1;for(let a=t+1;a<e.length;a+=1){let t=e[a];if(i){r+=t,i=!1;continue}if(t===`\\`){i=!0;continue}if(t===n)return r;r+=t}return null},H=e=>{let t=e.trim();if(!/^[[{]/.test(t))return null;let n=t.replace(/([{,]\s*)([A-Za-z_$][\w$-]*)(\s*:)/g,`$1"$2"$3`).replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g,(e,t)=>JSON.stringify(t.replace(/\\'/g,`'`))).replace(/,\s*([}\]])/g,`$1`);try{return JSON.parse(n)}catch{return null}},U=e=>e.replace(/([#.:[\],=])/g,`\\$1`),W=e=>e?.textContent?.replace(/\s+/g,` `).trim()||null,G=e=>Array.from(e).map(e=>W(e)).filter(e=>!!e),K=e=>G(e.querySelectorAll(`span, div, code`)).filter(e=>e.trim().length>0).sort((e,t)=>t.length-e.length)[0]??W(e),Ne=(e,t)=>{let n=new URL(t),r=e.sidebarLinks.map(e=>e.href).filter(e=>!!e).map(e=>new URL(e,n).toString()).filter(e=>/^https?:\/\//i.test(e));return Array.from(new Set(r))};var Pe=class{config;constructor(e={}){this.config=y(e)}getConfig(e={}){return{...this.config,...e}}configure(e){return this.config=y({...this.config,...e}),this.config}async crawlReadmeOperations(e,t,n){let i=this.resolveCrawlBaseUrl(e,n);if(!i)throw Error(`Crawl mode requires a remote source URL or --base-url when using a local file`);let a=!!h();a||await g(this.config);try{let e=Ne(t,i),n=new URL(i).toString(),a=this.attachSourceUrl(n,t),o=e.filter(e=>e!==n);return{rootSource:n,discoveredUrls:e,operations:[a,...(await Promise.all(o.map(async e=>{let t=Date.now(),n=C(await this.loadHtmlSource(e)),a=Date.now()-t;return n.method?(r.twoColumnDetail(r.log([[`Crawled`,`green`],[`${a/1e3}s`,`gray`]],` `,!1),e.replace(i,``)),this.attachSourceUrl(e,n)):null}))).filter(e=>e!==null)]}}finally{a||await _()}}attachSourceUrl(e,t){return{sourceUrl:e,...t}}resolveCrawlBaseUrl(e,t){return t?new URL(t).toString():/^https?:\/\//i.test(e)?e:null}async loadHtmlSource(e,t){if(!e)throw Error(`A source path or URL is required`);return/^https?:\/\//i.test(e)?te(e,this.config,t):o(i.resolve(process.cwd(),e),`utf8`)}};const Fe=l(import.meta.url);var Ie=class extends s{signature=`init
|
|
3
|
+
{--f|force : Overwrite existing config}
|
|
4
|
+
`;description=`Generate a default oapiex.config.ts in the current directory`;async handle(){let e=process.cwd(),t=i.join(e,`oapiex.config.js`),n=!!this.option(`force`,!1),r=this.buildConfigTemplate();try{await a.access(t),n||(this.error(`Config file already exists at ${t}. Use --force to overwrite.`),process.exit(1))}catch{}await a.writeFile(t,r,`utf8`),this.line(`Created ${t} `)}buildConfigTemplate(){let e=p;return[`import { defineConfig } from '${Fe.includes(`node_modules`)?`oapiex`:`./src/Manager`}'`,``,`/**`,` * See https://oapi-extractor.toneflix.net/configuration for docs`,` */`,`export default defineConfig({`,` outputFormat: '${e.outputFormat}',`,` outputShape: '${e.outputShape}',`,` browser: '${e.browser}',`,` requestTimeout: ${e.requestTimeout},`,` maxRedirects: ${e.maxRedirects},`,` userAgent: '${e.userAgent}',`,` retryCount: ${e.retryCount},`,` retryDelay: ${e.retryDelay},`,`})`].join(`
|
|
5
|
+
`)}};const q=(e,t=`Extracted API`,n=`0.0.0`)=>{let r={};for(let t of e){let e=Re(t);!e||Le(e)||(r[e.path]??={},r[e.path][e.method]=e.operation)}return{openapi:`3.1.0`,info:{title:t,version:n},paths:r}},Le=e=>e.path===`/`&&e.method===`get`&&e.operation.operationId===`get`&&Object.keys(e.operation.responses).length===0,Re=e=>{if(!e.method||!e.url)return null;let t=new URL(e.url);if(ze(t,e))return null;let n=e.method.toLowerCase(),r=Be(t.pathname);return{path:r,method:n,operation:{summary:e.sidebarLinks.find(e=>e.active)?.label,description:e.description??void 0,operationId:Ye(n,r),parameters:He(e.requestParams),requestBody:Ue(e.requestParams,e.requestExampleNormalized?.body,Ve(e.requestParams)?null:Je(e)),responses:Ke(e.responseSchemas,e.responseBodies)}}},ze=(e,t)=>e.hostname!==`example.com`||e.pathname!==`/`?!1:t.requestParams.length===0&&t.responseSchemas.length===0&&t.responseBodies.length===0&&t.requestExampleNormalized?.url===`https://example.com/`,Be=e=>e.split(`/`).map(e=>{if(!e)return e;try{return decodeURIComponent(e)}catch{return e}}).join(`/`),Ve=e=>e.some(e=>e.in===`body`||e.in===null),He=e=>{let t=e.filter(e=>Xe(e.in)).map(e=>Y(e));return t.length>0?t:void 0},Ue=(e,t,n)=>{let r=e.filter(e=>e.in===`body`||e.in===null);if(r.length===0&&t==null)return;let i=We(r,t,n);return{required:r.length>0?r.some(e=>e.required):!1,content:{"application/json":{schema:i,...t==null?{}:{example:t}}}}},We=(e,t,n)=>{let r=$(Q(t),Q(n))??{type:`object`};t==null?n!=null&&(r.example=n):r.example=t;for(let t of e)Ge(r,t);return r},J=e=>{if(Array.isArray(e))return{type:`array`,items:J(e[0])??{},example:e};if(x(e))return{type:`object`,properties:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,J(t)??{}])),example:e};if(typeof e==`string`)return{type:`string`,example:e};if(typeof e==`number`)return{type:Number.isInteger(e)?`integer`:`number`,example:e};if(typeof e==`boolean`)return{type:`boolean`,example:e};if(e===null)return{}},Ge=(e,t)=>{let n=t.path.length>0?t.path:[t.name],r=e;for(let[e,i]of n.slice(0,-1).entries())r.properties??={},r.properties[i]??={type:`object`},t.required&&(r.required=Array.from(new Set([...r.required??[],i]))),r=r.properties[i],r.type??=`object`,e===n.length-2&&t.required&&(r.required??=[]);let i=n[n.length-1]??t.name;r.properties??={},r.properties[i]=X(t),t.required&&(r.required=Array.from(new Set([...r.required??[],i])))},Y=e=>({name:e.name,in:e.in,required:e.in===`path`?!0:e.required,description:e.description??void 0,schema:X(e),example:e.defaultValue??void 0}),X=e=>({type:e.type??void 0,description:e.description??void 0,default:e.defaultValue??void 0}),Ke=(e,t)=>{let n={};for(let r of e){if(!r.statusCode)continue;let e=Z(t.filter(e=>e.statusCode===r.statusCode));n[r.statusCode]={description:r.description??r.statusCode,...e?{content:e}:{}}}for(let e of t){if(!e.statusCode||n[e.statusCode])continue;let t=Z([e]);n[e.statusCode]={description:e.label??e.statusCode,...t?{content:t}:{}}}return n},Z=e=>{if(e.length===0)return;let t={};for(let n of e){let e=n.contentType??(n.format===`json`?`application/json`:`text/plain`);t[e]={schema:qe(n.body,n.format),example:n.body}}return t},qe=(e,t)=>{if(t===`json`)return J(e);if(t===`text`)return{type:`string`,example:e}},Je=e=>e.responseBodies.find(e=>e.format===`json`)?.body??(typeof e.responseExample==`object`&&e.responseExample!==null?e.responseExample:typeof e.responseExampleRaw==`string`?S(e.responseExampleRaw):typeof e.responseExample==`string`?S(e.responseExample):null),Q=e=>e==null?null:J(e)??null,$=(e,t)=>{if(!e)return t;if(!t)return e;let n={...t,...e,...e.type||t.type?{type:e.type??t.type}:{},...e.description||t.description?{description:e.description??t.description}:{},...e.default!==void 0||t.default!==void 0?{default:e.default??t.default}:{},...e.example!==void 0||t.example!==void 0?{example:e.example??t.example}:{}};if(e.properties||t.properties){let r=new Set([...Object.keys(e.properties??{}),...Object.keys(t.properties??{})]);n.properties=Object.fromEntries(Array.from(r).map(n=>[n,$(e.properties?.[n]??null,t.properties?.[n]??null)??{}]))}return(e.items||t.items)&&(n.items=$(e.items??null,t.items??null)??{}),(e.required||t.required)&&(n.required=Array.from(new Set([...t.required??[],...e.required??[]]))),n},Ye=(e,t)=>`${e}${t.replace(/\{([^}]+)\}/g,`$1`).split(`/`).filter(Boolean).map(e=>e.replace(/[^a-zA-Z0-9]+/g,` `)).map(e=>e.trim()).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1).replace(/\s+(.)/g,(e,t)=>t.toUpperCase())).join(``)}`,Xe=e=>e===`query`||e===`header`||e===`path`||e===`cookie`;var Ze=class extends s{signature=`parse
|
|
6
|
+
{source : Local HTML file path or remote URL}
|
|
7
|
+
{--O|output=pretty : Output format [pretty,json,js]}
|
|
8
|
+
{--S|shape=raw : Result shape [raw,openapi]}
|
|
9
|
+
{--B|browser? : Remote loader [axios,happy-dom,jsdom,puppeteer]}
|
|
10
|
+
{--c|crawl : Crawl sidebar links and parse every discovered operation}
|
|
11
|
+
{--b|base-url? : Base URL used to resolve sidebar links when crawling from a local file}
|
|
12
|
+
`;description=`Parse a saved ReadMe page or remote documentation URL and print normalized output`;async handle(){let e=this.app.getConfig(),t=String(this.argument(`source`,``)).trim(),n=String(this.option(`output`,e.outputFormat)).trim().toLowerCase(),i=String(this.option(`shape`,e.outputShape)).trim().toLowerCase(),a=String(this.option(`browser`,e.browser)).trim().toLowerCase(),o=this.option(`crawl`),s=String(this.option(`baseUrl`,``)).trim()||null,c=this.spinner(`${o?`Crawling and p`:`P`}arsing source...`).start(),l=!1;try{let e=Date.now();if(!ee(a))throw Error(`Unsupported browser: ${a}`);this.app.configure({browser:a}),o&&(await g(this.app.getConfig()),l=!0);let u=C(await this.app.loadHtmlSource(t,!0)),d=o?await this.app.crawlReadmeOperations(t,u,s):u,f=i===`openapi`?this.buildOpenApiPayload(d):d,p=n===`js`?`export default ${JSON.stringify(f,null,2)}`:JSON.stringify(f,null,n===`json`?0:2),m=await this.saveOutputToFile(p,t,i,n),h=Date.now()-e;r.twoColumnDetail(r.log([[`Output`,`green`],[`${h/1e3}s`,`gray`]],` `,!1),m.replace(process.cwd(),`.`)),c.succeed(`Parsing completed`)}catch(e){let t=e instanceof Error?e.message:`Unknown error`;c.fail(`Failed to parse source: ${t}`),process.exitCode=1}finally{l&&await _()}}buildOpenApiPayload=e=>`operations`in e?q(e.operations,`Extracted API`,`0.0.0`):q([e],`Extracted API`,`0.0.0`);saveOutputToFile=async(e,t,n,r)=>{let o={pretty:`txt`,json:`json`,js:`js`}[r],s=i.resolve(process.cwd(),`output`);await a.mkdir(s,{recursive:!0});let c=`${t.replace(/[^a-zA-Z0-9_-]+/g,`_`).replace(/^_+|_+$/g,``)||`output`}${n===`openapi`?`.openapi`:``}.${o}`,l=i.join(s,c);return r===`js`&&(e=await u.format(e,{parser:`babel`,semi:!1,singleQuote:!0})),await a.writeFile(l,e,`utf8`),l}};const Qe=[`oapiex.config.ts`,`oapiex.config.js`,`oapiex.config.cjs`];async function $e(e=process.cwd()){for(let t of Qe){let n=i.join(e,t);try{await a.access(n);let e=await import(d(n).href),t=e.default||e.config||e;if(typeof t==`object`&&t)return t}catch{continue}}return null}async function et(e={}){let t=await $e();return{...p,...t,...e,happyDom:{...p.happyDom,...t?.happyDom||{},...e.happyDom||{}}}}const tt=new Pe(await et());await c.init(tt,{name:`OAPIEX`,logo:``,allowRebuilds:!1,packages:[`@h3ravel/musket`],baseCommands:[Ie,Ze]});export{};
|
package/dist/index.cjs
CHANGED
|
@@ -971,10 +971,10 @@ var InitCommand = class extends _h3ravel_musket.Command {
|
|
|
971
971
|
signature = `init
|
|
972
972
|
{--f|force : Overwrite existing config}
|
|
973
973
|
`;
|
|
974
|
-
description = "Generate a default
|
|
974
|
+
description = "Generate a default oapiex.config.ts in the current directory";
|
|
975
975
|
async handle() {
|
|
976
976
|
const cwd = process.cwd();
|
|
977
|
-
const configPath = node_path.default.join(cwd, "
|
|
977
|
+
const configPath = node_path.default.join(cwd, "oapiex.config.js");
|
|
978
978
|
const force = Boolean(this.option("force", false));
|
|
979
979
|
const configTemplate = this.buildConfigTemplate();
|
|
980
980
|
try {
|
|
@@ -990,7 +990,7 @@ var InitCommand = class extends _h3ravel_musket.Command {
|
|
|
990
990
|
buildConfigTemplate() {
|
|
991
991
|
const def = defaultConfig;
|
|
992
992
|
return [
|
|
993
|
-
`import { defineConfig } from '${__filename$1.includes("node_modules") ? "
|
|
993
|
+
`import { defineConfig } from '${__filename$1.includes("node_modules") ? "oapiex" : "./src/Manager"}'`,
|
|
994
994
|
"",
|
|
995
995
|
"/**",
|
|
996
996
|
" * See https://oapi-extractor.toneflix.net/configuration for docs",
|
|
@@ -1298,9 +1298,9 @@ var ParseCommand = class extends _h3ravel_musket.Command {
|
|
|
1298
1298
|
//#endregion
|
|
1299
1299
|
//#region src/ConfigLoader.ts
|
|
1300
1300
|
const CONFIG_BASENAMES = [
|
|
1301
|
-
"
|
|
1302
|
-
"
|
|
1303
|
-
"
|
|
1301
|
+
"oapiex.config.ts",
|
|
1302
|
+
"oapiex.config.js",
|
|
1303
|
+
"oapiex.config.cjs"
|
|
1304
1304
|
];
|
|
1305
1305
|
async function loadUserConfig(rootDir = process.cwd()) {
|
|
1306
1306
|
for (const basename of CONFIG_BASENAMES) {
|
package/dist/index.mjs
CHANGED
|
@@ -939,10 +939,10 @@ var InitCommand = class extends Command {
|
|
|
939
939
|
signature = `init
|
|
940
940
|
{--f|force : Overwrite existing config}
|
|
941
941
|
`;
|
|
942
|
-
description = "Generate a default
|
|
942
|
+
description = "Generate a default oapiex.config.ts in the current directory";
|
|
943
943
|
async handle() {
|
|
944
944
|
const cwd = process.cwd();
|
|
945
|
-
const configPath = path.join(cwd, "
|
|
945
|
+
const configPath = path.join(cwd, "oapiex.config.js");
|
|
946
946
|
const force = Boolean(this.option("force", false));
|
|
947
947
|
const configTemplate = this.buildConfigTemplate();
|
|
948
948
|
try {
|
|
@@ -958,7 +958,7 @@ var InitCommand = class extends Command {
|
|
|
958
958
|
buildConfigTemplate() {
|
|
959
959
|
const def = defaultConfig;
|
|
960
960
|
return [
|
|
961
|
-
`import { defineConfig } from '${__filename.includes("node_modules") ? "
|
|
961
|
+
`import { defineConfig } from '${__filename.includes("node_modules") ? "oapiex" : "./src/Manager"}'`,
|
|
962
962
|
"",
|
|
963
963
|
"/**",
|
|
964
964
|
" * See https://oapi-extractor.toneflix.net/configuration for docs",
|
|
@@ -1266,9 +1266,9 @@ var ParseCommand = class extends Command {
|
|
|
1266
1266
|
//#endregion
|
|
1267
1267
|
//#region src/ConfigLoader.ts
|
|
1268
1268
|
const CONFIG_BASENAMES = [
|
|
1269
|
-
"
|
|
1270
|
-
"
|
|
1271
|
-
"
|
|
1269
|
+
"oapiex.config.ts",
|
|
1270
|
+
"oapiex.config.js",
|
|
1271
|
+
"oapiex.config.cjs"
|
|
1272
1272
|
];
|
|
1273
1273
|
async function loadUserConfig(rootDir = process.cwd()) {
|
|
1274
1274
|
for (const basename of CONFIG_BASENAMES) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oapiex",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "A CLI tool to extract OpenAPI specifications from documentations generated by readme.com and other similar platforms, and generate TypeScript SDKs for seamless API integration.",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"private": false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"oapie": "bin/cli.mjs",
|
|
18
|
-
"
|
|
18
|
+
"oapiex": "bin/cli.mjs"
|
|
19
19
|
},
|
|
20
20
|
"module": "./dist/index.mjs",
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"keywords": [
|
|
34
34
|
"oapie",
|
|
35
|
-
"
|
|
35
|
+
"oapiex",
|
|
36
36
|
"cli",
|
|
37
37
|
"command-line",
|
|
38
38
|
"tool",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"cmd": "tsx src/cli.ts",
|
|
87
87
|
"build": "tsdown",
|
|
88
88
|
"version": "pnpm run build",
|
|
89
|
-
"unlink": "pnpm uninstall --global
|
|
89
|
+
"unlink": "pnpm uninstall --global oapiex || true",
|
|
90
90
|
"release": "pnpm changeset version && pnpm changeset publish",
|
|
91
91
|
"publish-it": "pnpm publish --tag latest",
|
|
92
92
|
"docs:dev": "vitepress dev docs",
|