@jsheaven/astro-client-generator
Version:
Generates TypeScript API client code for your Astro endpoints. No manual `fetch()` code writing anymore.
57 lines (46 loc) • 8.02 kB
JavaScript
;(()=>{var B=Object.create;var R=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var m=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,o)=>(typeof require<"u"?require:t)[o]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var z=(e,t,o,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of V(t))!W.call(e,n)&&n!==o&&R(e,n,{get:()=>t[n],enumerable:!(r=G(t,n))||r.enumerable});return e};var I=(e,t,o)=>(o=e!=null?B(J(e)):{},z(t||!e||!e.__esModule?R(o,"default",{value:e,enumerable:!0}):o,e));var c=I(m("kleur/colors"),1),H=I(m("yargs-parser"),1);var w=I(m("fast-glob"),1),A=m("path"),P=m("fs"),b=m("ts-morph"),Q=m("chokidar"),x={apiDir:"./src/pages/api",baseUrl:"",outDir:"./src/pages/api-client",tsConfigPath:"./tsconfig.json",site:"http://localhost:3000"},O=["GET","POST","DELETE","PATCH","HEAD","PUT","OPTIONS","ALL"],S=["POST","DELETE","GET","PATCH","HEAD","PUT","OPTIONS"],C=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`,_=e=>`${e.charAt(0).toLowerCase()}${e.slice(1)}`,D=e=>e.join(`
`).trim(),K=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?S:t},X=e=>({...x,...e});var Y=(e,t)=>{let o=new b.Project({tsConfigFilePath:t}),r=[];return e.forEach(n=>{let p=o.getSourceFile(n),i=p.getImportDeclarations().map(a=>a.getFullText()),u=[],l="",f="";p.getInterfaces().map(a=>{switch(a.getName()){case"ApiRequest":l=a.getText();break;case"ApiResponse":f=a.getText();break;default:u.push(a.getText())}});let d=p.getTypeAliases().map(a=>a.getText()),g=[];p.getExportSymbols().forEach(a=>{let y=O.indexOf(a.getName().toUpperCase());if(y>-1){let s=O[y];s==="DELETE"?g.push("DELETE"):s==="ALL"?g=S:g.push(s)}}),g.forEach(a=>{r.push({apiRoute:n,imports:i,method:a,requestInterface:l,responseInterface:f,genericInterfaces:u,genericTypes:d})})}),r},Z=e=>{let t=[];return e.forEach(o=>{let r=(0,P.readFileSync)(o,{encoding:"utf-8"}),n=r.split(`
`),p=[],i=[],u=[],l,f,d,g=0,a=[-1,-1],y=[-1,-1];n.forEach((s,h)=>{let U=/^import (.*) from (.*)/.test(s.trim()),T=s.indexOf("interface ")>-1,k=/^\s*(?:export\s+)?type\s+\w+\s*=\s*[\w<>,\s|]+\s*;?\s*$/.test(s.trim()),F=s.indexOf(" ApiRequest ")>-1,N=s.indexOf(" ApiResponse ")>-1,M=s.indexOf("{")>-1,j=s.indexOf("}")>-1;if(U){p.push(s);return}if(k){u.push(s);return}T&&F&&(f=h),T&&N&&(d=h),T&&f!==h&&d!==h&&(l=h),(f||d||l)&&M&&g++,(f||d||l)&&j&&(g--,g===0&&(f&&(a=[f,h],f=void 0),d&&(y=[d,h],d=void 0),l&&(i.push(D(n.slice(l,h+1))),l=void 0)))}),K(r).forEach(s=>{t.push({apiRoute:o,imports:p,method:s,requestInterface:D(n.slice(a[0],a[1]+1)),responseInterface:D(n.slice(y[0],y[1]+1)),genericInterfaces:i,genericTypes:u})})}),t},ee=e=>e.reduce((t,o)=>{let{path:r}=o;return t[r]||(t[r]=[]),t[r].push(o),t},{}),v=e=>{console.log("\u2699\uFE0F Generating endpoint clients..."),e=X(e);let t=(0,A.resolve)(process.cwd(),e.apiDir),o=w.default.sync(`${t}/**/*.ts`),r;switch(e.parser){case"baseline":r=Y(o,e.tsConfigPath);break;case"naive":default:r=Z(o);break}r=r.filter(i=>i.responseInterface!==""),r.map(i=>(i.relativePath=`api${i.apiRoute.replace(t,"")}`.trim(),i.path=`${(0,A.parse)(i.relativePath).dir}/${(0,A.parse)(i.relativePath).name}`,i.camelCaseName=i.path.replace(/^api/i,"").split("/").map(u=>C(u)).join("").split(/[-_\ \.]/g).reduce((u,l)=>u+C(l),""),i));let n=ee(r),p=[];Object.keys(n).forEach(i=>{let u=n[i],l=oe(u,e),f=(0,A.resolve)(process.cwd(),e.outDir,u[0].relativePath.replace(/^api\//,"")),d=(0,A.parse)(f),g=d.dir,a=`${g}${A.sep}${d.name}-client.ts`.toLowerCase();p.push(a);let y=new b.Project({tsConfigFilePath:e.tsConfigPath});(0,P.mkdirSync)(g,{recursive:!0});let s=y.createSourceFile(a,l,{overwrite:!0}),h;do h=s.getFullWidth(),s.fixUnusedIdentifiers();while(h!==s.getFullWidth());s.fixMissingImports(),s.organizeImports(),s.formatText(),s.saveSync()}),console.log("\u{1F680} Finished building endpoint clients.")},te=(e,t,o,r,n)=>{let p=n?C(e.method.toLowerCase()):"";return`
/** return (await fetch('${t.baseUrl}/${e.path}', { method: '${e.method}', ... })).json() */
export const ${_(e.camelCaseName)}${p} = async(${o}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()
}`},re=(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}`,oe=(e,t)=>{let o=e[0].requestInterface?`${e[0].requestInterface}`:"",r=e[0].requestInterface?"payload: ApiRequest, ":"",n="";return e.forEach(p=>{let i=p.method!=="HEAD"&&p.method!=="GET"&&p.requestInterface?"options.body = JSON.stringify(payload)":"";n+=te(p,t,r,i,e.length>1)}),`${re(e[0],o)}
${n}`};var E=m("path"),L=m("url"),q=m("fs/promises"),se={},ne=async e=>JSON.parse(await(0,q.readFile)(e,{encoding:"utf-8"})),$=async()=>await ne((0,E.resolve)((0,E.parse)((0,L.fileURLToPath)(se.url)).dir,"../package.json"));var ie=(r=>(r.HELP="help",r.VERSION="version",r.GENERATE="generate",r))(ie||{}),ae=e=>{let t={apiDir:typeof e.apiDir=="string"?e.apiDir:x.apiDir,baseUrl:typeof e.baseUrl=="string"?e.baseUrl:x.baseUrl,outDir:typeof e.outDir=="string"?e.outDir:x.outDir};if(e.version)return{cmd:"version",options:t};if(e.help)return{cmd:"help",options:t};switch(e._[2]){case"help":return{cmd:"help",options:t};case"generate":return{cmd:"generate",options:t};default:return{cmd:"version",options:t}}},ce=()=>{console.error(`
${c.bold("astro-client-generator")} - generates TypeScript clients for Astro endpoints
${c.bold("Commands:")}
generate Generates the TypeScript clients for the endpoints.
version Show the program version.
help Show this help message.
${c.bold("Flags:")}
--apiDir <string> Folder to the API directory on disk (source code), default: './src/pages/api'
--baseUrl <string> API base URL for calling the API (only relevant if you host in a subdir, it's very unlikely), default: ''
--outDir <string> Folder on disk to write the client code to, default: './src/pages/api-client'
--version Show the version number and exit.
--help Show this help message.
${c.bold("Example(s):")}
npx @jsheaven/astro-client-generator generate
`)},pe=async()=>{console.log((await $()).version)},le=async e=>{let t=(0,H.default)(e),o=ae(t),r={...o.options};switch(console.log(c.dim(">"),`${c.bold(c.yellow("astro-client-generator"))} @ ${c.dim((await $()).version)}: ${c.magenta(c.bold(o.cmd))}`,c.gray("...")),o.cmd){case"help":ce(),process.exit(0);case"version":await pe(),process.exit(0);case"generate":{try{await v(r)}catch(n){fe(n)}process.exit(0)}default:throw new Error(`Error running ${o.cmd}`)}},ue=e=>console.error(c.red(e.toString()||e)),fe=e=>{ue(e),process.exit(1)};try{le(process.argv)}catch(e){console.error(e),process.exit(1)}})();
//# sourceMappingURL=cli.iife.js.map