oapiex 0.1.0
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/LICENSE +15 -0
- package/README.md +221 -0
- package/bin/cli.mjs +2 -0
- package/dist/index.cjs +1424 -0
- package/dist/index.d.ts +310 -0
- package/dist/index.mjs +1302 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ToneFlix Technologies Limited
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# OPENAPIE
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/openapie)
|
|
4
|
+
[](https://www.npmjs.com/package/openapie)
|
|
5
|
+
[](https://github.com/toneflix/openapie/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/toneflix/openapie/actions/workflows/docs.yml)
|
|
7
|
+
[](https://github.com/toneflix/openapie/actions/workflows/test.yml)
|
|
8
|
+
[](https://github.com/toneflix/openapie/actions/workflows/publish.yml)
|
|
9
|
+
|
|
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
|
+
|
|
12
|
+
It currently focuses on ReadMe-powered API docs and saved HTML pages, with room to expand to additional documentation platforms.
|
|
13
|
+
|
|
14
|
+
- npm: https://www.npmjs.com/package/openapie
|
|
15
|
+
- docs: https://toneflix.github.io/openapie
|
|
16
|
+
- repository: https://github.com/toneflix/oapi
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- parse remote documentation URLs or saved local HTML files
|
|
21
|
+
- extract methods, URLs, parameters, request examples, and response examples
|
|
22
|
+
- crawl linked sidebar pages for multi-endpoint references
|
|
23
|
+
- transform extracted operations into an OpenAPI-like document
|
|
24
|
+
- use the tool as a CLI or as a programmatic package
|
|
25
|
+
- choose among `axios`, `happy-dom`, `jsdom`, and `puppeteer` loaders
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### CLI
|
|
30
|
+
|
|
31
|
+
Install globally if you want the `openapie` command available everywhere:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add -g openapie
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm i -g openapie
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
yarn global add openapie
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Library
|
|
46
|
+
|
|
47
|
+
Install locally if you want to use OPENAPIE from your own scripts or tooling:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm add openapie
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm i openapie
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
yarn add openapie
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI Quick Start
|
|
62
|
+
|
|
63
|
+
Extract a single page into an OpenAPI-like JSON document:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
oapie parse https://maplerad.dev/reference/create-a-customer \
|
|
67
|
+
--shape=openapi \
|
|
68
|
+
--output=json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Crawl a sidebar section and write a JavaScript module:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
oapie parse https://maplerad.dev/reference/create-a-customer \
|
|
75
|
+
--shape=openapi \
|
|
76
|
+
--output=js \
|
|
77
|
+
--crawl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Generate a config file:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
oapie init
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Programmatic Usage
|
|
87
|
+
|
|
88
|
+
Extract a single operation:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { Application, extractReadmeOperationFromHtml } from 'openapie';
|
|
92
|
+
|
|
93
|
+
const app = new Application({
|
|
94
|
+
browser: 'puppeteer',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const html = await app.loadHtmlSource(
|
|
98
|
+
'https://maplerad.dev/reference/create-a-customer',
|
|
99
|
+
true,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const operation = extractReadmeOperationFromHtml(html);
|
|
103
|
+
|
|
104
|
+
console.log(operation.method);
|
|
105
|
+
console.log(operation.url);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Convert extracted data into an OpenAPI-like document:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import {
|
|
112
|
+
Application,
|
|
113
|
+
createOpenApiDocumentFromReadmeOperations,
|
|
114
|
+
extractReadmeOperationFromHtml,
|
|
115
|
+
} from 'openapie';
|
|
116
|
+
|
|
117
|
+
const app = new Application({ browser: 'puppeteer' });
|
|
118
|
+
const html = await app.loadHtmlSource(
|
|
119
|
+
'https://maplerad.dev/reference/create-a-customer',
|
|
120
|
+
true,
|
|
121
|
+
);
|
|
122
|
+
const operation = extractReadmeOperationFromHtml(html);
|
|
123
|
+
|
|
124
|
+
const document = createOpenApiDocumentFromReadmeOperations(
|
|
125
|
+
[operation],
|
|
126
|
+
'Extracted API',
|
|
127
|
+
'0.0.0',
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
console.log(document.paths);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
OPENAPIE looks for one of these files in the current working directory:
|
|
136
|
+
|
|
137
|
+
- `openapie.config.ts`
|
|
138
|
+
- `openapie.config.js`
|
|
139
|
+
- `openapie.config.cjs`
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { defineConfig } from 'openapie';
|
|
145
|
+
|
|
146
|
+
export default defineConfig({
|
|
147
|
+
outputFormat: 'json',
|
|
148
|
+
outputShape: 'openapi',
|
|
149
|
+
browser: 'puppeteer',
|
|
150
|
+
requestTimeout: 50000,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Supported Loaders
|
|
155
|
+
|
|
156
|
+
- `axios`
|
|
157
|
+
- `happy-dom`
|
|
158
|
+
- `jsdom`
|
|
159
|
+
- `puppeteer`
|
|
160
|
+
|
|
161
|
+
The default browser is `puppeteer`.
|
|
162
|
+
|
|
163
|
+
## Output
|
|
164
|
+
|
|
165
|
+
CLI parse results are written to the local `output/` directory.
|
|
166
|
+
|
|
167
|
+
Available output shapes:
|
|
168
|
+
|
|
169
|
+
- `raw`
|
|
170
|
+
- `openapi`
|
|
171
|
+
|
|
172
|
+
Available output formats:
|
|
173
|
+
|
|
174
|
+
- `pretty`
|
|
175
|
+
- `json`
|
|
176
|
+
- `js`
|
|
177
|
+
|
|
178
|
+
## Documentation
|
|
179
|
+
|
|
180
|
+
Full documentation is available at https://oapi-extractor.toneflix.net.
|
|
181
|
+
|
|
182
|
+
Useful sections:
|
|
183
|
+
|
|
184
|
+
- Getting started: https://toneflix.github.io/openapie/guide/getting-started
|
|
185
|
+
- CLI reference: https://toneflix.github.io/openapie/reference/cli
|
|
186
|
+
- Programmatic usage: https://toneflix.github.io/openapie/reference/programmatic-usage
|
|
187
|
+
- Configuration: https://toneflix.github.io/openapie/reference/configuration
|
|
188
|
+
- Roadmap: https://toneflix.github.io/openapie/project/roadmap
|
|
189
|
+
|
|
190
|
+
## Roadmap Highlights
|
|
191
|
+
|
|
192
|
+
Current future-looking areas include:
|
|
193
|
+
|
|
194
|
+
- improved ReadMe extraction coverage
|
|
195
|
+
- stronger schema and example inference
|
|
196
|
+
- broader programmatic API helpers
|
|
197
|
+
- support for Apidog documentation pages
|
|
198
|
+
- support for Postman documentation pages
|
|
199
|
+
|
|
200
|
+
See the full roadmap at https://toneflix.github.io/openapie/project/roadmap.
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
Install dependencies:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pnpm install
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Common commands:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pnpm test
|
|
214
|
+
pnpm build
|
|
215
|
+
pnpm docs:dev
|
|
216
|
+
pnpm docs:build
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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},R=e=>{let t=e.querySelector(`header`);if(!t)return null;let n=Array.from(t.querySelectorAll(`button`)).map(e=>$(e)).filter(e=>!!e);return n.find(e=>e.toLowerCase()!==`examples`)??n[0]??null},z=e=>e&&e.getAttribute(`value`)?.trim()||null,B=(e,t)=>Q(e.querySelectorAll(`span`)).filter(e=>!t||e.toUpperCase()!==t).sort((e,t)=>t.length-e.length)[0]??Z(e)??``,V=e=>Q(e.querySelectorAll(`div`)).filter(e=>/^[\w.+-]+\/[\w.+-]+$/i.test(e)),H=e=>Array.from(e.querySelectorAll(`button, [role="button"]`)).map(e=>$(e)).filter(e=>!!e).filter(e=>/\b\d{3}\b/.test(e)),le=(e,t)=>{let n=e.getAttribute(`for`),r=e;for(;r;){let e=n?r.querySelector(`#${X(n)}`):null,i=r.querySelector(`input, textarea, select`);if((e||i)&&r!==t)return r;r=r.parentElement}return e},U=(e,t)=>{let n=t.getAttribute(`for`),r=n?e.querySelector(`#${X(n)}`):null,i=e.querySelector(`input, textarea, select`);return r??i},W=(e,t,n)=>{let r=G(`${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=G(Z(e)??``);if(t)return t}e=e.previousElementSibling}i=i.parentElement}return null},G=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},ue=(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]:[]},de=(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},fe=(e,t)=>t?.hasAttribute(`required`)?!0:Q(e.querySelectorAll(`*`)).some(e=>e.toLowerCase()===`required`),pe=e=>Z(e.querySelector(`[id$="__description"]`)||(Array.from(e.querySelectorAll(`[data-testid="RDMD"]`))[0]??null)),K=(e,t)=>{let n=e.trim();if(t?.toLowerCase().includes(`json`)||/^(?:\{|\[)/.test(n)){let t=re(n);return t===null?{format:`text`,body:e}:{format:`json`,body:t}}return{format:`text`,body:e}},me=e=>e?he(e)??ge(e)??{sourceLabel:e.label,method:null,url:null,headers:{},bodyFormat:null,body:null,rawBody:null}:null,he=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?K(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}},ge=e=>{let t=e.body.match(/fetch\(\s*(["'])(.*?)\1\s*,\s*\{([\s\S]*)\}\s*\)/);if(!t)return null;let[,,n,r]=t,i=q(r,`method`)?.toUpperCase()??null,a=_e(r),o=ve(r),s=a[`content-type`]??a[`Content-Type`]??null,c=o?ye(o,s):null;return{sourceLabel:e.label,method:i,url:n,headers:a,bodyFormat:c?.format??null,body:c?.body??null,rawBody:o}},_e=e=>{let t=q(e,`headers`);if(!t)return{};let n=Y(t);return y(n)?Object.fromEntries(Object.entries(n).map(([e,t])=>[e,String(t)])):{}},ve=e=>q(e,`body`),ye=(e,t)=>{let n=Y(e);return n!==null&&(t?.toLowerCase().includes(`json`)||/^[[{]/.test(e.trim()))?{format:`json`,body:n}:K(e,t)},q=(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:J(e,t,`(`,`)`)?.slice(1,-1).trim()??null}if(e[r]===`{`||e[r]===`[`){let t=e[r]===`{`?`}`:`]`;return J(e,r,e[r],t)?.trim()??null}return e[r]===`"`||e[r]===`'`?be(e,r):(e.slice(r).match(/^([^,\n]+)/)?.[1])?.trim()??null},J=(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},be=(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},Y=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}},X=e=>e.replace(/([#.:[\],=])/g,`\\$1`),Z=e=>e?.textContent?.replace(/\s+/g,` `).trim()||null,Q=e=>Array.from(e).map(e=>Z(e)).filter(e=>!!e),$=e=>Q(e.querySelectorAll(`span, div, code`)).filter(e=>e.trim().length>0).sort((e,t)=>t.length-e.length)[0]??Z(e),xe=(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 Se=class{config;constructor(e={}){this.config=m(e)}getConfig(e={}){return{...this.config,...e}}configure(e){return this.config=m({...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=!!d();a||await ee(this.config);try{let e=xe(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=ce(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 f()}}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 Ce=[`openapie.config.ts`,`openapie.config.js`,`openapie.config.cjs`];async function we(e=process.cwd()){for(let t of Ce){let n=i.join(e,t);try{await a.access(n);let e=await import(c(n).href),t=e.default||e.config||e;if(typeof t==`object`&&t)return t}catch{continue}}return null}async function Te(e={}){let t=await we();return{...l,...t,...e,happyDom:{...l.happyDom,...t?.happyDom||{},...e.happyDom||{}}}}const Ee=new Se(await Te());await s.init(Ee,{name:`mycli`,discoveryPaths:[i.join(process.cwd(),`src/Commands/*.ts`)]});export{};
|