@getcronit/pylon-builder
Version:
46 lines (41 loc) • 19.3 kB
JavaScript
import Y from"path";import E from"fs";import Z from"chokidar";import{build as G}from"esbuild";import H from"esbuild-plugin-tsc";import N from"path";import U from"consola";var I=class{sfiFilePath;outputDir;cachedTypeDefs=null;constructor(r,t="./.pylon"){this.sfiFilePath=r,this.outputDir=t}async build(r){let t=async()=>{let e=Date.now(),{typeDefs:n,resolvers:s}=r.getBuildDefs(),c=q(s),i={name:"inject-code",setup(d){d.onLoad({filter:/src[\/\\]index\.ts$/,namespace:"file"},async b=>N.relative(process.cwd(),b.path)!==N.join("src","index.ts")?void 0:{loader:"ts",contents:await E.promises.readFile(b.path,"utf-8")+`
import {handler as __internalPylonHandler} from "@getcronit/pylon"
let __internalPylonConfig = undefined
try {
__internalPylonConfig = config
} catch {
// config is not declared, pylonConfig remains undefined
}
app.use(__internalPylonHandler({
typeDefs: ${JSON.stringify(n)},
graphql,
resolvers: ${c},
config: __internalPylonConfig
}))
`})}},a=N.join(process.cwd(),this.sfiFilePath),p=N.join(process.cwd(),this.outputDir),o=await G({logLevel:"silent",metafile:!0,entryPoints:[a],outdir:p,bundle:!0,format:"esm",sourcemap:"inline",packages:"external",plugins:[i,H({tsconfigPath:N.join(process.cwd(),"tsconfig.json")})]});if(o.errors.length>0){for(let d of o.errors)U.error(d);throw new Error("Failed to build Pylon")}if(o.warnings.length>0)for(let d of o.warnings)U.warn(d);let f=this.cachedTypeDefs!==n;this.cachedTypeDefs=n;let u=Date.now()-e,T=Object.keys(o.metafile.inputs).length,h=Object.values(o.metafile.outputs).reduce((d,b)=>d+b.bytes,0),m=N.join(process.cwd(),this.outputDir,"schema.graphql");await E.promises.writeFile(m,n);let g=N.join(process.cwd(),this.outputDir,"resolvers.js");return await E.promises.writeFile(g,`export const resolvers = ${c}`),{totalFiles:T,totalSize:h,schemaChanged:f,duration:u}};if(r.watch){let e=N.dirname(this.sfiFilePath);Z.watch(e).on("change",async()=>{try{let n=await t();r.onWatch&&r.onWatch(n)}catch(n){U.error(n)}})}return await t()}};function q(l){return`{${Object.entries(l).map(([t,e])=>{if(e!==void 0)return typeof e=="string"?`${t}:${e}`:typeof e=="function"?`${t}:${e.toString()}`:typeof e=="object"&&!Array.isArray(e)?`${t}:${q(e)}`:`${t}:${JSON.stringify(e)}`}).join(",")}}`}import v from"typescript";import D from"typescript";import y from"typescript";var W=l=>l.flags&y.TypeFlags.Object&&l.getProperties().length===0&&l.getCallSignatures().length===0,P=(l,r)=>{let t=l.typeToTypeNode(r,void 0,void 0),e=!!(t&&(t.kind===y.SyntaxKind.ArrayType||t.kind===y.SyntaxKind.TupleType));return!e&&r.getSymbol()?.getName()==="Array"?!0:e};var S=l=>l.flags&y.TypeFlags.String||l.flags&y.TypeFlags.Number||l.flags&y.TypeFlags.Boolean||l.flags&y.TypeFlags.StringLiteral||l.flags&y.TypeFlags.NumberLiteral||l.flags&y.TypeFlags.BooleanLiteral,R=l=>l.getCallSignatures().length>0,w=l=>S(l)?!1:!!(l.types?.length>1&&l.types.every(e=>e.flags&y.TypeFlags.StringLiteral||e.flags&y.TypeFlags.NumberLiteral||e.flags&y.TypeFlags.BooleanLiteral)),j=l=>l.getSymbol()?.getName()==="Promise",$=l=>{if(l.objectFlags&y.ObjectFlags.Reference){let e=l.typeArguments;if(e&&e.length>0)return e[0]}},z=l=>{if(!l.isUnion())return{type:l,wasOptional:!1};let t=l.types.filter(e=>!(e.flags&y.TypeFlags.Undefined||e.flags&y.TypeFlags.Null)).length!==l.types.length;return{type:l.getNonNullableType(),wasOptional:t}};function Q(l,r){let t=r;if(t.isUnion()){let s=t.types.filter(a=>!(a.flags&y.TypeFlags.Object&&a.objectFlags&y.ObjectFlags.Reference&&a.typeArguments?.length)),c=t.types.filter(a=>a.flags&y.TypeFlags.Object&&a.objectFlags&y.ObjectFlags.Reference&&a.typeArguments?.length).map(a=>a.typeArguments[0]),i=s.filter(a=>!c.some(p=>p===a));t=l.getUnionType(i)}let e=[];if(P(l,t)||S(t)||w(t))return e;let n=[];n=t.resolvedProperties||t.getProperties()||[];for(let s of n)(s.valueDeclaration&&!(y.getCombinedModifierFlags(s.valueDeclaration)&y.ModifierFlags.Private)&&!s.getName().startsWith("#"))!==!1&&e.push(s);return e}var k=l=>l.replace(/[^0-9a-zA-Z_]/g,"_"),O=l=>!!l.getSymbol()?.getDeclarations()?.some(r=>r.getSourceFile().fileName.includes("@repeaterjs"));import F from"typescript";var x=class{checker;schema;typesNameMap=new Map;inputsNameMap=new Map;unions=[];enums=[];constructor(r,t){this.checker=r,this.schema=t}getTypeDefinition=(r,t={isInputType:!1})=>{let{type:e,wasOptional:n}=z(r);if(e.flags&F.TypeFlags.Void||e.flags&F.TypeFlags.Undefined||e.flags&F.TypeFlags.Null)return{name:"Void",isList:!1,isRequired:!1};if(O(e)){let p=this.checker.getTypeArguments(e)[0];if(p)return this.getTypeDefinition(p,t)}if(j(e)){let p=$(e);if(p)return this.getTypeDefinition(p,t)}let s=t.isRequired!==void 0?t.isRequired:!n;if(W(e))return{name:"Object",isList:!1,isRequired:s};let c=t.isInputType?this.inputsNameMap:this.typesNameMap;if(c.has(e))return{name:c.get(e),isList:!1,isRequired:s};let i=e.aliasSymbol?.escapedName?.toString()||e.symbol?.escapedName.toString();(i==="__type"||i==="__object")&&(t.propertyName?i=t.propertyName.charAt(0).toUpperCase()+t.propertyName.slice(1):i=void 0),i&&(i=k(i));let a=p=>Array.from(c.values()).includes(p);if(i&&!this.schema.scalars.includes(i)){t.isInputType&&(i=`${i}Input`);let p=1,o=i;for(;a(i);)i=`${o}_${p}`,p++}if(i&&this.schema.scalars.includes(i))return{name:i,isList:!1,isRequired:s};if(P(this.checker,e)){let p=this.checker.getIndexTypeOfType(e,F.IndexKind.Number);if(p){let o=this.getTypeDefinition(p,t);return{name:o.name,isList:!0,isRequired:o.isRequired,isListRequired:s}}}else if(w(e)){let p=this.checker.typeToTypeNode(e,void 0,void 0),f=e.types.map(u=>{if(u.isLiteral()){let T=u.value?.toString();if(!T)throw new Error("Enum member name is undefined");return k(T)}throw new Error("Invalid type for enum member")});f.length>0&&(i=i||p.typeName?.symbol?.escapedName,i||(i=f.join("_").toUpperCase(),i=t.isInputType?`${i}Input`:i),this.enums.push({name:i,values:f,rawType:e}))}else if(S(e)){let p=this.checker.typeToString(e);return e.flags&F.TypeFlags.StringLiteral?p="String":e.flags&F.TypeFlags.NumberLiteral?p="Number":e.flags&F.TypeFlags.BooleanLiteral?p="Boolean":e.flags&F.TypeFlags.String?p="String":e.flags&F.TypeFlags.Number?p="Number":e.flags&F.TypeFlags.Boolean&&(p="Boolean"),{name:k(p),isList:!1,isRequired:s}}else if(e.isIntersection()){let o=e.types.map(f=>this.getTypeDefinition(f,t).name);i=k(i||o.join("And"))}else if(e.isUnion()){let p=e.types,o=e.types.length===p.length,f=p.some(m=>{let g=P(this.checker,m)?this.checker.getIndexTypeOfType(m,F.IndexKind.Number):m;return g?S(g)||w(g):!0}),u=[...new Set(p.map(m=>this.getTypeDefinition(m,{...t,isRequired:o})))],T=u.filter(m=>m.isList);for(let m of T){let g=u.findIndex(d=>d.name===m.name&&!d.isList);g>-1&&u.splice(g,1)}let h=u.map(m=>m.name);if(i=k(i||h.join("Or")),i){let m=1,g=i;for(;a(i);)i=`${g}_${m}`,m++}if(u.length>1&&!t.isInputType&&!f)this.unions.push({name:i,types:h,rawType:e});else{let m=u[0];if(!m)throw new Error("Cannot get type definition");let g=u.some(d=>d.isList&&d.name===m.name);return{name:m.name,isList:g,isRequired:s}}}else if(R(e)){let o=e.getCallSignatures()[0]?.getReturnType();if(o&&j(o)){let f=$(o);f&&(o=f)}if(o)return this.getTypeDefinition(o,t)}return i||(i="Any"),c.set(e,i),{name:i,isList:!1,isRequired:s}};getUnions(){return this.unions}getEnums(){return this.enums}typeDefinitionToGraphQLType=r=>{let t=r.name,{isList:e,isRequired:n,isListRequired:s}=r;return n&&(t=`${t}!`),e&&(t=`[${t}]`,s&&(t=`${t}!`)),t}};import M from"consola";var L=class{schema;checker;program;sfiFile;typeDefinitionBuilder;constructor(r,t,e){this.schema={types:[],inputs:[],interfaces:[],unions:[],enums:[],scalars:["ID","Int","Float","Number","Any","Void","Object","File","Date","JSON","String","Boolean"]},this.checker=r,this.sfiFile=t,this.program=e,this.typeDefinitionBuilder=new x(r,this.schema)}parse(r){let t=this.makeReferenceSchema(r);for(let[e,n]of t.types){let s;r.Query===e?s="Query":r.Mutation===e?s="Mutation":r.Subscription===e&&(s="Subscription"),this.processSchemaReference(e,n,s,"types")}for(let[e,n]of t.inputs)this.processSchemaReference(e,n,void 0,"inputs");this.extractForbiddenFieldNamesFromSchema(),this.schema.unions=this.typeDefinitionBuilder.getUnions().map(e=>({...e,description:this.getTypeDocumentation(e.rawType)})),this.schema.enums=this.typeDefinitionBuilder.getEnums().map(e=>({...e,description:this.getTypeDocumentation(e.rawType)})),this.schema.types=this.schema.types.filter(e=>!this.schema.enums.find(n=>n.name===e.name)),this.schema.inputs=this.schema.inputs.filter(e=>!this.schema.enums.find(n=>n.name===e.name)),this.schema.unions.forEach(e=>{let n=this.checkIfInterfaceIsPossibleForUnion(e,this.schema.types);n&&(this.schema.interfaces.push(n),this.schema.unions=this.schema.unions.filter(s=>s.name!==e.name),this.schema.types.map(s=>{if(e.types.includes(s.name))return s.implements||(s.implements=[]),s.implements.push(n.name),s}))});for(let[e,n]of t.classImplementsMap){this.schema.types.map(s=>{let c=this.schema.types.find(i=>i.rawType===e);return c&&(c.implements=Array.from(new Set([...c.implements||[],...n.map(i=>this.checker.typeToString(i))]))),s});for(let s of n){let c=this.schema.types.find(i=>i.rawType===s);c&&(this.schema.interfaces.push({name:this.checker.typeToString(s),description:this.getTypeDocumentation(s),fields:c.fields}),this.schema.types=this.schema.types.filter(i=>i.rawType!==s))}}this.schema.types=this.schema.types.map(e=>((e.implements||this.schema.unions.find(n=>n.types.includes(e.name)))&&(e.fields=e.fields.filter(n=>n.type.name!=="Void")),e)),this.schema.unions=this.schema.unions.map(e=>({...e,__resolveType:this.getResolveTypeForUnionOrInterface(e,this.schema.types)})),this.schema.interfaces=this.schema.interfaces.map(e=>({...e,__resolveType:this.getResolveTypeForUnionOrInterface(e,this.schema.types)}))}checkIfInterfaceIsPossibleForUnion(r,t){let e=r.types.map(c=>{let i=t.find(a=>a.name===c);if(!i)throw new Error(`Type ${c} not found`);return i}),s=e[0].fields.filter(c=>e.every(i=>i.fields.some(a=>JSON.stringify(a)===JSON.stringify(c))));return s.length>0?{name:r.name,description:r.description,fields:s}:null}getResolveTypeForUnionOrInterface(r,t){let e="types"in r?t.filter(i=>r.types.includes(i.name)):t.filter(i=>i.implements?.includes(r.name));e.sort((i,a)=>a.fields.length-i.fields.length);let n=new Map;e.forEach(i=>{let a=i.fields.map(p=>p.name).sort().join(", ");if(n.has(a)){let p=n.get(a);M.warn(`Warning: Union types "${i.name}" and "${p?.name}" have the same fields: [${a}].
Consider differentiating these types by adding unique fields or using different type names.
This may cause ambiguity in type resolution.`)}else n.set(a,i)});let c=`function resolveType(node) { if (node && typeof node === 'object') { ${e.map(i=>`if (${i.fields.map(o=>`"${o.name}" in node`).join(" && ")}) {return '${i.name}'};`).join(" ")} } }`;return new Function("return "+c)()}toString(){let{typeDefinitionToGraphQLType:r}=this.typeDefinitionBuilder,t=n=>n?`"""
${n}
"""
`:"",e="";for(let n of this.schema.inputs){e+=t(n.description),e+=`input ${n.name} {
`,n.fields.length===0&&(e+=` _ : String
`);for(let s of n.fields)e+=`${t(s.type.description)}`,e+=` ${s.name}: ${r(s.type)}
`;e+=`}
`}for(let n of this.schema.types)if(n.fields.length!==0){e+=t(n.description),e+=`type ${n.name}`,n.implements&&(e+=` implements ${n.implements.join(" & ")}`),e+=` {
`;for(let s of n.fields){let c="";s.args.length>0&&(c=`(${s.args.map(i=>`${t(i.type.description)}${i.name}: ${r(i.type)}`).join(", ")})`),e+=`${t(s.type.description)}`,e+=`${s.name}${c}: ${r(s.type)}
`}e+=`}
`}for(let n of this.schema.unions)e+=t(n.description),e+=`union ${n.name} = ${n.types.join(" | ")}
`;for(let n of this.schema.interfaces){e+=t(n.description),e+=`interface ${n.name} {
`;for(let s of n.fields)e+=`${t(s.type.description)}`,e+=`${s.name}: ${r(s.type)}
`;e+=`}
`}for(let n of this.schema.scalars)e+=`scalar ${n}
`;for(let n of this.schema.enums){e+=t(n.description),e+=`enum ${n.name} {
`;for(let s of n.values)e+=` ${s}
`;e+=`}
`}return e}getSchema(){return this.schema}getResolvers(){let r={};for(let t of this.schema.unions)r[t.name]={__resolveType:t.__resolveType};for(let t of this.schema.interfaces)r[t.name]={__resolveType:t.__resolveType};return r}processSchemaReference(r,t,e,n="types"){let{getTypeDefinition:s}=this.typeDefinitionBuilder,c=n==="inputs",i=s(r,{isInputType:c}),a=e||i.name,p=this.schema[n].find(o=>o.name===a);if(!p){if(this.schema.scalars.includes(a))return;this.schema[n].push({name:a,description:this.getTypeDocumentation(r),fields:[],rawType:P(this.checker,r)&&(r.getNumberIndexType()||r.getStringIndexType())||r}),p=this.schema[n][this.schema[n].length-1]}for(let[o,f]of Object.entries(t)){let u=f.returnType,T=s(u,{isInputType:c,propertyName:o});if(n==="types"){let h={name:o,type:{...T,description:this.getSymbolDocumentation(f.symbol)},args:[]};if(f.args)for(let[m,g]of Object.entries(f.args)){let d=g,b=s(d.type,{isInputType:!0,propertyName:m,isRequired:g.isRequired});this.schema.scalars.includes(this.checker.typeToString(d.type))&&(b.name=this.checker.typeToString(d.type)),h.args.push({name:m,type:{...b,description:d.documentation}})}p.fields.push(h)}else if(n==="inputs"){let h={name:o,type:{...T,description:this.getTypeDocumentation(u)}};p.fields.find(m=>m.name===h.name)||p.fields.push(h)}}}getSymbolDocumentation(r){let t="";t+=D.displayPartsToString(r.getDocumentationComment(this.checker));let e=r.getJsDocTags(this.checker).map(n=>`@${n.name} ${D.displayPartsToString(n.text)}`).join(`
`);return e&&(t+=`
`+e),t}getTypeDocumentation=r=>{let t=r.getSymbol();return t?this.getSymbolDocumentation(t):""};extractForbiddenFieldNamesFromSchema(){let r=/^[_A-Za-z][_0-9A-Za-z]*$/,t=e=>r.test(e)?e.startsWith("__"):!0;for(let e of this.schema.types)e.fields=e.fields.filter(n=>!t(n.name));for(let e of this.schema.inputs)e.fields=e.fields.filter(n=>!t(n.name))}makeReferenceSchema(r){let t={types:new Map,inputs:new Map,classImplementsMap:new Map},e=(s,c={},i="types",a=[])=>{if(t[i].has(s))return;if(O(s)){let o=this.checker.getTypeArguments(s)[0];e(o,c,i,[...a,"REPEATER_ITEM"]);return}if(this.checker.typeToString(s)!=="[]"&&!this.schema.scalars.includes(this.checker.typeToString(s))&&!S(s)){if(j(s)){if(i==="inputs")return;let o=$(s);o&&e(o,c,i,[...a,"PROMISE"]);return}if(s.isClass()){let o=s.getBaseTypes();o&&o.forEach(f=>{t.classImplementsMap.has(f)||t.classImplementsMap.set(f,[]),t.classImplementsMap.get(f).push(s)})}if(s.isUnion())if(w(s))t[i].has(s)||t[i].set(s,{}),e(s,c,i,[...a,"ENUM"]);else if(i==="types")s.types.forEach(o=>{o.flags&D.TypeFlags.Null||o.flags&D.TypeFlags.Undefined||S(o)||e(o,c,i,[...a,o.symbol?.getName()||`N/A ${this.checker.typeToString(o)}`])});else{let o=s.getNonNullableType();o.isUnion()&&!S(o)&&(M.warn(`Warning: Union types in input fields are not supported yet. Defaulting to the first type (${this.checker.typeToString(o)}) at path: ${a.join(" > ")}`),o=o.types[0]),e(o,c,i,[...a,"NON_NULLABLE"])}else if(R(s)){if(i==="inputs")return;if(!c.parentType)throw new Error("Cannot have a function without a parent type");let o=s.getCallSignatures()[0],f=o?.getParameters()||[],u=o?.getReturnType(),T=t[i].get(c.parentType)[c.propetyName];f.forEach(h=>{let m=this.checker.getTypeOfSymbolAtLocation(h,this.sfiFile);if(this.checker.isTupleType(m))m.target.labeledElementDeclarations.forEach((b,J)=>{let C=this.checker.getTypeAtLocation(b),B=b.name.text,K=this.checker.getSymbolAtLocation(b),V=this.getSymbolDocumentation(K||h);T.args[B]={type:C,isRequired:b.initializer===void 0?void 0:!1,documentation:V},e(C,{parentType:s},"inputs",[...a,B])});else if(h.valueDeclaration&&D.isParameter(h.valueDeclaration)&&h.valueDeclaration.dotDotDotToken)M.warn(`Warning: Rest parameters without explicit names are not supported. Skipping rest parameter at path: ${a.join(" > ")}.
Unsupported: \`function example(...args: any[]) { }\`.
Supported: \`function example(...namedArgs: [first: string, second: number]) { }\`.
Please provide named rest parameters to ensure proper type resolution.`);else{let g=h.valueDeclaration;T.args&&(T.args[h.escapedName]={type:m,isRequired:g.initializer===void 0?void 0:!1,documentation:this.getSymbolDocumentation(h)},e(m,{parentType:s},"inputs",[...a,h.escapedName]))}}),u&&e(u,c,i,[...a,"RETURN_TYPE"])}else if(P(this.checker,s)){let o=this.checker.getIndexTypeOfType(s,D.IndexKind.Number);o&&!S(o)&&(t[i].has(s)||t[i].set(s,{}),e(o,c,i,[...a,"ITEM_TYPE"]))}else if(!S(s)){let o=Q(this.checker,s);t[i].has(s)||t[i].set(s,{}),o.forEach(f=>{let u=this.checker.getTypeOfSymbolAtLocation(f,this.sfiFile);t[i].get(s)[f.escapedName]||(t[i].get(s)[f.escapedName]={symbol:f,returnType:u,args:{}}),e(u,{propetyName:f.escapedName,parentType:s},i,[...a,f.escapedName])})}}};r.Query&&e(r.Query),r.Mutation&&e(r.Mutation),r.Subscription&&e(r.Subscription);let n=this.program.getSourceFiles();for(let s of n)D.forEachChild(s,c=>{if(D.isClassDeclaration(c)){let i=c.heritageClauses?.flatMap(a=>a.types.map(p=>p))||[];if(i.length>0)for(let a of i)t.types.has(this.checker.getTypeAtLocation(a))&&(t.classImplementsMap.set(this.checker.getTypeAtLocation(c),[this.checker.getTypeAtLocation(a)]),e(this.checker.getTypeAtLocation(c)))}});return t}};import A from"path";var _=class{program;checker;sfiFile;sfi;sfiFilePath;constructor(r){this.sfiFilePath=r;let t=this.loadTsConfigOptions(),e=v.sys.readDirectory(A.dirname(this.sfiFilePath),[".ts"],[".d.ts"]).concat([A.join(A.dirname(this.sfiFilePath),"..","pylon.d.ts")]);this.program=v.createProgram(e,t),this.checker=this.program.getTypeChecker(),this.loadSfi()}loadSfi(){let t=this.program.getSourceFiles().find(c=>A.resolve(c.fileName)===this.sfiFilePath);if(!t)throw new Error("Could not find index.ts (pylon entrypoint)");this.sfiFile=t;let e=this.checker.getSymbolAtLocation(t),s=this.checker.getExportsOfModule(e).find(c=>c.escapedName==="graphql");if(!s)throw new Error("Could not find graphql export");this.sfi=s}loadTsConfigOptions(){let r={target:v.ScriptTarget.ESNext,module:v.ModuleKind.CommonJS,strict:!0,esModuleInterop:!0,skipLibCheck:!1,forceConsistentCasingInFileNames:!0,noImplicitAny:!0,experimentalDecorators:!0},t=v.findConfigFile(A.dirname(this.sfiFilePath),v.sys.fileExists,"tsconfig.json");if(!t)return console.log("Could not find tsconfig.json"),r;let e=v.readConfigFile(t,v.sys.readFile);return e.error?(console.log("Could not read tsconfig.json",e.error),r):v.parseJsonConfigFileContent(e.config,v.sys,A.dirname(t)).options}build(){let r=this.checker.getTypeOfSymbolAtLocation(this.sfi,this.sfiFile),t=r.getProperty("Query"),e=r.getProperty("Mutation"),n=r.getProperty("Subscription"),s=t?this.checker.getTypeOfSymbolAtLocation(t,this.sfiFile):void 0,c=e?this.checker.getTypeOfSymbolAtLocation(e,this.sfiFile):void 0,i=n?this.checker.getTypeOfSymbolAtLocation(n,this.sfiFile):void 0,a=new L(this.checker,this.sfiFile,this.program);return a.parse({Query:s,Mutation:c,Subscription:i}),{typeDefs:a.toString(),schema:a.getSchema(),resolvers:a.getResolvers()}}};var we=async l=>await new I(l.sfiFilePath,l.outputFilePath).build({getBuildDefs:()=>{let e=new _(Y.join(process.cwd(),l.sfiFilePath)).build();return{typeDefs:e.typeDefs,resolvers:e.resolvers}},watch:l.watch,onWatch:l.onWatch});export{_ as SchemaBuilder,we as build};
//# sourceMappingURL=index.js.map