moonflower 1.5.1 → 1.5.3

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.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("crypto"),q=require("fs"),z=require("path"),U=require("url"),S=require("ts-morph"),f=require("../../utils/logger.cjs"),R=require("../discoveryModule/discoverImports/discoverImports.cjs"),I=require("../discoveryModule/discoverRouterFiles/discoverRouterFiles.cjs"),K=require("../discoveryModule/discoverRouters/discoverRouters.cjs"),W=require("../manager/OpenApiManager.cjs"),_=require("./getSourceFileTimestamp.cjs"),H=require("./nodeParsers.cjs"),k=require("./parseExposedModels.cjs"),C=require("./sourceFileCache.cjs"),D=require("./workerPool.cjs");var P=typeof document<"u"?document.currentScript:null;function B(s){const o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const a in s)if(a!=="default"){const l=Object.getOwnPropertyDescriptor(s,a);Object.defineProperty(o,a,l.get?l:{enumerable:!0,get:()=>s[a]})}}return o.default=s,Object.freeze(o)}const M=B(z);function V(){const s=["./analyzerWorker.mjs","./analyzerWorker.test.mjs"];for(const o of s){const a=new URL(o,typeof document>"u"?require("url").pathToFileURL(__filename).href:P&&P.tagName.toUpperCase()==="SCRIPT"&&P.src||new URL("openapi/analyzerModule/analyzerModule.cjs",document.baseURI).href);if(q.existsSync(U.fileURLToPath(a)))return a}throw new Error("analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).")}const G=async({logLevel:s,tsconfigPath:o,sourceFilePaths:a,sourceFileDiscovery:l,incremental:c,profiling:m="stats"})=>{const t=W.OpenApiManager.getInstance();if(t.isReady())return;s&&f.Logger.setLevel(s),f.Logger.info("Preparing OpenAPI spec");const d=new S.Project({tsConfigFilePath:M.resolve(o),skipFileDependencyResolution:!0}),{explicitRouters:h,discoveredRouterFiles:F,allSourceFiles:N}=(()=>{const u=(a??[]).map(p=>M.resolve(p)).map(p=>d.getSourceFileOrThrow(p)),L=u.flatMap(p=>({fileName:p.getFilePath(),sourceFile:p,routers:K.discoverRouters(p)})),{discoveredRouterFiles:w,discoveredSourceFiles:O}=(()=>{if(l===!1)return{discoveredRouterFiles:[],discoveredSourceFiles:[]};const p=performance.now(),x=I.discoverRouterFiles({targetPath:typeof l=="object"?l.rootPath:".",tsConfigPath:o});return m!=="off"&&f.Logger.info(`File discovery took ${Math.round(performance.now()-p)}ms`),x})(),$=u.reduce((p,x)=>p.some(b=>b.getFilePath()===x.getFilePath())?p:p.concat(x),O);return{explicitRouters:L,discoveredRouterFiles:w,allSourceFiles:$}})(),T=h.reduce((i,y)=>i.some(u=>u.fileName===y.fileName)?i:i.concat(y),F),e=N.flatMap(i=>E(i)).filter(i=>!!i);e.length>0&&e[0]&&t.setHeader(e[0]);const r=N.flatMap(i=>v(i));t.setExposedModels(r);const n=typeof c=="object"&&c.cachePath?c.cachePath:M.resolve(process.cwd(),"node_modules",".cache","moonflower"),g=await A(T,{incremental:c!==!1,cachePath:n,timestampCache:{},profiling:m,tsconfigPath:M.resolve(o)});t.setStats({discoveredRouterFiles:F.map(i=>({path:i.fileName,routers:i.routers.named.map(y=>({name:y,endpoints:g.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))})),explicitRouterFiles:h.map(i=>({path:i.fileName,routers:i.routers.named.map(y=>({name:y,endpoints:g.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))}))}),t.setEndpoints(g),t.markAsReady()},A=async(s,o,a)=>{const l=o.profiling??"stats",c=performance.now(),m=[],t=[];for(const e of s){const r=_.getSourceFileTimestamp(e.sourceFile,o.timestampCache),n=o.incremental?C.SourceFileCache.getCachedResults(e.sourceFile,r,o.cachePath):null;n?(f.Logger.debug(`[${e.fileName}] Found cached results`),m.push({endpoints:n.endpoints,fileName:e.fileName,timing:0,endpointTimings:[]})):t.push({file:e,timestamp:r})}if(t.length===0)return l!=="off"&&f.Logger.info(`Router analysis took ${Math.round(performance.now()-c)}ms`),m.flatMap(e=>e.endpoints);const d=t.map(({file:e})=>({fileName:e.fileName,task:{taskId:j.randomUUID(),tsconfigPath:o.tsconfigPath,sourceFilePath:e.sourceFile.getFilePath(),routerNames:e.routers.named,filterEndpointPaths:a}})),h=new D.WorkerPool(V(),d.length);let F;try{F=await h.runAll(d.map(e=>e.task))}finally{h.terminate()}const N=new Map;for(const{file:e}of t)N.set(e.fileName,{endpoints:[],fileName:e.fileName,timing:0,endpointTimings:[]});for(let e=0;e<F.length;e++){const r=F[e],n=d[e].fileName,g=N.get(n);if("error"in r){f.Logger.error(`[${n}] Worker error: ${r.error}`);continue}g.endpoints=r.endpoints,g.endpointTimings=r.endpointTimings,g.timing=r.endpointTimings.reduce((i,y)=>i+y.timing,0)}for(const{file:e,timestamp:r}of t){const n=N.get(e.fileName);n.endpoints.length>0&&C.SourceFileCache.cacheResults(e.sourceFile,r,o.cachePath,n.endpoints)}const T=[...m,...Array.from(N.values())];return l!=="off"&&f.Logger.info(`Router analysis took ${Math.round(performance.now()-c)}ms`),l==="stats"?T.map(e=>({fileName:e.fileName,timeTaken:e.timing})).sort((e,r)=>r.timeTaken-e.timeTaken).filter(e=>e.timeTaken>500).forEach(e=>{f.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`)}):l==="debug"&&T.map(e=>({fileName:e.fileName,timeTaken:e.timing,endpointTimings:e.endpointTimings})).sort((e,r)=>r.timeTaken-e.timeTaken).forEach(e=>{f.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`),e.endpointTimings.sort((r,n)=>n.timing-r.timing).forEach(r=>{f.Logger.info(` - ${r.method} ${r.path} (${Math.round(r.timing)}ms)`),r.sectionTimings.filter(n=>n.timing>=1).sort((n,g)=>g.timing-n.timing).forEach(n=>{f.Logger.info(` - ${n.section}: ${Math.round(n.timing)}ms`)})})}),T.flatMap(e=>e.endpoints)},E=s=>{const o=R.discoverImportedName({sourceFile:s,originalName:"useApiHeader"});if(!o)return null;const a=s.forEachChildAsArray().filter(t=>t.isKind(S.SyntaxKind.ExpressionStatement)).find(t=>o&&t.getText().startsWith(o));if(!a)return null;const l=a.getFirstDescendantByKindOrThrow(S.SyntaxKind.ObjectLiteralExpression),c=H.getValuesOfObjectLiteral(l),m=t=>typeof t=="string"||Array.isArray(t)&&t.every(d=>typeof d=="string")?t:t.reduce((d,h)=>typeof h=="string"?d:{...d,[h.identifier]:m(h.value)},{});return m(c)},v=s=>{const o=[],a=R.discoverImportedName({sourceFile:s,originalName:"useExposeApiModel"}),l=R.discoverImportedName({sourceFile:s,originalName:"useExposeNamedApiModels"});return s.forEachChildAsArray().filter(c=>c.isKind(S.SyntaxKind.ExpressionStatement)).map(c=>{if(a&&c.getText().startsWith(a)){const d=(c.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!d)return;o.push(k.parseExposedModel(d));return}if(l&&c.getText().startsWith(l)){const d=(c.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!d)return;k.parseNamedExposedModels(d).forEach(F=>o.push(F))}}),o};exports.analyzeMultipleSourceFiles=A;exports.analyzeSourceFileApiHeader=E;exports.analyzeSourceFileExposedModels=v;exports.prepareOpenApiSpec=G;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const q=require("crypto"),I=require("fs"),U=require("path"),K=require("url"),S=require("ts-morph"),y=require("../../utils/logger.cjs"),R=require("../discoveryModule/discoverImports/discoverImports.cjs"),_=require("../discoveryModule/discoverRouterFiles/discoverRouterFiles.cjs"),W=require("../discoveryModule/discoverRouters/discoverRouters.cjs"),H=require("../manager/OpenApiManager.cjs"),D=require("./getSourceFileTimestamp.cjs"),w=require("./nodeParsers.cjs"),B=require("./parseEndpoint.cjs"),k=require("./parseExposedModels.cjs"),C=require("./sourceFileCache.cjs"),V=require("./workerPool.cjs");var P=typeof document<"u"?document.currentScript:null;function Y(r){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const a in r)if(a!=="default"){const l=Object.getOwnPropertyDescriptor(r,a);Object.defineProperty(n,a,l.get?l:{enumerable:!0,get:()=>r[a]})}}return n.default=r,Object.freeze(n)}const E=Y(U);function G(){const r=["./analyzerWorker.mjs","./analyzerWorker.test.mjs"];for(const n of r){const a=new URL(n,typeof document>"u"?require("url").pathToFileURL(__filename).href:P&&P.tagName.toUpperCase()==="SCRIPT"&&P.src||new URL("openapi/analyzerModule/analyzerModule.cjs",document.baseURI).href);if(I.existsSync(K.fileURLToPath(a)))return a}throw new Error("analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).")}const J=2,Q=async({logLevel:r,tsconfigPath:n,sourceFilePaths:a,sourceFileDiscovery:l,incremental:d,profiling:m="stats"})=>{const t=H.OpenApiManager.getInstance();if(t.isReady())return;r&&y.Logger.setLevel(r),y.Logger.info("Preparing OpenAPI spec");const p=new S.Project({tsConfigFilePath:E.resolve(n),skipFileDependencyResolution:!0}),{explicitRouters:f,discoveredRouterFiles:g,allSourceFiles:T}=(()=>{const u=(a??[]).map(h=>E.resolve(h)).map(h=>p.getSourceFileOrThrow(h)),M=u.flatMap(h=>({fileName:h.getFilePath(),sourceFile:h,routers:W.discoverRouters(h)})),{discoveredRouterFiles:$,discoveredSourceFiles:b}=(()=>{if(l===!1)return{discoveredRouterFiles:[],discoveredSourceFiles:[]};const h=performance.now(),x=_.discoverRouterFiles({targetPath:typeof l=="object"?l.rootPath:".",tsConfigPath:n});return m!=="off"&&y.Logger.info(`File discovery took ${Math.round(performance.now()-h)}ms`),x})(),j=u.reduce((h,x)=>h.some(z=>z.getFilePath()===x.getFilePath())?h:h.concat(x),b);return{explicitRouters:M,discoveredRouterFiles:$,allSourceFiles:j}})(),e=f.reduce((i,N)=>i.some(u=>u.fileName===N.fileName)?i:i.concat(N),g),o=T.flatMap(i=>v(i)).filter(i=>!!i);o.length>0&&o[0]&&t.setHeader(o[0]);const s=T.flatMap(i=>O(i));t.setExposedModels(s);const c=typeof d=="object"&&d.cachePath?d.cachePath:E.resolve(process.cwd(),"node_modules",".cache","moonflower"),F=await A(e,{incremental:d!==!1,cachePath:c,timestampCache:{},profiling:m,tsconfigPath:E.resolve(n)});t.setStats({discoveredRouterFiles:g.map(i=>({path:i.fileName,routers:i.routers.named.map(N=>({name:N,endpoints:F.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))})),explicitRouterFiles:f.map(i=>({path:i.fileName,routers:i.routers.named.map(N=>({name:N,endpoints:F.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))}))}),t.setEndpoints(F),t.markAsReady()},A=async(r,n,a)=>{const l=n.profiling??"stats",d=performance.now(),m=[],t=[],p=performance.now(),f=new Map;for(const e of r){const o=D.getSourceFileTimestamp(e.sourceFile,n.timestampCache,f),s=n.incremental?C.SourceFileCache.getCachedResults(e.sourceFile,o,n.cachePath):null;s?(y.Logger.debug(`[${e.fileName}] Found cached results`),m.push({endpoints:s.endpoints,fileName:e.fileName,timing:0,endpointTimings:[]})):t.push({file:e,timestamp:o})}if(l!=="off"&&y.Logger.info(`Cache freshness check took ${Math.round(performance.now()-p)}ms`),t.length===0)return l!=="off"&&y.Logger.info(`Router analysis took ${Math.round(performance.now()-d)}ms`),m.flatMap(e=>e.endpoints);const g=new Map;for(const{file:e}of t)g.set(e.fileName,{endpoints:[],fileName:e.fileName,timing:0,endpointTimings:[]});if(t.length<=J)for(const{file:e}of t){const{endpoints:o,endpointTimings:s}=L(e),c=g.get(e.fileName);c.endpoints=o,c.endpointTimings=s,c.timing=s.reduce((F,i)=>F+i.timing,0)}else{const e=t.map(({file:c})=>({fileName:c.fileName,task:{taskId:q.randomUUID(),tsconfigPath:n.tsconfigPath,sourceFilePath:c.sourceFile.getFilePath(),routerNames:c.routers.named,filterEndpointPaths:a}})),o=new V.WorkerPool(G(),e.length);let s;try{s=await o.runAll(e.map(c=>c.task))}finally{o.terminate()}for(let c=0;c<s.length;c++){const F=s[c],i=e[c].fileName,N=g.get(i);if("error"in F){y.Logger.error(`[${i}] Worker error: ${F.error}`);continue}N.endpoints=F.endpoints,N.endpointTimings=F.endpointTimings,N.timing=F.endpointTimings.reduce((u,M)=>u+M.timing,0)}}for(const{file:e,timestamp:o}of t){const s=g.get(e.fileName);s.endpoints.length>0&&C.SourceFileCache.cacheResults(e.sourceFile,o,n.cachePath,s.endpoints)}const T=[...m,...Array.from(g.values())];return l!=="off"&&y.Logger.info(`Router analysis took ${Math.round(performance.now()-d)}ms`),l==="stats"?T.map(e=>({fileName:e.fileName,timeTaken:e.timing})).sort((e,o)=>o.timeTaken-e.timeTaken).filter(e=>e.timeTaken>500).forEach(e=>{y.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`)}):l==="debug"&&T.map(e=>({fileName:e.fileName,timeTaken:e.timing,endpointTimings:e.endpointTimings})).sort((e,o)=>o.timeTaken-e.timeTaken).forEach(e=>{y.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`),e.endpointTimings.sort((o,s)=>s.timing-o.timing).forEach(o=>{y.Logger.info(` - ${o.method} ${o.path} (${Math.round(o.timing)}ms)`),o.sectionTimings.filter(s=>s.timing>=1).sort((s,c)=>c.timing-s.timing).forEach(s=>{y.Logger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)})})}),T.flatMap(e=>e.endpoints)},L=(r,n)=>{const a=[],l=[],m=["get","post","put","delete","del","patch"].join("|");return r.routers.named.forEach(t=>{const p=new RegExp(`${t}\\.(?:${m})`);r.sourceFile.forEachChild(f=>{const g=f.getText();if(p.test(g)){w.resolveEndpointPath(f);const T=performance.now(),{endpoint:e,sectionTimings:o}=B.parseEndpoint(f,r.fileName);l.push({method:e.method,path:e.path,timing:performance.now()-T,sectionTimings:o}),a.push(e)}})}),{endpoints:a,endpointTimings:l}},v=r=>{const n=R.discoverImportedName({sourceFile:r,originalName:"useApiHeader"});if(!n)return null;const a=r.forEachChildAsArray().filter(t=>t.isKind(S.SyntaxKind.ExpressionStatement)).find(t=>n&&t.getText().startsWith(n));if(!a)return null;const l=a.getFirstDescendantByKindOrThrow(S.SyntaxKind.ObjectLiteralExpression),d=w.getValuesOfObjectLiteral(l),m=t=>typeof t=="string"||Array.isArray(t)&&t.every(p=>typeof p=="string")?t:t.reduce((p,f)=>typeof f=="string"?p:{...p,[f.identifier]:m(f.value)},{});return m(d)},O=r=>{const n=[],a=R.discoverImportedName({sourceFile:r,originalName:"useExposeApiModel"}),l=R.discoverImportedName({sourceFile:r,originalName:"useExposeNamedApiModels"});return r.forEachChildAsArray().filter(d=>d.isKind(S.SyntaxKind.ExpressionStatement)).map(d=>{if(a&&d.getText().startsWith(a)){const p=(d.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!p)return;n.push(k.parseExposedModel(p));return}if(l&&d.getText().startsWith(l)){const p=(d.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!p)return;k.parseNamedExposedModels(p).forEach(g=>n.push(g))}}),n};exports.analyzeMultipleSourceFiles=A;exports.analyzeSourceFileApiHeader=v;exports.analyzeSourceFileEndpoints=L;exports.analyzeSourceFileExposedModels=O;exports.prepareOpenApiSpec=Q;
2
2
  //# sourceMappingURL=analyzerModule.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzerModule.cjs","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"sourcesContent":["import crypto from 'crypto'\nimport { existsSync } from 'fs'\nimport * as path from 'path'\nimport { fileURLToPath } from 'url'\n\nfunction resolveWorkerUrl(): URL {\n\tconst candidates = ['./analyzerWorker.mjs', './analyzerWorker.test.mjs']\n\tfor (const candidate of candidates) {\n\t\tconst url = new URL(candidate, import.meta.url)\n\t\tif (existsSync(fileURLToPath(url))) {\n\t\t\treturn url\n\t\t}\n\t}\n\tthrow new Error(\n\t\t'analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).',\n\t)\n}\nimport { SourceFile, SyntaxKind } from 'ts-morph'\nimport { Project } from 'ts-morph'\n\nimport { Logger } from '../../utils/logger'\nimport { discoverImportedName } from '../discoveryModule/discoverImports/discoverImports'\nimport {\n\tDiscoveredSourceFile,\n\tdiscoverRouterFiles,\n} from '../discoveryModule/discoverRouterFiles/discoverRouterFiles'\nimport { discoverRouters } from '../discoveryModule/discoverRouters/discoverRouters'\nimport { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'\nimport { EndpointData, ExposedModelData } from '../types'\nimport { getSourceFileTimestamp, TimestampCache } from './getSourceFileTimestamp'\nimport { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint, SectionTiming } from './parseEndpoint'\nimport { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'\nimport { SourceFileCache } from './sourceFileCache'\nimport { WorkerPool, WorkerResult, WorkerTask } from './workerPool'\n\ntype Props = {\n\tlogLevel?: Parameters<(typeof Logger)['setLevel']>[0]\n\ttsconfigPath: string\n\tsourceFilePaths?: string[]\n\tsourceFileDiscovery?: boolean | FileDiscoveryConfig\n\tincremental?:\n\t\t| boolean\n\t\t| {\n\t\t\t\tcachePath: string\n\t\t }\n\tprofiling?: 'stats' | 'off' | 'debug'\n}\n\ntype FileDiscoveryConfig = {\n\trootPath: string\n}\n\ntype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\n\n/**\n * @param tsconfigPath Path to tsconfig file relative to project root\n * @param sourceFilePaths Array of router source files relative to project root\n */\nexport const prepareOpenApiSpec = async ({\n\tlogLevel,\n\ttsconfigPath,\n\tsourceFilePaths,\n\tsourceFileDiscovery,\n\tincremental,\n\tprofiling = 'stats',\n}: Props): Promise<void> => {\n\tconst openApiManager = OpenApiManager.getInstance()\n\n\tif (openApiManager.isReady()) {\n\t\treturn\n\t}\n\n\tif (logLevel) {\n\t\tLogger.setLevel(logLevel)\n\t}\n\n\tLogger.info('Preparing OpenAPI spec')\n\n\tconst project = new Project({\n\t\ttsConfigFilePath: path.resolve(tsconfigPath),\n\t\tskipFileDependencyResolution: true,\n\t})\n\n\tconst { explicitRouters, discoveredRouterFiles, allSourceFiles } = (() => {\n\t\tconst sourceFilesToAdd = sourceFilePaths ?? []\n\t\tconst resolvedSourceFilePaths = sourceFilesToAdd.map((filepath) => path.resolve(filepath))\n\t\tconst sourceFiles = resolvedSourceFilePaths.map((filePath) => project.getSourceFileOrThrow(filePath))\n\t\tconst explicitRouters = sourceFiles.flatMap((file) => ({\n\t\t\tfileName: file.getFilePath(),\n\t\t\tsourceFile: file,\n\t\t\trouters: discoverRouters(file),\n\t\t}))\n\n\t\tconst { discoveredRouterFiles, discoveredSourceFiles } = (() => {\n\t\t\tif (sourceFileDiscovery === false) {\n\t\t\t\treturn { discoveredRouterFiles: [], discoveredSourceFiles: [] }\n\t\t\t}\n\n\t\t\tconst startTime = performance.now()\n\t\t\tconst files = discoverRouterFiles({\n\t\t\t\ttargetPath: typeof sourceFileDiscovery === 'object' ? sourceFileDiscovery.rootPath : '.',\n\t\t\t\ttsConfigPath: tsconfigPath,\n\t\t\t})\n\t\t\tif (profiling !== 'off') {\n\t\t\t\tLogger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)\n\t\t\t}\n\t\t\treturn files\n\t\t})()\n\n\t\tconst allSourceFiles = sourceFiles.reduce(\n\t\t\t(acc, current) =>\n\t\t\t\tacc.some((r) => r.getFilePath() === current.getFilePath()) ? acc : acc.concat(current),\n\t\t\tdiscoveredSourceFiles,\n\t\t)\n\n\t\treturn { explicitRouters, discoveredRouterFiles, allSourceFiles }\n\t})()\n\n\tconst filesToAnalyze = explicitRouters.reduce(\n\t\t(acc, current) => (acc.some((r) => r.fileName === current.fileName) ? acc : acc.concat(current)),\n\t\tdiscoveredRouterFiles,\n\t)\n\n\tconst apiHeaders = allSourceFiles\n\t\t.flatMap((file) => analyzeSourceFileApiHeader(file))\n\t\t.filter((headers) => !!headers)\n\tif (apiHeaders.length > 0 && apiHeaders[0]) {\n\t\topenApiManager.setHeader(apiHeaders[0])\n\t}\n\n\tconst exposedModels = allSourceFiles.flatMap((file) => analyzeSourceFileExposedModels(file))\n\n\topenApiManager.setExposedModels(exposedModels)\n\n\tconst cachePath = (() => {\n\t\tif (typeof incremental === 'object' && incremental.cachePath) {\n\t\t\treturn incremental.cachePath\n\t\t}\n\t\treturn path.resolve(process.cwd(), 'node_modules', '.cache', 'moonflower')\n\t})()\n\tconst endpoints = await analyzeMultipleSourceFiles(filesToAnalyze, {\n\t\tincremental: incremental !== false,\n\t\tcachePath,\n\t\ttimestampCache: {},\n\t\tprofiling,\n\t\ttsconfigPath: path.resolve(tsconfigPath),\n\t})\n\n\topenApiManager.setStats({\n\t\tdiscoveredRouterFiles: discoveredRouterFiles.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t\texplicitRouterFiles: explicitRouters.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t})\n\n\topenApiManager.setEndpoints(endpoints)\n\topenApiManager.markAsReady()\n}\n\nexport const analyzeMultipleSourceFiles = async (\n\tfiles: DiscoveredSourceFile[],\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t\ttsconfigPath: string\n\t},\n\tfilterEndpointPaths?: string[],\n): Promise<EndpointData[]> => {\n\tconst profiling = config.profiling ?? 'stats'\n\tconst startTime = performance.now()\n\n\t// Separate cached files from those needing analysis\n\ttype CachedFile = { endpoints: EndpointData[]; fileName: string; timing: 0; endpointTimings: [] }\n\ttype UncachedFile = { file: DiscoveredSourceFile; timestamp: number }\n\n\tconst cached: CachedFile[] = []\n\tconst uncached: UncachedFile[] = []\n\n\tfor (const file of files) {\n\t\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\t\tconst hit = config.incremental\n\t\t\t? SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\t\t\t: null\n\t\tif (hit) {\n\t\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\t\tcached.push({ endpoints: hit.endpoints, fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t\t} else {\n\t\t\tuncached.push({ file, timestamp })\n\t\t}\n\t}\n\n\tif (uncached.length === 0) {\n\t\tif (profiling !== 'off') {\n\t\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t\t}\n\t\treturn cached.flatMap((f) => f.endpoints)\n\t}\n\n\t// Build one task per uncached file. Each worker analyzes a whole file in a single pass, so it\n\t// pays the ts-morph Project cold-start (full TS program load + type-checker warmup) once and\n\t// reuses the warmed-up checker for every endpoint in that file.\n\ttype FileTask = { task: WorkerTask; fileName: string }\n\tconst allTasks: FileTask[] = uncached.map(({ file }) => ({\n\t\tfileName: file.fileName,\n\t\ttask: {\n\t\t\ttaskId: crypto.randomUUID(),\n\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\trouterNames: file.routers.named,\n\t\t\tfilterEndpointPaths,\n\t\t},\n\t}))\n\n\t// Dispatch all tasks to the worker pool, capped at one worker per file.\n\tconst pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)\n\n\ttype FileResult = {\n\t\tendpoints: EndpointData[]\n\t\tfileName: string\n\t\ttiming: number\n\t\tendpointTimings: EndpointTiming[]\n\t}\n\n\tlet results: WorkerResult[]\n\ttry {\n\t\tresults = await pool.runAll(allTasks.map((ft) => ft.task))\n\t} finally {\n\t\tpool.terminate()\n\t}\n\n\t// Each result maps 1:1 to a file task.\n\tconst byFile = new Map<string, FileResult>()\n\tfor (const { file } of uncached) {\n\t\tbyFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t}\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst result = results[i]\n\t\tconst fileName = allTasks[i].fileName\n\t\tconst fileResult = byFile.get(fileName)!\n\n\t\tif ('error' in result) {\n\t\t\tLogger.error(`[${fileName}] Worker error: ${result.error}`)\n\t\t\tcontinue\n\t\t}\n\n\t\tfileResult.endpoints = result.endpoints\n\t\tfileResult.endpointTimings = result.endpointTimings\n\t\tfileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t}\n\n\t// Write cache for each uncached file\n\tfor (const { file, timestamp } of uncached) {\n\t\tconst fileResult = byFile.get(file.fileName)!\n\t\tif (fileResult.endpoints.length > 0) {\n\t\t\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, fileResult.endpoints)\n\t\t}\n\t}\n\n\tconst analyzedFiles = [...cached, ...Array.from(byFile.values())]\n\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t}\n\n\tif (profiling === 'stats') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.filter((t) => t.timeTaken > 500)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t})\n\t} else if (profiling === 'debug') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing, endpointTimings: f.endpointTimings }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t\tt.endpointTimings\n\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t.forEach((ep) => {\n\t\t\t\t\t\tLogger.info(` - ${ep.method} ${ep.path} (${Math.round(ep.timing)}ms)`)\n\t\t\t\t\t\tep.sectionTimings\n\t\t\t\t\t\t\t.filter((s) => s.timing >= 1)\n\t\t\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t\t\t.forEach((s) => {\n\t\t\t\t\t\t\t\tLogger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t})\n\t}\n\n\treturn analyzedFiles.flatMap((f) => f.endpoints)\n}\n\nexport const analyzeSourceFileWithCache = (\n\tfile: DiscoveredSourceFile,\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t},\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; timing: number; endpointTimings: EndpointTiming[] } => {\n\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\tconst cachedResults = SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\n\tif (cachedResults) {\n\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\treturn { endpoints: cachedResults.endpoints, timing: 0, endpointTimings: [] }\n\t}\n\tLogger.debug(`[${file.fileName}] Analyzing...`)\n\n\tconst t1 = performance.now()\n\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\tconst t2 = performance.now()\n\tLogger.debug(`[${file.fileName}] Analyzed in ${t2 - t1}ms`)\n\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, endpoints)\n\treturn { endpoints, timing: t2 - t1, endpointTimings }\n}\n\nexport const analyzeSourceFileEndpoints = (\n\tfile: DiscoveredSourceFile,\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; endpointTimings: EndpointTiming[] } => {\n\tconst endpoints: EndpointData[] = []\n\tconst endpointTimings: EndpointTiming[] = []\n\tconst operations = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst joinedOperations = operations.join('|')\n\n\tfile.routers.named.forEach((routerName) => {\n\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${joinedOperations})`)\n\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\tconst nodeText = node.getText()\n\n\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\n\t\t\t\tif (filterEndpointPaths && !filterEndpointPaths.some((path) => endpointPath.includes(path))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, file.fileName)\n\t\t\t\tendpointTimings.push({\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\tpath: endpoint.path,\n\t\t\t\t\ttiming: performance.now() - t1,\n\t\t\t\t\tsectionTimings,\n\t\t\t\t})\n\t\t\t\tendpoints.push(endpoint)\n\t\t\t}\n\t\t})\n\t})\n\n\treturn { endpoints, endpointTimings }\n}\n\nexport const analyzeSourceFileApiHeader = (sourceFile: SourceFile): ApiDocsHeader | null => {\n\tconst nameOfUseApiHeader = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useApiHeader',\n\t})\n\n\tif (!nameOfUseApiHeader) {\n\t\treturn null\n\t}\n\n\tconst node = sourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.find((node) => nameOfUseApiHeader && node.getText().startsWith(nameOfUseApiHeader))\n\n\tif (!node) {\n\t\treturn null\n\t}\n\n\tconst targetNode = node.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression)\n\tconst values = getValuesOfObjectLiteral(targetNode)\n\n\tconst collapseObject = (v: string | string[] | typeof values): any => {\n\t\tif (typeof v === 'string') {\n\t\t\treturn v\n\t\t}\n\t\tif (Array.isArray(v) && v.every((value) => typeof value === 'string')) {\n\t\t\treturn v\n\t\t}\n\n\t\treturn v.reduce((acc, current) => {\n\t\t\tif (typeof current === 'string') {\n\t\t\t\treturn acc\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t[current.identifier]: collapseObject(current.value as string[]),\n\t\t\t}\n\t\t}, {})\n\t}\n\treturn collapseObject(values)\n}\n\nexport const analyzeSourceFileExposedModels = (sourceFile: SourceFile): ExposedModelData[] => {\n\tconst models: ExposedModelData[] = []\n\n\tconst nameOfUseExposeApiModel = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeApiModel',\n\t})\n\n\tconst nameOfUseExposeNamedApiModels = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeNamedApiModels',\n\t})\n\n\tsourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.map((node) => {\n\t\t\tif (nameOfUseExposeApiModel && node.getText().startsWith(nameOfUseExposeApiModel)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tmodels.push(parseExposedModel(firstChild))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (nameOfUseExposeNamedApiModels && node.getText().startsWith(nameOfUseExposeNamedApiModels)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst parsedModels = parseNamedExposedModels(firstChild)\n\t\t\t\tparsedModels.forEach((model) => models.push(model))\n\t\t\t}\n\t\t})\n\treturn models\n}\n"],"names":["resolveWorkerUrl","candidates","candidate","url","_documentCurrentScript","existsSync","fileURLToPath","prepareOpenApiSpec","logLevel","tsconfigPath","sourceFilePaths","sourceFileDiscovery","incremental","profiling","openApiManager","OpenApiManager","Logger","project","Project","path","explicitRouters","discoveredRouterFiles","allSourceFiles","sourceFiles","filepath","filePath","file","discoverRouters","discoveredSourceFiles","startTime","files","discoverRouterFiles","acc","current","r","filesToAnalyze","apiHeaders","analyzeSourceFileApiHeader","headers","exposedModels","analyzeSourceFileExposedModels","cachePath","endpoints","analyzeMultipleSourceFiles","e","config","filterEndpointPaths","cached","uncached","timestamp","getSourceFileTimestamp","hit","SourceFileCache","f","allTasks","crypto","pool","WorkerPool","results","ft","byFile","i","result","fileName","fileResult","sum","t","analyzedFiles","a","b","ep","s","sourceFile","nameOfUseApiHeader","discoverImportedName","node","SyntaxKind","targetNode","values","getValuesOfObjectLiteral","collapseObject","v","value","models","nameOfUseExposeApiModel","nameOfUseExposeNamedApiModels","firstChild","parseExposedModel","parseNamedExposedModels","model"],"mappings":"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"}
