moonflower 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/openapi/analyzerModule/analyzerModule.cjs +1 -1
- package/dist/openapi/analyzerModule/analyzerModule.cjs.map +1 -1
- package/dist/openapi/analyzerModule/analyzerModule.d.ts.map +1 -1
- package/dist/openapi/analyzerModule/analyzerModule.mjs +147 -120
- package/dist/openapi/analyzerModule/analyzerModule.mjs.map +1 -1
- package/package.json +1 -1
- package/src/openapi/analyzerModule/analyzerModule.ts +56 -36
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
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"),g=require("../../utils/logger.cjs"),P=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"),A=require("./nodeParsers.cjs"),B=require("./parseEndpoint.cjs"),R=require("./parseExposedModels.cjs"),k=require("./sourceFileCache.cjs"),V=require("./workerPool.cjs");var M=typeof document<"u"?document.currentScript:null;function Y(i){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const a in i)if(a!=="default"){const d=Object.getOwnPropertyDescriptor(i,a);Object.defineProperty(n,a,d.get?d:{enumerable:!0,get:()=>i[a]})}}return n.default=i,Object.freeze(n)}const E=Y(U);function G(){const i=["./analyzerWorker.mjs","./analyzerWorker.test.mjs"];for(const n of i){const a=new URL(n,typeof document>"u"?require("url").pathToFileURL(__filename).href:M&&M.tagName.toUpperCase()==="SCRIPT"&&M.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:i,tsconfigPath:n,sourceFilePaths:a,sourceFileDiscovery:d,incremental:p,profiling:f="stats"})=>{const t=H.OpenApiManager.getInstance();if(t.isReady())return;i&&g.Logger.setLevel(i),g.Logger.info("Preparing OpenAPI spec");const l=new S.Project({tsConfigFilePath:E.resolve(n),skipFileDependencyResolution:!0}),{explicitRouters:u,discoveredRouterFiles:e,allSourceFiles:r}=(()=>{const m=(a??[]).map(h=>E.resolve(h)).map(h=>l.getSourceFileOrThrow(h)),O=m.flatMap(h=>({fileName:h.getFilePath(),sourceFile:h,routers:W.discoverRouters(h)})),{discoveredRouterFiles:$,discoveredSourceFiles:b}=(()=>{if(d===!1)return{discoveredRouterFiles:[],discoveredSourceFiles:[]};const h=performance.now(),x=_.discoverRouterFiles({targetPath:typeof d=="object"?d.rootPath:".",tsConfigPath:n});return f!=="off"&&g.Logger.info(`File discovery took ${Math.round(performance.now()-h)}ms`),x})(),j=m.reduce((h,x)=>h.some(z=>z.getFilePath()===x.getFilePath())?h:h.concat(x),b);return{explicitRouters:O,discoveredRouterFiles:$,allSourceFiles:j}})(),o=u.reduce((c,F)=>c.some(m=>m.fileName===F.fileName)?c:c.concat(F),e),s=r.flatMap(c=>v(c)).filter(c=>!!c);s.length>0&&s[0]&&t.setHeader(s[0]);const y=r.flatMap(c=>w(c));t.setExposedModels(y);const T=typeof p=="object"&&p.cachePath?p.cachePath:E.resolve(process.cwd(),"node_modules",".cache","moonflower"),N=await C(o,{incremental:p!==!1,cachePath:T,timestampCache:{},profiling:f,tsconfigPath:E.resolve(n)});t.setStats({discoveredRouterFiles:e.map(c=>({path:c.fileName,routers:c.routers.named.map(F=>({name:F,endpoints:N.filter(m=>m.sourceFilePath===c.fileName).map(m=>`${m.method.toUpperCase()} ${m.path}`)}))})),explicitRouterFiles:u.map(c=>({path:c.fileName,routers:c.routers.named.map(F=>({name:F,endpoints:N.filter(m=>m.sourceFilePath===c.fileName).map(m=>`${m.method.toUpperCase()} ${m.path}`)}))}))}),t.setEndpoints(N),t.markAsReady()},C=async(i,n,a)=>{const d=n.profiling??"stats",p=performance.now(),f=[],t=[];for(const e of i){const r=D.getSourceFileTimestamp(e.sourceFile,n.timestampCache),o=n.incremental?k.SourceFileCache.getCachedResults(e.sourceFile,r,n.cachePath):null;o?(g.Logger.debug(`[${e.fileName}] Found cached results`),f.push({endpoints:o.endpoints,fileName:e.fileName,timing:0,endpointTimings:[]})):t.push({file:e,timestamp:r})}if(t.length===0)return d!=="off"&&g.Logger.info(`Router analysis took ${Math.round(performance.now()-p)}ms`),f.flatMap(e=>e.endpoints);const l=new Map;for(const{file:e}of t)l.set(e.fileName,{endpoints:[],fileName:e.fileName,timing:0,endpointTimings:[]});if(t.length<=J)for(const{file:e}of t){const{endpoints:r,endpointTimings:o}=L(e),s=l.get(e.fileName);s.endpoints=r,s.endpointTimings=o,s.timing=o.reduce((y,T)=>y+T.timing,0)}else{const e=t.map(({file:s})=>({fileName:s.fileName,task:{taskId:q.randomUUID(),tsconfigPath:n.tsconfigPath,sourceFilePath:s.sourceFile.getFilePath(),routerNames:s.routers.named,filterEndpointPaths:a}})),r=new V.WorkerPool(G(),e.length);let o;try{o=await r.runAll(e.map(s=>s.task))}finally{r.terminate()}for(let s=0;s<o.length;s++){const y=o[s],T=e[s].fileName,N=l.get(T);if("error"in y){g.Logger.error(`[${T}] Worker error: ${y.error}`);continue}N.endpoints=y.endpoints,N.endpointTimings=y.endpointTimings,N.timing=y.endpointTimings.reduce((c,F)=>c+F.timing,0)}}for(const{file:e,timestamp:r}of t){const o=l.get(e.fileName);o.endpoints.length>0&&k.SourceFileCache.cacheResults(e.sourceFile,r,n.cachePath,o.endpoints)}const u=[...f,...Array.from(l.values())];return d!=="off"&&g.Logger.info(`Router analysis took ${Math.round(performance.now()-p)}ms`),d==="stats"?u.map(e=>({fileName:e.fileName,timeTaken:e.timing})).sort((e,r)=>r.timeTaken-e.timeTaken).filter(e=>e.timeTaken>500).forEach(e=>{g.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`)}):d==="debug"&&u.map(e=>({fileName:e.fileName,timeTaken:e.timing,endpointTimings:e.endpointTimings})).sort((e,r)=>r.timeTaken-e.timeTaken).forEach(e=>{g.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`),e.endpointTimings.sort((r,o)=>o.timing-r.timing).forEach(r=>{g.Logger.info(` - ${r.method} ${r.path} (${Math.round(r.timing)}ms)`),r.sectionTimings.filter(o=>o.timing>=1).sort((o,s)=>s.timing-o.timing).forEach(o=>{g.Logger.info(` - ${o.section}: ${Math.round(o.timing)}ms`)})})}),u.flatMap(e=>e.endpoints)},L=(i,n)=>{const a=[],d=[],f=["get","post","put","delete","del","patch"].join("|");return i.routers.named.forEach(t=>{const l=new RegExp(`${t}\\.(?:${f})`);i.sourceFile.forEachChild(u=>{const e=u.getText();if(l.test(e)){A.resolveEndpointPath(u);const r=performance.now(),{endpoint:o,sectionTimings:s}=B.parseEndpoint(u,i.fileName);d.push({method:o.method,path:o.path,timing:performance.now()-r,sectionTimings:s}),a.push(o)}})}),{endpoints:a,endpointTimings:d}},v=i=>{const n=P.discoverImportedName({sourceFile:i,originalName:"useApiHeader"});if(!n)return null;const a=i.forEachChildAsArray().filter(t=>t.isKind(S.SyntaxKind.ExpressionStatement)).find(t=>n&&t.getText().startsWith(n));if(!a)return null;const d=a.getFirstDescendantByKindOrThrow(S.SyntaxKind.ObjectLiteralExpression),p=A.getValuesOfObjectLiteral(d),f=t=>typeof t=="string"||Array.isArray(t)&&t.every(l=>typeof l=="string")?t:t.reduce((l,u)=>typeof u=="string"?l:{...l,[u.identifier]:f(u.value)},{});return f(p)},w=i=>{const n=[],a=P.discoverImportedName({sourceFile:i,originalName:"useExposeApiModel"}),d=P.discoverImportedName({sourceFile:i,originalName:"useExposeNamedApiModels"});return i.forEachChildAsArray().filter(p=>p.isKind(S.SyntaxKind.ExpressionStatement)).map(p=>{if(a&&p.getText().startsWith(a)){const l=(p.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!l)return;n.push(R.parseExposedModel(l));return}if(d&&p.getText().startsWith(d)){const l=(p.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!l)return;R.parseNamedExposedModels(l).forEach(e=>n.push(e))}}),n};exports.analyzeMultipleSourceFiles=C;exports.analyzeSourceFileApiHeader=v;exports.analyzeSourceFileEndpoints=L;exports.analyzeSourceFileExposedModels=w;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, 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\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\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","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":"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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;
|
|
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;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,CA4IxB,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,22 +1,23 @@
|
|
|
1
1
|
import $ from "crypto";
|
|
2
2
|
import { existsSync as v } from "fs";
|
|
3
|
-
import * as
|
|
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
|
|
7
|
-
import { discoverImportedName as
|
|
6
|
+
import { Logger as g } from "../../utils/logger.mjs";
|
|
7
|
+
import { discoverImportedName as M } 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 {
|
|
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";
|
|
14
15
|
import { SourceFileCache as P } from "./sourceFileCache.mjs";
|
|
15
|
-
import { WorkerPool as
|
|
16
|
+
import { WorkerPool as D } from "./workerPool.mjs";
|
|
16
17
|
function B() {
|
|
17
|
-
const
|
|
18
|
-
for (const
|
|
19
|
-
const p = new URL(
|
|
18
|
+
const l = ["./analyzerWorker.mjs", "./analyzerWorker.test.mjs"];
|
|
19
|
+
for (const n of l) {
|
|
20
|
+
const p = new URL(n, import.meta.url);
|
|
20
21
|
if (v(O(p)))
|
|
21
22
|
return p;
|
|
22
23
|
}
|
|
@@ -24,161 +25,187 @@ function B() {
|
|
|
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
|
|
28
|
-
logLevel:
|
|
29
|
-
tsconfigPath:
|
|
28
|
+
const V = 2, de = async ({
|
|
29
|
+
logLevel: l,
|
|
30
|
+
tsconfigPath: n,
|
|
30
31
|
sourceFilePaths: p,
|
|
31
32
|
sourceFileDiscovery: c,
|
|
32
|
-
incremental:
|
|
33
|
-
profiling:
|
|
33
|
+
incremental: m,
|
|
34
|
+
profiling: u = "stats"
|
|
34
35
|
}) => {
|
|
35
36
|
const t = U.getInstance();
|
|
36
37
|
if (t.isReady())
|
|
37
38
|
return;
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
tsConfigFilePath:
|
|
39
|
+
l && g.setLevel(l), g.info("Preparing OpenAPI spec");
|
|
40
|
+
const r = new L({
|
|
41
|
+
tsConfigFilePath: k.resolve(n),
|
|
41
42
|
skipFileDependencyResolution: !0
|
|
42
|
-
}), { explicitRouters:
|
|
43
|
-
const
|
|
44
|
-
fileName:
|
|
45
|
-
sourceFile:
|
|
46
|
-
routers: z(
|
|
47
|
-
})), { discoveredRouterFiles: A, discoveredSourceFiles:
|
|
43
|
+
}), { explicitRouters: d, discoveredRouterFiles: e, allSourceFiles: i } = (() => {
|
|
44
|
+
const f = (p ?? []).map((h) => k.resolve(h)).map((h) => r.getSourceFileOrThrow(h)), R = f.flatMap((h) => ({
|
|
45
|
+
fileName: h.getFilePath(),
|
|
46
|
+
sourceFile: h,
|
|
47
|
+
routers: z(h)
|
|
48
|
+
})), { discoveredRouterFiles: A, discoveredSourceFiles: S } = (() => {
|
|
48
49
|
if (c === !1)
|
|
49
50
|
return { discoveredRouterFiles: [], discoveredSourceFiles: [] };
|
|
50
|
-
const
|
|
51
|
+
const h = performance.now(), E = j({
|
|
51
52
|
targetPath: typeof c == "object" ? c.rootPath : ".",
|
|
52
|
-
tsConfigPath:
|
|
53
|
+
tsConfigPath: n
|
|
53
54
|
});
|
|
54
|
-
return
|
|
55
|
-
})(),
|
|
56
|
-
(
|
|
57
|
-
|
|
55
|
+
return u !== "off" && g.info(`File discovery took ${Math.round(performance.now() - h)}ms`), E;
|
|
56
|
+
})(), C = f.reduce(
|
|
57
|
+
(h, E) => h.some((w) => w.getFilePath() === E.getFilePath()) ? h : h.concat(E),
|
|
58
|
+
S
|
|
58
59
|
);
|
|
59
|
-
return { explicitRouters: R, discoveredRouterFiles: A, allSourceFiles:
|
|
60
|
-
})(),
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
),
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
t.setExposedModels(
|
|
67
|
-
const
|
|
68
|
-
incremental:
|
|
69
|
-
cachePath:
|
|
60
|
+
return { explicitRouters: R, discoveredRouterFiles: A, allSourceFiles: C };
|
|
61
|
+
})(), o = d.reduce(
|
|
62
|
+
(a, y) => a.some((f) => f.fileName === y.fileName) ? a : a.concat(y),
|
|
63
|
+
e
|
|
64
|
+
), s = i.flatMap((a) => G(a)).filter((a) => !!a);
|
|
65
|
+
s.length > 0 && s[0] && t.setHeader(s[0]);
|
|
66
|
+
const F = i.flatMap((a) => J(a));
|
|
67
|
+
t.setExposedModels(F);
|
|
68
|
+
const T = typeof m == "object" && m.cachePath ? m.cachePath : k.resolve(process.cwd(), "node_modules", ".cache", "moonflower"), N = await Y(o, {
|
|
69
|
+
incremental: m !== !1,
|
|
70
|
+
cachePath: T,
|
|
70
71
|
timestampCache: {},
|
|
71
|
-
profiling:
|
|
72
|
-
tsconfigPath:
|
|
72
|
+
profiling: u,
|
|
73
|
+
tsconfigPath: k.resolve(n)
|
|
73
74
|
});
|
|
74
75
|
t.setStats({
|
|
75
|
-
discoveredRouterFiles:
|
|
76
|
-
path:
|
|
77
|
-
routers:
|
|
76
|
+
discoveredRouterFiles: e.map((a) => ({
|
|
77
|
+
path: a.fileName,
|
|
78
|
+
routers: a.routers.named.map((y) => ({
|
|
78
79
|
name: y,
|
|
79
|
-
endpoints:
|
|
80
|
+
endpoints: N.filter((f) => f.sourceFilePath === a.fileName).map((f) => `${f.method.toUpperCase()} ${f.path}`)
|
|
80
81
|
}))
|
|
81
82
|
})),
|
|
82
|
-
explicitRouterFiles:
|
|
83
|
-
path:
|
|
84
|
-
routers:
|
|
83
|
+
explicitRouterFiles: d.map((a) => ({
|
|
84
|
+
path: a.fileName,
|
|
85
|
+
routers: a.routers.named.map((y) => ({
|
|
85
86
|
name: y,
|
|
86
|
-
endpoints:
|
|
87
|
+
endpoints: N.filter((f) => f.sourceFilePath === a.fileName).map((f) => `${f.method.toUpperCase()} ${f.path}`)
|
|
87
88
|
}))
|
|
88
89
|
}))
|
|
89
|
-
}), t.setEndpoints(
|
|
90
|
-
},
|
|
91
|
-
const c =
|
|
92
|
-
for (const e of
|
|
93
|
-
const
|
|
94
|
-
|
|
90
|
+
}), t.setEndpoints(N), t.markAsReady();
|
|
91
|
+
}, Y = async (l, n, p) => {
|
|
92
|
+
const c = n.profiling ?? "stats", m = performance.now(), u = [], t = [];
|
|
93
|
+
for (const e of l) {
|
|
94
|
+
const i = b(e.sourceFile, n.timestampCache), o = n.incremental ? P.getCachedResults(e.sourceFile, i, n.cachePath) : null;
|
|
95
|
+
o ? (g.debug(`[${e.fileName}] Found cached results`), u.push({ endpoints: o.endpoints, fileName: e.fileName, timing: 0, endpointTimings: [] })) : t.push({ file: e, timestamp: i });
|
|
95
96
|
}
|
|
96
97
|
if (t.length === 0)
|
|
97
|
-
return c !== "off" &&
|
|
98
|
-
const
|
|
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();
|
|
98
|
+
return c !== "off" && g.info(`Router analysis took ${Math.round(performance.now() - m)}ms`), u.flatMap((e) => e.endpoints);
|
|
99
|
+
const r = /* @__PURE__ */ new Map();
|
|
115
100
|
for (const { file: e } of t)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
r.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: i, endpointTimings: o } = q(e), s = r.get(e.fileName);
|
|
105
|
+
s.endpoints = i, s.endpointTimings = o, s.timing = o.reduce((F, T) => F + T.timing, 0);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const e = t.map(({ file: s }) => ({
|
|
109
|
+
fileName: s.fileName,
|
|
110
|
+
task: {
|
|
111
|
+
taskId: $.randomUUID(),
|
|
112
|
+
tsconfigPath: n.tsconfigPath,
|
|
113
|
+
sourceFilePath: s.sourceFile.getFilePath(),
|
|
114
|
+
routerNames: s.routers.named,
|
|
115
|
+
filterEndpointPaths: p
|
|
116
|
+
}
|
|
117
|
+
})), i = new D(B(), e.length);
|
|
118
|
+
let o;
|
|
119
|
+
try {
|
|
120
|
+
o = await i.runAll(e.map((s) => s.task));
|
|
121
|
+
} finally {
|
|
122
|
+
i.terminate();
|
|
123
|
+
}
|
|
124
|
+
for (let s = 0; s < o.length; s++) {
|
|
125
|
+
const F = o[s], T = e[s].fileName, N = r.get(T);
|
|
126
|
+
if ("error" in F) {
|
|
127
|
+
g.error(`[${T}] Worker error: ${F.error}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
N.endpoints = F.endpoints, N.endpointTimings = F.endpointTimings, N.timing = F.endpointTimings.reduce((a, y) => a + y.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
|
-
for (const { file: e, timestamp:
|
|
126
|
-
const
|
|
127
|
-
|
|
133
|
+
for (const { file: e, timestamp: i } of t) {
|
|
134
|
+
const o = r.get(e.fileName);
|
|
135
|
+
o.endpoints.length > 0 && P.cacheResults(e.sourceFile, i, n.cachePath, o.endpoints);
|
|
128
136
|
}
|
|
129
|
-
const
|
|
130
|
-
return c !== "off" &&
|
|
131
|
-
|
|
132
|
-
}) : c === "debug" &&
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
const d = [...u, ...Array.from(r.values())];
|
|
138
|
+
return c !== "off" && g.info(`Router analysis took ${Math.round(performance.now() - m)}ms`), c === "stats" ? d.map((e) => ({ fileName: e.fileName, timeTaken: e.timing })).sort((e, i) => i.timeTaken - e.timeTaken).filter((e) => e.timeTaken > 500).forEach((e) => {
|
|
139
|
+
g.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`);
|
|
140
|
+
}) : c === "debug" && d.map((e) => ({ fileName: e.fileName, timeTaken: e.timing, endpointTimings: e.endpointTimings })).sort((e, i) => i.timeTaken - e.timeTaken).forEach((e) => {
|
|
141
|
+
g.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`), e.endpointTimings.sort((i, o) => o.timing - i.timing).forEach((i) => {
|
|
142
|
+
g.info(` - ${i.method} ${i.path} (${Math.round(i.timing)}ms)`), i.sectionTimings.filter((o) => o.timing >= 1).sort((o, s) => s.timing - o.timing).forEach((o) => {
|
|
143
|
+
g.info(` - ${o.section}: ${Math.round(o.timing)}ms`);
|
|
136
144
|
});
|
|
137
145
|
});
|
|
138
|
-
}),
|
|
139
|
-
},
|
|
140
|
-
const
|
|
141
|
-
|
|
146
|
+
}), d.flatMap((e) => e.endpoints);
|
|
147
|
+
}, q = (l, n) => {
|
|
148
|
+
const p = [], c = [], u = ["get", "post", "put", "delete", "del", "patch"].join("|");
|
|
149
|
+
return l.routers.named.forEach((t) => {
|
|
150
|
+
const r = new RegExp(`${t}\\.(?:${u})`);
|
|
151
|
+
l.sourceFile.forEachChild((d) => {
|
|
152
|
+
const e = d.getText();
|
|
153
|
+
if (r.test(e)) {
|
|
154
|
+
W(d);
|
|
155
|
+
const i = performance.now(), { endpoint: o, sectionTimings: s } = H(d, l.fileName);
|
|
156
|
+
c.push({
|
|
157
|
+
method: o.method,
|
|
158
|
+
path: o.path,
|
|
159
|
+
timing: performance.now() - i,
|
|
160
|
+
sectionTimings: s
|
|
161
|
+
}), p.push(o);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}), { endpoints: p, endpointTimings: c };
|
|
165
|
+
}, G = (l) => {
|
|
166
|
+
const n = M({
|
|
167
|
+
sourceFile: l,
|
|
142
168
|
originalName: "useApiHeader"
|
|
143
169
|
});
|
|
144
|
-
if (!
|
|
170
|
+
if (!n)
|
|
145
171
|
return null;
|
|
146
|
-
const p =
|
|
172
|
+
const p = l.forEachChildAsArray().filter((t) => t.isKind(x.ExpressionStatement)).find((t) => n && t.getText().startsWith(n));
|
|
147
173
|
if (!p)
|
|
148
174
|
return null;
|
|
149
|
-
const c = p.getFirstDescendantByKindOrThrow(x.ObjectLiteralExpression),
|
|
150
|
-
...
|
|
151
|
-
[
|
|
175
|
+
const c = p.getFirstDescendantByKindOrThrow(x.ObjectLiteralExpression), m = I(c), u = (t) => typeof t == "string" || Array.isArray(t) && t.every((r) => typeof r == "string") ? t : t.reduce((r, d) => typeof d == "string" ? r : {
|
|
176
|
+
...r,
|
|
177
|
+
[d.identifier]: u(d.value)
|
|
152
178
|
}, {});
|
|
153
|
-
return
|
|
154
|
-
},
|
|
155
|
-
const
|
|
156
|
-
sourceFile:
|
|
179
|
+
return u(m);
|
|
180
|
+
}, J = (l) => {
|
|
181
|
+
const n = [], p = M({
|
|
182
|
+
sourceFile: l,
|
|
157
183
|
originalName: "useExposeApiModel"
|
|
158
|
-
}), c =
|
|
159
|
-
sourceFile:
|
|
184
|
+
}), c = M({
|
|
185
|
+
sourceFile: l,
|
|
160
186
|
originalName: "useExposeNamedApiModels"
|
|
161
187
|
});
|
|
162
|
-
return
|
|
163
|
-
if (p &&
|
|
164
|
-
const
|
|
165
|
-
if (!
|
|
188
|
+
return l.forEachChildAsArray().filter((m) => m.isKind(x.ExpressionStatement)).map((m) => {
|
|
189
|
+
if (p && m.getText().startsWith(p)) {
|
|
190
|
+
const r = (m.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
|
|
191
|
+
if (!r)
|
|
166
192
|
return;
|
|
167
|
-
|
|
193
|
+
n.push(K(r));
|
|
168
194
|
return;
|
|
169
195
|
}
|
|
170
|
-
if (c &&
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
196
|
+
if (c && m.getText().startsWith(c)) {
|
|
197
|
+
const r = (m.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
|
|
198
|
+
if (!r)
|
|
173
199
|
return;
|
|
174
|
-
|
|
200
|
+
_(r).forEach((e) => n.push(e));
|
|
175
201
|
}
|
|
176
|
-
}),
|
|
202
|
+
}), n;
|
|
177
203
|
};
|
|
178
204
|
export {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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, 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\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\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","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":"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"}
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -213,24 +220,6 @@ export const analyzeMultipleSourceFiles = async (
|
|
|
213
220
|
return cached.flatMap((f) => f.endpoints)
|
|
214
221
|
}
|
|
215
222
|
|
|
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
223
|
type FileResult = {
|
|
235
224
|
endpoints: EndpointData[]
|
|
236
225
|
fileName: string
|
|
@@ -238,32 +227,63 @@ export const analyzeMultipleSourceFiles = async (
|
|
|
238
227
|
endpointTimings: EndpointTiming[]
|
|
239
228
|
}
|
|
240
229
|
|
|
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
230
|
const byFile = new Map<string, FileResult>()
|
|
250
231
|
for (const { file } of uncached) {
|
|
251
232
|
byFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })
|
|
252
233
|
}
|
|
253
234
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
235
|
+
// The caller (prepareOpenApiSpec) already built and warmed a ts-morph Project on this thread, so
|
|
236
|
+
// inline analysis runs against a hot type-checker. A worker, by contrast, must spawn a thread and
|
|
237
|
+
// build its own Project from scratch — a multi-second cold-start. That cold-start only pays off
|
|
238
|
+
// when there are enough uncached files that spreading the parse work across workers beats it; below
|
|
239
|
+
// the threshold (e.g. the common single-file incremental rebuild), inline is strictly faster.
|
|
240
|
+
if (uncached.length <= INLINE_ANALYSIS_FILE_THRESHOLD) {
|
|
241
|
+
for (const { file } of uncached) {
|
|
242
|
+
const { endpoints, endpointTimings } = analyzeSourceFileEndpoints(file, filterEndpointPaths)
|
|
243
|
+
const fileResult = byFile.get(file.fileName)!
|
|
244
|
+
fileResult.endpoints = endpoints
|
|
245
|
+
fileResult.endpointTimings = endpointTimings
|
|
246
|
+
fileResult.timing = endpointTimings.reduce((sum, t) => sum + t.timing, 0)
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
// One task per file: each worker analyzes a whole file in a single pass, paying its Project
|
|
250
|
+
// cold-start once and reusing the warmed-up checker for every endpoint in that file. Cap the
|
|
251
|
+
// pool at one worker per file so we never spin up a worker with nothing to do.
|
|
252
|
+
type FileTask = { task: WorkerTask; fileName: string }
|
|
253
|
+
const allTasks: FileTask[] = uncached.map(({ file }) => ({
|
|
254
|
+
fileName: file.fileName,
|
|
255
|
+
task: {
|
|
256
|
+
taskId: crypto.randomUUID(),
|
|
257
|
+
tsconfigPath: config.tsconfigPath,
|
|
258
|
+
sourceFilePath: file.sourceFile.getFilePath(),
|
|
259
|
+
routerNames: file.routers.named,
|
|
260
|
+
filterEndpointPaths,
|
|
261
|
+
},
|
|
262
|
+
}))
|
|
258
263
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
const pool = new WorkerPool(resolveWorkerUrl(), allTasks.length)
|
|
265
|
+
let results: WorkerResult[]
|
|
266
|
+
try {
|
|
267
|
+
results = await pool.runAll(allTasks.map((ft) => ft.task))
|
|
268
|
+
} finally {
|
|
269
|
+
pool.terminate()
|
|
262
270
|
}
|
|
263
271
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
// Each result maps 1:1 to a file task.
|
|
273
|
+
for (let i = 0; i < results.length; i++) {
|
|
274
|
+
const result = results[i]
|
|
275
|
+
const fileName = allTasks[i].fileName
|
|
276
|
+
const fileResult = byFile.get(fileName)!
|
|
277
|
+
|
|
278
|
+
if ('error' in result) {
|
|
279
|
+
Logger.error(`[${fileName}] Worker error: ${result.error}`)
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fileResult.endpoints = result.endpoints
|
|
284
|
+
fileResult.endpointTimings = result.endpointTimings
|
|
285
|
+
fileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)
|
|
286
|
+
}
|
|
267
287
|
}
|
|
268
288
|
|
|
269
289
|
// Write cache for each uncached file
|