@contractspec/lib.source-extractors 2.7.20 → 2.7.22
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/dist/browser/index.js +15 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/node/index.js +15 -15
- package/dist/source-extractors.feature.d.ts +1 -0
- package/package.json +3 -3
package/dist/browser/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var
|
|
2
|
-
`)}function
|
|
3
|
-
`),type:"registry"}}function
|
|
1
|
+
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var L=(s,d)=>{for(var t in d)e(s,t,{get:d[t],enumerable:!0,configurable:!0,set:ts.bind(d,t)})};var A={};L(A,{generateSchemas:()=>P,generateSchema:()=>_,generateRegistry:()=>F,generateOperations:()=>W,generateOperation:()=>Y});function Y(s,d){let t=ds(s),o=`${os(s)}.ts`,i=is(s,t,d);return{path:o,content:i,type:"operation"}}function W(s,d){return s.endpoints.map((t)=>Y(t,d))}function ds(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.map((i)=>i.charAt(0).toUpperCase()+i.slice(1)).join("");return`${t}${o}Spec`}function os(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.join("-");return`${t}-${o}`.replace(/--+/g,"-")}function is(s,d,t){let i=s.kind==="command"?"defineCommand":"defineQuery",u=t.defaultAuth??"user",n=t.defaultOwners??["team"];return["/**",` * ${s.method} ${s.path}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Confidence: ${s.confidence.level}`," */","",`import { ${i} } from '@contractspec/lib.contracts-spec';`,"import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';","","// TODO: Define input schema based on extracted information","const inputSchema = fromZod(z.object({"," // Add fields here","}));","","// TODO: Define output schema","const outputSchema = fromZod(z.object({"," // Add fields here","}));","",`export const ${d} = ${i}({`," meta: {",` name: '${s.handlerName??s.id}',`," version: 1,"," stability: 'experimental',",` owners: ${JSON.stringify(n)},`," goal: 'TODO: Describe the business goal',",` context: 'Generated from ${s.source.file}',`," },"," io: {"," input: inputSchema,"," output: outputSchema,"," },"," policy: {",` auth: '${u}',`," },"," transport: {"," rest: {",` method: '${s.method}',`,` path: '${s.path}',`," },"," },","});",""].join(`
|
|
2
|
+
`)}function F(s){let d=s.filter((o)=>o.type==="operation").map((o)=>{let i=o.path.replace(".ts","").replace(/-/g,"_"),u=ns(i)+"Spec";return{path:o.path,name:i,specName:u}}),t=["/**"," * Generated operation registry."," */","","import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';",""];for(let o of d){let i=`./${o.path.replace(".ts","")}`;t.push(`import { ${o.specName} } from '${i}';`)}t.push(""),t.push("export const operationRegistry = new OperationSpecRegistry();"),t.push("");for(let o of d)t.push(`operationRegistry.register(${o.specName});`);return t.push(""),{path:"registry.ts",content:t.join(`
|
|
3
|
+
`),type:"registry"}}function ns(s){return s.split(/[-_]/).map((d)=>d.charAt(0).toUpperCase()+d.slice(1)).join("")}function _(s,d){let t=`${us(s.name)}.ts`,o=as(s);return{path:`schemas/${t}`,content:o,type:"schema"}}function P(s,d){return s.schemas.map((t)=>_(t,d))}function us(s){return s.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").toLowerCase()}function as(s){let d=["/**",` * ${s.name}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Schema type: ${s.schemaType}`,` * Confidence: ${s.confidence.level}`," */","","import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';",""];if(s.rawDefinition&&s.schemaType==="zod")d.push("// Original definition from source:"),d.push(`// ${s.rawDefinition.split(`
|
|
4
4
|
`)[0]}`),d.push("");if(d.push(`export const ${s.name}Schema = fromZod(z.object({`),s.fields&&s.fields.length>0)for(let t of s.fields){let o=Es(t.type,t.optional);d.push(` ${t.name}: ${o},`)}else d.push(" // TODO: Define schema fields");return d.push("}));"),d.push(""),d.push(`export type ${s.name} = z.infer<typeof ${s.name}Schema.zodSchema>;`),d.push(""),d.join(`
|
|
5
|
-
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};
|
|
6
|
-
`).length,
|
|
7
|
-
`).length,
|
|
8
|
-
`).length,
|
|
9
|
-
`).length,
|
|
10
|
-
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:
|
|
11
|
-
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),
|
|
12
|
-
`).length,
|
|
13
|
-
`).length,a=t.slice(
|
|
14
|
-
`).length,a=0,
|
|
15
|
-
`)){
|
|
16
|
-
`).length-1,g={id:this.generateSchemaId(
|
|
5
|
+
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};L(j,{registerAllExtractors:()=>Cs,ZodSchemaExtractor:()=>Z,TrpcExtractor:()=>S,NextApiExtractor:()=>f,NestJsExtractor:()=>V,HonoExtractor:()=>q,FastifyExtractor:()=>O,ExpressExtractor:()=>G,ElysiaExtractor:()=>h,BaseExtractor:()=>c});class J{extractors=new Map;register(s){this.extractors.set(s.id,s)}unregister(s){return this.extractors.delete(s)}get(s){return this.extractors.get(s)}getAll(){return Array.from(this.extractors.values())}async findMatching(s){let d=[];for(let t of this.extractors.values())try{if(await t.detect(s))d.push(t)}catch{}return d.sort((t,o)=>o.priority-t.priority)}findByFramework(s){let d=[];for(let t of this.extractors.values())if(t.frameworks.includes(s))d.push(t);return d.sort((t,o)=>o.priority-t.priority)}findForFramework(s){return this.findByFramework(s)}hasExtractorFor(s){for(let d of this.extractors.values())if(d.frameworks.includes(s)||d.id===s)return!0;return!1}getSupportedFrameworks(){let s=new Set;for(let d of this.extractors.values()){s.add(d.id);for(let t of d.frameworks)s.add(t)}return Array.from(s)}}var v=new J;function qs(){}async function Ss(s,d={}){let t;if(d.framework){if(t=v.findByFramework(d.framework),t.length===0)return{success:!1,errors:[{code:"EXTRACTOR_NOT_FOUND",message:`No extractor found for framework: ${d.framework}`,recoverable:!1}]}}else if(t=await v.findMatching(s),t.length===0)return{success:!1,errors:[{code:"NO_FRAMEWORK_DETECTED",message:"No supported framework detected in project",recoverable:!1}]};let o=t[0];if(!o)return{success:!1,errors:[{code:"NO_EXTRACTOR",message:"No extractor available",recoverable:!1}]};return await o.extract(s,d)}function Zs(s){if(s.length===0)throw Error("Cannot merge empty IR array");if(s.length===1){if(!s[0])throw Error("First IR is undefined");return s[0]}let d=s[0];if(!d)throw Error("First IR is undefined");let t={version:"1.0",extractedAt:new Date().toISOString(),project:d.project,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}};for(let o of s)t.endpoints.push(...o.endpoints),t.schemas.push(...o.schemas),t.errors.push(...o.errors),t.events.push(...o.events),t.ambiguities.push(...o.ambiguities),t.stats.filesScanned+=o.stats.filesScanned,t.stats.endpointsFound+=o.stats.endpointsFound,t.stats.schemasFound+=o.stats.schemasFound,t.stats.errorsFound+=o.stats.errorsFound,t.stats.eventsFound+=o.stats.eventsFound,t.stats.ambiguitiesFound+=o.stats.ambiguitiesFound,t.stats.highConfidence+=o.stats.highConfidence,t.stats.mediumConfidence+=o.stats.mediumConfidence,t.stats.lowConfidence+=o.stats.lowConfidence;return t}function m(s){return{version:"1.0",extractedAt:new Date().toISOString(),project:s,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}}}class c{priority=10;fs;setFs(s){this.fs=s}async detect(s){return s.frameworks.some((d)=>this.frameworks.includes(d.id))}async extract(s,d){if(!this.fs)return{success:!1,errors:[{code:"NO_FS_ADAPTER",message:"File system adapter not configured",recoverable:!1}]};let t=m(s),o={project:s,options:d,fs:this.fs,ir:t};try{return await this.doExtract(o),this.calculateStats(t),{success:!0,ir:t}}catch(i){return{success:!1,errors:[{code:"EXTRACTION_ERROR",message:i instanceof Error?i.message:String(i),recoverable:!1}]}}}calculateStats(s){s.stats.endpointsFound=s.endpoints.length,s.stats.schemasFound=s.schemas.length,s.stats.errorsFound=s.errors.length,s.stats.eventsFound=s.events.length,s.stats.ambiguitiesFound=s.ambiguities.length;let d=[...s.endpoints,...s.schemas,...s.errors,...s.events];for(let t of d)switch(t.confidence.level){case"high":s.stats.highConfidence++;break;case"medium":s.stats.mediumConfidence++;break;case"low":case"ambiguous":s.stats.lowConfidence++;break}}generateEndpointId(s,d,t){let o=d.replace(/^\//,"").replace(/\//g,".").replace(/:/g,"").replace(/\{/g,"").replace(/\}/g,""),i=`${s.toLowerCase()}.${o}`;return t?`${i}.${t}`:i}generateSchemaId(s,d){return`${d.replace(/\.ts$/,"").replace(/\//g,".").replace(/^\.+/,"")}.${s}`}methodToOpKind(s){switch(s){case"GET":case"HEAD":case"OPTIONS":return"query";default:return"command"}}createLocation(s,d,t){return{file:s,startLine:d,endLine:t}}createConfidence(s,...d){return{level:s,reasons:d}}addEndpoint(s,d){s.ir.endpoints.push(d)}addSchema(s,d){s.ir.schemas.push(d)}}var b={route:/\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,tSchema:/body:\s*t\.\w+/g};class h extends c{id="elysia";name="Elysia Extractor";frameworks=["elysia"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("elysia"))continue;await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(b.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
6
|
+
`).length,C=t.slice(E,E+500),p=b.tSchema.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var I={route:/(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,routerUse:/(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidate:/validate\s*\(\s*(\w+)\s*\)/g};class G extends c{id="express";name="Express Extractor";frameworks=["express"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(I.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
7
|
+
`).length,C=t.slice(E,E+500),p=C.match(/(?:async\s+)?(?:function\s+)?(\w+)|,\s*(\w+)\s*\)/),w=p?.[1]??p?.[2]??"handler",g=I.zodValidate.test(C),y={id:this.generateEndpointId(u,n,w),method:u,path:n,kind:this.methodToOpKind(u),handlerName:w,source:this.createLocation(d,a,a+5),confidence:this.createConfidence(g?"high":"medium",g?"explicit-schema":"decorator-hints")};this.addEndpoint(s,y)}}}var k={route:/(?:fastify|app|server)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,schemaOption:/schema\s*:\s*\{/g,bodySchema:/body\s*:\s*(\w+)/g,responseSchema:/response\s*:\s*\{/g};class O extends c{id="fastify";name="Fastify Extractor";frameworks=["fastify"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(k.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
8
|
+
`).length,C=t.slice(E,E+1000),p=k.schemaOption.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints"),frameworkMeta:{hasSchema:p}};this.addEndpoint(s,w)}}}var T={route:/(?:app|hono)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidator:/zValidator\s*\(\s*['"`](\w+)['"`]\s*,\s*(\w+)/g};class q extends c{id="hono";name="Hono Extractor";frameworks=["hono"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(T.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
9
|
+
`).length,C=t.slice(E,E+500),p=T.zodValidator.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var H={controller:/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,route:/@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,body:/@Body\s*\(\s*\)/g,param:/@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,query:/@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,dto:/class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,classValidator:/@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g};class V extends c{id="nestjs";name="NestJS Extractor";frameworks=["nestjs"];priority=20;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".spec.")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractControllers(s,n,a),await this.extractDtos(s,n,a)}}async extractControllers(s,d,t){let o=[...t.matchAll(H.controller)];for(let i of o){let u=i[1]||"",n=i.index??0,C=t.slice(n).match(/class\s+(\w+)/)?.[1]??"UnknownController",p=t.indexOf("@Controller",n+1),w=p>0?t.slice(n,p):t.slice(n),g=[...w.matchAll(H.route)];for(let y of g){let $=y[1]?.toUpperCase(),X=y[2]||"",K=this.normalizePath(`/${u}/${X}`),Q=w.slice(y.index??0),z=Q.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/)?.[1]??"unknownHandler",N=n+(y.index??0),D=t.slice(0,N).split(`
|
|
10
|
+
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:C,source:this.createLocation(d,D,D+10),confidence:this.createConfidence("medium","decorator-hints"),frameworkMeta:{hasBody:l,hasParams:R,hasQuery:x}};this.addEndpoint(s,r)}}}async extractDtos(s,d,t){let o=[...t.matchAll(H.dto)];for(let i of o){let u=i[1]??"UnknownDto",n=i.index??0,E=t.slice(0,n).split(`
|
|
11
|
+
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),C={id:this.generateSchemaId(u,d),name:u,schemaType:a?"class-validator":"typescript",source:this.createLocation(d,E,E+20),confidence:this.createConfidence(a?"high":"medium",a?"explicit-schema":"inferred-types")};this.addSchema(s,C)}}normalizePath(s){return"/"+s.replace(/\/+/g,"/").replace(/^\/+|\/+$/g,"")}}var U={appRouterExport:/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,pagesHandler:/export\s+default\s+(?:async\s+)?function/g,zodSchema:/z\.\w+\(/g};class f extends c{id="next-api";name="Next.js API Extractor";frameworks=["next-api"];priority=15;async doExtract(s){let{project:d,fs:t}=s,o=await t.glob("**/app/api/**/route.ts",{cwd:d.rootPath}),i=await t.glob("**/pages/api/**/*.ts",{cwd:d.rootPath}),u=[...o,...i];s.ir.stats.filesScanned=u.length;for(let n of u){let E=`${d.rootPath}/${n}`,a=await t.readFile(E);if(n.includes("/app/api/"))await this.extractAppRoutes(s,n,a);else await this.extractPagesRoutes(s,n,a)}}async extractAppRoutes(s,d,t){let o=d.match(/app\/api\/(.+)\/route\.ts$/),i=o?`/api/${o[1]}`:"/api",u=[...t.matchAll(U.appRouterExport)];for(let n of u){let E=n[1]?.toUpperCase()??"GET",a=n.index??0,C=t.slice(0,a).split(`
|
|
12
|
+
`).length,p=U.zodSchema.test(t),w={id:this.generateEndpointId(E,i),method:E,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(E),handlerName:E,source:this.createLocation(d,C,C+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"inferred-types"),frameworkMeta:{routeType:"app-router"}};this.addEndpoint(s,w)}}async extractPagesRoutes(s,d,t){let o=d.match(/pages\/api\/(.+)\.ts$/),i=o?`/api/${o[1]}`:"/api";if(!U.pagesHandler.test(t))return;let u=1,n=U.zodSchema.test(t),E=["GET","POST"];for(let a of E){let C={id:this.generateEndpointId(a,i),method:a,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(a),handlerName:"handler",source:this.createLocation(d,u,u+20),confidence:this.createConfidence("low","naming-convention"),frameworkMeta:{routeType:"pages-router"}};this.addEndpoint(s,C)}}}var B={procedure:/\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,procedureName:/(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,zodInput:/\.input\s*\(\s*(\w+)/g,zodOutput:/\.output\s*\(\s*(\w+)/g};class S extends c{id="trpc";name="tRPC Extractor";frameworks=["trpc"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("trpc")&&!a.includes("Procedure"))continue;await this.extractProcedures(s,n,a)}}async extractProcedures(s,d,t){let o=[...t.matchAll(B.procedureName)];for(let i of o){let u=i[1]??"unknownProcedure",n=i.index??0,E=t.slice(0,n).split(`
|
|
13
|
+
`).length,a=t.slice(n,n+500),C=a.includes(".query("),p=a.includes(".mutation(");if(!C&&!p)continue;let w=B.zodInput.test(a),g=B.zodOutput.test(a),y=w||g,$=p?"POST":"GET",X={id:`trpc.${u}`,method:$,path:`/trpc/${u}`,kind:p?"command":"query",handlerName:u,source:this.createLocation(d,E,E+10),confidence:this.createConfidence(y?"high":"medium",y?"explicit-schema":"inferred-types"),frameworkMeta:{procedureType:p?"mutation":"query",hasInput:w,hasOutput:g}};this.addEndpoint(s,X)}}}var ps={zodSchema:/(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,zodInfer:/type\s+(\w+)\s*=\s*z\.infer<typeof\s+(\w+)>/g};class Z extends c{id="zod";name="Zod Schema Extractor";frameworks=["zod"];priority=5;async detect(){return!0}async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("z."))continue;await this.extractSchemas(s,n,a)}}async extractSchemas(s,d,t){let o=[...t.matchAll(ps.zodSchema)];for(let i of o){let u=i[1]??"unknownSchema",n=i.index??0,E=t.slice(0,n).split(`
|
|
14
|
+
`).length,a=0,C=n;for(let y=n;y<t.length;y++){let $=t[y];if($==="("||$==="{"||$==="[")a++;if($===")"||$==="}"||$==="]")a--;if(a===0&&($===";"||$===`
|
|
15
|
+
`)){C=y;break}}let p=t.slice(n,C+1),w=E+p.split(`
|
|
16
|
+
`).length-1,g={id:this.generateSchemaId(u,d),name:u,schemaType:"zod",rawDefinition:p,source:this.createLocation(d,E,w),confidence:this.createConfidence("high","explicit-schema")};this.addSchema(s,g)}}}function Cs(){v.register(new V),v.register(new G),v.register(new O),v.register(new q),v.register(new h),v.register(new S),v.register(new f),v.register(new Z)}var M=[{id:"nestjs",name:"NestJS",packages:["@nestjs/core","@nestjs/common"],importPatterns:[/@nestjs\//]},{id:"express",name:"Express",packages:["express"],importPatterns:[/from ['"]express['"]/]},{id:"fastify",name:"Fastify",packages:["fastify"],importPatterns:[/from ['"]fastify['"]/]},{id:"hono",name:"Hono",packages:["hono"],importPatterns:[/from ['"]hono['"]/]},{id:"elysia",name:"Elysia",packages:["elysia"],importPatterns:[/from ['"]elysia['"]/]},{id:"trpc",name:"tRPC",packages:["@trpc/server"],importPatterns:[/@trpc\/server/]},{id:"next-api",name:"Next.js API",packages:["next"],filePatterns:[/app\/api\/.*\/route\.ts$/,/pages\/api\/.*\.ts$/]},{id:"koa",name:"Koa",packages:["koa","@koa/router"],importPatterns:[/from ['"]koa['"]/]},{id:"hapi",name:"Hapi",packages:["@hapi/hapi"],importPatterns:[/@hapi\/hapi/]}];function cs(s){let d={...s.dependencies,...s.devDependencies,...s.peerDependencies},t=[];for(let o of M)for(let i of o.packages)if(i in d){t.push({id:o.id,name:o.name,version:d[i],confidence:"high"});break}return t}function pt(s){let d=[],t=new Set;for(let o of M){if(!o.importPatterns)continue;for(let i of o.importPatterns)if(i.test(s)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function Ct(s){let d=[],t=new Set;for(let o of M){if(!o.filePatterns)continue;for(let i of o.filePatterns)for(let u of s)if(i.test(u)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function ct(...s){let d=new Map,t={high:3,medium:2,low:1,ambiguous:0};for(let o of s)for(let i of o){let u=d.get(i.id);if(!u||t[i.confidence]>t[u.confidence])d.set(i.id,i)}return Array.from(d.values())}async function wt(s,d){let t={rootPath:s,frameworks:[]};if(d?.readFile)try{let o=`${s}/package.json`,i=await d.readFile(o),u=JSON.parse(i);t.packageJsonPath=o,t.frameworks=cs(u)}catch{}if(d?.readFile)try{let o=`${s}/tsconfig.json`;await d.readFile(o),t.tsConfigPath=o}catch{}return t}function yt(){return M.map((s)=>s.id)}function $t(s){return M.some((d)=>d.id===s)}import{defineFeature as ws}from"@contractspec/lib.contracts-spec/features";var Ht=ws({meta:{key:"libs.source-extractors",version:"1.0.0",title:"Source Extractors",description:"Extract contract candidates from TypeScript source code across multiple frameworks (NestJS, Express, Fastify, Hono, Elysia, tRPC, Next.js)",domain:"source-extractors",owners:["@contractspec-core"],tags:["package","libs","source-extractors"],stability:"experimental"}});export{qs as registerBuiltInExtractors,Zs as mergeIRs,ct as mergeFrameworkDetections,$t as isFrameworkSupported,yt as getSupportedFrameworks,j as extractors,v as extractorRegistry,Ss as extractFromProject,Ct as detectFrameworksFromPaths,cs as detectFrameworksFromPackageJson,pt as detectFrameworksFromCode,wt as detectFramework,m as createEmptyIR,A as codegen,Ht as SourceExtractorsFeature,J as ExtractorRegistry};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var
|
|
3
|
-
`)}function
|
|
4
|
-
`),type:"registry"}}function
|
|
2
|
+
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var L=(s,d)=>{for(var t in d)e(s,t,{get:d[t],enumerable:!0,configurable:!0,set:ts.bind(d,t)})};var A={};L(A,{generateSchemas:()=>P,generateSchema:()=>_,generateRegistry:()=>F,generateOperations:()=>W,generateOperation:()=>Y});function Y(s,d){let t=ds(s),o=`${os(s)}.ts`,i=is(s,t,d);return{path:o,content:i,type:"operation"}}function W(s,d){return s.endpoints.map((t)=>Y(t,d))}function ds(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.map((i)=>i.charAt(0).toUpperCase()+i.slice(1)).join("");return`${t}${o}Spec`}function os(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.join("-");return`${t}-${o}`.replace(/--+/g,"-")}function is(s,d,t){let i=s.kind==="command"?"defineCommand":"defineQuery",u=t.defaultAuth??"user",n=t.defaultOwners??["team"];return["/**",` * ${s.method} ${s.path}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Confidence: ${s.confidence.level}`," */","",`import { ${i} } from '@contractspec/lib.contracts-spec';`,"import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';","","// TODO: Define input schema based on extracted information","const inputSchema = fromZod(z.object({"," // Add fields here","}));","","// TODO: Define output schema","const outputSchema = fromZod(z.object({"," // Add fields here","}));","",`export const ${d} = ${i}({`," meta: {",` name: '${s.handlerName??s.id}',`," version: 1,"," stability: 'experimental',",` owners: ${JSON.stringify(n)},`," goal: 'TODO: Describe the business goal',",` context: 'Generated from ${s.source.file}',`," },"," io: {"," input: inputSchema,"," output: outputSchema,"," },"," policy: {",` auth: '${u}',`," },"," transport: {"," rest: {",` method: '${s.method}',`,` path: '${s.path}',`," },"," },","});",""].join(`
|
|
3
|
+
`)}function F(s){let d=s.filter((o)=>o.type==="operation").map((o)=>{let i=o.path.replace(".ts","").replace(/-/g,"_"),u=ns(i)+"Spec";return{path:o.path,name:i,specName:u}}),t=["/**"," * Generated operation registry."," */","","import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';",""];for(let o of d){let i=`./${o.path.replace(".ts","")}`;t.push(`import { ${o.specName} } from '${i}';`)}t.push(""),t.push("export const operationRegistry = new OperationSpecRegistry();"),t.push("");for(let o of d)t.push(`operationRegistry.register(${o.specName});`);return t.push(""),{path:"registry.ts",content:t.join(`
|
|
4
|
+
`),type:"registry"}}function ns(s){return s.split(/[-_]/).map((d)=>d.charAt(0).toUpperCase()+d.slice(1)).join("")}function _(s,d){let t=`${us(s.name)}.ts`,o=as(s);return{path:`schemas/${t}`,content:o,type:"schema"}}function P(s,d){return s.schemas.map((t)=>_(t,d))}function us(s){return s.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").toLowerCase()}function as(s){let d=["/**",` * ${s.name}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Schema type: ${s.schemaType}`,` * Confidence: ${s.confidence.level}`," */","","import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';",""];if(s.rawDefinition&&s.schemaType==="zod")d.push("// Original definition from source:"),d.push(`// ${s.rawDefinition.split(`
|
|
5
5
|
`)[0]}`),d.push("");if(d.push(`export const ${s.name}Schema = fromZod(z.object({`),s.fields&&s.fields.length>0)for(let t of s.fields){let o=Es(t.type,t.optional);d.push(` ${t.name}: ${o},`)}else d.push(" // TODO: Define schema fields");return d.push("}));"),d.push(""),d.push(`export type ${s.name} = z.infer<typeof ${s.name}Schema.zodSchema>;`),d.push(""),d.join(`
|
|
6
|
-
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};
|
|
7
|
-
`).length,
|
|
8
|
-
`).length,
|
|
9
|
-
`).length,
|
|
10
|
-
`).length,
|
|
11
|
-
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:
|
|
12
|
-
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),
|
|
13
|
-
`).length,
|
|
14
|
-
`).length,a=t.slice(
|
|
15
|
-
`).length,a=0,
|
|
16
|
-
`)){
|
|
17
|
-
`).length-1,g={id:this.generateSchemaId(
|
|
6
|
+
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};L(j,{registerAllExtractors:()=>Cs,ZodSchemaExtractor:()=>Z,TrpcExtractor:()=>S,NextApiExtractor:()=>f,NestJsExtractor:()=>V,HonoExtractor:()=>q,FastifyExtractor:()=>O,ExpressExtractor:()=>G,ElysiaExtractor:()=>h,BaseExtractor:()=>c});class J{extractors=new Map;register(s){this.extractors.set(s.id,s)}unregister(s){return this.extractors.delete(s)}get(s){return this.extractors.get(s)}getAll(){return Array.from(this.extractors.values())}async findMatching(s){let d=[];for(let t of this.extractors.values())try{if(await t.detect(s))d.push(t)}catch{}return d.sort((t,o)=>o.priority-t.priority)}findByFramework(s){let d=[];for(let t of this.extractors.values())if(t.frameworks.includes(s))d.push(t);return d.sort((t,o)=>o.priority-t.priority)}findForFramework(s){return this.findByFramework(s)}hasExtractorFor(s){for(let d of this.extractors.values())if(d.frameworks.includes(s)||d.id===s)return!0;return!1}getSupportedFrameworks(){let s=new Set;for(let d of this.extractors.values()){s.add(d.id);for(let t of d.frameworks)s.add(t)}return Array.from(s)}}var v=new J;function qs(){}async function Ss(s,d={}){let t;if(d.framework){if(t=v.findByFramework(d.framework),t.length===0)return{success:!1,errors:[{code:"EXTRACTOR_NOT_FOUND",message:`No extractor found for framework: ${d.framework}`,recoverable:!1}]}}else if(t=await v.findMatching(s),t.length===0)return{success:!1,errors:[{code:"NO_FRAMEWORK_DETECTED",message:"No supported framework detected in project",recoverable:!1}]};let o=t[0];if(!o)return{success:!1,errors:[{code:"NO_EXTRACTOR",message:"No extractor available",recoverable:!1}]};return await o.extract(s,d)}function Zs(s){if(s.length===0)throw Error("Cannot merge empty IR array");if(s.length===1){if(!s[0])throw Error("First IR is undefined");return s[0]}let d=s[0];if(!d)throw Error("First IR is undefined");let t={version:"1.0",extractedAt:new Date().toISOString(),project:d.project,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}};for(let o of s)t.endpoints.push(...o.endpoints),t.schemas.push(...o.schemas),t.errors.push(...o.errors),t.events.push(...o.events),t.ambiguities.push(...o.ambiguities),t.stats.filesScanned+=o.stats.filesScanned,t.stats.endpointsFound+=o.stats.endpointsFound,t.stats.schemasFound+=o.stats.schemasFound,t.stats.errorsFound+=o.stats.errorsFound,t.stats.eventsFound+=o.stats.eventsFound,t.stats.ambiguitiesFound+=o.stats.ambiguitiesFound,t.stats.highConfidence+=o.stats.highConfidence,t.stats.mediumConfidence+=o.stats.mediumConfidence,t.stats.lowConfidence+=o.stats.lowConfidence;return t}function m(s){return{version:"1.0",extractedAt:new Date().toISOString(),project:s,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}}}class c{priority=10;fs;setFs(s){this.fs=s}async detect(s){return s.frameworks.some((d)=>this.frameworks.includes(d.id))}async extract(s,d){if(!this.fs)return{success:!1,errors:[{code:"NO_FS_ADAPTER",message:"File system adapter not configured",recoverable:!1}]};let t=m(s),o={project:s,options:d,fs:this.fs,ir:t};try{return await this.doExtract(o),this.calculateStats(t),{success:!0,ir:t}}catch(i){return{success:!1,errors:[{code:"EXTRACTION_ERROR",message:i instanceof Error?i.message:String(i),recoverable:!1}]}}}calculateStats(s){s.stats.endpointsFound=s.endpoints.length,s.stats.schemasFound=s.schemas.length,s.stats.errorsFound=s.errors.length,s.stats.eventsFound=s.events.length,s.stats.ambiguitiesFound=s.ambiguities.length;let d=[...s.endpoints,...s.schemas,...s.errors,...s.events];for(let t of d)switch(t.confidence.level){case"high":s.stats.highConfidence++;break;case"medium":s.stats.mediumConfidence++;break;case"low":case"ambiguous":s.stats.lowConfidence++;break}}generateEndpointId(s,d,t){let o=d.replace(/^\//,"").replace(/\//g,".").replace(/:/g,"").replace(/\{/g,"").replace(/\}/g,""),i=`${s.toLowerCase()}.${o}`;return t?`${i}.${t}`:i}generateSchemaId(s,d){return`${d.replace(/\.ts$/,"").replace(/\//g,".").replace(/^\.+/,"")}.${s}`}methodToOpKind(s){switch(s){case"GET":case"HEAD":case"OPTIONS":return"query";default:return"command"}}createLocation(s,d,t){return{file:s,startLine:d,endLine:t}}createConfidence(s,...d){return{level:s,reasons:d}}addEndpoint(s,d){s.ir.endpoints.push(d)}addSchema(s,d){s.ir.schemas.push(d)}}var b={route:/\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,tSchema:/body:\s*t\.\w+/g};class h extends c{id="elysia";name="Elysia Extractor";frameworks=["elysia"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("elysia"))continue;await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(b.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
7
|
+
`).length,C=t.slice(E,E+500),p=b.tSchema.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var I={route:/(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,routerUse:/(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidate:/validate\s*\(\s*(\w+)\s*\)/g};class G extends c{id="express";name="Express Extractor";frameworks=["express"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(I.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
8
|
+
`).length,C=t.slice(E,E+500),p=C.match(/(?:async\s+)?(?:function\s+)?(\w+)|,\s*(\w+)\s*\)/),w=p?.[1]??p?.[2]??"handler",g=I.zodValidate.test(C),y={id:this.generateEndpointId(u,n,w),method:u,path:n,kind:this.methodToOpKind(u),handlerName:w,source:this.createLocation(d,a,a+5),confidence:this.createConfidence(g?"high":"medium",g?"explicit-schema":"decorator-hints")};this.addEndpoint(s,y)}}}var k={route:/(?:fastify|app|server)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,schemaOption:/schema\s*:\s*\{/g,bodySchema:/body\s*:\s*(\w+)/g,responseSchema:/response\s*:\s*\{/g};class O extends c{id="fastify";name="Fastify Extractor";frameworks=["fastify"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(k.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
9
|
+
`).length,C=t.slice(E,E+1000),p=k.schemaOption.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints"),frameworkMeta:{hasSchema:p}};this.addEndpoint(s,w)}}}var T={route:/(?:app|hono)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidator:/zValidator\s*\(\s*['"`](\w+)['"`]\s*,\s*(\w+)/g};class q extends c{id="hono";name="Hono Extractor";frameworks=["hono"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(T.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
10
|
+
`).length,C=t.slice(E,E+500),p=T.zodValidator.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var H={controller:/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,route:/@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,body:/@Body\s*\(\s*\)/g,param:/@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,query:/@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,dto:/class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,classValidator:/@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g};class V extends c{id="nestjs";name="NestJS Extractor";frameworks=["nestjs"];priority=20;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".spec.")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractControllers(s,n,a),await this.extractDtos(s,n,a)}}async extractControllers(s,d,t){let o=[...t.matchAll(H.controller)];for(let i of o){let u=i[1]||"",n=i.index??0,C=t.slice(n).match(/class\s+(\w+)/)?.[1]??"UnknownController",p=t.indexOf("@Controller",n+1),w=p>0?t.slice(n,p):t.slice(n),g=[...w.matchAll(H.route)];for(let y of g){let $=y[1]?.toUpperCase(),X=y[2]||"",K=this.normalizePath(`/${u}/${X}`),Q=w.slice(y.index??0),z=Q.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/)?.[1]??"unknownHandler",N=n+(y.index??0),D=t.slice(0,N).split(`
|
|
11
|
+
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:C,source:this.createLocation(d,D,D+10),confidence:this.createConfidence("medium","decorator-hints"),frameworkMeta:{hasBody:l,hasParams:R,hasQuery:x}};this.addEndpoint(s,r)}}}async extractDtos(s,d,t){let o=[...t.matchAll(H.dto)];for(let i of o){let u=i[1]??"UnknownDto",n=i.index??0,E=t.slice(0,n).split(`
|
|
12
|
+
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),C={id:this.generateSchemaId(u,d),name:u,schemaType:a?"class-validator":"typescript",source:this.createLocation(d,E,E+20),confidence:this.createConfidence(a?"high":"medium",a?"explicit-schema":"inferred-types")};this.addSchema(s,C)}}normalizePath(s){return"/"+s.replace(/\/+/g,"/").replace(/^\/+|\/+$/g,"")}}var U={appRouterExport:/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,pagesHandler:/export\s+default\s+(?:async\s+)?function/g,zodSchema:/z\.\w+\(/g};class f extends c{id="next-api";name="Next.js API Extractor";frameworks=["next-api"];priority=15;async doExtract(s){let{project:d,fs:t}=s,o=await t.glob("**/app/api/**/route.ts",{cwd:d.rootPath}),i=await t.glob("**/pages/api/**/*.ts",{cwd:d.rootPath}),u=[...o,...i];s.ir.stats.filesScanned=u.length;for(let n of u){let E=`${d.rootPath}/${n}`,a=await t.readFile(E);if(n.includes("/app/api/"))await this.extractAppRoutes(s,n,a);else await this.extractPagesRoutes(s,n,a)}}async extractAppRoutes(s,d,t){let o=d.match(/app\/api\/(.+)\/route\.ts$/),i=o?`/api/${o[1]}`:"/api",u=[...t.matchAll(U.appRouterExport)];for(let n of u){let E=n[1]?.toUpperCase()??"GET",a=n.index??0,C=t.slice(0,a).split(`
|
|
13
|
+
`).length,p=U.zodSchema.test(t),w={id:this.generateEndpointId(E,i),method:E,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(E),handlerName:E,source:this.createLocation(d,C,C+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"inferred-types"),frameworkMeta:{routeType:"app-router"}};this.addEndpoint(s,w)}}async extractPagesRoutes(s,d,t){let o=d.match(/pages\/api\/(.+)\.ts$/),i=o?`/api/${o[1]}`:"/api";if(!U.pagesHandler.test(t))return;let u=1,n=U.zodSchema.test(t),E=["GET","POST"];for(let a of E){let C={id:this.generateEndpointId(a,i),method:a,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(a),handlerName:"handler",source:this.createLocation(d,u,u+20),confidence:this.createConfidence("low","naming-convention"),frameworkMeta:{routeType:"pages-router"}};this.addEndpoint(s,C)}}}var B={procedure:/\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,procedureName:/(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,zodInput:/\.input\s*\(\s*(\w+)/g,zodOutput:/\.output\s*\(\s*(\w+)/g};class S extends c{id="trpc";name="tRPC Extractor";frameworks=["trpc"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("trpc")&&!a.includes("Procedure"))continue;await this.extractProcedures(s,n,a)}}async extractProcedures(s,d,t){let o=[...t.matchAll(B.procedureName)];for(let i of o){let u=i[1]??"unknownProcedure",n=i.index??0,E=t.slice(0,n).split(`
|
|
14
|
+
`).length,a=t.slice(n,n+500),C=a.includes(".query("),p=a.includes(".mutation(");if(!C&&!p)continue;let w=B.zodInput.test(a),g=B.zodOutput.test(a),y=w||g,$=p?"POST":"GET",X={id:`trpc.${u}`,method:$,path:`/trpc/${u}`,kind:p?"command":"query",handlerName:u,source:this.createLocation(d,E,E+10),confidence:this.createConfidence(y?"high":"medium",y?"explicit-schema":"inferred-types"),frameworkMeta:{procedureType:p?"mutation":"query",hasInput:w,hasOutput:g}};this.addEndpoint(s,X)}}}var ps={zodSchema:/(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,zodInfer:/type\s+(\w+)\s*=\s*z\.infer<typeof\s+(\w+)>/g};class Z extends c{id="zod";name="Zod Schema Extractor";frameworks=["zod"];priority=5;async detect(){return!0}async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("z."))continue;await this.extractSchemas(s,n,a)}}async extractSchemas(s,d,t){let o=[...t.matchAll(ps.zodSchema)];for(let i of o){let u=i[1]??"unknownSchema",n=i.index??0,E=t.slice(0,n).split(`
|
|
15
|
+
`).length,a=0,C=n;for(let y=n;y<t.length;y++){let $=t[y];if($==="("||$==="{"||$==="[")a++;if($===")"||$==="}"||$==="]")a--;if(a===0&&($===";"||$===`
|
|
16
|
+
`)){C=y;break}}let p=t.slice(n,C+1),w=E+p.split(`
|
|
17
|
+
`).length-1,g={id:this.generateSchemaId(u,d),name:u,schemaType:"zod",rawDefinition:p,source:this.createLocation(d,E,w),confidence:this.createConfidence("high","explicit-schema")};this.addSchema(s,g)}}}function Cs(){v.register(new V),v.register(new G),v.register(new O),v.register(new q),v.register(new h),v.register(new S),v.register(new f),v.register(new Z)}var M=[{id:"nestjs",name:"NestJS",packages:["@nestjs/core","@nestjs/common"],importPatterns:[/@nestjs\//]},{id:"express",name:"Express",packages:["express"],importPatterns:[/from ['"]express['"]/]},{id:"fastify",name:"Fastify",packages:["fastify"],importPatterns:[/from ['"]fastify['"]/]},{id:"hono",name:"Hono",packages:["hono"],importPatterns:[/from ['"]hono['"]/]},{id:"elysia",name:"Elysia",packages:["elysia"],importPatterns:[/from ['"]elysia['"]/]},{id:"trpc",name:"tRPC",packages:["@trpc/server"],importPatterns:[/@trpc\/server/]},{id:"next-api",name:"Next.js API",packages:["next"],filePatterns:[/app\/api\/.*\/route\.ts$/,/pages\/api\/.*\.ts$/]},{id:"koa",name:"Koa",packages:["koa","@koa/router"],importPatterns:[/from ['"]koa['"]/]},{id:"hapi",name:"Hapi",packages:["@hapi/hapi"],importPatterns:[/@hapi\/hapi/]}];function cs(s){let d={...s.dependencies,...s.devDependencies,...s.peerDependencies},t=[];for(let o of M)for(let i of o.packages)if(i in d){t.push({id:o.id,name:o.name,version:d[i],confidence:"high"});break}return t}function pt(s){let d=[],t=new Set;for(let o of M){if(!o.importPatterns)continue;for(let i of o.importPatterns)if(i.test(s)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function Ct(s){let d=[],t=new Set;for(let o of M){if(!o.filePatterns)continue;for(let i of o.filePatterns)for(let u of s)if(i.test(u)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function ct(...s){let d=new Map,t={high:3,medium:2,low:1,ambiguous:0};for(let o of s)for(let i of o){let u=d.get(i.id);if(!u||t[i.confidence]>t[u.confidence])d.set(i.id,i)}return Array.from(d.values())}async function wt(s,d){let t={rootPath:s,frameworks:[]};if(d?.readFile)try{let o=`${s}/package.json`,i=await d.readFile(o),u=JSON.parse(i);t.packageJsonPath=o,t.frameworks=cs(u)}catch{}if(d?.readFile)try{let o=`${s}/tsconfig.json`;await d.readFile(o),t.tsConfigPath=o}catch{}return t}function yt(){return M.map((s)=>s.id)}function $t(s){return M.some((d)=>d.id===s)}import{defineFeature as ws}from"@contractspec/lib.contracts-spec/features";var Ht=ws({meta:{key:"libs.source-extractors",version:"1.0.0",title:"Source Extractors",description:"Extract contract candidates from TypeScript source code across multiple frameworks (NestJS, Express, Fastify, Hono, Elysia, tRPC, Next.js)",domain:"source-extractors",owners:["@contractspec-core"],tags:["package","libs","source-extractors"],stability:"experimental"}});export{qs as registerBuiltInExtractors,Zs as mergeIRs,ct as mergeFrameworkDetections,$t as isFrameworkSupported,yt as getSupportedFrameworks,j as extractors,v as extractorRegistry,Ss as extractFromProject,Ct as detectFrameworksFromPaths,cs as detectFrameworksFromPackageJson,pt as detectFrameworksFromCode,wt as detectFramework,m as createEmptyIR,A as codegen,Ht as SourceExtractorsFeature,J as ExtractorRegistry};
|
package/dist/node/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var
|
|
2
|
-
`)}function
|
|
3
|
-
`),type:"registry"}}function
|
|
1
|
+
var e=Object.defineProperty;var ss=(s)=>s;function ts(s,d){this[s]=ss.bind(null,d)}var L=(s,d)=>{for(var t in d)e(s,t,{get:d[t],enumerable:!0,configurable:!0,set:ts.bind(d,t)})};var A={};L(A,{generateSchemas:()=>P,generateSchema:()=>_,generateRegistry:()=>F,generateOperations:()=>W,generateOperation:()=>Y});function Y(s,d){let t=ds(s),o=`${os(s)}.ts`,i=is(s,t,d);return{path:o,content:i,type:"operation"}}function W(s,d){return s.endpoints.map((t)=>Y(t,d))}function ds(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.map((i)=>i.charAt(0).toUpperCase()+i.slice(1)).join("");return`${t}${o}Spec`}function os(s){let d=s.path.replace(/^\//,"").split("/").filter((i)=>i&&!i.startsWith(":")&&!i.startsWith("{")),t=s.method.toLowerCase(),o=d.join("-");return`${t}-${o}`.replace(/--+/g,"-")}function is(s,d,t){let i=s.kind==="command"?"defineCommand":"defineQuery",u=t.defaultAuth??"user",n=t.defaultOwners??["team"];return["/**",` * ${s.method} ${s.path}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Confidence: ${s.confidence.level}`," */","",`import { ${i} } from '@contractspec/lib.contracts-spec';`,"import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';","","// TODO: Define input schema based on extracted information","const inputSchema = fromZod(z.object({"," // Add fields here","}));","","// TODO: Define output schema","const outputSchema = fromZod(z.object({"," // Add fields here","}));","",`export const ${d} = ${i}({`," meta: {",` name: '${s.handlerName??s.id}',`," version: 1,"," stability: 'experimental',",` owners: ${JSON.stringify(n)},`," goal: 'TODO: Describe the business goal',",` context: 'Generated from ${s.source.file}',`," },"," io: {"," input: inputSchema,"," output: outputSchema,"," },"," policy: {",` auth: '${u}',`," },"," transport: {"," rest: {",` method: '${s.method}',`,` path: '${s.path}',`," },"," },","});",""].join(`
|
|
2
|
+
`)}function F(s){let d=s.filter((o)=>o.type==="operation").map((o)=>{let i=o.path.replace(".ts","").replace(/-/g,"_"),u=ns(i)+"Spec";return{path:o.path,name:i,specName:u}}),t=["/**"," * Generated operation registry."," */","","import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';",""];for(let o of d){let i=`./${o.path.replace(".ts","")}`;t.push(`import { ${o.specName} } from '${i}';`)}t.push(""),t.push("export const operationRegistry = new OperationSpecRegistry();"),t.push("");for(let o of d)t.push(`operationRegistry.register(${o.specName});`);return t.push(""),{path:"registry.ts",content:t.join(`
|
|
3
|
+
`),type:"registry"}}function ns(s){return s.split(/[-_]/).map((d)=>d.charAt(0).toUpperCase()+d.slice(1)).join("")}function _(s,d){let t=`${us(s.name)}.ts`,o=as(s);return{path:`schemas/${t}`,content:o,type:"schema"}}function P(s,d){return s.schemas.map((t)=>_(t,d))}function us(s){return s.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").toLowerCase()}function as(s){let d=["/**",` * ${s.name}`," *",` * Generated from: ${s.source.file}:${s.source.startLine}`,` * Schema type: ${s.schemaType}`,` * Confidence: ${s.confidence.level}`," */","","import { fromZod } from '@contractspec/lib.schema';","import { z } from 'zod';",""];if(s.rawDefinition&&s.schemaType==="zod")d.push("// Original definition from source:"),d.push(`// ${s.rawDefinition.split(`
|
|
4
4
|
`)[0]}`),d.push("");if(d.push(`export const ${s.name}Schema = fromZod(z.object({`),s.fields&&s.fields.length>0)for(let t of s.fields){let o=Es(t.type,t.optional);d.push(` ${t.name}: ${o},`)}else d.push(" // TODO: Define schema fields");return d.push("}));"),d.push(""),d.push(`export type ${s.name} = z.infer<typeof ${s.name}Schema.zodSchema>;`),d.push(""),d.join(`
|
|
5
|
-
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};
|
|
6
|
-
`).length,
|
|
7
|
-
`).length,
|
|
8
|
-
`).length,
|
|
9
|
-
`).length,
|
|
10
|
-
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:
|
|
11
|
-
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),
|
|
12
|
-
`).length,
|
|
13
|
-
`).length,a=t.slice(
|
|
14
|
-
`).length,a=0,
|
|
15
|
-
`)){
|
|
16
|
-
`).length-1,g={id:this.generateSchemaId(
|
|
5
|
+
`)}function Es(s,d){let t;switch(s.toLowerCase()){case"string":t="z.string()";break;case"number":t="z.number()";break;case"boolean":t="z.boolean()";break;case"date":t="z.date()";break;case"string[]":case"array<string>":t="z.array(z.string())";break;case"number[]":case"array<number>":t="z.array(z.number())";break;default:t="z.unknown()"}return d?`${t}.optional()`:t}var j={};L(j,{registerAllExtractors:()=>Cs,ZodSchemaExtractor:()=>Z,TrpcExtractor:()=>S,NextApiExtractor:()=>f,NestJsExtractor:()=>V,HonoExtractor:()=>q,FastifyExtractor:()=>O,ExpressExtractor:()=>G,ElysiaExtractor:()=>h,BaseExtractor:()=>c});class J{extractors=new Map;register(s){this.extractors.set(s.id,s)}unregister(s){return this.extractors.delete(s)}get(s){return this.extractors.get(s)}getAll(){return Array.from(this.extractors.values())}async findMatching(s){let d=[];for(let t of this.extractors.values())try{if(await t.detect(s))d.push(t)}catch{}return d.sort((t,o)=>o.priority-t.priority)}findByFramework(s){let d=[];for(let t of this.extractors.values())if(t.frameworks.includes(s))d.push(t);return d.sort((t,o)=>o.priority-t.priority)}findForFramework(s){return this.findByFramework(s)}hasExtractorFor(s){for(let d of this.extractors.values())if(d.frameworks.includes(s)||d.id===s)return!0;return!1}getSupportedFrameworks(){let s=new Set;for(let d of this.extractors.values()){s.add(d.id);for(let t of d.frameworks)s.add(t)}return Array.from(s)}}var v=new J;function qs(){}async function Ss(s,d={}){let t;if(d.framework){if(t=v.findByFramework(d.framework),t.length===0)return{success:!1,errors:[{code:"EXTRACTOR_NOT_FOUND",message:`No extractor found for framework: ${d.framework}`,recoverable:!1}]}}else if(t=await v.findMatching(s),t.length===0)return{success:!1,errors:[{code:"NO_FRAMEWORK_DETECTED",message:"No supported framework detected in project",recoverable:!1}]};let o=t[0];if(!o)return{success:!1,errors:[{code:"NO_EXTRACTOR",message:"No extractor available",recoverable:!1}]};return await o.extract(s,d)}function Zs(s){if(s.length===0)throw Error("Cannot merge empty IR array");if(s.length===1){if(!s[0])throw Error("First IR is undefined");return s[0]}let d=s[0];if(!d)throw Error("First IR is undefined");let t={version:"1.0",extractedAt:new Date().toISOString(),project:d.project,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}};for(let o of s)t.endpoints.push(...o.endpoints),t.schemas.push(...o.schemas),t.errors.push(...o.errors),t.events.push(...o.events),t.ambiguities.push(...o.ambiguities),t.stats.filesScanned+=o.stats.filesScanned,t.stats.endpointsFound+=o.stats.endpointsFound,t.stats.schemasFound+=o.stats.schemasFound,t.stats.errorsFound+=o.stats.errorsFound,t.stats.eventsFound+=o.stats.eventsFound,t.stats.ambiguitiesFound+=o.stats.ambiguitiesFound,t.stats.highConfidence+=o.stats.highConfidence,t.stats.mediumConfidence+=o.stats.mediumConfidence,t.stats.lowConfidence+=o.stats.lowConfidence;return t}function m(s){return{version:"1.0",extractedAt:new Date().toISOString(),project:s,endpoints:[],schemas:[],errors:[],events:[],ambiguities:[],stats:{filesScanned:0,endpointsFound:0,schemasFound:0,errorsFound:0,eventsFound:0,ambiguitiesFound:0,highConfidence:0,mediumConfidence:0,lowConfidence:0}}}class c{priority=10;fs;setFs(s){this.fs=s}async detect(s){return s.frameworks.some((d)=>this.frameworks.includes(d.id))}async extract(s,d){if(!this.fs)return{success:!1,errors:[{code:"NO_FS_ADAPTER",message:"File system adapter not configured",recoverable:!1}]};let t=m(s),o={project:s,options:d,fs:this.fs,ir:t};try{return await this.doExtract(o),this.calculateStats(t),{success:!0,ir:t}}catch(i){return{success:!1,errors:[{code:"EXTRACTION_ERROR",message:i instanceof Error?i.message:String(i),recoverable:!1}]}}}calculateStats(s){s.stats.endpointsFound=s.endpoints.length,s.stats.schemasFound=s.schemas.length,s.stats.errorsFound=s.errors.length,s.stats.eventsFound=s.events.length,s.stats.ambiguitiesFound=s.ambiguities.length;let d=[...s.endpoints,...s.schemas,...s.errors,...s.events];for(let t of d)switch(t.confidence.level){case"high":s.stats.highConfidence++;break;case"medium":s.stats.mediumConfidence++;break;case"low":case"ambiguous":s.stats.lowConfidence++;break}}generateEndpointId(s,d,t){let o=d.replace(/^\//,"").replace(/\//g,".").replace(/:/g,"").replace(/\{/g,"").replace(/\}/g,""),i=`${s.toLowerCase()}.${o}`;return t?`${i}.${t}`:i}generateSchemaId(s,d){return`${d.replace(/\.ts$/,"").replace(/\//g,".").replace(/^\.+/,"")}.${s}`}methodToOpKind(s){switch(s){case"GET":case"HEAD":case"OPTIONS":return"query";default:return"command"}}createLocation(s,d,t){return{file:s,startLine:d,endLine:t}}createConfidence(s,...d){return{level:s,reasons:d}}addEndpoint(s,d){s.ir.endpoints.push(d)}addSchema(s,d){s.ir.schemas.push(d)}}var b={route:/\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,tSchema:/body:\s*t\.\w+/g};class h extends c{id="elysia";name="Elysia Extractor";frameworks=["elysia"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("elysia"))continue;await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(b.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
6
|
+
`).length,C=t.slice(E,E+500),p=b.tSchema.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var I={route:/(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,routerUse:/(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidate:/validate\s*\(\s*(\w+)\s*\)/g};class G extends c{id="express";name="Express Extractor";frameworks=["express"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(I.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
7
|
+
`).length,C=t.slice(E,E+500),p=C.match(/(?:async\s+)?(?:function\s+)?(\w+)|,\s*(\w+)\s*\)/),w=p?.[1]??p?.[2]??"handler",g=I.zodValidate.test(C),y={id:this.generateEndpointId(u,n,w),method:u,path:n,kind:this.methodToOpKind(u),handlerName:w,source:this.createLocation(d,a,a+5),confidence:this.createConfidence(g?"high":"medium",g?"explicit-schema":"decorator-hints")};this.addEndpoint(s,y)}}}var k={route:/(?:fastify|app|server)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,schemaOption:/schema\s*:\s*\{/g,bodySchema:/body\s*:\s*(\w+)/g,responseSchema:/response\s*:\s*\{/g};class O extends c{id="fastify";name="Fastify Extractor";frameworks=["fastify"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(k.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
8
|
+
`).length,C=t.slice(E,E+1000),p=k.schemaOption.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints"),frameworkMeta:{hasSchema:p}};this.addEndpoint(s,w)}}}var T={route:/(?:app|hono)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,zodValidator:/zValidator\s*\(\s*['"`](\w+)['"`]\s*,\s*(\w+)/g};class q extends c{id="hono";name="Hono Extractor";frameworks=["hono"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractRoutes(s,n,a)}}async extractRoutes(s,d,t){let o=[...t.matchAll(T.route)];for(let i of o){let u=i[1]?.toUpperCase()??"GET",n=i[2]??"/",E=i.index??0,a=t.slice(0,E).split(`
|
|
9
|
+
`).length,C=t.slice(E,E+500),p=T.zodValidator.test(C),w={id:this.generateEndpointId(u,n),method:u,path:n,kind:this.methodToOpKind(u),handlerName:"handler",source:this.createLocation(d,a,a+5),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"decorator-hints")};this.addEndpoint(s,w)}}}var H={controller:/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g,route:/@(Get|Post|Put|Patch|Delete|Head|Options)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,body:/@Body\s*\(\s*\)/g,param:/@Param\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,query:/@Query\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g,dto:/class\s+(\w+(?:Dto|DTO|Request|Response|Input|Output))\s*\{/g,classValidator:/@(IsString|IsNumber|IsBoolean|IsArray|IsOptional|IsNotEmpty|Min|Max|Length|Matches)/g};class V extends c{id="nestjs";name="NestJS Extractor";frameworks=["nestjs"];priority=20;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".spec.")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);await this.extractControllers(s,n,a),await this.extractDtos(s,n,a)}}async extractControllers(s,d,t){let o=[...t.matchAll(H.controller)];for(let i of o){let u=i[1]||"",n=i.index??0,C=t.slice(n).match(/class\s+(\w+)/)?.[1]??"UnknownController",p=t.indexOf("@Controller",n+1),w=p>0?t.slice(n,p):t.slice(n),g=[...w.matchAll(H.route)];for(let y of g){let $=y[1]?.toUpperCase(),X=y[2]||"",K=this.normalizePath(`/${u}/${X}`),Q=w.slice(y.index??0),z=Q.match(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+(?:<[^>]+>)?)?\s*\{/)?.[1]??"unknownHandler",N=n+(y.index??0),D=t.slice(0,N).split(`
|
|
10
|
+
`).length,l=H.body.test(Q.slice(0,200)),R=H.param.test(Q.slice(0,200)),x=H.query.test(Q.slice(0,200)),r={id:this.generateEndpointId($,K,z),method:$,path:K,kind:this.methodToOpKind($),handlerName:z,controllerName:C,source:this.createLocation(d,D,D+10),confidence:this.createConfidence("medium","decorator-hints"),frameworkMeta:{hasBody:l,hasParams:R,hasQuery:x}};this.addEndpoint(s,r)}}}async extractDtos(s,d,t){let o=[...t.matchAll(H.dto)];for(let i of o){let u=i[1]??"UnknownDto",n=i.index??0,E=t.slice(0,n).split(`
|
|
11
|
+
`).length,a=t.includes("class-validator")||t.includes("@IsString")||t.includes("@IsNumber"),C={id:this.generateSchemaId(u,d),name:u,schemaType:a?"class-validator":"typescript",source:this.createLocation(d,E,E+20),confidence:this.createConfidence(a?"high":"medium",a?"explicit-schema":"inferred-types")};this.addSchema(s,C)}}normalizePath(s){return"/"+s.replace(/\/+/g,"/").replace(/^\/+|\/+$/g,"")}}var U={appRouterExport:/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/gi,pagesHandler:/export\s+default\s+(?:async\s+)?function/g,zodSchema:/z\.\w+\(/g};class f extends c{id="next-api";name="Next.js API Extractor";frameworks=["next-api"];priority=15;async doExtract(s){let{project:d,fs:t}=s,o=await t.glob("**/app/api/**/route.ts",{cwd:d.rootPath}),i=await t.glob("**/pages/api/**/*.ts",{cwd:d.rootPath}),u=[...o,...i];s.ir.stats.filesScanned=u.length;for(let n of u){let E=`${d.rootPath}/${n}`,a=await t.readFile(E);if(n.includes("/app/api/"))await this.extractAppRoutes(s,n,a);else await this.extractPagesRoutes(s,n,a)}}async extractAppRoutes(s,d,t){let o=d.match(/app\/api\/(.+)\/route\.ts$/),i=o?`/api/${o[1]}`:"/api",u=[...t.matchAll(U.appRouterExport)];for(let n of u){let E=n[1]?.toUpperCase()??"GET",a=n.index??0,C=t.slice(0,a).split(`
|
|
12
|
+
`).length,p=U.zodSchema.test(t),w={id:this.generateEndpointId(E,i),method:E,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(E),handlerName:E,source:this.createLocation(d,C,C+10),confidence:this.createConfidence(p?"high":"medium",p?"explicit-schema":"inferred-types"),frameworkMeta:{routeType:"app-router"}};this.addEndpoint(s,w)}}async extractPagesRoutes(s,d,t){let o=d.match(/pages\/api\/(.+)\.ts$/),i=o?`/api/${o[1]}`:"/api";if(!U.pagesHandler.test(t))return;let u=1,n=U.zodSchema.test(t),E=["GET","POST"];for(let a of E){let C={id:this.generateEndpointId(a,i),method:a,path:i.replace(/\[(\w+)\]/g,":$1"),kind:this.methodToOpKind(a),handlerName:"handler",source:this.createLocation(d,u,u+20),confidence:this.createConfidence("low","naming-convention"),frameworkMeta:{routeType:"pages-router"}};this.addEndpoint(s,C)}}}var B={procedure:/\.(query|mutation)\s*\(\s*(?:\{[^}]*\}|[^)]+)\)/gi,procedureName:/(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure)/g,zodInput:/\.input\s*\(\s*(\w+)/g,zodOutput:/\.output\s*\(\s*(\w+)/g};class S extends c{id="trpc";name="tRPC Extractor";frameworks=["trpc"];priority=15;async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("trpc")&&!a.includes("Procedure"))continue;await this.extractProcedures(s,n,a)}}async extractProcedures(s,d,t){let o=[...t.matchAll(B.procedureName)];for(let i of o){let u=i[1]??"unknownProcedure",n=i.index??0,E=t.slice(0,n).split(`
|
|
13
|
+
`).length,a=t.slice(n,n+500),C=a.includes(".query("),p=a.includes(".mutation(");if(!C&&!p)continue;let w=B.zodInput.test(a),g=B.zodOutput.test(a),y=w||g,$=p?"POST":"GET",X={id:`trpc.${u}`,method:$,path:`/trpc/${u}`,kind:p?"command":"query",handlerName:u,source:this.createLocation(d,E,E+10),confidence:this.createConfidence(y?"high":"medium",y?"explicit-schema":"inferred-types"),frameworkMeta:{procedureType:p?"mutation":"query",hasInput:w,hasOutput:g}};this.addEndpoint(s,X)}}}var ps={zodSchema:/(?:export\s+)?const\s+(\w+)\s*=\s*z\.(?:object|string|number|boolean|array|enum|union|intersection|literal|tuple|record)/g,zodInfer:/type\s+(\w+)\s*=\s*z\.infer<typeof\s+(\w+)>/g};class Z extends c{id="zod";name="Zod Schema Extractor";frameworks=["zod"];priority=5;async detect(){return!0}async doExtract(s){let{project:d,options:t,fs:o}=s,i=t.scope?.length?t.scope.map((n)=>`${n}/**/*.ts`).join(","):"**/*.ts",u=await o.glob(i,{cwd:d.rootPath});s.ir.stats.filesScanned=u.length;for(let n of u){if(n.includes("node_modules")||n.includes(".test."))continue;let E=`${d.rootPath}/${n}`,a=await o.readFile(E);if(!a.includes("z."))continue;await this.extractSchemas(s,n,a)}}async extractSchemas(s,d,t){let o=[...t.matchAll(ps.zodSchema)];for(let i of o){let u=i[1]??"unknownSchema",n=i.index??0,E=t.slice(0,n).split(`
|
|
14
|
+
`).length,a=0,C=n;for(let y=n;y<t.length;y++){let $=t[y];if($==="("||$==="{"||$==="[")a++;if($===")"||$==="}"||$==="]")a--;if(a===0&&($===";"||$===`
|
|
15
|
+
`)){C=y;break}}let p=t.slice(n,C+1),w=E+p.split(`
|
|
16
|
+
`).length-1,g={id:this.generateSchemaId(u,d),name:u,schemaType:"zod",rawDefinition:p,source:this.createLocation(d,E,w),confidence:this.createConfidence("high","explicit-schema")};this.addSchema(s,g)}}}function Cs(){v.register(new V),v.register(new G),v.register(new O),v.register(new q),v.register(new h),v.register(new S),v.register(new f),v.register(new Z)}var M=[{id:"nestjs",name:"NestJS",packages:["@nestjs/core","@nestjs/common"],importPatterns:[/@nestjs\//]},{id:"express",name:"Express",packages:["express"],importPatterns:[/from ['"]express['"]/]},{id:"fastify",name:"Fastify",packages:["fastify"],importPatterns:[/from ['"]fastify['"]/]},{id:"hono",name:"Hono",packages:["hono"],importPatterns:[/from ['"]hono['"]/]},{id:"elysia",name:"Elysia",packages:["elysia"],importPatterns:[/from ['"]elysia['"]/]},{id:"trpc",name:"tRPC",packages:["@trpc/server"],importPatterns:[/@trpc\/server/]},{id:"next-api",name:"Next.js API",packages:["next"],filePatterns:[/app\/api\/.*\/route\.ts$/,/pages\/api\/.*\.ts$/]},{id:"koa",name:"Koa",packages:["koa","@koa/router"],importPatterns:[/from ['"]koa['"]/]},{id:"hapi",name:"Hapi",packages:["@hapi/hapi"],importPatterns:[/@hapi\/hapi/]}];function cs(s){let d={...s.dependencies,...s.devDependencies,...s.peerDependencies},t=[];for(let o of M)for(let i of o.packages)if(i in d){t.push({id:o.id,name:o.name,version:d[i],confidence:"high"});break}return t}function pt(s){let d=[],t=new Set;for(let o of M){if(!o.importPatterns)continue;for(let i of o.importPatterns)if(i.test(s)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function Ct(s){let d=[],t=new Set;for(let o of M){if(!o.filePatterns)continue;for(let i of o.filePatterns)for(let u of s)if(i.test(u)&&!t.has(o.id)){d.push({id:o.id,name:o.name,confidence:"medium"}),t.add(o.id);break}}return d}function ct(...s){let d=new Map,t={high:3,medium:2,low:1,ambiguous:0};for(let o of s)for(let i of o){let u=d.get(i.id);if(!u||t[i.confidence]>t[u.confidence])d.set(i.id,i)}return Array.from(d.values())}async function wt(s,d){let t={rootPath:s,frameworks:[]};if(d?.readFile)try{let o=`${s}/package.json`,i=await d.readFile(o),u=JSON.parse(i);t.packageJsonPath=o,t.frameworks=cs(u)}catch{}if(d?.readFile)try{let o=`${s}/tsconfig.json`;await d.readFile(o),t.tsConfigPath=o}catch{}return t}function yt(){return M.map((s)=>s.id)}function $t(s){return M.some((d)=>d.id===s)}import{defineFeature as ws}from"@contractspec/lib.contracts-spec/features";var Ht=ws({meta:{key:"libs.source-extractors",version:"1.0.0",title:"Source Extractors",description:"Extract contract candidates from TypeScript source code across multiple frameworks (NestJS, Express, Fastify, Hono, Elysia, tRPC, Next.js)",domain:"source-extractors",owners:["@contractspec-core"],tags:["package","libs","source-extractors"],stability:"experimental"}});export{qs as registerBuiltInExtractors,Zs as mergeIRs,ct as mergeFrameworkDetections,$t as isFrameworkSupported,yt as getSupportedFrameworks,j as extractors,v as extractorRegistry,Ss as extractFromProject,Ct as detectFrameworksFromPaths,cs as detectFrameworksFromPackageJson,pt as detectFrameworksFromCode,wt as detectFramework,m as createEmptyIR,A as codegen,Ht as SourceExtractorsFeature,J as ExtractorRegistry};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SourceExtractorsFeature: import("@contractspec/lib.contracts-spec").FeatureModuleSpec;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractspec/lib.source-extractors",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.22",
|
|
4
4
|
"description": "Extract contract candidates from TypeScript source code across multiple frameworks (NestJS, Express, Fastify, Hono, Elysia, tRPC, Next.js)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"contractspec",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"typecheck": "tsc --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@contractspec/lib.contracts-spec": "5.
|
|
33
|
+
"@contractspec/lib.contracts-spec": "5.5.1",
|
|
34
34
|
"@contractspec/lib.schema": "3.7.14",
|
|
35
35
|
"typescript": "^5.9.3",
|
|
36
36
|
"zod": "^4.3.5"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@contractspec/tool.typescript": "3.7.13",
|
|
40
|
-
"@contractspec/tool.bun": "3.7.
|
|
40
|
+
"@contractspec/tool.bun": "3.7.17"
|
|
41
41
|
},
|
|
42
42
|
"types": "./dist/index.d.ts",
|
|
43
43
|
"files": [
|