1
+ {"version":3,"file":"analyzerModule.cjs","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"sourcesContent":["import crypto from 'crypto'\nimport { existsSync } from 'fs'\nimport * as path from 'path'\nimport { fileURLToPath } from 'url'\n\nfunction resolveWorkerUrl(): URL {\n\tconst candidates = ['./analyzerWorker.mjs', './analyzerWorker.test.mjs']\n\tfor (const candidate of candidates) {\n\t\tconst url = new URL(candidate, import.meta.url)\n\t\tif (existsSync(fileURLToPath(url))) {\n\t\t\treturn url\n\t\t}\n\t}\n\tthrow new Error(\n\t\t'analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).',\n\t)\n}\nimport { SourceFile, SyntaxKind } from 'ts-morph'\nimport { Project } from 'ts-morph'\n\nimport { Logger } from '../../utils/logger'\nimport { discoverImportedName } from '../discoveryModule/discoverImports/discoverImports'\nimport {\n\tDiscoveredSourceFile,\n\tdiscoverRouterFiles,\n} from '../discoveryModule/discoverRouterFiles/discoverRouterFiles'\nimport { discoverRouters } from '../discoveryModule/discoverRouters/discoverRouters'\nimport { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'\nimport { EndpointData, ExposedModelData } from '../types'\nimport { getSourceFileTimestamp, MtimeCache, TimestampCache } from './getSourceFileTimestamp'\nimport { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint, SectionTiming } from './parseEndpoint'\nimport { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'\nimport { SourceFileCache } from './sourceFileCache'\nimport { WorkerPool, WorkerResult, WorkerTask } from './workerPool'\n\ntype Props = {\n\tlogLevel?: Parameters<(typeof Logger)['setLevel']>[0]\n\ttsconfigPath: string\n\tsourceFilePaths?: string[]\n\tsourceFileDiscovery?: boolean | FileDiscoveryConfig\n\tincremental?:\n\t\t| boolean\n\t\t| {\n\t\t\t\tcachePath: string\n\t\t }\n\tprofiling?: 'stats' | 'off' | 'debug'\n}\n\ntype FileDiscoveryConfig = {\n\trootPath: string\n}\n\ntype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\n\n/**\n * Number of uncached files at or below which analysis runs inline on the (already-warm) main-thread\n * Project instead of fanning out to worker threads. Each worker pays a multi-second cold-start to\n * build its own Project, so parallelism only wins once there are several files to share that cost.\n */\nconst INLINE_ANALYSIS_FILE_THRESHOLD = 2\n\n/**\n * @param tsconfigPath Path to tsconfig file relative to project root\n * @param sourceFilePaths Array of router source files relative to project root\n */\nexport const prepareOpenApiSpec = async ({\n\tlogLevel,\n\ttsconfigPath,\n\tsourceFilePaths,\n\tsourceFileDiscovery,\n\tincremental,\n\tprofiling = 'stats',\n}: Props): Promise<void> => {\n\tconst openApiManager = OpenApiManager.getInstance()\n\n\tif (openApiManager.isReady()) {\n\t\treturn\n\t}\n\n\tif (logLevel) {\n\t\tLogger.setLevel(logLevel)\n\t}\n\n\tLogger.info('Preparing OpenAPI spec')\n\n\tconst project = new Project({\n\t\ttsConfigFilePath: path.resolve(tsconfigPath),\n\t\tskipFileDependencyResolution: true,\n\t})\n\n\tconst { explicitRouters, discoveredRouterFiles, allSourceFiles } = (() => {\n\t\tconst sourceFilesToAdd = sourceFilePaths ?? []\n\t\tconst resolvedSourceFilePaths = sourceFilesToAdd.map((filepath) => path.resolve(filepath))\n\t\tconst sourceFiles = resolvedSourceFilePaths.map((filePath) => project.getSourceFileOrThrow(filePath))\n\t\tconst explicitRouters = sourceFiles.flatMap((file) => ({\n\t\t\tfileName: file.getFilePath(),\n\t\t\tsourceFile: file,\n\t\t\trouters: discoverRouters(file),\n\t\t}))\n\n\t\tconst { discoveredRouterFiles, discoveredSourceFiles } = (() => {\n\t\t\tif (sourceFileDiscovery === false) {\n\t\t\t\treturn { discoveredRouterFiles: [], discoveredSourceFiles: [] }\n\t\t\t}\n\n\t\t\tconst startTime = performance.now()\n\t\t\tconst files = discoverRouterFiles({\n\t\t\t\ttargetPath: typeof sourceFileDiscovery === 'object' ? sourceFileDiscovery.rootPath : '.',\n\t\t\t\ttsConfigPath: tsconfigPath,\n\t\t\t})\n\t\t\tif (profiling !== 'off') {\n\t\t\t\tLogger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)\n\t\t\t}\n\t\t\treturn files\n\t\t})()\n\n\t\tconst allSourceFiles = sourceFiles.reduce(\n\t\t\t(acc, current) =>\n\t\t\t\tacc.some((r) => r.getFilePath() === current.getFilePath()) ? acc : acc.concat(current),\n\t\t\tdiscoveredSourceFiles,\n\t\t)\n\n\t\treturn { explicitRouters, discoveredRouterFiles, allSourceFiles }\n\t})()\n\n\tconst filesToAnalyze = explicitRouters.reduce(\n\t\t(acc, current) => (acc.some((r) => r.fileName === current.fileName) ? acc : acc.concat(current)),\n\t\tdiscoveredRouterFiles,\n\t)\n\n\tconst apiHeaders = allSourceFiles\n\t\t.flatMap((file) => analyzeSourceFileApiHeader(file))\n\t\t.filter((headers) => !!headers)\n\tif (apiHeaders.length > 0 && apiHeaders[0]) {\n\t\topenApiManager.setHeader(apiHeaders[0])\n\t}\n\n\tconst exposedModels = allSourceFiles.flatMap((file) => analyzeSourceFileExposedModels(file))\n\n\topenApiManager.setExposedModels(exposedModels)\n\n\tconst cachePath = (() => {\n\t\tif (typeof incremental === 'object' && incremental.cachePath) {\n\t\t\treturn incremental.cachePath\n\t\t}\n\t\treturn path.resolve(process.cwd(), 'node_modules', '.cache', 'moonflower')\n\t})()\n\tconst endpoints = await analyzeMultipleSourceFiles(filesToAnalyze, {\n\t\tincremental: incremental !== false,\n\t\tcachePath,\n\t\ttimestampCache: {},\n\t\tprofiling,\n\t\ttsconfigPath: path.resolve(tsconfigPath),\n\t})\n\n\topenApiManager.setStats({\n\t\tdiscoveredRouterFiles: discoveredRouterFiles.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t\texplicitRouterFiles: explicitRouters.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t})\n\n\topenApiManager.setEndpoints(endpoints)\n\topenApiManager.markAsReady()\n}\n\nexport const analyzeMultipleSourceFiles = async (\n\tfiles: DiscoveredSourceFile[],\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t\ttsconfigPath: string\n\t},\n\tfilterEndpointPaths?: string[],\n): Promise<EndpointData[]> => {\n\tconst profiling = config.profiling ?? 'stats'\n\tconst startTime = performance.now()\n\n\t// Separate cached files from those needing analysis\n\ttype CachedFile = { endpoints: EndpointData[]; fileName: string; timing: 0; endpointTimings: [] }\n\ttype UncachedFile = { file: DiscoveredSourceFile; timestamp: number }\n\n\tconst cached: CachedFile[] = []\n\tconst uncached: UncachedFile[] = []\n\n\tconst freshnessCheckStart = performance.now()\n\tconst mtimeCache: MtimeCache = new Map()\n\tfor (const file of files) {\n\t\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache, mtimeCache)\n\t\tconst hit = config.incremental\n\t\t\t? SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\t\t\t: null\n\t\tif (hit) {\n\t\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\t\tcached.push({ endpoints: hit.endpoints, fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t\t} else {\n\t\t\tuncached.push({ file, timestamp })\n\t\t}\n\t}\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Cache freshness check took ${Math.round(performance.now() - freshnessCheckStart)}ms`)\n\t}\n\n\tif (uncached.length === 0) {\n\t\tif (profiling !== 'off') {\n\t\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t\t}\n\t\treturn cached.flatMap((f) => f.endpoints)\n\t}\n\n\ttype FileResult = {\n\t\tendpoints: EndpointData[]\n\t\tfileName: string\n\t\ttiming: number\n\t\tendpointTimings: EndpointTiming[]\n\t}\n\n\tconst byFile = new Map<string, FileResult>()\n\tfor (const { file } of uncached) {\n\t\tbyFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t}\n\n\t// The caller (prepareOpenApiSpec) already built and warmed a ts-morph Project on this thread, so\n\t// inline analysis runs against a hot type-checker. A worker, by contrast, must spawn a thread and\n\t// build its own Project from scratch — a multi-second cold-start. That cold-start only pays off\n\t// when there are enough uncached files that spreading the parse work across workers beats it; below\n\t// the threshold (e.g. the common single-file incremental rebuild), inline is strictly faster.\n\tif (uncached.length <= INLINE_ANALYSIS_FILE_THRESHOLD) {\n\t\tfor (const { file } of uncached) {\n\t\t\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\t\t\tconst fileResult = byFile.get(file.fileName)!\n\t\t\tfileResult.endpoints = endpoints\n\t\t\tfileResult.endpointTimings = endpointTimings\n\t\t\tfileResult.timing = endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t\t}\n\t} else {\n\t\t// One task per file: each worker analyzes a whole file in a single pass, paying its Project\n\t\t// cold-start once and reusing the warmed-up checker for every endpoint in that file. Cap the\n\t\t// pool at one worker per file so we never spin up a worker with nothing to do.\n\t\ttype FileTask = { task: WorkerTask; fileName: string }\n\t\tconst allTasks: FileTask[] = uncached.map(({ file }) => ({\n\t\t\tfileName: file.fileName,\n\t\t\ttask: {\n\t\t\t\ttaskId: crypto.randomUUID(),\n\t\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\t\trouterNames: file.routers.named,\n\t\t\t\tfilterEndpointPaths,\n\t\t\t},\n\t\t}))\n\n\t\tconst pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)\n\t\tlet results: WorkerResult[]\n\t\ttry {\n\t\t\tresults = await pool.runAll(allTasks.map((ft) => ft.task))\n\t\t} finally {\n\t\t\tpool.terminate()\n\t\t}\n\n\t\t// Each result maps 1:1 to a file task.\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i]\n\t\t\tconst fileName = allTasks[i].fileName\n\t\t\tconst fileResult = byFile.get(fileName)!\n\n\t\t\tif ('error' in result) {\n\t\t\t\tLogger.error(`[${fileName}] Worker error: ${result.error}`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfileResult.endpoints = result.endpoints\n\t\t\tfileResult.endpointTimings = result.endpointTimings\n\t\t\tfileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t\t}\n\t}\n\n\t// Write cache for each uncached file\n\tfor (const { file, timestamp } of uncached) {\n\t\tconst fileResult = byFile.get(file.fileName)!\n\t\tif (fileResult.endpoints.length > 0) {\n\t\t\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, fileResult.endpoints)\n\t\t}\n\t}\n\n\tconst analyzedFiles = [...cached, ...Array.from(byFile.values())]\n\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t}\n\n\tif (profiling === 'stats') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.filter((t) => t.timeTaken > 500)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t})\n\t} else if (profiling === 'debug') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing, endpointTimings: f.endpointTimings }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t\tt.endpointTimings\n\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t.forEach((ep) => {\n\t\t\t\t\t\tLogger.info(` - ${ep.method} ${ep.path} (${Math.round(ep.timing)}ms)`)\n\t\t\t\t\t\tep.sectionTimings\n\t\t\t\t\t\t\t.filter((s) => s.timing >= 1)\n\t\t\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t\t\t.forEach((s) => {\n\t\t\t\t\t\t\t\tLogger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t})\n\t}\n\n\treturn analyzedFiles.flatMap((f) => f.endpoints)\n}\n\nexport const analyzeSourceFileWithCache = (\n\tfile: DiscoveredSourceFile,\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t},\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; timing: number; endpointTimings: EndpointTiming[] } => {\n\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\tconst cachedResults = SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\n\tif (cachedResults) {\n\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\treturn { endpoints: cachedResults.endpoints, timing: 0, endpointTimings: [] }\n\t}\n\tLogger.debug(`[${file.fileName}] Analyzing...`)\n\n\tconst t1 = performance.now()\n\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\tconst t2 = performance.now()\n\tLogger.debug(`[${file.fileName}] Analyzed in ${t2 - t1}ms`)\n\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, endpoints)\n\treturn { endpoints, timing: t2 - t1, endpointTimings }\n}\n\nexport const analyzeSourceFileEndpoints = (\n\tfile: DiscoveredSourceFile,\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; endpointTimings: EndpointTiming[] } => {\n\tconst endpoints: EndpointData[] = []\n\tconst endpointTimings: EndpointTiming[] = []\n\tconst operations = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst joinedOperations = operations.join('|')\n\n\tfile.routers.named.forEach((routerName) => {\n\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${joinedOperations})`)\n\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\tconst nodeText = node.getText()\n\n\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\n\t\t\t\tif (filterEndpointPaths && !filterEndpointPaths.some((path) => endpointPath.includes(path))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, file.fileName)\n\t\t\t\tendpointTimings.push({\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\tpath: endpoint.path,\n\t\t\t\t\ttiming: performance.now() - t1,\n\t\t\t\t\tsectionTimings,\n\t\t\t\t})\n\t\t\t\tendpoints.push(endpoint)\n\t\t\t}\n\t\t})\n\t})\n\n\treturn { endpoints, endpointTimings }\n}\n\nexport const analyzeSourceFileApiHeader = (sourceFile: SourceFile): ApiDocsHeader | null => {\n\tconst nameOfUseApiHeader = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useApiHeader',\n\t})\n\n\tif (!nameOfUseApiHeader) {\n\t\treturn null\n\t}\n\n\tconst node = sourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.find((node) => nameOfUseApiHeader && node.getText().startsWith(nameOfUseApiHeader))\n\n\tif (!node) {\n\t\treturn null\n\t}\n\n\tconst targetNode = node.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression)\n\tconst values = getValuesOfObjectLiteral(targetNode)\n\n\tconst collapseObject = (v: string | string[] | typeof values): any => {\n\t\tif (typeof v === 'string') {\n\t\t\treturn v\n\t\t}\n\t\tif (Array.isArray(v) && v.every((value) => typeof value === 'string')) {\n\t\t\treturn v\n\t\t}\n\n\t\treturn v.reduce((acc, current) => {\n\t\t\tif (typeof current === 'string') {\n\t\t\t\treturn acc\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t[current.identifier]: collapseObject(current.value as string[]),\n\t\t\t}\n\t\t}, {})\n\t}\n\treturn collapseObject(values)\n}\n\nexport const analyzeSourceFileExposedModels = (sourceFile: SourceFile): ExposedModelData[] => {\n\tconst models: ExposedModelData[] = []\n\n\tconst nameOfUseExposeApiModel = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeApiModel',\n\t})\n\n\tconst nameOfUseExposeNamedApiModels = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeNamedApiModels',\n\t})\n\n\tsourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.map((node) => {\n\t\t\tif (nameOfUseExposeApiModel && node.getText().startsWith(nameOfUseExposeApiModel)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tmodels.push(parseExposedModel(firstChild))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (nameOfUseExposeNamedApiModels && node.getText().startsWith(nameOfUseExposeNamedApiModels)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst parsedModels = parseNamedExposedModels(firstChild)\n\t\t\t\tparsedModels.forEach((model) => models.push(model))\n\t\t\t}\n\t\t})\n\treturn models\n}\n"],"names":["resolveWorkerUrl","candidates","candidate","url","_documentCurrentScript","existsSync","fileURLToPath","INLINE_ANALYSIS_FILE_THRESHOLD","prepareOpenApiSpec","logLevel","tsconfigPath","sourceFilePaths","sourceFileDiscovery","incremental","profiling","openApiManager","OpenApiManager","Logger","project","Project","path","explicitRouters","discoveredRouterFiles","allSourceFiles","sourceFiles","filepath","filePath","file","discoverRouters","discoveredSourceFiles","startTime","files","discoverRouterFiles","acc","current","r","filesToAnalyze","apiHeaders","analyzeSourceFileApiHeader","headers","exposedModels","analyzeSourceFileExposedModels","cachePath","endpoints","analyzeMultipleSourceFiles","e","config","filterEndpointPaths","cached","uncached","freshnessCheckStart","mtimeCache","timestamp","getSourceFileTimestamp","hit","SourceFileCache","f","byFile","endpointTimings","analyzeSourceFileEndpoints","fileResult","sum","t","allTasks","crypto","pool","WorkerPool","results","ft","i","result","fileName","analyzedFiles","a","b","ep","joinedOperations","routerName","routerPattern","node","nodeText","resolveEndpointPath","t1","endpoint","sectionTimings","parseEndpoint","sourceFile","nameOfUseApiHeader","discoverImportedName","SyntaxKind","targetNode","values","getValuesOfObjectLiteral","collapseObject","v","value","models","nameOfUseExposeApiModel","nameOfUseExposeNamedApiModels","firstChild","parseExposedModel","parseNamedExposedModels","model"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"analyzerModule.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C,OAAO,EACN,oBAAoB,EAEpB,MAAM,4DAA4D,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAkB,MAAM,2BAA2B,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAA0B,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEjF,OAAO,EAAiB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAK9D,KAAK,KAAK,GAAG;IACZ,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;IACnD,WAAW,CAAC,EACT,OAAO,GACP;QACA,SAAS,EAAE,MAAM,CAAA;KAChB,CAAA;IACJ,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,CAAA;AAED,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,aAAa,EAAE,CAAA;CAAE,CAAA;AAMvG,eAAO,MAAM,kBAAkB,8FAO5B,KAAK,KAAG,OAAO,CAAC,IAAI,CA0GtB,CAAA;AAED,eAAO,MAAM,0BAA0B,UAC/B,oBAAoB,EAAE,UACrB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;IACrC,YAAY,EAAE,MAAM,CAAA;CACpB,wBACqB,MAAM,EAAE,KAC5B,OAAO,CAAC,YAAY,EAAE,CA+HxB,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,UAClB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,wBACqB,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgBhF,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,wBACJ,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgChE,CAAA;AAED,eAAO,MAAM,0BAA0B,eAAgB,UAAU,KAAG,aAAa,GAAG,IAyCnF,CAAA;AAED,eAAO,MAAM,8BAA8B,eAAgB,UAAU,KAAG,gBAAgB,EA4CvF,CAAA"}
1
+ {"version":3,"file":"analyzerModule.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C,OAAO,EACN,oBAAoB,EAEpB,MAAM,4DAA4D,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAkB,MAAM,2BAA2B,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAsC,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAE7F,OAAO,EAAiB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAK9D,KAAK,KAAK,GAAG;IACZ,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;IACnD,WAAW,CAAC,EACT,OAAO,GACP;QACA,SAAS,EAAE,MAAM,CAAA;KAChB,CAAA;IACJ,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,CAAA;AAED,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,aAAa,EAAE,CAAA;CAAE,CAAA;AAavG,eAAO,MAAM,kBAAkB,8FAO5B,KAAK,KAAG,OAAO,CAAC,IAAI,CA0GtB,CAAA;AAED,eAAO,MAAM,0BAA0B,UAC/B,oBAAoB,EAAE,UACrB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;IACrC,YAAY,EAAE,MAAM,CAAA;CACpB,wBACqB,MAAM,EAAE,KAC5B,OAAO,CAAC,YAAY,EAAE,CAiJxB,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,UAClB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,wBACqB,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgBhF,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,wBACJ,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgChE,CAAA;AAED,eAAO,MAAM,0BAA0B,eAAgB,UAAU,KAAG,aAAa,GAAG,IAyCnF,CAAA;AAED,eAAO,MAAM,8BAA8B,eAAgB,UAAU,KAAG,gBAAgB,EA4CvF,CAAA"}
@@ -1,184 +1,211 @@
1
1
  import $ from "crypto";
2
2
  import { existsSync as v } from "fs";
3
- import * as M from "path";
3
+ import * as k from "path";
4
4
  import { fileURLToPath as O } from "url";
5
5
  import { Project as L, SyntaxKind as x } from "ts-morph";
6
- import { Logger as u } from "../../utils/logger.mjs";
7
- import { discoverImportedName as E } from "../discoveryModule/discoverImports/discoverImports.mjs";
6
+ import { Logger as F } from "../../utils/logger.mjs";
7
+ import { discoverImportedName as P } from "../discoveryModule/discoverImports/discoverImports.mjs";
8
8
  import { discoverRouterFiles as j } from "../discoveryModule/discoverRouterFiles/discoverRouterFiles.mjs";
9
9
  import { discoverRouters as z } from "../discoveryModule/discoverRouters/discoverRouters.mjs";
10
10
  import { OpenApiManager as U } from "../manager/OpenApiManager.mjs";
11
11
  import { getSourceFileTimestamp as b } from "./getSourceFileTimestamp.mjs";
12
- import { getValuesOfObjectLiteral as W } from "./nodeParsers.mjs";
13
- import { parseExposedModel as K, parseNamedExposedModels as H } from "./parseExposedModels.mjs";
14
- import { SourceFileCache as P } from "./sourceFileCache.mjs";
15
- import { WorkerPool as I } from "./workerPool.mjs";
12
+ import { getValuesOfObjectLiteral as I, resolveEndpointPath as W } from "./nodeParsers.mjs";
13
+ import { parseEndpoint as H } from "./parseEndpoint.mjs";
14
+ import { parseExposedModel as K, parseNamedExposedModels as _ } from "./parseExposedModels.mjs";
15
+ import { SourceFileCache as R } from "./sourceFileCache.mjs";
16
+ import { WorkerPool as D } from "./workerPool.mjs";
16
17
  function B() {
17
- const m = ["./analyzerWorker.mjs", "./analyzerWorker.test.mjs"];
18
- for (const i of m) {
19
- const p = new URL(i, import.meta.url);
20
- if (v(O(p)))
21
- return p;
18
+ const a = ["./analyzerWorker.mjs", "./analyzerWorker.test.mjs"];
19
+ for (const n of a) {
20
+ const d = new URL(n, import.meta.url);
21
+ if (v(O(d)))
22
+ return d;
22
23
  }
23
24
  throw new Error(
24
25
  "analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first)."
25
26
  );
26
27
  }
27
- const ae = async ({
28
- logLevel: m,
29
- tsconfigPath: i,
30
- sourceFilePaths: p,
31
- sourceFileDiscovery: c,
32
- incremental: n,
28
+ const V = 2, de = async ({
29
+ logLevel: a,
30
+ tsconfigPath: n,
31
+ sourceFilePaths: d,
32
+ sourceFileDiscovery: l,
33
+ incremental: m,
33
34
  profiling: f = "stats"
34
35
  }) => {
35
36
  const t = U.getInstance();
36
37
  if (t.isReady())
37
38
  return;
38
- m && u.setLevel(m), u.info("Preparing OpenAPI spec");
39
- const a = new L({
40
- tsConfigFilePath: M.resolve(i),
39
+ a && F.setLevel(a), F.info("Preparing OpenAPI spec");
40
+ const c = new L({
41
+ tsConfigFilePath: k.resolve(n),
41
42
  skipFileDependencyResolution: !0
42
- }), { explicitRouters: h, discoveredRouterFiles: F, allSourceFiles: N } = (() => {
43
- const l = (p ?? []).map((d) => M.resolve(d)).map((d) => a.getSourceFileOrThrow(d)), R = l.flatMap((d) => ({
44
- fileName: d.getFilePath(),
45
- sourceFile: d,
46
- routers: z(d)
47
- })), { discoveredRouterFiles: A, discoveredSourceFiles: C } = (() => {
48
- if (c === !1)
43
+ }), { explicitRouters: u, discoveredRouterFiles: g, allSourceFiles: T } = (() => {
44
+ const p = (d ?? []).map((h) => k.resolve(h)).map((h) => c.getSourceFileOrThrow(h)), M = p.flatMap((h) => ({
45
+ fileName: h.getFilePath(),
46
+ sourceFile: h,
47
+ routers: z(h)
48
+ })), { discoveredRouterFiles: C, discoveredSourceFiles: S } = (() => {
49
+ if (l === !1)
49
50
  return { discoveredRouterFiles: [], discoveredSourceFiles: [] };
50
- const d = performance.now(), k = j({
51
- targetPath: typeof c == "object" ? c.rootPath : ".",
52
- tsConfigPath: i
51
+ const h = performance.now(), E = j({
52
+ targetPath: typeof l == "object" ? l.rootPath : ".",
53
+ tsConfigPath: n
53
54
  });
54
- return f !== "off" && u.info(`File discovery took ${Math.round(performance.now() - d)}ms`), k;
55
- })(), S = l.reduce(
56
- (d, k) => d.some((w) => w.getFilePath() === k.getFilePath()) ? d : d.concat(k),
57
- C
55
+ return f !== "off" && F.info(`File discovery took ${Math.round(performance.now() - h)}ms`), E;
56
+ })(), w = p.reduce(
57
+ (h, E) => h.some((A) => A.getFilePath() === E.getFilePath()) ? h : h.concat(E),
58
+ S
58
59
  );
59
- return { explicitRouters: R, discoveredRouterFiles: A, allSourceFiles: S };
60
- })(), T = h.reduce(
61
- (s, y) => s.some((l) => l.fileName === y.fileName) ? s : s.concat(y),
62
- F
63
- ), e = N.flatMap((s) => V(s)).filter((s) => !!s);
64
- e.length > 0 && e[0] && t.setHeader(e[0]);
65
- const o = N.flatMap((s) => _(s));
66
- t.setExposedModels(o);
67
- const r = typeof n == "object" && n.cachePath ? n.cachePath : M.resolve(process.cwd(), "node_modules", ".cache", "moonflower"), g = await D(T, {
68
- incremental: n !== !1,
60
+ return { explicitRouters: M, discoveredRouterFiles: C, allSourceFiles: w };
61
+ })(), e = u.reduce(
62
+ (s, N) => s.some((p) => p.fileName === N.fileName) ? s : s.concat(N),
63
+ g
64
+ ), o = T.flatMap((s) => G(s)).filter((s) => !!s);
65
+ o.length > 0 && o[0] && t.setHeader(o[0]);
66
+ const i = T.flatMap((s) => J(s));
67
+ t.setExposedModels(i);
68
+ const r = typeof m == "object" && m.cachePath ? m.cachePath : k.resolve(process.cwd(), "node_modules", ".cache", "moonflower"), y = await Y(e, {
69
+ incremental: m !== !1,
69
70
  cachePath: r,
70
71
  timestampCache: {},
71
72
  profiling: f,
72
- tsconfigPath: M.resolve(i)
73
+ tsconfigPath: k.resolve(n)
73
74
  });
74
75
  t.setStats({
75
- discoveredRouterFiles: F.map((s) => ({
76
+ discoveredRouterFiles: g.map((s) => ({
76
77
  path: s.fileName,
77
- routers: s.routers.named.map((y) => ({
78
- name: y,
79
- endpoints: g.filter((l) => l.sourceFilePath === s.fileName).map((l) => `${l.method.toUpperCase()} ${l.path}`)
78
+ routers: s.routers.named.map((N) => ({
79
+ name: N,
80
+ endpoints: y.filter((p) => p.sourceFilePath === s.fileName).map((p) => `${p.method.toUpperCase()} ${p.path}`)
80
81
  }))
81
82
  })),
82
- explicitRouterFiles: h.map((s) => ({
83
+ explicitRouterFiles: u.map((s) => ({
83
84
  path: s.fileName,
84
- routers: s.routers.named.map((y) => ({
85
- name: y,
86
- endpoints: g.filter((l) => l.sourceFilePath === s.fileName).map((l) => `${l.method.toUpperCase()} ${l.path}`)
85
+ routers: s.routers.named.map((N) => ({
86
+ name: N,
87
+ endpoints: y.filter((p) => p.sourceFilePath === s.fileName).map((p) => `${p.method.toUpperCase()} ${p.path}`)
87
88
  }))
88
89
  }))
89
- }), t.setEndpoints(g), t.markAsReady();
90
- }, D = async (m, i, p) => {
91
- const c = i.profiling ?? "stats", n = performance.now(), f = [], t = [];
92
- for (const e of m) {
93
- const o = b(e.sourceFile, i.timestampCache), r = i.incremental ? P.getCachedResults(e.sourceFile, o, i.cachePath) : null;
94
- r ? (u.debug(`[${e.fileName}] Found cached results`), f.push({ endpoints: r.endpoints, fileName: e.fileName, timing: 0, endpointTimings: [] })) : t.push({ file: e, timestamp: o });
90
+ }), t.setEndpoints(y), t.markAsReady();
91
+ }, Y = async (a, n, d) => {
92
+ const l = n.profiling ?? "stats", m = performance.now(), f = [], t = [], c = performance.now(), u = /* @__PURE__ */ new Map();
93
+ for (const e of a) {
94
+ const o = b(e.sourceFile, n.timestampCache, u), i = n.incremental ? R.getCachedResults(e.sourceFile, o, n.cachePath) : null;
95
+ i ? (F.debug(`[${e.fileName}] Found cached results`), f.push({ endpoints: i.endpoints, fileName: e.fileName, timing: 0, endpointTimings: [] })) : t.push({ file: e, timestamp: o });
95
96
  }
96
- if (t.length === 0)
97
- return c !== "off" && u.info(`Router analysis took ${Math.round(performance.now() - n)}ms`), f.flatMap((e) => e.endpoints);
98
- const a = t.map(({ file: e }) => ({
99
- fileName: e.fileName,
100
- task: {
101
- taskId: $.randomUUID(),
102
- tsconfigPath: i.tsconfigPath,
103
- sourceFilePath: e.sourceFile.getFilePath(),
104
- routerNames: e.routers.named,
105
- filterEndpointPaths: p
106
- }
107
- })), h = new I(B(), a.length);
108
- let F;
109
- try {
110
- F = await h.runAll(a.map((e) => e.task));
111
- } finally {
112
- h.terminate();
113
- }
114
- const N = /* @__PURE__ */ new Map();
97
+ if (l !== "off" && F.info(`Cache freshness check took ${Math.round(performance.now() - c)}ms`), t.length === 0)
98
+ return l !== "off" && F.info(`Router analysis took ${Math.round(performance.now() - m)}ms`), f.flatMap((e) => e.endpoints);
99
+ const g = /* @__PURE__ */ new Map();
115
100
  for (const { file: e } of t)
116
- N.set(e.fileName, { endpoints: [], fileName: e.fileName, timing: 0, endpointTimings: [] });
117
- for (let e = 0; e < F.length; e++) {
118
- const o = F[e], r = a[e].fileName, g = N.get(r);
119
- if ("error" in o) {
120
- u.error(`[${r}] Worker error: ${o.error}`);
121
- continue;
101
+ g.set(e.fileName, { endpoints: [], fileName: e.fileName, timing: 0, endpointTimings: [] });
102
+ if (t.length <= V)
103
+ for (const { file: e } of t) {
104
+ const { endpoints: o, endpointTimings: i } = q(e), r = g.get(e.fileName);
105
+ r.endpoints = o, r.endpointTimings = i, r.timing = i.reduce((y, s) => y + s.timing, 0);
106
+ }
107
+ else {
108
+ const e = t.map(({ file: r }) => ({
109
+ fileName: r.fileName,
110
+ task: {
111
+ taskId: $.randomUUID(),
112
+ tsconfigPath: n.tsconfigPath,
113
+ sourceFilePath: r.sourceFile.getFilePath(),
114
+ routerNames: r.routers.named,
115
+ filterEndpointPaths: d
116
+ }
117
+ })), o = new D(B(), e.length);
118
+ let i;
119
+ try {
120
+ i = await o.runAll(e.map((r) => r.task));
121
+ } finally {
122
+ o.terminate();
123
+ }
124
+ for (let r = 0; r < i.length; r++) {
125
+ const y = i[r], s = e[r].fileName, N = g.get(s);
126
+ if ("error" in y) {
127
+ F.error(`[${s}] Worker error: ${y.error}`);
128
+ continue;
129
+ }
130
+ N.endpoints = y.endpoints, N.endpointTimings = y.endpointTimings, N.timing = y.endpointTimings.reduce((p, M) => p + M.timing, 0);
122
131
  }
123
- g.endpoints = o.endpoints, g.endpointTimings = o.endpointTimings, g.timing = o.endpointTimings.reduce((s, y) => s + y.timing, 0);
124
132
  }
125
133
  for (const { file: e, timestamp: o } of t) {
126
- const r = N.get(e.fileName);
127
- r.endpoints.length > 0 && P.cacheResults(e.sourceFile, o, i.cachePath, r.endpoints);
134
+ const i = g.get(e.fileName);
135
+ i.endpoints.length > 0 && R.cacheResults(e.sourceFile, o, n.cachePath, i.endpoints);
128
136
  }
129
- const T = [...f, ...Array.from(N.values())];
130
- return c !== "off" && u.info(`Router analysis took ${Math.round(performance.now() - n)}ms`), c === "stats" ? T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing })).sort((e, o) => o.timeTaken - e.timeTaken).filter((e) => e.timeTaken > 500).forEach((e) => {
131
- u.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`);
132
- }) : c === "debug" && T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing, endpointTimings: e.endpointTimings })).sort((e, o) => o.timeTaken - e.timeTaken).forEach((e) => {
133
- u.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`), e.endpointTimings.sort((o, r) => r.timing - o.timing).forEach((o) => {
134
- u.info(` - ${o.method} ${o.path} (${Math.round(o.timing)}ms)`), o.sectionTimings.filter((r) => r.timing >= 1).sort((r, g) => g.timing - r.timing).forEach((r) => {
135
- u.info(` - ${r.section}: ${Math.round(r.timing)}ms`);
137
+ const T = [...f, ...Array.from(g.values())];
138
+ return l !== "off" && F.info(`Router analysis took ${Math.round(performance.now() - m)}ms`), l === "stats" ? T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing })).sort((e, o) => o.timeTaken - e.timeTaken).filter((e) => e.timeTaken > 500).forEach((e) => {
139
+ F.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`);
140
+ }) : l === "debug" && T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing, endpointTimings: e.endpointTimings })).sort((e, o) => o.timeTaken - e.timeTaken).forEach((e) => {
141
+ F.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`), e.endpointTimings.sort((o, i) => i.timing - o.timing).forEach((o) => {
142
+ F.info(` - ${o.method} ${o.path} (${Math.round(o.timing)}ms)`), o.sectionTimings.filter((i) => i.timing >= 1).sort((i, r) => r.timing - i.timing).forEach((i) => {
143
+ F.info(` - ${i.section}: ${Math.round(i.timing)}ms`);
136
144
  });
137
145
  });
138
146
  }), T.flatMap((e) => e.endpoints);
139
- }, V = (m) => {
140
- const i = E({
141
- sourceFile: m,
147
+ }, q = (a, n) => {
148
+ const d = [], l = [], f = ["get", "post", "put", "delete", "del", "patch"].join("|");
149
+ return a.routers.named.forEach((t) => {
150
+ const c = new RegExp(`${t}\\.(?:${f})`);
151
+ a.sourceFile.forEachChild((u) => {
152
+ const g = u.getText();
153
+ if (c.test(g)) {
154
+ W(u);
155
+ const T = performance.now(), { endpoint: e, sectionTimings: o } = H(u, a.fileName);
156
+ l.push({
157
+ method: e.method,
158
+ path: e.path,
159
+ timing: performance.now() - T,
160
+ sectionTimings: o
161
+ }), d.push(e);
162
+ }
163
+ });
164
+ }), { endpoints: d, endpointTimings: l };
165
+ }, G = (a) => {
166
+ const n = P({
167
+ sourceFile: a,
142
168
  originalName: "useApiHeader"
143
169
  });
144
- if (!i)
170
+ if (!n)
145
171
  return null;
146
- const p = m.forEachChildAsArray().filter((t) => t.isKind(x.ExpressionStatement)).find((t) => i && t.getText().startsWith(i));
147
- if (!p)
172
+ const d = a.forEachChildAsArray().filter((t) => t.isKind(x.ExpressionStatement)).find((t) => n && t.getText().startsWith(n));
173
+ if (!d)
148
174
  return null;
149
- const c = p.getFirstDescendantByKindOrThrow(x.ObjectLiteralExpression), n = W(c), f = (t) => typeof t == "string" || Array.isArray(t) && t.every((a) => typeof a == "string") ? t : t.reduce((a, h) => typeof h == "string" ? a : {
150
- ...a,
151
- [h.identifier]: f(h.value)
175
+ const l = d.getFirstDescendantByKindOrThrow(x.ObjectLiteralExpression), m = I(l), f = (t) => typeof t == "string" || Array.isArray(t) && t.every((c) => typeof c == "string") ? t : t.reduce((c, u) => typeof u == "string" ? c : {
176
+ ...c,
177
+ [u.identifier]: f(u.value)
152
178
  }, {});
153
- return f(n);
154
- }, _ = (m) => {
155
- const i = [], p = E({
156
- sourceFile: m,
179
+ return f(m);
180
+ }, J = (a) => {
181
+ const n = [], d = P({
182
+ sourceFile: a,
157
183
  originalName: "useExposeApiModel"
158
- }), c = E({
159
- sourceFile: m,
184
+ }), l = P({
185
+ sourceFile: a,
160
186
  originalName: "useExposeNamedApiModels"
161
187
  });
162
- return m.forEachChildAsArray().filter((n) => n.isKind(x.ExpressionStatement)).map((n) => {
163
- if (p && n.getText().startsWith(p)) {
164
- const a = (n.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
165
- if (!a)
188
+ return a.forEachChildAsArray().filter((m) => m.isKind(x.ExpressionStatement)).map((m) => {
189
+ if (d && m.getText().startsWith(d)) {
190
+ const c = (m.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
191
+ if (!c)
166
192
  return;
167
- i.push(K(a));
193
+ n.push(K(c));
168
194
  return;
169
195
  }
170
- if (c && n.getText().startsWith(c)) {
171
- const a = (n.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
172
- if (!a)
196
+ if (l && m.getText().startsWith(l)) {
197
+ const c = (m.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
198
+ if (!c)
173
199
  return;
174
- H(a).forEach((F) => i.push(F));
200
+ _(c).forEach((g) => n.push(g));
175
201
  }
176
- }), i;
202
+ }), n;
177
203
  };
178
204
  export {
179
- D as analyzeMultipleSourceFiles,
180
- V as analyzeSourceFileApiHeader,
181
- _ as analyzeSourceFileExposedModels,
182
- ae as prepareOpenApiSpec
205
+ Y as analyzeMultipleSourceFiles,
206
+ G as analyzeSourceFileApiHeader,
207
+ q as analyzeSourceFileEndpoints,
208
+ J as analyzeSourceFileExposedModels,
209
+ de as prepareOpenApiSpec
183
210
  };
184
211
  //# sourceMappingURL=analyzerModule.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzerModule.mjs","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"sourcesContent":["import crypto from 'crypto'\nimport { existsSync } from 'fs'\nimport * as path from 'path'\nimport { fileURLToPath } from 'url'\n\nfunction resolveWorkerUrl(): URL {\n\tconst candidates = ['./analyzerWorker.mjs', './analyzerWorker.test.mjs']\n\tfor (const candidate of candidates) {\n\t\tconst url = new URL(candidate, import.meta.url)\n\t\tif (existsSync(fileURLToPath(url))) {\n\t\t\treturn url\n\t\t}\n\t}\n\tthrow new Error(\n\t\t'analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).',\n\t)\n}\nimport { SourceFile, SyntaxKind } from 'ts-morph'\nimport { Project } from 'ts-morph'\n\nimport { Logger } from '../../utils/logger'\nimport { discoverImportedName } from '../discoveryModule/discoverImports/discoverImports'\nimport {\n\tDiscoveredSourceFile,\n\tdiscoverRouterFiles,\n} from '../discoveryModule/discoverRouterFiles/discoverRouterFiles'\nimport { discoverRouters } from '../discoveryModule/discoverRouters/discoverRouters'\nimport { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'\nimport { EndpointData, ExposedModelData } from '../types'\nimport { getSourceFileTimestamp, TimestampCache } from './getSourceFileTimestamp'\nimport { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint, SectionTiming } from './parseEndpoint'\nimport { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'\nimport { SourceFileCache } from './sourceFileCache'\nimport { WorkerPool, WorkerResult, WorkerTask } from './workerPool'\n\ntype Props = {\n\tlogLevel?: Parameters<(typeof Logger)['setLevel']>[0]\n\ttsconfigPath: string\n\tsourceFilePaths?: string[]\n\tsourceFileDiscovery?: boolean | FileDiscoveryConfig\n\tincremental?:\n\t\t| boolean\n\t\t| {\n\t\t\t\tcachePath: string\n\t\t }\n\tprofiling?: 'stats' | 'off' | 'debug'\n}\n\ntype FileDiscoveryConfig = {\n\trootPath: string\n}\n\ntype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\n\n/**\n * @param tsconfigPath Path to tsconfig file relative to project root\n * @param sourceFilePaths Array of router source files relative to project root\n */\nexport const prepareOpenApiSpec = async ({\n\tlogLevel,\n\ttsconfigPath,\n\tsourceFilePaths,\n\tsourceFileDiscovery,\n\tincremental,\n\tprofiling = 'stats',\n}: Props): Promise<void> => {\n\tconst openApiManager = OpenApiManager.getInstance()\n\n\tif (openApiManager.isReady()) {\n\t\treturn\n\t}\n\n\tif (logLevel) {\n\t\tLogger.setLevel(logLevel)\n\t}\n\n\tLogger.info('Preparing OpenAPI spec')\n\n\tconst project = new Project({\n\t\ttsConfigFilePath: path.resolve(tsconfigPath),\n\t\tskipFileDependencyResolution: true,\n\t})\n\n\tconst { explicitRouters, discoveredRouterFiles, allSourceFiles } = (() => {\n\t\tconst sourceFilesToAdd = sourceFilePaths ?? []\n\t\tconst resolvedSourceFilePaths = sourceFilesToAdd.map((filepath) => path.resolve(filepath))\n\t\tconst sourceFiles = resolvedSourceFilePaths.map((filePath) => project.getSourceFileOrThrow(filePath))\n\t\tconst explicitRouters = sourceFiles.flatMap((file) => ({\n\t\t\tfileName: file.getFilePath(),\n\t\t\tsourceFile: file,\n\t\t\trouters: discoverRouters(file),\n\t\t}))\n\n\t\tconst { discoveredRouterFiles, discoveredSourceFiles } = (() => {\n\t\t\tif (sourceFileDiscovery === false) {\n\t\t\t\treturn { discoveredRouterFiles: [], discoveredSourceFiles: [] }\n\t\t\t}\n\n\t\t\tconst startTime = performance.now()\n\t\t\tconst files = discoverRouterFiles({\n\t\t\t\ttargetPath: typeof sourceFileDiscovery === 'object' ? sourceFileDiscovery.rootPath : '.',\n\t\t\t\ttsConfigPath: tsconfigPath,\n\t\t\t})\n\t\t\tif (profiling !== 'off') {\n\t\t\t\tLogger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)\n\t\t\t}\n\t\t\treturn files\n\t\t})()\n\n\t\tconst allSourceFiles = sourceFiles.reduce(\n\t\t\t(acc, current) =>\n\t\t\t\tacc.some((r) => r.getFilePath() === current.getFilePath()) ? acc : acc.concat(current),\n\t\t\tdiscoveredSourceFiles,\n\t\t)\n\n\t\treturn { explicitRouters, discoveredRouterFiles, allSourceFiles }\n\t})()\n\n\tconst filesToAnalyze = explicitRouters.reduce(\n\t\t(acc, current) => (acc.some((r) => r.fileName === current.fileName) ? acc : acc.concat(current)),\n\t\tdiscoveredRouterFiles,\n\t)\n\n\tconst apiHeaders = allSourceFiles\n\t\t.flatMap((file) => analyzeSourceFileApiHeader(file))\n\t\t.filter((headers) => !!headers)\n\tif (apiHeaders.length > 0 && apiHeaders[0]) {\n\t\topenApiManager.setHeader(apiHeaders[0])\n\t}\n\n\tconst exposedModels = allSourceFiles.flatMap((file) => analyzeSourceFileExposedModels(file))\n\n\topenApiManager.setExposedModels(exposedModels)\n\n\tconst cachePath = (() => {\n\t\tif (typeof incremental === 'object' && incremental.cachePath) {\n\t\t\treturn incremental.cachePath\n\t\t}\n\t\treturn path.resolve(process.cwd(), 'node_modules', '.cache', 'moonflower')\n\t})()\n\tconst endpoints = await analyzeMultipleSourceFiles(filesToAnalyze, {\n\t\tincremental: incremental !== false,\n\t\tcachePath,\n\t\ttimestampCache: {},\n\t\tprofiling,\n\t\ttsconfigPath: path.resolve(tsconfigPath),\n\t})\n\n\topenApiManager.setStats({\n\t\tdiscoveredRouterFiles: discoveredRouterFiles.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t\texplicitRouterFiles: explicitRouters.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t})\n\n\topenApiManager.setEndpoints(endpoints)\n\topenApiManager.markAsReady()\n}\n\nexport const analyzeMultipleSourceFiles = async (\n\tfiles: DiscoveredSourceFile[],\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t\ttsconfigPath: string\n\t},\n\tfilterEndpointPaths?: string[],\n): Promise<EndpointData[]> => {\n\tconst profiling = config.profiling ?? 'stats'\n\tconst startTime = performance.now()\n\n\t// Separate cached files from those needing analysis\n\ttype CachedFile = { endpoints: EndpointData[]; fileName: string; timing: 0; endpointTimings: [] }\n\ttype UncachedFile = { file: DiscoveredSourceFile; timestamp: number }\n\n\tconst cached: CachedFile[] = []\n\tconst uncached: UncachedFile[] = []\n\n\tfor (const file of files) {\n\t\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\t\tconst hit = config.incremental\n\t\t\t? SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\t\t\t: null\n\t\tif (hit) {\n\t\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\t\tcached.push({ endpoints: hit.endpoints, fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t\t} else {\n\t\t\tuncached.push({ file, timestamp })\n\t\t}\n\t}\n\n\tif (uncached.length === 0) {\n\t\tif (profiling !== 'off') {\n\t\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t\t}\n\t\treturn cached.flatMap((f) => f.endpoints)\n\t}\n\n\t// Build one task per uncached file. Each worker analyzes a whole file in a single pass, so it\n\t// pays the ts-morph Project cold-start (full TS program load + type-checker warmup) once and\n\t// reuses the warmed-up checker for every endpoint in that file.\n\ttype FileTask = { task: WorkerTask; fileName: string }\n\tconst allTasks: FileTask[] = uncached.map(({ file }) => ({\n\t\tfileName: file.fileName,\n\t\ttask: {\n\t\t\ttaskId: crypto.randomUUID(),\n\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\trouterNames: file.routers.named,\n\t\t\tfilterEndpointPaths,\n\t\t},\n\t}))\n\n\t// Dispatch all tasks to the worker pool, capped at one worker per file.\n\tconst pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)\n\n\ttype FileResult = {\n\t\tendpoints: EndpointData[]\n\t\tfileName: string\n\t\ttiming: number\n\t\tendpointTimings: EndpointTiming[]\n\t}\n\n\tlet results: WorkerResult[]\n\ttry {\n\t\tresults = await pool.runAll(allTasks.map((ft) => ft.task))\n\t} finally {\n\t\tpool.terminate()\n\t}\n\n\t// Each result maps 1:1 to a file task.\n\tconst byFile = new Map<string, FileResult>()\n\tfor (const { file } of uncached) {\n\t\tbyFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t}\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst result = results[i]\n\t\tconst fileName = allTasks[i].fileName\n\t\tconst fileResult = byFile.get(fileName)!\n\n\t\tif ('error' in result) {\n\t\t\tLogger.error(`[${fileName}] Worker error: ${result.error}`)\n\t\t\tcontinue\n\t\t}\n\n\t\tfileResult.endpoints = result.endpoints\n\t\tfileResult.endpointTimings = result.endpointTimings\n\t\tfileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t}\n\n\t// Write cache for each uncached file\n\tfor (const { file, timestamp } of uncached) {\n\t\tconst fileResult = byFile.get(file.fileName)!\n\t\tif (fileResult.endpoints.length > 0) {\n\t\t\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, fileResult.endpoints)\n\t\t}\n\t}\n\n\tconst analyzedFiles = [...cached, ...Array.from(byFile.values())]\n\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t}\n\n\tif (profiling === 'stats') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.filter((t) => t.timeTaken > 500)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t})\n\t} else if (profiling === 'debug') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing, endpointTimings: f.endpointTimings }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t\tt.endpointTimings\n\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t.forEach((ep) => {\n\t\t\t\t\t\tLogger.info(` - ${ep.method} ${ep.path} (${Math.round(ep.timing)}ms)`)\n\t\t\t\t\t\tep.sectionTimings\n\t\t\t\t\t\t\t.filter((s) => s.timing >= 1)\n\t\t\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t\t\t.forEach((s) => {\n\t\t\t\t\t\t\t\tLogger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t})\n\t}\n\n\treturn analyzedFiles.flatMap((f) => f.endpoints)\n}\n\nexport const analyzeSourceFileWithCache = (\n\tfile: DiscoveredSourceFile,\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t},\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; timing: number; endpointTimings: EndpointTiming[] } => {\n\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\tconst cachedResults = SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\n\tif (cachedResults) {\n\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\treturn { endpoints: cachedResults.endpoints, timing: 0, endpointTimings: [] }\n\t}\n\tLogger.debug(`[${file.fileName}] Analyzing...`)\n\n\tconst t1 = performance.now()\n\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\tconst t2 = performance.now()\n\tLogger.debug(`[${file.fileName}] Analyzed in ${t2 - t1}ms`)\n\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, endpoints)\n\treturn { endpoints, timing: t2 - t1, endpointTimings }\n}\n\nexport const analyzeSourceFileEndpoints = (\n\tfile: DiscoveredSourceFile,\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; endpointTimings: EndpointTiming[] } => {\n\tconst endpoints: EndpointData[] = []\n\tconst endpointTimings: EndpointTiming[] = []\n\tconst operations = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst joinedOperations = operations.join('|')\n\n\tfile.routers.named.forEach((routerName) => {\n\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${joinedOperations})`)\n\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\tconst nodeText = node.getText()\n\n\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\n\t\t\t\tif (filterEndpointPaths && !filterEndpointPaths.some((path) => endpointPath.includes(path))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, file.fileName)\n\t\t\t\tendpointTimings.push({\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\tpath: endpoint.path,\n\t\t\t\t\ttiming: performance.now() - t1,\n\t\t\t\t\tsectionTimings,\n\t\t\t\t})\n\t\t\t\tendpoints.push(endpoint)\n\t\t\t}\n\t\t})\n\t})\n\n\treturn { endpoints, endpointTimings }\n}\n\nexport const analyzeSourceFileApiHeader = (sourceFile: SourceFile): ApiDocsHeader | null => {\n\tconst nameOfUseApiHeader = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useApiHeader',\n\t})\n\n\tif (!nameOfUseApiHeader) {\n\t\treturn null\n\t}\n\n\tconst node = sourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.find((node) => nameOfUseApiHeader && node.getText().startsWith(nameOfUseApiHeader))\n\n\tif (!node) {\n\t\treturn null\n\t}\n\n\tconst targetNode = node.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression)\n\tconst values = getValuesOfObjectLiteral(targetNode)\n\n\tconst collapseObject = (v: string | string[] | typeof values): any => {\n\t\tif (typeof v === 'string') {\n\t\t\treturn v\n\t\t}\n\t\tif (Array.isArray(v) && v.every((value) => typeof value === 'string')) {\n\t\t\treturn v\n\t\t}\n\n\t\treturn v.reduce((acc, current) => {\n\t\t\tif (typeof current === 'string') {\n\t\t\t\treturn acc\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t[current.identifier]: collapseObject(current.value as string[]),\n\t\t\t}\n\t\t}, {})\n\t}\n\treturn collapseObject(values)\n}\n\nexport const analyzeSourceFileExposedModels = (sourceFile: SourceFile): ExposedModelData[] => {\n\tconst models: ExposedModelData[] = []\n\n\tconst nameOfUseExposeApiModel = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeApiModel',\n\t})\n\n\tconst nameOfUseExposeNamedApiModels = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeNamedApiModels',\n\t})\n\n\tsourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.map((node) => {\n\t\t\tif (nameOfUseExposeApiModel && node.getText().startsWith(nameOfUseExposeApiModel)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tmodels.push(parseExposedModel(firstChild))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (nameOfUseExposeNamedApiModels && node.getText().startsWith(nameOfUseExposeNamedApiModels)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst parsedModels = parseNamedExposedModels(firstChild)\n\t\t\t\tparsedModels.forEach((model) => models.push(model))\n\t\t\t}\n\t\t})\n\treturn models\n}\n"],"names":["resolveWorkerUrl","candidates","candidate","url","existsSync","fileURLToPath","prepareOpenApiSpec","logLevel","tsconfigPath","sourceFilePaths","sourceFileDiscovery","incremental","profiling","openApiManager","OpenApiManager","Logger","project","Project","path","explicitRouters","discoveredRouterFiles","allSourceFiles","sourceFiles","filepath","filePath","file","discoverRouters","discoveredSourceFiles","startTime","files","discoverRouterFiles","acc","current","r","filesToAnalyze","apiHeaders","analyzeSourceFileApiHeader","headers","exposedModels","analyzeSourceFileExposedModels","cachePath","endpoints","analyzeMultipleSourceFiles","e","config","filterEndpointPaths","cached","uncached","timestamp","getSourceFileTimestamp","hit","SourceFileCache","f","allTasks","crypto","pool","WorkerPool","results","ft","byFile","i","result","fileName","fileResult","sum","t","analyzedFiles","a","b","ep","s","sourceFile","nameOfUseApiHeader","discoverImportedName","node","SyntaxKind","targetNode","values","getValuesOfObjectLiteral","collapseObject","v","value","models","nameOfUseExposeApiModel","nameOfUseExposeNamedApiModels","firstChild","parseExposedModel","parseNamedExposedModels","model"],"mappings":"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"}
1
+ {"version":3,"file":"analyzerModule.mjs","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"sourcesContent":["import crypto from 'crypto'\nimport { existsSync } from 'fs'\nimport * as path from 'path'\nimport { fileURLToPath } from 'url'\n\nfunction resolveWorkerUrl(): URL {\n\tconst candidates = ['./analyzerWorker.mjs', './analyzerWorker.test.mjs']\n\tfor (const candidate of candidates) {\n\t\tconst url = new URL(candidate, import.meta.url)\n\t\tif (existsSync(fileURLToPath(url))) {\n\t\t\treturn url\n\t\t}\n\t}\n\tthrow new Error(\n\t\t'analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).',\n\t)\n}\nimport { SourceFile, SyntaxKind } from 'ts-morph'\nimport { Project } from 'ts-morph'\n\nimport { Logger } from '../../utils/logger'\nimport { discoverImportedName } from '../discoveryModule/discoverImports/discoverImports'\nimport {\n\tDiscoveredSourceFile,\n\tdiscoverRouterFiles,\n} from '../discoveryModule/discoverRouterFiles/discoverRouterFiles'\nimport { discoverRouters } from '../discoveryModule/discoverRouters/discoverRouters'\nimport { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'\nimport { EndpointData, ExposedModelData } from '../types'\nimport { getSourceFileTimestamp, MtimeCache, TimestampCache } from './getSourceFileTimestamp'\nimport { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint, SectionTiming } from './parseEndpoint'\nimport { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'\nimport { SourceFileCache } from './sourceFileCache'\nimport { WorkerPool, WorkerResult, WorkerTask } from './workerPool'\n\ntype Props = {\n\tlogLevel?: Parameters<(typeof Logger)['setLevel']>[0]\n\ttsconfigPath: string\n\tsourceFilePaths?: string[]\n\tsourceFileDiscovery?: boolean | FileDiscoveryConfig\n\tincremental?:\n\t\t| boolean\n\t\t| {\n\t\t\t\tcachePath: string\n\t\t }\n\tprofiling?: 'stats' | 'off' | 'debug'\n}\n\ntype FileDiscoveryConfig = {\n\trootPath: string\n}\n\ntype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\n\n/**\n * Number of uncached files at or below which analysis runs inline on the (already-warm) main-thread\n * Project instead of fanning out to worker threads. Each worker pays a multi-second cold-start to\n * build its own Project, so parallelism only wins once there are several files to share that cost.\n */\nconst INLINE_ANALYSIS_FILE_THRESHOLD = 2\n\n/**\n * @param tsconfigPath Path to tsconfig file relative to project root\n * @param sourceFilePaths Array of router source files relative to project root\n */\nexport const prepareOpenApiSpec = async ({\n\tlogLevel,\n\ttsconfigPath,\n\tsourceFilePaths,\n\tsourceFileDiscovery,\n\tincremental,\n\tprofiling = 'stats',\n}: Props): Promise<void> => {\n\tconst openApiManager = OpenApiManager.getInstance()\n\n\tif (openApiManager.isReady()) {\n\t\treturn\n\t}\n\n\tif (logLevel) {\n\t\tLogger.setLevel(logLevel)\n\t}\n\n\tLogger.info('Preparing OpenAPI spec')\n\n\tconst project = new Project({\n\t\ttsConfigFilePath: path.resolve(tsconfigPath),\n\t\tskipFileDependencyResolution: true,\n\t})\n\n\tconst { explicitRouters, discoveredRouterFiles, allSourceFiles } = (() => {\n\t\tconst sourceFilesToAdd = sourceFilePaths ?? []\n\t\tconst resolvedSourceFilePaths = sourceFilesToAdd.map((filepath) => path.resolve(filepath))\n\t\tconst sourceFiles = resolvedSourceFilePaths.map((filePath) => project.getSourceFileOrThrow(filePath))\n\t\tconst explicitRouters = sourceFiles.flatMap((file) => ({\n\t\t\tfileName: file.getFilePath(),\n\t\t\tsourceFile: file,\n\t\t\trouters: discoverRouters(file),\n\t\t}))\n\n\t\tconst { discoveredRouterFiles, discoveredSourceFiles } = (() => {\n\t\t\tif (sourceFileDiscovery === false) {\n\t\t\t\treturn { discoveredRouterFiles: [], discoveredSourceFiles: [] }\n\t\t\t}\n\n\t\t\tconst startTime = performance.now()\n\t\t\tconst files = discoverRouterFiles({\n\t\t\t\ttargetPath: typeof sourceFileDiscovery === 'object' ? sourceFileDiscovery.rootPath : '.',\n\t\t\t\ttsConfigPath: tsconfigPath,\n\t\t\t})\n\t\t\tif (profiling !== 'off') {\n\t\t\t\tLogger.info(`File discovery took ${Math.round(performance.now() - startTime)}ms`)\n\t\t\t}\n\t\t\treturn files\n\t\t})()\n\n\t\tconst allSourceFiles = sourceFiles.reduce(\n\t\t\t(acc, current) =>\n\t\t\t\tacc.some((r) => r.getFilePath() === current.getFilePath()) ? acc : acc.concat(current),\n\t\t\tdiscoveredSourceFiles,\n\t\t)\n\n\t\treturn { explicitRouters, discoveredRouterFiles, allSourceFiles }\n\t})()\n\n\tconst filesToAnalyze = explicitRouters.reduce(\n\t\t(acc, current) => (acc.some((r) => r.fileName === current.fileName) ? acc : acc.concat(current)),\n\t\tdiscoveredRouterFiles,\n\t)\n\n\tconst apiHeaders = allSourceFiles\n\t\t.flatMap((file) => analyzeSourceFileApiHeader(file))\n\t\t.filter((headers) => !!headers)\n\tif (apiHeaders.length > 0 && apiHeaders[0]) {\n\t\topenApiManager.setHeader(apiHeaders[0])\n\t}\n\n\tconst exposedModels = allSourceFiles.flatMap((file) => analyzeSourceFileExposedModels(file))\n\n\topenApiManager.setExposedModels(exposedModels)\n\n\tconst cachePath = (() => {\n\t\tif (typeof incremental === 'object' && incremental.cachePath) {\n\t\t\treturn incremental.cachePath\n\t\t}\n\t\treturn path.resolve(process.cwd(), 'node_modules', '.cache', 'moonflower')\n\t})()\n\tconst endpoints = await analyzeMultipleSourceFiles(filesToAnalyze, {\n\t\tincremental: incremental !== false,\n\t\tcachePath,\n\t\ttimestampCache: {},\n\t\tprofiling,\n\t\ttsconfigPath: path.resolve(tsconfigPath),\n\t})\n\n\topenApiManager.setStats({\n\t\tdiscoveredRouterFiles: discoveredRouterFiles.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t\texplicitRouterFiles: explicitRouters.map((file) => ({\n\t\t\tpath: file.fileName,\n\t\t\trouters: file.routers.named.map((r) => ({\n\t\t\t\tname: r,\n\t\t\t\tendpoints: endpoints\n\t\t\t\t\t.filter((e) => e.sourceFilePath === file.fileName)\n\t\t\t\t\t.map((e) => `${e.method.toUpperCase()} ${e.path}`),\n\t\t\t})),\n\t\t})),\n\t})\n\n\topenApiManager.setEndpoints(endpoints)\n\topenApiManager.markAsReady()\n}\n\nexport const analyzeMultipleSourceFiles = async (\n\tfiles: DiscoveredSourceFile[],\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t\ttsconfigPath: string\n\t},\n\tfilterEndpointPaths?: string[],\n): Promise<EndpointData[]> => {\n\tconst profiling = config.profiling ?? 'stats'\n\tconst startTime = performance.now()\n\n\t// Separate cached files from those needing analysis\n\ttype CachedFile = { endpoints: EndpointData[]; fileName: string; timing: 0; endpointTimings: [] }\n\ttype UncachedFile = { file: DiscoveredSourceFile; timestamp: number }\n\n\tconst cached: CachedFile[] = []\n\tconst uncached: UncachedFile[] = []\n\n\tconst freshnessCheckStart = performance.now()\n\tconst mtimeCache: MtimeCache = new Map()\n\tfor (const file of files) {\n\t\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache, mtimeCache)\n\t\tconst hit = config.incremental\n\t\t\t? SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\t\t\t: null\n\t\tif (hit) {\n\t\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\t\tcached.push({ endpoints: hit.endpoints, fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t\t} else {\n\t\t\tuncached.push({ file, timestamp })\n\t\t}\n\t}\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Cache freshness check took ${Math.round(performance.now() - freshnessCheckStart)}ms`)\n\t}\n\n\tif (uncached.length === 0) {\n\t\tif (profiling !== 'off') {\n\t\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t\t}\n\t\treturn cached.flatMap((f) => f.endpoints)\n\t}\n\n\ttype FileResult = {\n\t\tendpoints: EndpointData[]\n\t\tfileName: string\n\t\ttiming: number\n\t\tendpointTimings: EndpointTiming[]\n\t}\n\n\tconst byFile = new Map<string, FileResult>()\n\tfor (const { file } of uncached) {\n\t\tbyFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })\n\t}\n\n\t// The caller (prepareOpenApiSpec) already built and warmed a ts-morph Project on this thread, so\n\t// inline analysis runs against a hot type-checker. A worker, by contrast, must spawn a thread and\n\t// build its own Project from scratch — a multi-second cold-start. That cold-start only pays off\n\t// when there are enough uncached files that spreading the parse work across workers beats it; below\n\t// the threshold (e.g. the common single-file incremental rebuild), inline is strictly faster.\n\tif (uncached.length <= INLINE_ANALYSIS_FILE_THRESHOLD) {\n\t\tfor (const { file } of uncached) {\n\t\t\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\t\t\tconst fileResult = byFile.get(file.fileName)!\n\t\t\tfileResult.endpoints = endpoints\n\t\t\tfileResult.endpointTimings = endpointTimings\n\t\t\tfileResult.timing = endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t\t}\n\t} else {\n\t\t// One task per file: each worker analyzes a whole file in a single pass, paying its Project\n\t\t// cold-start once and reusing the warmed-up checker for every endpoint in that file. Cap the\n\t\t// pool at one worker per file so we never spin up a worker with nothing to do.\n\t\ttype FileTask = { task: WorkerTask; fileName: string }\n\t\tconst allTasks: FileTask[] = uncached.map(({ file }) => ({\n\t\t\tfileName: file.fileName,\n\t\t\ttask: {\n\t\t\t\ttaskId: crypto.randomUUID(),\n\t\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\t\trouterNames: file.routers.named,\n\t\t\t\tfilterEndpointPaths,\n\t\t\t},\n\t\t}))\n\n\t\tconst pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)\n\t\tlet results: WorkerResult[]\n\t\ttry {\n\t\t\tresults = await pool.runAll(allTasks.map((ft) => ft.task))\n\t\t} finally {\n\t\t\tpool.terminate()\n\t\t}\n\n\t\t// Each result maps 1:1 to a file task.\n\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\tconst result = results[i]\n\t\t\tconst fileName = allTasks[i].fileName\n\t\t\tconst fileResult = byFile.get(fileName)!\n\n\t\t\tif ('error' in result) {\n\t\t\t\tLogger.error(`[${fileName}] Worker error: ${result.error}`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfileResult.endpoints = result.endpoints\n\t\t\tfileResult.endpointTimings = result.endpointTimings\n\t\t\tfileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)\n\t\t}\n\t}\n\n\t// Write cache for each uncached file\n\tfor (const { file, timestamp } of uncached) {\n\t\tconst fileResult = byFile.get(file.fileName)!\n\t\tif (fileResult.endpoints.length > 0) {\n\t\t\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, fileResult.endpoints)\n\t\t}\n\t}\n\n\tconst analyzedFiles = [...cached, ...Array.from(byFile.values())]\n\n\tif (profiling !== 'off') {\n\t\tLogger.info(`Router analysis took ${Math.round(performance.now() - startTime)}ms`)\n\t}\n\n\tif (profiling === 'stats') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.filter((t) => t.timeTaken > 500)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t})\n\t} else if (profiling === 'debug') {\n\t\tanalyzedFiles\n\t\t\t.map((f) => ({ fileName: f.fileName, timeTaken: f.timing, endpointTimings: f.endpointTimings }))\n\t\t\t.sort((a, b) => b.timeTaken - a.timeTaken)\n\t\t\t.forEach((t) => {\n\t\t\t\tLogger.info(`- [${t.fileName}] Took ${Math.round(t.timeTaken)}ms to analyze`)\n\t\t\t\tt.endpointTimings\n\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t.forEach((ep) => {\n\t\t\t\t\t\tLogger.info(` - ${ep.method} ${ep.path} (${Math.round(ep.timing)}ms)`)\n\t\t\t\t\t\tep.sectionTimings\n\t\t\t\t\t\t\t.filter((s) => s.timing >= 1)\n\t\t\t\t\t\t\t.sort((a, b) => b.timing - a.timing)\n\t\t\t\t\t\t\t.forEach((s) => {\n\t\t\t\t\t\t\t\tLogger.info(` - ${s.section}: ${Math.round(s.timing)}ms`)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t})\n\t}\n\n\treturn analyzedFiles.flatMap((f) => f.endpoints)\n}\n\nexport const analyzeSourceFileWithCache = (\n\tfile: DiscoveredSourceFile,\n\tconfig: {\n\t\tincremental: boolean\n\t\tcachePath: string\n\t\ttimestampCache: TimestampCache\n\t\tprofiling?: 'stats' | 'off' | 'debug'\n\t},\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; timing: number; endpointTimings: EndpointTiming[] } => {\n\tconst timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)\n\tconst cachedResults = SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)\n\n\tif (cachedResults) {\n\t\tLogger.debug(`[${file.fileName}] Found cached results`)\n\t\treturn { endpoints: cachedResults.endpoints, timing: 0, endpointTimings: [] }\n\t}\n\tLogger.debug(`[${file.fileName}] Analyzing...`)\n\n\tconst t1 = performance.now()\n\tconst { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)\n\tconst t2 = performance.now()\n\tLogger.debug(`[${file.fileName}] Analyzed in ${t2 - t1}ms`)\n\tSourceFileCache.cacheResults(file.sourceFile, timestamp, config.cachePath, endpoints)\n\treturn { endpoints, timing: t2 - t1, endpointTimings }\n}\n\nexport const analyzeSourceFileEndpoints = (\n\tfile: DiscoveredSourceFile,\n\tfilterEndpointPaths?: string[],\n): { endpoints: EndpointData[]; endpointTimings: EndpointTiming[] } => {\n\tconst endpoints: EndpointData[] = []\n\tconst endpointTimings: EndpointTiming[] = []\n\tconst operations = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst joinedOperations = operations.join('|')\n\n\tfile.routers.named.forEach((routerName) => {\n\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${joinedOperations})`)\n\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\tconst nodeText = node.getText()\n\n\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\n\t\t\t\tif (filterEndpointPaths && !filterEndpointPaths.some((path) => endpointPath.includes(path))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, file.fileName)\n\t\t\t\tendpointTimings.push({\n\t\t\t\t\tmethod: endpoint.method,\n\t\t\t\t\tpath: endpoint.path,\n\t\t\t\t\ttiming: performance.now() - t1,\n\t\t\t\t\tsectionTimings,\n\t\t\t\t})\n\t\t\t\tendpoints.push(endpoint)\n\t\t\t}\n\t\t})\n\t})\n\n\treturn { endpoints, endpointTimings }\n}\n\nexport const analyzeSourceFileApiHeader = (sourceFile: SourceFile): ApiDocsHeader | null => {\n\tconst nameOfUseApiHeader = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useApiHeader',\n\t})\n\n\tif (!nameOfUseApiHeader) {\n\t\treturn null\n\t}\n\n\tconst node = sourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.find((node) => nameOfUseApiHeader && node.getText().startsWith(nameOfUseApiHeader))\n\n\tif (!node) {\n\t\treturn null\n\t}\n\n\tconst targetNode = node.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression)\n\tconst values = getValuesOfObjectLiteral(targetNode)\n\n\tconst collapseObject = (v: string | string[] | typeof values): any => {\n\t\tif (typeof v === 'string') {\n\t\t\treturn v\n\t\t}\n\t\tif (Array.isArray(v) && v.every((value) => typeof value === 'string')) {\n\t\t\treturn v\n\t\t}\n\n\t\treturn v.reduce((acc, current) => {\n\t\t\tif (typeof current === 'string') {\n\t\t\t\treturn acc\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...acc,\n\t\t\t\t[current.identifier]: collapseObject(current.value as string[]),\n\t\t\t}\n\t\t}, {})\n\t}\n\treturn collapseObject(values)\n}\n\nexport const analyzeSourceFileExposedModels = (sourceFile: SourceFile): ExposedModelData[] => {\n\tconst models: ExposedModelData[] = []\n\n\tconst nameOfUseExposeApiModel = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeApiModel',\n\t})\n\n\tconst nameOfUseExposeNamedApiModels = discoverImportedName({\n\t\tsourceFile,\n\t\toriginalName: 'useExposeNamedApiModels',\n\t})\n\n\tsourceFile\n\t\t.forEachChildAsArray()\n\t\t.filter((node) => node.isKind(SyntaxKind.ExpressionStatement))\n\t\t.map((node) => {\n\t\t\tif (nameOfUseExposeApiModel && node.getText().startsWith(nameOfUseExposeApiModel)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tmodels.push(parseExposedModel(firstChild))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (nameOfUseExposeNamedApiModels && node.getText().startsWith(nameOfUseExposeNamedApiModels)) {\n\t\t\t\tconst callExpressionNode = node.getFirstChild()\n\t\t\t\tconst syntaxListChildren = callExpressionNode?.getChildrenOfKind(SyntaxKind.SyntaxList) || []\n\n\t\t\t\tconst firstChild = syntaxListChildren[0].getFirstChild()\n\t\t\t\tif (!firstChild) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst parsedModels = parseNamedExposedModels(firstChild)\n\t\t\t\tparsedModels.forEach((model) => models.push(model))\n\t\t\t}\n\t\t})\n\treturn models\n}\n"],"names":["resolveWorkerUrl","candidates","candidate","url","existsSync","fileURLToPath","INLINE_ANALYSIS_FILE_THRESHOLD","prepareOpenApiSpec","logLevel","tsconfigPath","sourceFilePaths","sourceFileDiscovery","incremental","profiling","openApiManager","OpenApiManager","Logger","project","Project","path","explicitRouters","discoveredRouterFiles","allSourceFiles","sourceFiles","filepath","filePath","file","discoverRouters","discoveredSourceFiles","startTime","files","discoverRouterFiles","acc","current","r","filesToAnalyze","apiHeaders","analyzeSourceFileApiHeader","headers","exposedModels","analyzeSourceFileExposedModels","cachePath","endpoints","analyzeMultipleSourceFiles","e","config","filterEndpointPaths","cached","uncached","freshnessCheckStart","mtimeCache","timestamp","getSourceFileTimestamp","hit","SourceFileCache","f","byFile","endpointTimings","analyzeSourceFileEndpoints","fileResult","sum","t","allTasks","crypto","pool","WorkerPool","results","ft","i","result","fileName","analyzedFiles","a","b","ep","s","joinedOperations","routerName","routerPattern","node","nodeText","resolveEndpointPath","t1","endpoint","sectionTimings","parseEndpoint","sourceFile","nameOfUseApiHeader","discoverImportedName","SyntaxKind","targetNode","values","getValuesOfObjectLiteral","collapseObject","v","value","models","nameOfUseExposeApiModel","nameOfUseExposeNamedApiModels","firstChild","parseExposedModel","parseNamedExposedModels","model"],"mappings":";;;;;;;;;;;;;;;;AAKA,SAASA,IAAwB;AAC1B,QAAAC,IAAa,CAAC,wBAAwB,2BAA2B;AACvE,aAAWC,KAAaD,GAAY;AACnC,UAAME,IAAM,IAAI,IAAID,GAAW,YAAY,GAAG;AAC9C,QAAIE,EAAWC,EAAcF,CAAG,CAAC;AACzB,aAAAA;AAAA,EACR;AAED,QAAM,IAAI;AAAA,IACT;AAAA,EACD;AACD;AA4CA,MAAMG,IAAiC,GAM1BC,KAAqB,OAAO;AAAA,EACxC,UAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,WAAAC,IAAY;AACb,MAA4B;AACrB,QAAAC,IAAiBC,EAAe,YAAY;AAE9C,MAAAD,EAAe;AAClB;AAGD,EAAIN,KACHQ,EAAO,SAASR,CAAQ,GAGzBQ,EAAO,KAAK,wBAAwB;AAE9B,QAAAC,IAAU,IAAIC,EAAQ;AAAA,IAC3B,kBAAkBC,EAAK,QAAQV,CAAY;AAAA,IAC3C,8BAA8B;AAAA,EAAA,CAC9B,GAEK,EAAE,iBAAAW,GAAiB,uBAAAC,GAAuB,gBAAAC,EAAA,KAAoB,MAAM;AAGnE,UAAAC,KAFmBb,KAAmB,CAAC,GACI,IAAI,CAACc,MAAaL,EAAK,QAAQK,CAAQ,CAAC,EAC7C,IAAI,CAACC,MAAaR,EAAQ,qBAAqBQ,CAAQ,CAAC,GAC9FL,IAAkBG,EAAY,QAAQ,CAACG,OAAU;AAAA,MACtD,UAAUA,EAAK,YAAY;AAAA,MAC3B,YAAYA;AAAA,MACZ,SAASC,EAAgBD,CAAI;AAAA,IAAA,EAC5B,GAEI,EAAE,uBAAAL,GAAuB,uBAAAO,EAAA,KAA2B,MAAM;AAC/D,UAAIjB,MAAwB;AAC3B,eAAO,EAAE,uBAAuB,IAAI,uBAAuB,CAAA,EAAG;AAGzD,YAAAkB,IAAY,YAAY,IAAI,GAC5BC,IAAQC,EAAoB;AAAA,QACjC,YAAY,OAAOpB,KAAwB,WAAWA,EAAoB,WAAW;AAAA,QACrF,cAAcF;AAAA,MAAA,CACd;AACD,aAAII,MAAc,SACVG,EAAA,KAAK,uBAAuB,KAAK,MAAM,YAAY,IAAI,IAAIa,CAAS,CAAC,IAAI,GAE1EC;AAAA,IAAA,GACL,GAEGR,IAAiBC,EAAY;AAAA,MAClC,CAACS,GAAKC,MACLD,EAAI,KAAK,CAACE,MAAMA,EAAE,YAAY,MAAMD,EAAQ,YAAY,CAAC,IAAID,IAAMA,EAAI,OAAOC,CAAO;AAAA,MACtFL;AAAA,IACD;AAEA,WAAO,EAAE,iBAAAR,GAAiB,uBAAAC,GAAuB,gBAAAC,EAAe;AAAA,EAAA,GAC9D,GAEGa,IAAiBf,EAAgB;AAAA,IACtC,CAACY,GAAKC,MAAaD,EAAI,KAAK,CAACE,MAAMA,EAAE,aAAaD,EAAQ,QAAQ,IAAID,IAAMA,EAAI,OAAOC,CAAO;AAAA,IAC9FZ;AAAA,EACD,GAEMe,IAAad,EACjB,QAAQ,CAACI,MAASW,EAA2BX,CAAI,CAAC,EAClD,OAAO,CAACY,MAAY,CAAC,CAACA,CAAO;AAC/B,EAAIF,EAAW,SAAS,KAAKA,EAAW,CAAC,KACzBtB,EAAA,UAAUsB,EAAW,CAAC,CAAC;AAGvC,QAAMG,IAAgBjB,EAAe,QAAQ,CAACI,MAASc,EAA+Bd,CAAI,CAAC;AAE3F,EAAAZ,EAAe,iBAAiByB,CAAa;AAE7C,QAAME,IACD,OAAO7B,KAAgB,YAAYA,EAAY,YAC3CA,EAAY,YAEbO,EAAK,QAAQ,QAAQ,OAAO,gBAAgB,UAAU,YAAY,GAEpEuB,IAAY,MAAMC,EAA2BR,GAAgB;AAAA,IAClE,aAAavB,MAAgB;AAAA,IAC7B,WAAA6B;AAAA,IACA,gBAAgB,CAAC;AAAA,IACjB,WAAA5B;AAAA,IACA,cAAcM,EAAK,QAAQV,CAAY;AAAA,EAAA,CACvC;AAED,EAAAK,EAAe,SAAS;AAAA,IACvB,uBAAuBO,EAAsB,IAAI,CAACK,OAAU;AAAA,MAC3D,MAAMA,EAAK;AAAA,MACX,SAASA,EAAK,QAAQ,MAAM,IAAI,CAACQ,OAAO;AAAA,QACvC,MAAMA;AAAA,QACN,WAAWQ,EACT,OAAO,CAACE,MAAMA,EAAE,mBAAmBlB,EAAK,QAAQ,EAChD,IAAI,CAACkB,MAAM,GAAGA,EAAE,OAAO,aAAa,IAAIA,EAAE,IAAI,EAAE;AAAA,MAAA,EACjD;AAAA,IAAA,EACD;AAAA,IACF,qBAAqBxB,EAAgB,IAAI,CAACM,OAAU;AAAA,MACnD,MAAMA,EAAK;AAAA,MACX,SAASA,EAAK,QAAQ,MAAM,IAAI,CAACQ,OAAO;AAAA,QACvC,MAAMA;AAAA,QACN,WAAWQ,EACT,OAAO,CAACE,MAAMA,EAAE,mBAAmBlB,EAAK,QAAQ,EAChD,IAAI,CAACkB,MAAM,GAAGA,EAAE,OAAO,aAAa,IAAIA,EAAE,IAAI,EAAE;AAAA,MAAA,EACjD;AAAA,IAAA,EACD;AAAA,EAAA,CACF,GAED9B,EAAe,aAAa4B,CAAS,GACrC5B,EAAe,YAAY;AAC5B,GAEa6B,IAA6B,OACzCb,GACAe,GAOAC,MAC6B;AACvB,QAAAjC,IAAYgC,EAAO,aAAa,SAChChB,IAAY,YAAY,IAAI,GAM5BkB,IAAuB,CAAC,GACxBC,IAA2B,CAAC,GAE5BC,IAAsB,YAAY,IAAI,GACtCC,wBAA6B,IAAI;AACvC,aAAWxB,KAAQI,GAAO;AACzB,UAAMqB,IAAYC,EAAuB1B,EAAK,YAAYmB,EAAO,gBAAgBK,CAAU,GACrFG,IAAMR,EAAO,cAChBS,EAAgB,iBAAiB5B,EAAK,YAAYyB,GAAWN,EAAO,SAAS,IAC7E;AACH,IAAIQ,KACHrC,EAAO,MAAM,IAAIU,EAAK,QAAQ,wBAAwB,GACtDqB,EAAO,KAAK,EAAE,WAAWM,EAAI,WAAW,UAAU3B,EAAK,UAAU,QAAQ,GAAG,iBAAiB,IAAI,KAEjGsB,EAAS,KAAK,EAAE,MAAAtB,GAAM,WAAAyB,EAAA,CAAW;AAAA,EAClC;AAMG,MAJAtC,MAAc,SACVG,EAAA,KAAK,8BAA8B,KAAK,MAAM,YAAY,IAAI,IAAIiC,CAAmB,CAAC,IAAI,GAG9FD,EAAS,WAAW;AACvB,WAAInC,MAAc,SACVG,EAAA,KAAK,wBAAwB,KAAK,MAAM,YAAY,IAAI,IAAIa,CAAS,CAAC,IAAI,GAE3EkB,EAAO,QAAQ,CAACQ,MAAMA,EAAE,SAAS;AAUnC,QAAAC,wBAAa,IAAwB;AAChC,aAAA,EAAE,MAAA9B,EAAK,KAAKsB;AACtB,IAAAQ,EAAO,IAAI9B,EAAK,UAAU,EAAE,WAAW,CAAC,GAAG,UAAUA,EAAK,UAAU,QAAQ,GAAG,iBAAiB,IAAI;AAQjG,MAAAsB,EAAS,UAAU1C;AACX,eAAA,EAAE,MAAAoB,EAAK,KAAKsB,GAAU;AAChC,YAAM,EAAE,WAAAN,GAAW,iBAAAe,MAAoBC,EAA2BhC,CAAyB,GACrFiC,IAAaH,EAAO,IAAI9B,EAAK,QAAQ;AAC3C,MAAAiC,EAAW,YAAYjB,GACvBiB,EAAW,kBAAkBF,GAClBE,EAAA,SAASF,EAAgB,OAAO,CAACG,GAAKC,MAAMD,IAAMC,EAAE,QAAQ,CAAC;AAAA,IAAA;AAAA,OAEnE;AAKN,UAAMC,IAAuBd,EAAS,IAAI,CAAC,EAAE,MAAAtB,SAAY;AAAA,MACxD,UAAUA,EAAK;AAAA,MACf,MAAM;AAAA,QACL,QAAQqC,EAAO,WAAW;AAAA,QAC1B,cAAclB,EAAO;AAAA,QACrB,gBAAgBnB,EAAK,WAAW,YAAY;AAAA,QAC5C,aAAaA,EAAK,QAAQ;AAAA,QAC1B,qBAAAoB;AAAA,MAAA;AAAA,IACD,EACC,GAEIkB,IAAO,IAAIC,EAAWjE,EAAiB,GAAG8D,EAAS,MAAM;AAC3D,QAAAI;AACA,QAAA;AACO,MAAAA,IAAA,MAAMF,EAAK,OAAOF,EAAS,IAAI,CAACK,MAAOA,EAAG,IAAI,CAAC;AAAA,IAAA,UACxD;AACD,MAAAH,EAAK,UAAU;AAAA,IAAA;AAIhB,aAASI,IAAI,GAAGA,IAAIF,EAAQ,QAAQE,KAAK;AAClC,YAAAC,IAASH,EAAQE,CAAC,GAClBE,IAAWR,EAASM,CAAC,EAAE,UACvBT,IAAaH,EAAO,IAAIc,CAAQ;AAEtC,UAAI,WAAWD,GAAQ;AACtB,QAAArD,EAAO,MAAM,IAAIsD,CAAQ,mBAAmBD,EAAO,KAAK,EAAE;AAC1D;AAAA,MAAA;AAGD,MAAAV,EAAW,YAAYU,EAAO,WAC9BV,EAAW,kBAAkBU,EAAO,iBACzBV,EAAA,SAASU,EAAO,gBAAgB,OAAO,CAACT,GAAKC,MAAMD,IAAMC,EAAE,QAAQ,CAAC;AAAA,IAAA;AAAA,EAChF;AAID,aAAW,EAAE,MAAAnC,GAAM,WAAAyB,EAAU,KAAKH,GAAU;AAC3C,UAAMW,IAAaH,EAAO,IAAI9B,EAAK,QAAQ;AACvC,IAAAiC,EAAW,UAAU,SAAS,KACjCL,EAAgB,aAAa5B,EAAK,YAAYyB,GAAWN,EAAO,WAAWc,EAAW,SAAS;AAAA,EAChG;AAGK,QAAAY,IAAgB,CAAC,GAAGxB,GAAQ,GAAG,MAAM,KAAKS,EAAO,OAAO,CAAC,CAAC;AAEhE,SAAI3C,MAAc,SACVG,EAAA,KAAK,wBAAwB,KAAK,MAAM,YAAY,IAAI,IAAIa,CAAS,CAAC,IAAI,GAG9EhB,MAAc,UACjB0D,EACE,IAAI,CAAChB,OAAO,EAAE,UAAUA,EAAE,UAAU,WAAWA,EAAE,OAAS,EAAA,EAC1D,KAAK,CAACiB,GAAGC,MAAMA,EAAE,YAAYD,EAAE,SAAS,EACxC,OAAO,CAACX,MAAMA,EAAE,YAAY,GAAG,EAC/B,QAAQ,CAACA,MAAM;AACR,IAAA7C,EAAA,KAAK,MAAM6C,EAAE,QAAQ,UAAU,KAAK,MAAMA,EAAE,SAAS,CAAC,eAAe;AAAA,EAAA,CAC5E,IACQhD,MAAc,WAEtB0D,EAAA,IAAI,CAAChB,OAAO,EAAE,UAAUA,EAAE,UAAU,WAAWA,EAAE,QAAQ,iBAAiBA,EAAE,kBAAkB,EAC9F,KAAK,CAACiB,GAAGC,MAAMA,EAAE,YAAYD,EAAE,SAAS,EACxC,QAAQ,CAACX,MAAM;AACR,IAAA7C,EAAA,KAAK,MAAM6C,EAAE,QAAQ,UAAU,KAAK,MAAMA,EAAE,SAAS,CAAC,eAAe,GAC5EA,EAAE,gBACA,KAAK,CAACW,GAAGC,MAAMA,EAAE,SAASD,EAAE,MAAM,EAClC,QAAQ,CAACE,MAAO;AAChB,MAAA1D,EAAO,KAAK,OAAO0D,EAAG,MAAM,IAAIA,EAAG,IAAI,KAAK,KAAK,MAAMA,EAAG,MAAM,CAAC,KAAK,GACtEA,EAAG,eACD,OAAO,CAACC,MAAMA,EAAE,UAAU,CAAC,EAC3B,KAAK,CAACH,GAAGC,MAAMA,EAAE,SAASD,EAAE,MAAM,EAClC,QAAQ,CAACG,MAAM;AACR,QAAA3D,EAAA,KAAK,SAAS2D,EAAE,OAAO,KAAK,KAAK,MAAMA,EAAE,MAAM,CAAC,IAAI;AAAA,MAAA,CAC3D;AAAA,IAAA,CACF;AAAA,EAAA,CACF,GAGIJ,EAAc,QAAQ,CAAChB,MAAMA,EAAE,SAAS;AAChD,GA6BaG,IAA6B,CACzChC,GACAoB,MACsE;AACtE,QAAMJ,IAA4B,CAAC,GAC7Be,IAAoC,CAAC,GAErCmB,IADa,CAAC,OAAO,QAAQ,OAAO,UAAU,OAAO,OAAO,EAC9B,KAAK,GAAG;AAE5C,SAAAlD,EAAK,QAAQ,MAAM,QAAQ,CAACmD,MAAe;AAC1C,UAAMC,IAAgB,IAAI,OAAO,GAAGD,CAAU,SAASD,CAAgB,GAAG;AACrE,IAAAlD,EAAA,WAAW,aAAa,CAACqD,MAAS;AAChC,YAAAC,IAAWD,EAAK,QAAQ;AAE1B,UAAAD,EAAc,KAAKE,CAAQ,GAAG;AACZ,QAAAC,EAAoBF,CAAI;AAMvC,cAAAG,IAAK,YAAY,IAAI,GACrB,EAAE,UAAAC,GAAU,gBAAAC,MAAmBC,EAAcN,GAAMrD,EAAK,QAAQ;AACtE,QAAA+B,EAAgB,KAAK;AAAA,UACpB,QAAQ0B,EAAS;AAAA,UACjB,MAAMA,EAAS;AAAA,UACf,QAAQ,YAAY,IAAA,IAAQD;AAAA,UAC5B,gBAAAE;AAAA,QAAA,CACA,GACD1C,EAAU,KAAKyC,CAAQ;AAAA,MAAA;AAAA,IACxB,CACA;AAAA,EAAA,CACD,GAEM,EAAE,WAAAzC,GAAW,iBAAAe,EAAgB;AACrC,GAEapB,IAA6B,CAACiD,MAAiD;AAC3F,QAAMC,IAAqBC,EAAqB;AAAA,IAC/C,YAAAF;AAAA,IACA,cAAc;AAAA,EAAA,CACd;AAED,MAAI,CAACC;AACG,WAAA;AAGF,QAAAR,IAAOO,EACX,oBAAoB,EACpB,OAAO,CAACP,MAASA,EAAK,OAAOU,EAAW,mBAAmB,CAAC,EAC5D,KAAK,CAACV,MAASQ,KAAsBR,EAAK,QAAQ,EAAE,WAAWQ,CAAkB,CAAC;AAEpF,MAAI,CAACR;AACG,WAAA;AAGR,QAAMW,IAAaX,EAAK,gCAAgCU,EAAW,uBAAuB,GACpFE,IAASC,EAAyBF,CAAU,GAE5CG,IAAiB,CAACC,MACnB,OAAOA,KAAM,YAGb,MAAM,QAAQA,CAAC,KAAKA,EAAE,MAAM,CAACC,MAAU,OAAOA,KAAU,QAAQ,IAC5DD,IAGDA,EAAE,OAAO,CAAC9D,GAAKC,MACjB,OAAOA,KAAY,WACfD,IAED;AAAA,IACN,GAAGA;AAAA,IACH,CAACC,EAAQ,UAAU,GAAG4D,EAAe5D,EAAQ,KAAiB;AAAA,EAC/D,GACE,EAAE;AAEN,SAAO4D,EAAeF,CAAM;AAC7B,GAEanD,IAAiC,CAAC8C,MAA+C;AAC7F,QAAMU,IAA6B,CAAC,GAE9BC,IAA0BT,EAAqB;AAAA,IACpD,YAAAF;AAAA,IACA,cAAc;AAAA,EAAA,CACd,GAEKY,IAAgCV,EAAqB;AAAA,IAC1D,YAAAF;AAAA,IACA,cAAc;AAAA,EAAA,CACd;AAED,SAAAA,EACE,oBAAoB,EACpB,OAAO,CAACP,MAASA,EAAK,OAAOU,EAAW,mBAAmB,CAAC,EAC5D,IAAI,CAACV,MAAS;AACd,QAAIkB,KAA2BlB,EAAK,QAAU,EAAA,WAAWkB,CAAuB,GAAG;AAIlF,YAAME,KAHqBpB,EAAK,cAAc,GACC,kBAAkBU,EAAW,UAAU,KAAK,CAAC,GAEtD,CAAC,EAAE,cAAc;AACvD,UAAI,CAACU;AACJ;AAGM,MAAAH,EAAA,KAAKI,EAAkBD,CAAU,CAAC;AACzC;AAAA,IAAA;AAGD,QAAID,KAAiCnB,EAAK,QAAU,EAAA,WAAWmB,CAA6B,GAAG;AAI9F,YAAMC,KAHqBpB,EAAK,cAAc,GACC,kBAAkBU,EAAW,UAAU,KAAK,CAAC,GAEtD,CAAC,EAAE,cAAc;AACvD,UAAI,CAACU;AACJ;AAID,MADqBE,EAAwBF,CAAU,EAC1C,QAAQ,CAACG,MAAUN,EAAO,KAAKM,CAAK,CAAC;AAAA,IAAA;AAAA,EACnD,CACA,GACKN;AACR;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("fs"),l=require("ts-morph"),c=require("../../utils/logger.cjs");function u(e,n){const o=p(e,n),r=o.map(s=>d.statSync(s.getFilePath()).mtimeMs),t=Math.max(...r),a=e.getFilePath().split("/").pop(),i=o.length;return c.Logger.debug(`[${a}] Found ${i} imports, latest touched at ${c.formatTimestamp(t)}.`),t}function p(e,n){const o=e.getFilePath().split("/").pop();if(!o)return[];const r=n[e.getFilePath()];if(r)return r.dependencies;n[e.getFilePath()]={dependencies:[]};try{const t=[e],a=e.getDescendantsOfKind(l.SyntaxKind.ImportDeclaration);for(const i of a){const s=i.getModuleSpecifierSourceFile();if(!s){c.Logger.debug(`[${o}] Could not resolve import ${i.getModuleSpecifierValue()}.`);continue}const g=p(s,n);t.push(...g)}return n[e.getFilePath()]={dependencies:t},t}catch(t){return c.Logger.warn(`[${o}] Caught an error while processing imports:`,t),n[e.getFilePath()]={dependencies:[]},[]}}exports.getSourceFileTimestamp=u;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("fs"),m=require("ts-morph"),a=require("../../utils/logger.cjs");function h(e,t,o=new Map){const r=u(e,t),n=r.map(d=>{const p=d.getFilePath(),g=o.get(p);if(g!==void 0)return g;const l=f.statSync(p).mtimeMs;return o.set(p,l),l}),i=Math.max(...n),s=e.getFilePath().split("/").pop(),c=r.length;return a.Logger.debug(`[${s}] Found ${c} imports, latest touched at ${a.formatTimestamp(i)}.`),i}function u(e,t){const o=e.getFilePath().split("/").pop();if(!o)return[];const r=t[e.getFilePath()];if(r)return r.dependencies;t[e.getFilePath()]={dependencies:[]};try{const n=[e],i=e.getDescendantsOfKind(m.SyntaxKind.ImportDeclaration);for(const s of i){const c=s.getModuleSpecifierSourceFile();if(!c){a.Logger.debug(`[${o}] Could not resolve import ${s.getModuleSpecifierValue()}.`);continue}const d=u(c,t);n.push(...d)}return t[e.getFilePath()]={dependencies:n},n}catch(n){return a.Logger.warn(`[${o}] Caught an error while processing imports:`,n),t[e.getFilePath()]={dependencies:[]},[]}}exports.getSourceFileTimestamp=h;
2
2
  //# sourceMappingURL=getSourceFileTimestamp.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"getSourceFileTimestamp.cjs","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"sourcesContent":["import fs from 'fs'\nimport { SourceFile, SyntaxKind } from 'ts-morph'\n\nimport { formatTimestamp, Logger } from '../../utils/logger'\n\nexport type TimestampCache = Record<string, { dependencies: SourceFile[] }>\n\nexport function getSourceFileTimestamp(sourceFile: SourceFile, timestampCache: TimestampCache) {\n\tconst dependencies = getFileDependencies(sourceFile, timestampCache)\n\tconst timestamps = dependencies.map((dep) => {\n\t\tconst stat = fs.statSync(dep.getFilePath())\n\t\treturn stat.mtimeMs\n\t})\n\tconst latestTimestamp = Math.max(...timestamps)\n\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tconst depsCount = dependencies.length\n\tLogger.debug(\n\t\t`[${fileName}] Found ${depsCount} imports, latest touched at ${formatTimestamp(latestTimestamp)}.`,\n\t)\n\n\treturn latestTimestamp\n}\n\nfunction getFileDependencies(sourceFile: SourceFile, timestampCache: TimestampCache): SourceFile[] {\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tif (!fileName) {\n\t\treturn []\n\t}\n\n\tconst cacheHit = timestampCache[sourceFile.getFilePath()]\n\tif (cacheHit) {\n\t\treturn cacheHit.dependencies\n\t}\n\n\t// Initialize cache entry early to prevent circular dependencies\n\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\n\ttry {\n\t\tconst results = [sourceFile]\n\n\t\tconst importDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration)\n\n\t\tfor (const declaration of importDeclarations) {\n\t\t\tconst importedSourceFile = declaration.getModuleSpecifierSourceFile()\n\t\t\tif (!importedSourceFile) {\n\t\t\t\tLogger.debug(`[${fileName}] Could not resolve import ${declaration.getModuleSpecifierValue()}.`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst deps = getFileDependencies(importedSourceFile, timestampCache)\n\t\t\tresults.push(...deps)\n\t\t}\n\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: results }\n\t\treturn results\n\t} catch (error) {\n\t\tLogger.warn(`[${fileName}] Caught an error while processing imports:`, error)\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\t\treturn []\n\t}\n}\n"],"names":["getSourceFileTimestamp","sourceFile","timestampCache","dependencies","getFileDependencies","timestamps","dep","fs","latestTimestamp","fileName","depsCount","Logger","formatTimestamp","cacheHit","results","importDeclarations","SyntaxKind","declaration","importedSourceFile","deps","error"],"mappings":"gKAOgB,SAAAA,EAAuBC,EAAwBC,EAAgC,CACxF,MAAAC,EAAeC,EAAoBH,EAAYC,CAAc,EAC7DG,EAAaF,EAAa,IAAKG,GACvBC,EAAG,SAASD,EAAI,aAAa,EAC9B,OACZ,EACKE,EAAkB,KAAK,IAAI,GAAGH,CAAU,EAExCI,EAAWR,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,EACnDS,EAAYP,EAAa,OACxBQ,OAAAA,EAAAA,OAAA,MACN,IAAIF,CAAQ,WAAWC,CAAS,+BAA+BE,EAAA,gBAAgBJ,CAAe,CAAC,GAChG,EAEOA,CACR,CAEA,SAASJ,EAAoBH,EAAwBC,EAA8C,CAClG,MAAMO,EAAWR,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,EACzD,GAAI,CAACQ,EACJ,MAAO,CAAC,EAGT,MAAMI,EAAWX,EAAeD,EAAW,YAAA,CAAa,EACxD,GAAIY,EACH,OAAOA,EAAS,aAIjBX,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAc,CAAA,CAAG,EAE1D,GAAA,CACG,MAAAa,EAAU,CAACb,CAAU,EAErBc,EAAqBd,EAAW,qBAAqBe,EAAAA,WAAW,iBAAiB,EAEvF,UAAWC,KAAeF,EAAoB,CACvC,MAAAG,EAAqBD,EAAY,6BAA6B,EACpE,GAAI,CAACC,EAAoB,CACxBP,SAAO,MAAM,IAAIF,CAAQ,8BAA8BQ,EAAY,yBAAyB,GAAG,EAC/F,QAAA,CAGK,MAAAE,EAAOf,EAAoBc,EAAoBhB,CAAc,EAC3DY,EAAA,KAAK,GAAGK,CAAI,CAAA,CAGrB,OAAAjB,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAca,CAAQ,EAC5DA,QACCM,EAAO,CACfT,OAAAA,EAAA,OAAO,KAAK,IAAIF,CAAQ,8CAA+CW,CAAK,EAC5ElB,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAc,CAAA,CAAG,EACvD,CAAC,CAAA,CAEV"}
1
+ {"version":3,"file":"getSourceFileTimestamp.cjs","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"sourcesContent":["import fs from 'fs'\nimport { SourceFile, SyntaxKind } from 'ts-morph'\n\nimport { formatTimestamp, Logger } from '../../utils/logger'\n\nexport type TimestampCache = Record<string, { dependencies: SourceFile[] }>\n\n/**\n * Per-run cache of file mtimes keyed by absolute path. Router files share large parts of their\n * transitive import graphs (common types, utils, models), so without this the same dependency gets\n * `statSync`-ed once per importing router — tens of thousands of redundant syscalls across a project\n * with dozens of routers. mtimes don't change mid-run, so memoizing is safe.\n */\nexport type MtimeCache = Map<string, number>\n\nexport function getSourceFileTimestamp(\n\tsourceFile: SourceFile,\n\ttimestampCache: TimestampCache,\n\tmtimeCache: MtimeCache = new Map(),\n) {\n\tconst dependencies = getFileDependencies(sourceFile, timestampCache)\n\tconst timestamps = dependencies.map((dep) => {\n\t\tconst depPath = dep.getFilePath()\n\t\tconst cached = mtimeCache.get(depPath)\n\t\tif (cached !== undefined) {\n\t\t\treturn cached\n\t\t}\n\t\tconst mtime = fs.statSync(depPath).mtimeMs\n\t\tmtimeCache.set(depPath, mtime)\n\t\treturn mtime\n\t})\n\tconst latestTimestamp = Math.max(...timestamps)\n\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tconst depsCount = dependencies.length\n\tLogger.debug(\n\t\t`[${fileName}] Found ${depsCount} imports, latest touched at ${formatTimestamp(latestTimestamp)}.`,\n\t)\n\n\treturn latestTimestamp\n}\n\nfunction getFileDependencies(sourceFile: SourceFile, timestampCache: TimestampCache): SourceFile[] {\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tif (!fileName) {\n\t\treturn []\n\t}\n\n\tconst cacheHit = timestampCache[sourceFile.getFilePath()]\n\tif (cacheHit) {\n\t\treturn cacheHit.dependencies\n\t}\n\n\t// Initialize cache entry early to prevent circular dependencies\n\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\n\ttry {\n\t\tconst results = [sourceFile]\n\n\t\tconst importDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration)\n\n\t\tfor (const declaration of importDeclarations) {\n\t\t\tconst importedSourceFile = declaration.getModuleSpecifierSourceFile()\n\t\t\tif (!importedSourceFile) {\n\t\t\t\tLogger.debug(`[${fileName}] Could not resolve import ${declaration.getModuleSpecifierValue()}.`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst deps = getFileDependencies(importedSourceFile, timestampCache)\n\t\t\tresults.push(...deps)\n\t\t}\n\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: results }\n\t\treturn results\n\t} catch (error) {\n\t\tLogger.warn(`[${fileName}] Caught an error while processing imports:`, error)\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\t\treturn []\n\t}\n}\n"],"names":["getSourceFileTimestamp","sourceFile","timestampCache","mtimeCache","dependencies","getFileDependencies","timestamps","dep","depPath","cached","mtime","fs","latestTimestamp","fileName","depsCount","Logger","formatTimestamp","cacheHit","results","importDeclarations","SyntaxKind","declaration","importedSourceFile","deps","error"],"mappings":"gKAeO,SAASA,EACfC,EACAC,EACAC,EAAyB,IAAI,IAC5B,CACK,MAAAC,EAAeC,EAAoBJ,EAAYC,CAAc,EAC7DI,EAAaF,EAAa,IAAKG,GAAQ,CACtC,MAAAC,EAAUD,EAAI,YAAY,EAC1BE,EAASN,EAAW,IAAIK,CAAO,EACrC,GAAIC,IAAW,OACP,OAAAA,EAER,MAAMC,EAAQC,EAAG,SAASH,CAAO,EAAE,QACxB,OAAAL,EAAA,IAAIK,EAASE,CAAK,EACtBA,CAAA,CACP,EACKE,EAAkB,KAAK,IAAI,GAAGN,CAAU,EAExCO,EAAWZ,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,EACnDa,EAAYV,EAAa,OACxBW,OAAAA,EAAAA,OAAA,MACN,IAAIF,CAAQ,WAAWC,CAAS,+BAA+BE,EAAA,gBAAgBJ,CAAe,CAAC,GAChG,EAEOA,CACR,CAEA,SAASP,EAAoBJ,EAAwBC,EAA8C,CAClG,MAAMW,EAAWZ,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,EACzD,GAAI,CAACY,EACJ,MAAO,CAAC,EAGT,MAAMI,EAAWf,EAAeD,EAAW,YAAA,CAAa,EACxD,GAAIgB,EACH,OAAOA,EAAS,aAIjBf,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAc,CAAA,CAAG,EAE1D,GAAA,CACG,MAAAiB,EAAU,CAACjB,CAAU,EAErBkB,EAAqBlB,EAAW,qBAAqBmB,EAAAA,WAAW,iBAAiB,EAEvF,UAAWC,KAAeF,EAAoB,CACvC,MAAAG,EAAqBD,EAAY,6BAA6B,EACpE,GAAI,CAACC,EAAoB,CACxBP,SAAO,MAAM,IAAIF,CAAQ,8BAA8BQ,EAAY,yBAAyB,GAAG,EAC/F,QAAA,CAGK,MAAAE,EAAOlB,EAAoBiB,EAAoBpB,CAAc,EAC3DgB,EAAA,KAAK,GAAGK,CAAI,CAAA,CAGrB,OAAArB,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAciB,CAAQ,EAC5DA,QACCM,EAAO,CACfT,OAAAA,EAAA,OAAO,KAAK,IAAIF,CAAQ,8CAA+CW,CAAK,EAC5EtB,EAAeD,EAAW,YAAY,CAAC,EAAI,CAAE,aAAc,CAAA,CAAG,EACvD,CAAC,CAAA,CAEV"}
@@ -3,5 +3,6 @@ import { SourceFile } from 'ts-morph';
3
3
  export type TimestampCache = Record<string, {
4
4
  dependencies: SourceFile[];
5
5
  }>;
6
- export declare function getSourceFileTimestamp(sourceFile: SourceFile, timestampCache: TimestampCache): number;
6
+ export type MtimeCache = Map<string, number>;
7
+ export declare function getSourceFileTimestamp(sourceFile: SourceFile, timestampCache: TimestampCache, mtimeCache?: MtimeCache): number;
7
8
  //# sourceMappingURL=getSourceFileTimestamp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getSourceFileTimestamp.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAIjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,YAAY,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAAA;AAE3E,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,UAe5F"}
1
+ {"version":3,"file":"getSourceFileTimestamp.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAIjD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,YAAY,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAAA;AAQ3E,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE5C,wBAAgB,sBAAsB,CACrC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,EAC9B,UAAU,GAAE,UAAsB,UAsBlC"}
@@ -1,37 +1,43 @@
1
- import l from "fs";
2
- import { SyntaxKind as g } from "ts-morph";
3
- import { Logger as p, formatTimestamp as m } from "../../utils/logger.mjs";
4
- function P(t, n) {
5
- const o = d(t, n), i = o.map((s) => l.statSync(s.getFilePath()).mtimeMs), e = Math.max(...i), a = t.getFilePath().split("/").pop(), r = o.length;
6
- return p.debug(
7
- `[${a}] Found ${r} imports, latest touched at ${m(e)}.`
8
- ), e;
1
+ import m from "fs";
2
+ import { SyntaxKind as u } from "ts-morph";
3
+ import { Logger as d, formatTimestamp as h } from "../../utils/logger.mjs";
4
+ function F(e, t, o = /* @__PURE__ */ new Map()) {
5
+ const i = f(e, t), n = i.map((a) => {
6
+ const c = a.getFilePath(), l = o.get(c);
7
+ if (l !== void 0)
8
+ return l;
9
+ const g = m.statSync(c).mtimeMs;
10
+ return o.set(c, g), g;
11
+ }), r = Math.max(...n), s = e.getFilePath().split("/").pop(), p = i.length;
12
+ return d.debug(
13
+ `[${s}] Found ${p} imports, latest touched at ${h(r)}.`
14
+ ), r;
9
15
  }
10
- function d(t, n) {
11
- const o = t.getFilePath().split("/").pop();
16
+ function f(e, t) {
17
+ const o = e.getFilePath().split("/").pop();
12
18
  if (!o)
13
19
  return [];
14
- const i = n[t.getFilePath()];
20
+ const i = t[e.getFilePath()];
15
21
  if (i)
16
22
  return i.dependencies;
17
- n[t.getFilePath()] = { dependencies: [] };
23
+ t[e.getFilePath()] = { dependencies: [] };
18
24
  try {
19
- const e = [t], a = t.getDescendantsOfKind(g.ImportDeclaration);
20
- for (const r of a) {
21
- const s = r.getModuleSpecifierSourceFile();
22
- if (!s) {
23
- p.debug(`[${o}] Could not resolve import ${r.getModuleSpecifierValue()}.`);
25
+ const n = [e], r = e.getDescendantsOfKind(u.ImportDeclaration);
26
+ for (const s of r) {
27
+ const p = s.getModuleSpecifierSourceFile();
28
+ if (!p) {
29
+ d.debug(`[${o}] Could not resolve import ${s.getModuleSpecifierValue()}.`);
24
30
  continue;
25
31
  }
26
- const c = d(s, n);
27
- e.push(...c);
32
+ const a = f(p, t);
33
+ n.push(...a);
28
34
  }
29
- return n[t.getFilePath()] = { dependencies: e }, e;
30
- } catch (e) {
31
- return p.warn(`[${o}] Caught an error while processing imports:`, e), n[t.getFilePath()] = { dependencies: [] }, [];
35
+ return t[e.getFilePath()] = { dependencies: n }, n;
36
+ } catch (n) {
37
+ return d.warn(`[${o}] Caught an error while processing imports:`, n), t[e.getFilePath()] = { dependencies: [] }, [];
32
38
  }
33
39
  }
34
40
  export {
35
- P as getSourceFileTimestamp
41
+ F as getSourceFileTimestamp
36
42
  };
37
43
  //# sourceMappingURL=getSourceFileTimestamp.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"getSourceFileTimestamp.mjs","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"sourcesContent":["import fs from 'fs'\nimport { SourceFile, SyntaxKind } from 'ts-morph'\n\nimport { formatTimestamp, Logger } from '../../utils/logger'\n\nexport type TimestampCache = Record<string, { dependencies: SourceFile[] }>\n\nexport function getSourceFileTimestamp(sourceFile: SourceFile, timestampCache: TimestampCache) {\n\tconst dependencies = getFileDependencies(sourceFile, timestampCache)\n\tconst timestamps = dependencies.map((dep) => {\n\t\tconst stat = fs.statSync(dep.getFilePath())\n\t\treturn stat.mtimeMs\n\t})\n\tconst latestTimestamp = Math.max(...timestamps)\n\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tconst depsCount = dependencies.length\n\tLogger.debug(\n\t\t`[${fileName}] Found ${depsCount} imports, latest touched at ${formatTimestamp(latestTimestamp)}.`,\n\t)\n\n\treturn latestTimestamp\n}\n\nfunction getFileDependencies(sourceFile: SourceFile, timestampCache: TimestampCache): SourceFile[] {\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tif (!fileName) {\n\t\treturn []\n\t}\n\n\tconst cacheHit = timestampCache[sourceFile.getFilePath()]\n\tif (cacheHit) {\n\t\treturn cacheHit.dependencies\n\t}\n\n\t// Initialize cache entry early to prevent circular dependencies\n\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\n\ttry {\n\t\tconst results = [sourceFile]\n\n\t\tconst importDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration)\n\n\t\tfor (const declaration of importDeclarations) {\n\t\t\tconst importedSourceFile = declaration.getModuleSpecifierSourceFile()\n\t\t\tif (!importedSourceFile) {\n\t\t\t\tLogger.debug(`[${fileName}] Could not resolve import ${declaration.getModuleSpecifierValue()}.`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst deps = getFileDependencies(importedSourceFile, timestampCache)\n\t\t\tresults.push(...deps)\n\t\t}\n\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: results }\n\t\treturn results\n\t} catch (error) {\n\t\tLogger.warn(`[${fileName}] Caught an error while processing imports:`, error)\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\t\treturn []\n\t}\n}\n"],"names":["getSourceFileTimestamp","sourceFile","timestampCache","dependencies","getFileDependencies","timestamps","dep","fs","latestTimestamp","fileName","depsCount","Logger","formatTimestamp","cacheHit","results","importDeclarations","SyntaxKind","declaration","importedSourceFile","deps","error"],"mappings":";;;AAOgB,SAAAA,EAAuBC,GAAwBC,GAAgC;AACxF,QAAAC,IAAeC,EAAoBH,GAAYC,CAAc,GAC7DG,IAAaF,EAAa,IAAI,CAACG,MACvBC,EAAG,SAASD,EAAI,aAAa,EAC9B,OACZ,GACKE,IAAkB,KAAK,IAAI,GAAGH,CAAU,GAExCI,IAAWR,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,GACnDS,IAAYP,EAAa;AACxB,SAAAQ,EAAA;AAAA,IACN,IAAIF,CAAQ,WAAWC,CAAS,+BAA+BE,EAAgBJ,CAAe,CAAC;AAAA,EAChG,GAEOA;AACR;AAEA,SAASJ,EAAoBH,GAAwBC,GAA8C;AAClG,QAAMO,IAAWR,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI;AACzD,MAAI,CAACQ;AACJ,WAAO,CAAC;AAGT,QAAMI,IAAWX,EAAeD,EAAW,YAAA,CAAa;AACxD,MAAIY;AACH,WAAOA,EAAS;AAIjB,EAAAX,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAc,CAAA,EAAG;AAE1D,MAAA;AACG,UAAAa,IAAU,CAACb,CAAU,GAErBc,IAAqBd,EAAW,qBAAqBe,EAAW,iBAAiB;AAEvF,eAAWC,KAAeF,GAAoB;AACvC,YAAAG,IAAqBD,EAAY,6BAA6B;AACpE,UAAI,CAACC,GAAoB;AACxB,QAAAP,EAAO,MAAM,IAAIF,CAAQ,8BAA8BQ,EAAY,yBAAyB,GAAG;AAC/F;AAAA,MAAA;AAGK,YAAAE,IAAOf,EAAoBc,GAAoBhB,CAAc;AAC3D,MAAAY,EAAA,KAAK,GAAGK,CAAI;AAAA,IAAA;AAGrB,WAAAjB,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAca,EAAQ,GAC5DA;AAAA,WACCM,GAAO;AACf,WAAAT,EAAO,KAAK,IAAIF,CAAQ,+CAA+CW,CAAK,GAC5ElB,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAc,CAAA,EAAG,GACvD,CAAC;AAAA,EAAA;AAEV;"}
1
+ {"version":3,"file":"getSourceFileTimestamp.mjs","sources":["../../../src/openapi/analyzerModule/getSourceFileTimestamp.ts"],"sourcesContent":["import fs from 'fs'\nimport { SourceFile, SyntaxKind } from 'ts-morph'\n\nimport { formatTimestamp, Logger } from '../../utils/logger'\n\nexport type TimestampCache = Record<string, { dependencies: SourceFile[] }>\n\n/**\n * Per-run cache of file mtimes keyed by absolute path. Router files share large parts of their\n * transitive import graphs (common types, utils, models), so without this the same dependency gets\n * `statSync`-ed once per importing router — tens of thousands of redundant syscalls across a project\n * with dozens of routers. mtimes don't change mid-run, so memoizing is safe.\n */\nexport type MtimeCache = Map<string, number>\n\nexport function getSourceFileTimestamp(\n\tsourceFile: SourceFile,\n\ttimestampCache: TimestampCache,\n\tmtimeCache: MtimeCache = new Map(),\n) {\n\tconst dependencies = getFileDependencies(sourceFile, timestampCache)\n\tconst timestamps = dependencies.map((dep) => {\n\t\tconst depPath = dep.getFilePath()\n\t\tconst cached = mtimeCache.get(depPath)\n\t\tif (cached !== undefined) {\n\t\t\treturn cached\n\t\t}\n\t\tconst mtime = fs.statSync(depPath).mtimeMs\n\t\tmtimeCache.set(depPath, mtime)\n\t\treturn mtime\n\t})\n\tconst latestTimestamp = Math.max(...timestamps)\n\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tconst depsCount = dependencies.length\n\tLogger.debug(\n\t\t`[${fileName}] Found ${depsCount} imports, latest touched at ${formatTimestamp(latestTimestamp)}.`,\n\t)\n\n\treturn latestTimestamp\n}\n\nfunction getFileDependencies(sourceFile: SourceFile, timestampCache: TimestampCache): SourceFile[] {\n\tconst fileName = sourceFile.getFilePath().split('/').pop()\n\tif (!fileName) {\n\t\treturn []\n\t}\n\n\tconst cacheHit = timestampCache[sourceFile.getFilePath()]\n\tif (cacheHit) {\n\t\treturn cacheHit.dependencies\n\t}\n\n\t// Initialize cache entry early to prevent circular dependencies\n\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\n\ttry {\n\t\tconst results = [sourceFile]\n\n\t\tconst importDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.ImportDeclaration)\n\n\t\tfor (const declaration of importDeclarations) {\n\t\t\tconst importedSourceFile = declaration.getModuleSpecifierSourceFile()\n\t\t\tif (!importedSourceFile) {\n\t\t\t\tLogger.debug(`[${fileName}] Could not resolve import ${declaration.getModuleSpecifierValue()}.`)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst deps = getFileDependencies(importedSourceFile, timestampCache)\n\t\t\tresults.push(...deps)\n\t\t}\n\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: results }\n\t\treturn results\n\t} catch (error) {\n\t\tLogger.warn(`[${fileName}] Caught an error while processing imports:`, error)\n\t\ttimestampCache[sourceFile.getFilePath()] = { dependencies: [] }\n\t\treturn []\n\t}\n}\n"],"names":["getSourceFileTimestamp","sourceFile","timestampCache","mtimeCache","dependencies","getFileDependencies","timestamps","dep","depPath","cached","mtime","fs","latestTimestamp","fileName","depsCount","Logger","formatTimestamp","cacheHit","results","importDeclarations","SyntaxKind","declaration","importedSourceFile","deps","error"],"mappings":";;;AAeO,SAASA,EACfC,GACAC,GACAC,IAAyB,oBAAI,OAC5B;AACK,QAAAC,IAAeC,EAAoBJ,GAAYC,CAAc,GAC7DI,IAAaF,EAAa,IAAI,CAACG,MAAQ;AACtC,UAAAC,IAAUD,EAAI,YAAY,GAC1BE,IAASN,EAAW,IAAIK,CAAO;AACrC,QAAIC,MAAW;AACP,aAAAA;AAER,UAAMC,IAAQC,EAAG,SAASH,CAAO,EAAE;AACxB,WAAAL,EAAA,IAAIK,GAASE,CAAK,GACtBA;AAAA,EAAA,CACP,GACKE,IAAkB,KAAK,IAAI,GAAGN,CAAU,GAExCO,IAAWZ,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI,GACnDa,IAAYV,EAAa;AACxB,SAAAW,EAAA;AAAA,IACN,IAAIF,CAAQ,WAAWC,CAAS,+BAA+BE,EAAgBJ,CAAe,CAAC;AAAA,EAChG,GAEOA;AACR;AAEA,SAASP,EAAoBJ,GAAwBC,GAA8C;AAClG,QAAMW,IAAWZ,EAAW,YAAA,EAAc,MAAM,GAAG,EAAE,IAAI;AACzD,MAAI,CAACY;AACJ,WAAO,CAAC;AAGT,QAAMI,IAAWf,EAAeD,EAAW,YAAA,CAAa;AACxD,MAAIgB;AACH,WAAOA,EAAS;AAIjB,EAAAf,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAc,CAAA,EAAG;AAE1D,MAAA;AACG,UAAAiB,IAAU,CAACjB,CAAU,GAErBkB,IAAqBlB,EAAW,qBAAqBmB,EAAW,iBAAiB;AAEvF,eAAWC,KAAeF,GAAoB;AACvC,YAAAG,IAAqBD,EAAY,6BAA6B;AACpE,UAAI,CAACC,GAAoB;AACxB,QAAAP,EAAO,MAAM,IAAIF,CAAQ,8BAA8BQ,EAAY,yBAAyB,GAAG;AAC/F;AAAA,MAAA;AAGK,YAAAE,IAAOlB,EAAoBiB,GAAoBpB,CAAc;AAC3D,MAAAgB,EAAA,KAAK,GAAGK,CAAI;AAAA,IAAA;AAGrB,WAAArB,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAciB,EAAQ,GAC5DA;AAAA,WACCM,GAAO;AACf,WAAAT,EAAO,KAAK,IAAIF,CAAQ,+CAA+CW,CAAK,GAC5EtB,EAAeD,EAAW,YAAY,CAAC,IAAI,EAAE,cAAc,CAAA,EAAG,GACvD,CAAC;AAAA,EAAA;AAEV;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moonflower",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "",
5
5
  "author": "tenebrie",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@ import {
27
27
  import { discoverRouters } from '../discoveryModule/discoverRouters/discoverRouters'
28
28
  import { ApiDocsHeader, OpenApiManager } from '../manager/OpenApiManager'
29
29
  import { EndpointData, ExposedModelData } from '../types'
30
- import { getSourceFileTimestamp, TimestampCache } from './getSourceFileTimestamp'
30
+ import { getSourceFileTimestamp, MtimeCache, TimestampCache } from './getSourceFileTimestamp'
31
31
  import { getValuesOfObjectLiteral, resolveEndpointPath } from './nodeParsers'
32
32
  import { parseEndpoint, SectionTiming } from './parseEndpoint'
33
33
  import { parseExposedModel, parseNamedExposedModels } from './parseExposedModels'
@@ -53,6 +53,13 @@ type FileDiscoveryConfig = {
53
53
 
54
54
  type EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }
55
55
 
56
+ /**
57
+ * Number of uncached files at or below which analysis runs inline on the (already-warm) main-thread
58
+ * Project instead of fanning out to worker threads. Each worker pays a multi-second cold-start to
59
+ * build its own Project, so parallelism only wins once there are several files to share that cost.
60
+ */
61
+ const INLINE_ANALYSIS_FILE_THRESHOLD = 2
62
+
56
63
  /**
57
64
  * @param tsconfigPath Path to tsconfig file relative to project root
58
65
  * @param sourceFilePaths Array of router source files relative to project root
@@ -193,8 +200,10 @@ export const analyzeMultipleSourceFiles = async (
193
200
  const cached: CachedFile[] = []
194
201
  const uncached: UncachedFile[] = []
195
202
 
203
+ const freshnessCheckStart = performance.now()
204
+ const mtimeCache: MtimeCache = new Map()
196
205
  for (const file of files) {
197
- const timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache)
206
+ const timestamp = getSourceFileTimestamp(file.sourceFile, config.timestampCache, mtimeCache)
198
207
  const hit = config.incremental
199
208
  ? SourceFileCache.getCachedResults(file.sourceFile, timestamp, config.cachePath)
200
209
  : null
@@ -205,6 +214,9 @@ export const analyzeMultipleSourceFiles = async (
205
214
  uncached.push({ file, timestamp })
206
215
  }
207
216
  }
217
+ if (profiling !== 'off') {
218
+ Logger.info(`Cache freshness check took ${Math.round(performance.now() - freshnessCheckStart)}ms`)
219
+ }
208
220
 
209
221
  if (uncached.length === 0) {
210
222
  if (profiling !== 'off') {
@@ -213,24 +225,6 @@ export const analyzeMultipleSourceFiles = async (
213
225
  return cached.flatMap((f) => f.endpoints)
214
226
  }
215
227
 
216
- // Build one task per uncached file. Each worker analyzes a whole file in a single pass, so it
217
- // pays the ts-morph Project cold-start (full TS program load + type-checker warmup) once and
218
- // reuses the warmed-up checker for every endpoint in that file.
219
- type FileTask = { task: WorkerTask; fileName: string }
220
- const allTasks: FileTask[] = uncached.map(({ file }) => ({
221
- fileName: file.fileName,
222
- task: {
223
- taskId: crypto.randomUUID(),
224
- tsconfigPath: config.tsconfigPath,
225
- sourceFilePath: file.sourceFile.getFilePath(),
226
- routerNames: file.routers.named,
227
- filterEndpointPaths,
228
- },
229
- }))
230
-
231
- // Dispatch all tasks to the worker pool, capped at one worker per file.
232
- const pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)
233
-
234
228
  type FileResult = {
235
229
  endpoints: EndpointData[]
236
230
  fileName: string
@@ -238,32 +232,63 @@ export const analyzeMultipleSourceFiles = async (
238
232
  endpointTimings: EndpointTiming[]
239
233
  }
240
234
 
241
- let results: WorkerResult[]
242
- try {
243
- results = await pool.runAll(allTasks.map((ft) => ft.task))
244
- } finally {
245
- pool.terminate()
246
- }
247
-
248
- // Each result maps 1:1 to a file task.
249
235
  const byFile = new Map<string, FileResult>()
250
236
  for (const { file } of uncached) {
251
237
  byFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })
252
238
  }
253
239
 
254
- for (let i = 0; i < results.length; i++) {
255
- const result = results[i]
256
- const fileName = allTasks[i].fileName
257
- const fileResult = byFile.get(fileName)!
240
+ // The caller (prepareOpenApiSpec) already built and warmed a ts-morph Project on this thread, so
241
+ // inline analysis runs against a hot type-checker. A worker, by contrast, must spawn a thread and
242
+ // build its own Project from scratch — a multi-second cold-start. That cold-start only pays off
243
+ // when there are enough uncached files that spreading the parse work across workers beats it; below
244
+ // the threshold (e.g. the common single-file incremental rebuild), inline is strictly faster.
245
+ if (uncached.length <= INLINE_ANALYSIS_FILE_THRESHOLD) {
246
+ for (const { file } of uncached) {
247
+ const { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)
248
+ const fileResult = byFile.get(file.fileName)!
249
+ fileResult.endpoints = endpoints
250
+ fileResult.endpointTimings = endpointTimings
251
+ fileResult.timing = endpointTimings.reduce((sum, t) => sum + t.timing, 0)
252
+ }
253
+ } else {
254
+ // One task per file: each worker analyzes a whole file in a single pass, paying its Project
255
+ // cold-start once and reusing the warmed-up checker for every endpoint in that file. Cap the
256
+ // pool at one worker per file so we never spin up a worker with nothing to do.
257
+ type FileTask = { task: WorkerTask; fileName: string }
258
+ const allTasks: FileTask[] = uncached.map(({ file }) => ({
259
+ fileName: file.fileName,
260
+ task: {
261
+ taskId: crypto.randomUUID(),
262
+ tsconfigPath: config.tsconfigPath,
263
+ sourceFilePath: file.sourceFile.getFilePath(),
264
+ routerNames: file.routers.named,
265
+ filterEndpointPaths,
266
+ },
267
+ }))
258
268
 
259
- if ('error' in result) {
260
- Logger.error(`[${fileName}] Worker error: ${result.error}`)
261
- continue
269
+ const pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)
270
+ let results: WorkerResult[]
271
+ try {
272
+ results = await pool.runAll(allTasks.map((ft) => ft.task))
273
+ } finally {
274
+ pool.terminate()
262
275
  }
263
276
 
264
- fileResult.endpoints = result.endpoints
265
- fileResult.endpointTimings = result.endpointTimings
266
- fileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)
277
+ // Each result maps 1:1 to a file task.
278
+ for (let i = 0; i < results.length; i++) {
279
+ const result = results[i]
280
+ const fileName = allTasks[i].fileName
281
+ const fileResult = byFile.get(fileName)!
282
+
283
+ if ('error' in result) {
284
+ Logger.error(`[${fileName}] Worker error: ${result.error}`)
285
+ continue
286
+ }
287
+
288
+ fileResult.endpoints = result.endpoints
289
+ fileResult.endpointTimings = result.endpointTimings
290
+ fileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)
291
+ }
267
292
  }
268
293
 
269
294
  // Write cache for each uncached file
@@ -5,11 +5,29 @@ import { formatTimestamp, Logger } from '../../utils/logger'
5
5
 
6
6
  export type TimestampCache = Record<string, { dependencies: SourceFile[] }>
7
7
 
8
- export function getSourceFileTimestamp(sourceFile: SourceFile, timestampCache: TimestampCache) {
8
+ /**
9
+ * Per-run cache of file mtimes keyed by absolute path. Router files share large parts of their
10
+ * transitive import graphs (common types, utils, models), so without this the same dependency gets
11
+ * `statSync`-ed once per importing router — tens of thousands of redundant syscalls across a project
12
+ * with dozens of routers. mtimes don't change mid-run, so memoizing is safe.
13
+ */
14
+ export type MtimeCache = Map<string, number>
15
+
16
+ export function getSourceFileTimestamp(
17
+ sourceFile: SourceFile,
18
+ timestampCache: TimestampCache,
19
+ mtimeCache: MtimeCache = new Map(),
20
+ ) {
9
21
  const dependencies = getFileDependencies(sourceFile, timestampCache)
10
22
  const timestamps = dependencies.map((dep) => {
11
- const stat = fs.statSync(dep.getFilePath())
12
- return stat.mtimeMs
23
+ const depPath = dep.getFilePath()
24
+ const cached = mtimeCache.get(depPath)
25
+ if (cached !== undefined) {
26
+ return cached
27
+ }
28
+ const mtime = fs.statSync(depPath).mtimeMs
29
+ mtimeCache.set(depPath, mtime)
30
+ return mtime
13
31
  })
14
32
  const latestTimestamp = Math.max(...timestamps)
15
33