@jsheaven/astro-client-generator
Version:
Generates TypeScript API client code for your Astro endpoints. No manual `fetch()` code writing anymore.
39 lines (31 loc) • 6.14 kB
JavaScript
import H from"fast-glob";import{resolve as E,parse as P,sep as w}from"path";import{readFileSync as k,mkdirSync as F}from"fs";import{Project as I}from"ts-morph";import{watch as v}from"chokidar";var C={apiDir:"./src/pages/api",baseUrl:"",outDir:"./src/pages/api-client",tsConfigPath:"./tsconfig.json",site:"http://localhost:3000"},T=["GET","POST","DELETE","PATCH","HEAD","PUT","OPTIONS","ALL"],D=["POST","DELETE","GET","PATCH","HEAD","PUT","OPTIONS"],x=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`,M=e=>`${e.charAt(0).toLowerCase()}${e.slice(1)}`,y=e=>e.join(`
`).trim(),U=e=>{let t=[];return(e.indexOf("function GET")>-1||e.indexOf("export const GET")>-1)&&t.push("GET"),(e.indexOf("function POST")>-1||e.indexOf("export const POST")>-1)&&t.push("POST"),(e.indexOf("function DELETE")>-1||e.indexOf("export const DELETE")>-1)&&t.push("DELETE"),(e.indexOf("function PATCH")>-1||e.indexOf("export const PATCH")>-1)&&t.push("PATCH"),(e.indexOf("function HEAD")>-1||e.indexOf("export const HEAD")>-1)&&t.push("HEAD"),(e.indexOf("function PUT")>-1||e.indexOf("export const PUT")>-1)&&t.push("PUT"),(e.indexOf("function OPTIONS")>-1||e.indexOf("export const OPTIONS")>-1)&&t.push("OPTIONS"),e.indexOf("function ALL")>-1||e.indexOf("export const ALL")>-1?D:t},R=e=>({...C,...e}),X=(e=C)=>(e=R(e),{name:"astro-client-generator",hooks:{"astro:server:start":async({address:t})=>{let c=t.family==="IPv4"?`http://${t.address}:${t.port}`:`http://[${t.address}]:${t.port}`;e.site&&e.site!==c&&!e.disableSiteAutoDiscovery&&(console.log(`\u{1F504} Endpoint host has changed to ${c}. Updating site accordingly.`),e.site=c);let r=v(e.apiDir,{ignoreInitial:!0,ignorePermissionErrors:!0,ignored:/(^|[\/\\])\../});r.on("change",i=>{console.log("\u{1F58A}\uFE0F Endpoint changed: ",i),A(e)}),r.on("add",(i,s)=>{s.ctimeMs!==s.mtimeMs&&(s.ctimeMs+100>Date.now()||(console.log("\u{1F58A}\uFE0F Endpoint added: ",i),A(e)))}),A(e)},"astro:build:setup":async()=>{A(e)}}}),N=(e,t)=>{let c=new I({tsConfigFilePath:t}),r=[];return e.forEach(i=>{let s=c.getSourceFile(i),o=s.getImportDeclarations().map(a=>a.getFullText()),u=[],p="",l="";s.getInterfaces().map(a=>{switch(a.getName()){case"ApiRequest":p=a.getText();break;case"ApiResponse":l=a.getText();break;default:u.push(a.getText())}});let f=s.getTypeAliases().map(a=>a.getText()),d=[];s.getExportSymbols().forEach(a=>{let h=T.indexOf(a.getName().toUpperCase());if(h>-1){let n=T[h];n==="DELETE"?d.push("DELETE"):n==="ALL"?d=D:d.push(n)}}),d.forEach(a=>{r.push({apiRoute:i,imports:o,method:a,requestInterface:p,responseInterface:l,genericInterfaces:u,genericTypes:f})})}),r},j=e=>{let t=[];return e.forEach(c=>{let r=k(c,{encoding:"utf-8"}),i=r.split(`
`),s=[],o=[],u=[],p,l,f,d=0,a=[-1,-1],h=[-1,-1];i.forEach((n,g)=>{let $=/^import (.*) from (.*)/.test(n.trim()),m=n.indexOf("interface ")>-1,O=/^\s*(?:export\s+)?type\s+\w+\s*=\s*[\w<>,\s|]+\s*;?\s*$/.test(n.trim()),b=n.indexOf(" ApiRequest ")>-1,L=n.indexOf(" ApiResponse ")>-1,q=n.indexOf("{")>-1,S=n.indexOf("}")>-1;if($){s.push(n);return}if(O){u.push(n);return}m&&b&&(l=g),m&&L&&(f=g),m&&l!==g&&f!==g&&(p=g),(l||f||p)&&q&&d++,(l||f||p)&&S&&(d--,d===0&&(l&&(a=[l,g],l=void 0),f&&(h=[f,g],f=void 0),p&&(o.push(y(i.slice(p,g+1))),p=void 0)))}),U(r).forEach(n=>{t.push({apiRoute:c,imports:s,method:n,requestInterface:y(i.slice(a[0],a[1]+1)),responseInterface:y(i.slice(h[0],h[1]+1)),genericInterfaces:o,genericTypes:u})})}),t},B=e=>e.reduce((t,c)=>{let{path:r}=c;return t[r]||(t[r]=[]),t[r].push(c),t},{}),A=e=>{console.log("\u2699\uFE0F Generating endpoint clients..."),e=R(e);let t=E(process.cwd(),e.apiDir),c=H.sync(`${t}/**/*.ts`),r;switch(e.parser){case"baseline":r=N(c,e.tsConfigPath);break;case"naive":default:r=j(c);break}r=r.filter(o=>o.responseInterface!==""),r.map(o=>(o.relativePath=`api${o.apiRoute.replace(t,"")}`.trim(),o.path=`${P(o.relativePath).dir}/${P(o.relativePath).name}`,o.camelCaseName=o.path.replace(/^api/i,"").split("/").map(u=>x(u)).join("").split(/[-_\ \.]/g).reduce((u,p)=>u+x(p),""),o));let i=B(r),s=[];Object.keys(i).forEach(o=>{let u=i[o],p=Q(u,e),l=E(process.cwd(),e.outDir,u[0].relativePath.replace(/^api\//,"")),f=P(l),d=f.dir,a=`${d}${w}${f.name}-client.ts`.toLowerCase();s.push(a);let h=new I({tsConfigFilePath:e.tsConfigPath});F(d,{recursive:!0});let n=h.createSourceFile(a,p,{overwrite:!0}),g;do g=n.getFullWidth(),n.fixUnusedIdentifiers();while(g!==n.getFullWidth());n.fixMissingImports(),n.organizeImports(),n.formatText(),n.saveSync()}),console.log("\u{1F680} Finished building endpoint clients.")},W=(e,t,c,r,i)=>{let s=i?x(e.method.toLowerCase()):"";return`
/** return (await fetch('${t.baseUrl}/${e.path}', { method: '${e.method}', ... })).json() */
export const ${M(e.camelCaseName)}${s} = async(${c}options: RequestOptions = {}): Promise<ApiResponse> => {
let requestUrl = '${t.site}${t.baseUrl}/${e.path}'
if (options && options.query) {
requestUrl += '?' + Object.keys(options.query)
.map((key) => key + '=' + options.query![key])
.join('&');
}
delete options.query
options.method = '${e.method}'
${r}
return (await fetch(requestUrl, options)).json()
}`},z=(e,t)=>`${e.imports.join(`
`)}
${e.genericTypes.join(`
`)}
${e.genericInterfaces.join(`
`)}
export interface QueryMap {
[key: string]: string
}
export interface RequestOptions extends RequestInit {
query?: QueryMap
}
${t}
${e.responseInterface}`,Q=(e,t)=>{let c=e[0].requestInterface?`${e[0].requestInterface}`:"",r=e[0].requestInterface?"payload: ApiRequest, ":"",i="";return e.forEach(s=>{let o=s.method!=="HEAD"&&s.method!=="GET"&&s.requestInterface?"options.body = JSON.stringify(payload)":"";i+=W(s,t,r,o,e.length>1)}),`${z(e[0],c)}
${i}`};export{T as AstroHttpEndpointMethodNames,D as HttpMethods,U as analyzeHttpMethodsImplemented,X as apiClientGenerator,C as apiGeneratorOptionsDefaults,y as cleanupInterfce,A as generateClientApis,B as groupByApiRoute,M as lowerCaseFirst,N as parseApiRoutesBaseline,j as parseApiRoutesNaive,Q as produceClientApiCode,z as produceClientApiHeaderCode,W as produceClientApiRequestImplCode,x as upperCaseFirst,R as validateConfig};
//# sourceMappingURL=index.esm.js.map