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 CHANGED
@@ -1,18 +1,18 @@
1
- # OPENAPIE
1
+ # OAPIEX
2
2
 
3
- [![NPM Downloads](https://img.shields.io/npm/dt/openapie.svg)](https://www.npmjs.com/package/openapie)
4
- [![npm version](https://img.shields.io/npm/v/openapie.svg)](https://www.npmjs.com/package/openapie)
5
- [![License](https://img.shields.io/npm/l/openapie.svg)](https://github.com/toneflix/openapie/blob/main/LICENSE)
6
- [![Deploy Docs](https://github.com/toneflix/openapie/actions/workflows/docs.yml/badge.svg)](https://github.com/toneflix/openapie/actions/workflows/docs.yml)
7
- [![Run Tests](https://github.com/toneflix/openapie/actions/workflows/test.yml/badge.svg)](https://github.com/toneflix/openapie/actions/workflows/test.yml)
8
- [![Publish Package](https://github.com/toneflix/openapie/actions/workflows/publish.yml/badge.svg)](https://github.com/toneflix/openapie/actions/workflows/publish.yml)
3
+ [![NPM Downloads](https://img.shields.io/npm/dt/oapiex.svg)](https://www.npmjs.com/package/oapiex)
4
+ [![npm version](https://img.shields.io/npm/v/oapiex.svg)](https://www.npmjs.com/package/oapiex)
5
+ [![License](https://img.shields.io/npm/l/oapiex.svg)](https://github.com/toneflix/oapiex/blob/main/LICENSE)
6
+ [![Deploy Docs](https://github.com/toneflix/oapiex/actions/workflows/docs.yml/badge.svg)](https://github.com/toneflix/oapiex/actions/workflows/docs.yml)
7
+ [![Run Tests](https://github.com/toneflix/oapiex/actions/workflows/test.yml/badge.svg)](https://github.com/toneflix/oapiex/actions/workflows/test.yml)
8
+ [![Publish Package](https://github.com/toneflix/oapiex/actions/workflows/publish.yml/badge.svg)](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/openapie
15
- - docs: https://toneflix.github.io/openapie
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 `openapie` command available everywhere:
31
+ Install globally if you want the `oapiex` command available everywhere:
32
32
 
33
33
  ```bash
34
- pnpm add -g openapie
34
+ pnpm add -g oapiex
35
35
  ```
36
36
 
37
37
  ```bash
38
- npm i -g openapie
38
+ npm i -g oapiex
39
39
  ```
40
40
 
41
41
  ```bash
42
- yarn global add openapie
42
+ yarn global add oapiex
43
43
  ```
44
44
 
45
45
  ### Library
46
46
 
47
- Install locally if you want to use OPENAPIE from your own scripts or tooling:
47
+ Install locally if you want to use OAPIEX from your own scripts or tooling:
48
48
 
49
49
  ```bash
50
- pnpm add openapie
50
+ pnpm add oapiex
51
51
  ```
52
52
 
53
53
  ```bash
54
- npm i openapie
54
+ npm i oapiex
55
55
  ```
56
56
 
57
57
  ```bash
58
- yarn add openapie
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 'openapie';
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 'openapie';
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
- OPENAPIE looks for one of these files in the current working directory:
135
+ OAPIEX looks for one of these files in the current working directory:
136
136
 
137
- - `openapie.config.ts`
138
- - `openapie.config.js`
139
- - `openapie.config.cjs`
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 'openapie';
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/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
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/openapie/project/roadmap.
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},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{};
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 openapie.config.ts in the current directory";
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, "openapie.config.js");
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") ? "openapie" : "./src/Manager"}'`,
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
- "openapie.config.ts",
1302
- "openapie.config.js",
1303
- "openapie.config.cjs"
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 openapie.config.ts in the current directory";
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, "openapie.config.js");
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") ? "openapie" : "./src/Manager"}'`,
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
- "openapie.config.ts",
1270
- "openapie.config.js",
1271
- "openapie.config.cjs"
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.0",
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
- "openpie": "bin/cli.mjs"
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
- "openapie",
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 openapie || true",
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",