moonflower 1.5.0 → 1.5.1
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 +120 -135
- package/dist/openapi/analyzerModule/analyzerModule.mjs.map +1 -1
- package/dist/openapi/analyzerModule/analyzerWorker.cjs +1 -1
- package/dist/openapi/analyzerModule/analyzerWorker.cjs.map +1 -1
- package/dist/openapi/analyzerModule/analyzerWorker.mjs +41 -33
- package/dist/openapi/analyzerModule/analyzerWorker.mjs.map +1 -1
- package/dist/openapi/analyzerModule/workerPool.cjs +1 -1
- package/dist/openapi/analyzerModule/workerPool.cjs.map +1 -1
- package/dist/openapi/analyzerModule/workerPool.d.ts +11 -6
- package/dist/openapi/analyzerModule/workerPool.d.ts.map +1 -1
- package/dist/openapi/analyzerModule/workerPool.mjs +23 -23
- package/dist/openapi/analyzerModule/workerPool.mjs.map +1 -1
- package/package.json +1 -1
- package/src/openapi/analyzerModule/analyzerModule.ts +20 -46
- package/src/openapi/analyzerModule/analyzerWorker.ts +30 -26
- package/src/openapi/analyzerModule/workerPool.ts +17 -7
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("crypto"),q=require("fs"),z=require("path"),U=require("url"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("crypto"),q=require("fs"),z=require("path"),U=require("url"),S=require("ts-morph"),f=require("../../utils/logger.cjs"),R=require("../discoveryModule/discoverImports/discoverImports.cjs"),I=require("../discoveryModule/discoverRouterFiles/discoverRouterFiles.cjs"),K=require("../discoveryModule/discoverRouters/discoverRouters.cjs"),W=require("../manager/OpenApiManager.cjs"),_=require("./getSourceFileTimestamp.cjs"),H=require("./nodeParsers.cjs"),k=require("./parseExposedModels.cjs"),C=require("./sourceFileCache.cjs"),D=require("./workerPool.cjs");var P=typeof document<"u"?document.currentScript:null;function B(s){const o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const a in s)if(a!=="default"){const l=Object.getOwnPropertyDescriptor(s,a);Object.defineProperty(o,a,l.get?l:{enumerable:!0,get:()=>s[a]})}}return o.default=s,Object.freeze(o)}const M=B(z);function V(){const s=["./analyzerWorker.mjs","./analyzerWorker.test.mjs"];for(const o of s){const a=new URL(o,typeof document>"u"?require("url").pathToFileURL(__filename).href:P&&P.tagName.toUpperCase()==="SCRIPT"&&P.src||new URL("openapi/analyzerModule/analyzerModule.cjs",document.baseURI).href);if(q.existsSync(U.fileURLToPath(a)))return a}throw new Error("analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first).")}const G=async({logLevel:s,tsconfigPath:o,sourceFilePaths:a,sourceFileDiscovery:l,incremental:c,profiling:m="stats"})=>{const t=W.OpenApiManager.getInstance();if(t.isReady())return;s&&f.Logger.setLevel(s),f.Logger.info("Preparing OpenAPI spec");const d=new S.Project({tsConfigFilePath:M.resolve(o),skipFileDependencyResolution:!0}),{explicitRouters:h,discoveredRouterFiles:F,allSourceFiles:N}=(()=>{const u=(a??[]).map(p=>M.resolve(p)).map(p=>d.getSourceFileOrThrow(p)),L=u.flatMap(p=>({fileName:p.getFilePath(),sourceFile:p,routers:K.discoverRouters(p)})),{discoveredRouterFiles:w,discoveredSourceFiles:O}=(()=>{if(l===!1)return{discoveredRouterFiles:[],discoveredSourceFiles:[]};const p=performance.now(),x=I.discoverRouterFiles({targetPath:typeof l=="object"?l.rootPath:".",tsConfigPath:o});return m!=="off"&&f.Logger.info(`File discovery took ${Math.round(performance.now()-p)}ms`),x})(),$=u.reduce((p,x)=>p.some(b=>b.getFilePath()===x.getFilePath())?p:p.concat(x),O);return{explicitRouters:L,discoveredRouterFiles:w,allSourceFiles:$}})(),T=h.reduce((i,y)=>i.some(u=>u.fileName===y.fileName)?i:i.concat(y),F),e=N.flatMap(i=>E(i)).filter(i=>!!i);e.length>0&&e[0]&&t.setHeader(e[0]);const r=N.flatMap(i=>v(i));t.setExposedModels(r);const n=typeof c=="object"&&c.cachePath?c.cachePath:M.resolve(process.cwd(),"node_modules",".cache","moonflower"),g=await A(T,{incremental:c!==!1,cachePath:n,timestampCache:{},profiling:m,tsconfigPath:M.resolve(o)});t.setStats({discoveredRouterFiles:F.map(i=>({path:i.fileName,routers:i.routers.named.map(y=>({name:y,endpoints:g.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))})),explicitRouterFiles:h.map(i=>({path:i.fileName,routers:i.routers.named.map(y=>({name:y,endpoints:g.filter(u=>u.sourceFilePath===i.fileName).map(u=>`${u.method.toUpperCase()} ${u.path}`)}))}))}),t.setEndpoints(g),t.markAsReady()},A=async(s,o,a)=>{const l=o.profiling??"stats",c=performance.now(),m=[],t=[];for(const e of s){const r=_.getSourceFileTimestamp(e.sourceFile,o.timestampCache),n=o.incremental?C.SourceFileCache.getCachedResults(e.sourceFile,r,o.cachePath):null;n?(f.Logger.debug(`[${e.fileName}] Found cached results`),m.push({endpoints:n.endpoints,fileName:e.fileName,timing:0,endpointTimings:[]})):t.push({file:e,timestamp:r})}if(t.length===0)return l!=="off"&&f.Logger.info(`Router analysis took ${Math.round(performance.now()-c)}ms`),m.flatMap(e=>e.endpoints);const d=t.map(({file:e})=>({fileName:e.fileName,task:{taskId:j.randomUUID(),tsconfigPath:o.tsconfigPath,sourceFilePath:e.sourceFile.getFilePath(),routerNames:e.routers.named,filterEndpointPaths:a}})),h=new D.WorkerPool(V(),d.length);let F;try{F=await h.runAll(d.map(e=>e.task))}finally{h.terminate()}const N=new Map;for(const{file:e}of t)N.set(e.fileName,{endpoints:[],fileName:e.fileName,timing:0,endpointTimings:[]});for(let e=0;e<F.length;e++){const r=F[e],n=d[e].fileName,g=N.get(n);if("error"in r){f.Logger.error(`[${n}] Worker error: ${r.error}`);continue}g.endpoints=r.endpoints,g.endpointTimings=r.endpointTimings,g.timing=r.endpointTimings.reduce((i,y)=>i+y.timing,0)}for(const{file:e,timestamp:r}of t){const n=N.get(e.fileName);n.endpoints.length>0&&C.SourceFileCache.cacheResults(e.sourceFile,r,o.cachePath,n.endpoints)}const T=[...m,...Array.from(N.values())];return l!=="off"&&f.Logger.info(`Router analysis took ${Math.round(performance.now()-c)}ms`),l==="stats"?T.map(e=>({fileName:e.fileName,timeTaken:e.timing})).sort((e,r)=>r.timeTaken-e.timeTaken).filter(e=>e.timeTaken>500).forEach(e=>{f.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`)}):l==="debug"&&T.map(e=>({fileName:e.fileName,timeTaken:e.timing,endpointTimings:e.endpointTimings})).sort((e,r)=>r.timeTaken-e.timeTaken).forEach(e=>{f.Logger.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`),e.endpointTimings.sort((r,n)=>n.timing-r.timing).forEach(r=>{f.Logger.info(` - ${r.method} ${r.path} (${Math.round(r.timing)}ms)`),r.sectionTimings.filter(n=>n.timing>=1).sort((n,g)=>g.timing-n.timing).forEach(n=>{f.Logger.info(` - ${n.section}: ${Math.round(n.timing)}ms`)})})}),T.flatMap(e=>e.endpoints)},E=s=>{const o=R.discoverImportedName({sourceFile:s,originalName:"useApiHeader"});if(!o)return null;const a=s.forEachChildAsArray().filter(t=>t.isKind(S.SyntaxKind.ExpressionStatement)).find(t=>o&&t.getText().startsWith(o));if(!a)return null;const l=a.getFirstDescendantByKindOrThrow(S.SyntaxKind.ObjectLiteralExpression),c=H.getValuesOfObjectLiteral(l),m=t=>typeof t=="string"||Array.isArray(t)&&t.every(d=>typeof d=="string")?t:t.reduce((d,h)=>typeof h=="string"?d:{...d,[h.identifier]:m(h.value)},{});return m(c)},v=s=>{const o=[],a=R.discoverImportedName({sourceFile:s,originalName:"useExposeApiModel"}),l=R.discoverImportedName({sourceFile:s,originalName:"useExposeNamedApiModels"});return s.forEachChildAsArray().filter(c=>c.isKind(S.SyntaxKind.ExpressionStatement)).map(c=>{if(a&&c.getText().startsWith(a)){const d=(c.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!d)return;o.push(k.parseExposedModel(d));return}if(l&&c.getText().startsWith(l)){const d=(c.getFirstChild()?.getChildrenOfKind(S.SyntaxKind.SyntaxList)||[])[0].getFirstChild();if(!d)return;k.parseNamedExposedModels(d).forEach(F=>o.push(F))}}),o};exports.analyzeMultipleSourceFiles=A;exports.analyzeSourceFileApiHeader=E;exports.analyzeSourceFileExposedModels=v;exports.prepareOpenApiSpec=G;
|
|
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 endpoint across all uncached files\n\tconst OPERATIONS = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst operationsPattern = OPERATIONS.join('|')\n\n\ttype FileTask = { task: WorkerTask; fileName: string }\n\tconst allTasks: FileTask[] = []\n\n\tfor (const { file } of uncached) {\n\t\tfor (const routerName of file.routers.named) {\n\t\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${operationsPattern})`)\n\t\t\tlet endpointIndex = 0\n\t\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\t\tconst nodeText = node.getText()\n\t\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!filterEndpointPaths ||\n\t\t\t\t\t\tfilterEndpointPaths.some((p) => resolveEndpointPath(node)?.includes(p))\n\t\t\t\t\t) {\n\t\t\t\t\t\tallTasks.push({\n\t\t\t\t\t\t\tfileName: file.fileName,\n\t\t\t\t\t\t\ttask: {\n\t\t\t\t\t\t\t\ttaskId: crypto.randomUUID(),\n\t\t\t\t\t\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\t\t\t\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\t\t\t\t\t\trouterName,\n\t\t\t\t\t\t\t\tendpointIndex,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tendpointIndex++\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\t// Dispatch all tasks to the worker pool\n\tconst pool = new WorkerPool(resolveWorkerUrl())\n\n\ttype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\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// Group results by file\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.push(result.endpoint)\n\t\tfileResult.timing += result.timing\n\t\tfileResult.endpointTimings.push({\n\t\t\tmethod: result.endpoint.method,\n\t\t\tpath: result.endpoint.path,\n\t\t\ttiming: result.timing,\n\t\t\tsectionTimings: result.sectionTimings,\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","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","operationsPattern","allTasks","routerName","routerPattern","endpointIndex","node","nodeText","crypto","pool","WorkerPool","results","ft","byFile","i","result","fileName","fileResult","analyzedFiles","a","b","t","ep","s","sourceFile","nameOfUseApiHeader","discoverImportedName","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 * @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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzerModule.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C,OAAO,EACN,oBAAoB,EAEpB,MAAM,4DAA4D,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAkB,MAAM,2BAA2B,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAA0B,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEjF,OAAO,EAAiB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAK9D,KAAK,KAAK,GAAG;IACZ,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;IACnD,WAAW,CAAC,EACT,OAAO,GACP;QACA,SAAS,EAAE,MAAM,CAAA;KAChB,CAAA;IACJ,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,CAAA;AAED,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,aAAa,EAAE,CAAA;CAAE,CAAA;AAMvG,eAAO,MAAM,kBAAkB,8FAO5B,KAAK,KAAG,OAAO,CAAC,IAAI,CA0GtB,CAAA;AAED,eAAO,MAAM,0BAA0B,UAC/B,oBAAoB,EAAE,UACrB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;IACrC,YAAY,EAAE,MAAM,CAAA;CACpB,wBACqB,MAAM,EAAE,KAC5B,OAAO,CAAC,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"analyzerModule.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/analyzerModule.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAc,MAAM,UAAU,CAAA;AAGjD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C,OAAO,EACN,oBAAoB,EAEpB,MAAM,4DAA4D,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAkB,MAAM,2BAA2B,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAA0B,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEjF,OAAO,EAAiB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAK9D,KAAK,KAAK,GAAG;IACZ,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;IACnD,WAAW,CAAC,EACT,OAAO,GACP;QACA,SAAS,EAAE,MAAM,CAAA;KAChB,CAAA;IACJ,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,CAAA;AAED,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,aAAa,EAAE,CAAA;CAAE,CAAA;AAMvG,eAAO,MAAM,kBAAkB,8FAO5B,KAAK,KAAG,OAAO,CAAC,IAAI,CA0GtB,CAAA;AAED,eAAO,MAAM,0BAA0B,UAC/B,oBAAoB,EAAE,UACrB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;IACrC,YAAY,EAAE,MAAM,CAAA;CACpB,wBACqB,MAAM,EAAE,KAC5B,OAAO,CAAC,YAAY,EAAE,CA+HxB,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,UAClB;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAA;CACrC,wBACqB,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgBhF,CAAA;AAED,eAAO,MAAM,0BAA0B,SAChC,oBAAoB,wBACJ,MAAM,EAAE,KAC5B;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,eAAe,EAAE,cAAc,EAAE,CAAA;CAgChE,CAAA;AAED,eAAO,MAAM,0BAA0B,eAAgB,UAAU,KAAG,aAAa,GAAG,IAyCnF,CAAA;AAED,eAAO,MAAM,8BAA8B,eAAgB,UAAU,KAAG,gBAAgB,EA4CvF,CAAA"}
|
|
@@ -1,194 +1,179 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import * as
|
|
4
|
-
import { fileURLToPath as
|
|
5
|
-
import { Project as
|
|
1
|
+
import $ from "crypto";
|
|
2
|
+
import { existsSync as v } from "fs";
|
|
3
|
+
import * as M from "path";
|
|
4
|
+
import { fileURLToPath as O } from "url";
|
|
5
|
+
import { Project as L, SyntaxKind as x } from "ts-morph";
|
|
6
6
|
import { Logger as u } from "../../utils/logger.mjs";
|
|
7
|
-
import { discoverImportedName as
|
|
8
|
-
import { discoverRouterFiles as
|
|
7
|
+
import { discoverImportedName as E } from "../discoveryModule/discoverImports/discoverImports.mjs";
|
|
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
12
|
import { getValuesOfObjectLiteral as W } from "./nodeParsers.mjs";
|
|
13
|
-
import { parseExposedModel as
|
|
14
|
-
import { SourceFileCache as
|
|
15
|
-
import { WorkerPool as
|
|
13
|
+
import { parseExposedModel as K, parseNamedExposedModels as H } from "./parseExposedModels.mjs";
|
|
14
|
+
import { SourceFileCache as P } from "./sourceFileCache.mjs";
|
|
15
|
+
import { WorkerPool as I } from "./workerPool.mjs";
|
|
16
16
|
function B() {
|
|
17
|
-
const
|
|
18
|
-
for (const
|
|
19
|
-
const
|
|
20
|
-
if (O(
|
|
21
|
-
return
|
|
17
|
+
const m = ["./analyzerWorker.mjs", "./analyzerWorker.test.mjs"];
|
|
18
|
+
for (const i of m) {
|
|
19
|
+
const p = new URL(i, import.meta.url);
|
|
20
|
+
if (v(O(p)))
|
|
21
|
+
return p;
|
|
22
22
|
}
|
|
23
23
|
throw new Error(
|
|
24
24
|
"analyzerWorker.mjs not found. Run yarn build, or run tests via yarn test (which compiles the worker first)."
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
const ae = async ({
|
|
28
|
-
logLevel:
|
|
29
|
-
tsconfigPath:
|
|
30
|
-
sourceFilePaths:
|
|
31
|
-
sourceFileDiscovery:
|
|
32
|
-
incremental:
|
|
28
|
+
logLevel: m,
|
|
29
|
+
tsconfigPath: i,
|
|
30
|
+
sourceFilePaths: p,
|
|
31
|
+
sourceFileDiscovery: c,
|
|
32
|
+
incremental: n,
|
|
33
33
|
profiling: f = "stats"
|
|
34
34
|
}) => {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
35
|
+
const t = U.getInstance();
|
|
36
|
+
if (t.isReady())
|
|
37
37
|
return;
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
tsConfigFilePath:
|
|
38
|
+
m && u.setLevel(m), u.info("Preparing OpenAPI spec");
|
|
39
|
+
const a = new L({
|
|
40
|
+
tsConfigFilePath: M.resolve(i),
|
|
41
41
|
skipFileDependencyResolution: !0
|
|
42
|
-
}), { explicitRouters: h, discoveredRouterFiles:
|
|
43
|
-
const
|
|
44
|
-
fileName:
|
|
45
|
-
sourceFile:
|
|
46
|
-
routers: z(
|
|
42
|
+
}), { explicitRouters: h, discoveredRouterFiles: F, allSourceFiles: N } = (() => {
|
|
43
|
+
const l = (p ?? []).map((d) => M.resolve(d)).map((d) => a.getSourceFileOrThrow(d)), R = l.flatMap((d) => ({
|
|
44
|
+
fileName: d.getFilePath(),
|
|
45
|
+
sourceFile: d,
|
|
46
|
+
routers: z(d)
|
|
47
47
|
})), { discoveredRouterFiles: A, discoveredSourceFiles: C } = (() => {
|
|
48
|
-
if (
|
|
48
|
+
if (c === !1)
|
|
49
49
|
return { discoveredRouterFiles: [], discoveredSourceFiles: [] };
|
|
50
|
-
const
|
|
51
|
-
targetPath: typeof
|
|
52
|
-
tsConfigPath:
|
|
50
|
+
const d = performance.now(), k = j({
|
|
51
|
+
targetPath: typeof c == "object" ? c.rootPath : ".",
|
|
52
|
+
tsConfigPath: i
|
|
53
53
|
});
|
|
54
|
-
return f !== "off" && u.info(`File discovery took ${Math.round(performance.now() -
|
|
55
|
-
})(), S =
|
|
56
|
-
(
|
|
54
|
+
return f !== "off" && u.info(`File discovery took ${Math.round(performance.now() - d)}ms`), k;
|
|
55
|
+
})(), S = l.reduce(
|
|
56
|
+
(d, k) => d.some((w) => w.getFilePath() === k.getFilePath()) ? d : d.concat(k),
|
|
57
57
|
C
|
|
58
58
|
);
|
|
59
|
-
return { explicitRouters:
|
|
60
|
-
})(),
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
),
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
incremental:
|
|
69
|
-
cachePath:
|
|
59
|
+
return { explicitRouters: R, discoveredRouterFiles: A, allSourceFiles: S };
|
|
60
|
+
})(), T = h.reduce(
|
|
61
|
+
(s, y) => s.some((l) => l.fileName === y.fileName) ? s : s.concat(y),
|
|
62
|
+
F
|
|
63
|
+
), e = N.flatMap((s) => V(s)).filter((s) => !!s);
|
|
64
|
+
e.length > 0 && e[0] && t.setHeader(e[0]);
|
|
65
|
+
const o = N.flatMap((s) => _(s));
|
|
66
|
+
t.setExposedModels(o);
|
|
67
|
+
const r = typeof n == "object" && n.cachePath ? n.cachePath : M.resolve(process.cwd(), "node_modules", ".cache", "moonflower"), g = await D(T, {
|
|
68
|
+
incremental: n !== !1,
|
|
69
|
+
cachePath: r,
|
|
70
70
|
timestampCache: {},
|
|
71
71
|
profiling: f,
|
|
72
|
-
tsconfigPath:
|
|
72
|
+
tsconfigPath: M.resolve(i)
|
|
73
73
|
});
|
|
74
|
-
|
|
75
|
-
discoveredRouterFiles:
|
|
76
|
-
path:
|
|
77
|
-
routers:
|
|
78
|
-
name:
|
|
79
|
-
endpoints:
|
|
74
|
+
t.setStats({
|
|
75
|
+
discoveredRouterFiles: F.map((s) => ({
|
|
76
|
+
path: s.fileName,
|
|
77
|
+
routers: s.routers.named.map((y) => ({
|
|
78
|
+
name: y,
|
|
79
|
+
endpoints: g.filter((l) => l.sourceFilePath === s.fileName).map((l) => `${l.method.toUpperCase()} ${l.path}`)
|
|
80
80
|
}))
|
|
81
81
|
})),
|
|
82
|
-
explicitRouterFiles: h.map((
|
|
83
|
-
path:
|
|
84
|
-
routers:
|
|
85
|
-
name:
|
|
86
|
-
endpoints:
|
|
82
|
+
explicitRouterFiles: h.map((s) => ({
|
|
83
|
+
path: s.fileName,
|
|
84
|
+
routers: s.routers.named.map((y) => ({
|
|
85
|
+
name: y,
|
|
86
|
+
endpoints: g.filter((l) => l.sourceFilePath === s.fileName).map((l) => `${l.method.toUpperCase()} ${l.path}`)
|
|
87
87
|
}))
|
|
88
88
|
}))
|
|
89
|
-
}),
|
|
90
|
-
}, D = async (
|
|
91
|
-
const
|
|
92
|
-
for (const e of
|
|
93
|
-
const o = b(e.sourceFile,
|
|
94
|
-
|
|
89
|
+
}), t.setEndpoints(g), t.markAsReady();
|
|
90
|
+
}, D = async (m, i, p) => {
|
|
91
|
+
const c = i.profiling ?? "stats", n = performance.now(), f = [], t = [];
|
|
92
|
+
for (const e of m) {
|
|
93
|
+
const o = b(e.sourceFile, i.timestampCache), r = i.incremental ? P.getCachedResults(e.sourceFile, o, i.cachePath) : null;
|
|
94
|
+
r ? (u.debug(`[${e.fileName}] Found cached results`), f.push({ endpoints: r.endpoints, fileName: e.fileName, timing: 0, endpointTimings: [] })) : t.push({ file: e, timestamp: o });
|
|
95
95
|
}
|
|
96
|
-
if (
|
|
97
|
-
return
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
e.sourceFile.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
fileName: e.fileName,
|
|
107
|
-
task: {
|
|
108
|
-
taskId: w.randomUUID(),
|
|
109
|
-
tsconfigPath: s.tsconfigPath,
|
|
110
|
-
sourceFilePath: e.sourceFile.getFilePath(),
|
|
111
|
-
routerName: o,
|
|
112
|
-
endpointIndex: n
|
|
113
|
-
}
|
|
114
|
-
}), n++);
|
|
115
|
-
});
|
|
96
|
+
if (t.length === 0)
|
|
97
|
+
return c !== "off" && u.info(`Router analysis took ${Math.round(performance.now() - n)}ms`), f.flatMap((e) => e.endpoints);
|
|
98
|
+
const a = t.map(({ file: e }) => ({
|
|
99
|
+
fileName: e.fileName,
|
|
100
|
+
task: {
|
|
101
|
+
taskId: $.randomUUID(),
|
|
102
|
+
tsconfigPath: i.tsconfigPath,
|
|
103
|
+
sourceFilePath: e.sourceFile.getFilePath(),
|
|
104
|
+
routerNames: e.routers.named,
|
|
105
|
+
filterEndpointPaths: p
|
|
116
106
|
}
|
|
117
|
-
|
|
118
|
-
let
|
|
107
|
+
})), h = new I(B(), a.length);
|
|
108
|
+
let F;
|
|
119
109
|
try {
|
|
120
|
-
|
|
110
|
+
F = await h.runAll(a.map((e) => e.task));
|
|
121
111
|
} finally {
|
|
122
|
-
|
|
112
|
+
h.terminate();
|
|
123
113
|
}
|
|
124
|
-
const
|
|
125
|
-
for (const { file: e } of
|
|
126
|
-
|
|
127
|
-
for (let e = 0; e <
|
|
128
|
-
const o =
|
|
114
|
+
const N = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const { file: e } of t)
|
|
116
|
+
N.set(e.fileName, { endpoints: [], fileName: e.fileName, timing: 0, endpointTimings: [] });
|
|
117
|
+
for (let e = 0; e < F.length; e++) {
|
|
118
|
+
const o = F[e], r = a[e].fileName, g = N.get(r);
|
|
129
119
|
if ("error" in o) {
|
|
130
|
-
u.error(`[${
|
|
120
|
+
u.error(`[${r}] Worker error: ${o.error}`);
|
|
131
121
|
continue;
|
|
132
122
|
}
|
|
133
|
-
|
|
134
|
-
method: o.endpoint.method,
|
|
135
|
-
path: o.endpoint.path,
|
|
136
|
-
timing: o.timing,
|
|
137
|
-
sectionTimings: o.sectionTimings
|
|
138
|
-
});
|
|
123
|
+
g.endpoints = o.endpoints, g.endpointTimings = o.endpointTimings, g.timing = o.endpointTimings.reduce((s, y) => s + y.timing, 0);
|
|
139
124
|
}
|
|
140
|
-
for (const { file: e, timestamp: o } of
|
|
141
|
-
const
|
|
142
|
-
|
|
125
|
+
for (const { file: e, timestamp: o } of t) {
|
|
126
|
+
const r = N.get(e.fileName);
|
|
127
|
+
r.endpoints.length > 0 && P.cacheResults(e.sourceFile, o, i.cachePath, r.endpoints);
|
|
143
128
|
}
|
|
144
|
-
const T = [...f, ...Array.from(
|
|
145
|
-
return
|
|
129
|
+
const T = [...f, ...Array.from(N.values())];
|
|
130
|
+
return c !== "off" && u.info(`Router analysis took ${Math.round(performance.now() - n)}ms`), c === "stats" ? T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing })).sort((e, o) => o.timeTaken - e.timeTaken).filter((e) => e.timeTaken > 500).forEach((e) => {
|
|
146
131
|
u.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`);
|
|
147
|
-
}) :
|
|
148
|
-
u.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`), e.endpointTimings.sort((o,
|
|
149
|
-
u.info(` - ${o.method} ${o.path} (${Math.round(o.timing)}ms)`), o.sectionTimings.filter((
|
|
150
|
-
u.info(` - ${
|
|
132
|
+
}) : c === "debug" && T.map((e) => ({ fileName: e.fileName, timeTaken: e.timing, endpointTimings: e.endpointTimings })).sort((e, o) => o.timeTaken - e.timeTaken).forEach((e) => {
|
|
133
|
+
u.info(`- [${e.fileName}] Took ${Math.round(e.timeTaken)}ms to analyze`), e.endpointTimings.sort((o, r) => r.timing - o.timing).forEach((o) => {
|
|
134
|
+
u.info(` - ${o.method} ${o.path} (${Math.round(o.timing)}ms)`), o.sectionTimings.filter((r) => r.timing >= 1).sort((r, g) => g.timing - r.timing).forEach((r) => {
|
|
135
|
+
u.info(` - ${r.section}: ${Math.round(r.timing)}ms`);
|
|
151
136
|
});
|
|
152
137
|
});
|
|
153
138
|
}), T.flatMap((e) => e.endpoints);
|
|
154
|
-
}, V = (
|
|
155
|
-
const
|
|
156
|
-
sourceFile:
|
|
139
|
+
}, V = (m) => {
|
|
140
|
+
const i = E({
|
|
141
|
+
sourceFile: m,
|
|
157
142
|
originalName: "useApiHeader"
|
|
158
143
|
});
|
|
159
|
-
if (!
|
|
144
|
+
if (!i)
|
|
160
145
|
return null;
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
146
|
+
const p = m.forEachChildAsArray().filter((t) => t.isKind(x.ExpressionStatement)).find((t) => i && t.getText().startsWith(i));
|
|
147
|
+
if (!p)
|
|
163
148
|
return null;
|
|
164
|
-
const
|
|
165
|
-
...
|
|
149
|
+
const c = p.getFirstDescendantByKindOrThrow(x.ObjectLiteralExpression), n = W(c), f = (t) => typeof t == "string" || Array.isArray(t) && t.every((a) => typeof a == "string") ? t : t.reduce((a, h) => typeof h == "string" ? a : {
|
|
150
|
+
...a,
|
|
166
151
|
[h.identifier]: f(h.value)
|
|
167
152
|
}, {});
|
|
168
|
-
return f(
|
|
169
|
-
}, _ = (
|
|
170
|
-
const
|
|
171
|
-
sourceFile:
|
|
153
|
+
return f(n);
|
|
154
|
+
}, _ = (m) => {
|
|
155
|
+
const i = [], p = E({
|
|
156
|
+
sourceFile: m,
|
|
172
157
|
originalName: "useExposeApiModel"
|
|
173
|
-
}),
|
|
174
|
-
sourceFile:
|
|
158
|
+
}), c = E({
|
|
159
|
+
sourceFile: m,
|
|
175
160
|
originalName: "useExposeNamedApiModels"
|
|
176
161
|
});
|
|
177
|
-
return
|
|
178
|
-
if (
|
|
179
|
-
const
|
|
180
|
-
if (!
|
|
162
|
+
return m.forEachChildAsArray().filter((n) => n.isKind(x.ExpressionStatement)).map((n) => {
|
|
163
|
+
if (p && n.getText().startsWith(p)) {
|
|
164
|
+
const a = (n.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
|
|
165
|
+
if (!a)
|
|
181
166
|
return;
|
|
182
|
-
|
|
167
|
+
i.push(K(a));
|
|
183
168
|
return;
|
|
184
169
|
}
|
|
185
|
-
if (
|
|
186
|
-
const
|
|
187
|
-
if (!
|
|
170
|
+
if (c && n.getText().startsWith(c)) {
|
|
171
|
+
const a = (n.getFirstChild()?.getChildrenOfKind(x.SyntaxList) || [])[0].getFirstChild();
|
|
172
|
+
if (!a)
|
|
188
173
|
return;
|
|
189
|
-
|
|
174
|
+
H(a).forEach((F) => i.push(F));
|
|
190
175
|
}
|
|
191
|
-
}),
|
|
176
|
+
}), i;
|
|
192
177
|
};
|
|
193
178
|
export {
|
|
194
179
|
D as analyzeMultipleSourceFiles,
|
|
@@ -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 endpoint across all uncached files\n\tconst OPERATIONS = ['get', 'post', 'put', 'delete', 'del', 'patch']\n\tconst operationsPattern = OPERATIONS.join('|')\n\n\ttype FileTask = { task: WorkerTask; fileName: string }\n\tconst allTasks: FileTask[] = []\n\n\tfor (const { file } of uncached) {\n\t\tfor (const routerName of file.routers.named) {\n\t\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${operationsPattern})`)\n\t\t\tlet endpointIndex = 0\n\t\t\tfile.sourceFile.forEachChild((node) => {\n\t\t\t\tconst nodeText = node.getText()\n\t\t\t\tif (routerPattern.test(nodeText)) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!filterEndpointPaths ||\n\t\t\t\t\t\tfilterEndpointPaths.some((p) => resolveEndpointPath(node)?.includes(p))\n\t\t\t\t\t) {\n\t\t\t\t\t\tallTasks.push({\n\t\t\t\t\t\t\tfileName: file.fileName,\n\t\t\t\t\t\t\ttask: {\n\t\t\t\t\t\t\t\ttaskId: crypto.randomUUID(),\n\t\t\t\t\t\t\t\ttsconfigPath: config.tsconfigPath,\n\t\t\t\t\t\t\t\tsourceFilePath: file.sourceFile.getFilePath(),\n\t\t\t\t\t\t\t\trouterName,\n\t\t\t\t\t\t\t\tendpointIndex,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tendpointIndex++\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\t// Dispatch all tasks to the worker pool\n\tconst pool = new WorkerPool(resolveWorkerUrl())\n\n\ttype EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }\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// Group results by file\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.push(result.endpoint)\n\t\tfileResult.timing += result.timing\n\t\tfileResult.endpointTimings.push({\n\t\t\tmethod: result.endpoint.method,\n\t\t\tpath: result.endpoint.path,\n\t\t\ttiming: result.timing,\n\t\t\tsectionTimings: result.sectionTimings,\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","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","operationsPattern","allTasks","routerName","routerPattern","endpointIndex","node","nodeText","crypto","pool","WorkerPool","results","ft","byFile","i","result","fileName","fileResult","analyzedFiles","a","b","t","ep","s","sourceFile","nameOfUseApiHeader","discoverImportedName","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 * @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,2 +1,2 @@
|
|
|
1
|
-
"use strict";const
|
|
1
|
+
"use strict";const E=require("ts-morph"),s=require("worker_threads"),m=require("./nodeParsers.cjs"),F=require("./parseEndpoint.cjs"),T=["get","post","put","delete","del","patch"],I=T.join("|");let i=null,u=null;function j(e){return(!i||u!==e)&&(i=new E.Project({tsConfigFilePath:e,skipFileDependencyResolution:!0}),u=e),i}s.parentPort.on("message",e=>{try{const r=j(e.tsconfigPath);let t=r.getSourceFile(e.sourceFilePath);t||(t=r.addSourceFileAtPath(e.sourceFilePath));const c=[],p=[];e.routerNames.forEach(l=>{const d=new RegExp(`${l}\\.(?:${I})`);t.forEachChild(n=>{if(!d.test(n.getText()))return;if(e.filterEndpointPaths){const f=m.resolveEndpointPath(n)??"";if(!e.filterEndpointPaths.some(g=>f.includes(g)))return}const h=performance.now(),{endpoint:o,sectionTimings:P}=F.parseEndpoint(n,e.sourceFilePath);p.push({method:o.method,path:o.path,timing:performance.now()-h,sectionTimings:P}),c.push(o)})});const a={taskId:e.taskId,endpoints:c,endpointTimings:p};s.parentPort.postMessage(a)}catch(r){const t={taskId:e.taskId,error:String(r)};s.parentPort.postMessage(t)}});
|
|
2
2
|
//# sourceMappingURL=analyzerWorker.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzerWorker.cjs","sources":["../../../src/openapi/analyzerModule/analyzerWorker.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"analyzerWorker.cjs","sources":["../../../src/openapi/analyzerModule/analyzerWorker.ts"],"sourcesContent":["import { Project } from 'ts-morph'\nimport { parentPort } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint } from './parseEndpoint'\nimport { EndpointTiming, WorkerResult, WorkerTask } from './workerPool'\n\nconst OPERATIONS = ['get', 'post', 'put', 'delete', 'del', 'patch']\nconst OPERATIONS_PATTERN = OPERATIONS.join('|')\n\nlet project: Project | null = null\nlet currentTsconfigPath: string | null = null\n\nfunction getProject(tsconfigPath: string): Project {\n\tif (!project || currentTsconfigPath !== tsconfigPath) {\n\t\tproject = new Project({\n\t\t\ttsConfigFilePath: tsconfigPath,\n\t\t\tskipFileDependencyResolution: true,\n\t\t})\n\t\tcurrentTsconfigPath = tsconfigPath\n\t}\n\treturn project\n}\n\nparentPort!.on('message', (task: WorkerTask) => {\n\ttry {\n\t\tconst proj = getProject(task.tsconfigPath)\n\n\t\tlet sourceFile = proj.getSourceFile(task.sourceFilePath)\n\t\tif (!sourceFile) {\n\t\t\tsourceFile = proj.addSourceFileAtPath(task.sourceFilePath)\n\t\t}\n\n\t\tconst endpoints: EndpointData[] = []\n\t\tconst endpointTimings: EndpointTiming[] = []\n\n\t\ttask.routerNames.forEach((routerName) => {\n\t\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${OPERATIONS_PATTERN})`)\n\t\t\tsourceFile!.forEachChild((node) => {\n\t\t\t\tif (!routerPattern.test(node.getText())) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (task.filterEndpointPaths) {\n\t\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\t\t\t\t\tif (!task.filterEndpointPaths.some((p) => endpointPath.includes(p))) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, task.sourceFilePath)\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\n\t\tconst result: WorkerResult = {\n\t\t\ttaskId: task.taskId,\n\t\t\tendpoints,\n\t\t\tendpointTimings,\n\t\t}\n\t\tparentPort!.postMessage(result)\n\t} catch (err) {\n\t\tconst result: WorkerResult = {\n\t\t\ttaskId: task.taskId,\n\t\t\terror: String(err),\n\t\t}\n\t\tparentPort!.postMessage(result)\n\t}\n})\n"],"names":["OPERATIONS","OPERATIONS_PATTERN","project","currentTsconfigPath","getProject","tsconfigPath","Project","parentPort","task","proj","sourceFile","endpoints","endpointTimings","routerName","routerPattern","node","endpointPath","resolveEndpointPath","p","t1","endpoint","sectionTimings","parseEndpoint","result","err"],"mappings":"qIAQMA,EAAa,CAAC,MAAO,OAAQ,MAAO,SAAU,MAAO,OAAO,EAC5DC,EAAqBD,EAAW,KAAK,GAAG,EAE9C,IAAIE,EAA0B,KAC1BC,EAAqC,KAEzC,SAASC,EAAWC,EAA+B,CAC9C,OAAA,CAACH,GAAWC,IAAwBE,KACvCH,EAAU,IAAII,EAAAA,QAAQ,CACrB,iBAAkBD,EAClB,6BAA8B,EAAA,CAC9B,EACqBF,EAAAE,GAEhBH,CACR,CAEAK,EAAAA,WAAY,GAAG,UAAYC,GAAqB,CAC3C,GAAA,CACG,MAAAC,EAAOL,EAAWI,EAAK,YAAY,EAEzC,IAAIE,EAAaD,EAAK,cAAcD,EAAK,cAAc,EAClDE,IACSA,EAAAD,EAAK,oBAAoBD,EAAK,cAAc,GAG1D,MAAMG,EAA4B,CAAC,EAC7BC,EAAoC,CAAC,EAEtCJ,EAAA,YAAY,QAASK,GAAe,CACxC,MAAMC,EAAgB,IAAI,OAAO,GAAGD,CAAU,SAASZ,CAAkB,GAAG,EAChES,EAAA,aAAcK,GAAS,CAClC,GAAI,CAACD,EAAc,KAAKC,EAAK,QAAS,CAAA,EACrC,OAGD,GAAIP,EAAK,oBAAqB,CACvB,MAAAQ,EAAeC,EAAAA,oBAAoBF,CAAI,GAAK,GAC9C,GAAA,CAACP,EAAK,oBAAoB,KAAMU,GAAMF,EAAa,SAASE,CAAC,CAAC,EACjE,MACD,CAGK,MAAAC,EAAK,YAAY,IAAI,EACrB,CAAE,SAAAC,EAAU,eAAAC,GAAmBC,EAAc,cAAAP,EAAMP,EAAK,cAAc,EAC5EI,EAAgB,KAAK,CACpB,OAAQQ,EAAS,OACjB,KAAMA,EAAS,KACf,OAAQ,YAAY,IAAA,EAAQD,EAC5B,eAAAE,CAAA,CACA,EACDV,EAAU,KAAKS,CAAQ,CAAA,CACvB,CAAA,CACD,EAED,MAAMG,EAAuB,CAC5B,OAAQf,EAAK,OACb,UAAAG,EACA,gBAAAC,CACD,EACAL,EAAA,WAAY,YAAYgB,CAAM,QACtBC,EAAK,CACb,MAAMD,EAAuB,CAC5B,OAAQf,EAAK,OACb,MAAO,OAAOgB,CAAG,CAClB,EACAjB,EAAA,WAAY,YAAYgB,CAAM,CAAA,CAEhC,CAAC"}
|
|
@@ -1,44 +1,52 @@
|
|
|
1
1
|
import { Project as g } from "ts-morph";
|
|
2
2
|
import { parentPort as i } from "worker_threads";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { resolveEndpointPath as E } from "./nodeParsers.mjs";
|
|
4
|
+
import { parseEndpoint as F } from "./parseEndpoint.mjs";
|
|
5
|
+
const T = ["get", "post", "put", "delete", "del", "patch"], I = T.join("|");
|
|
5
6
|
let s = null, l = null;
|
|
6
|
-
function
|
|
7
|
-
return (!s || l !==
|
|
8
|
-
tsConfigFilePath:
|
|
7
|
+
function j(t) {
|
|
8
|
+
return (!s || l !== t) && (s = new g({
|
|
9
|
+
tsConfigFilePath: t,
|
|
9
10
|
skipFileDependencyResolution: !0
|
|
10
|
-
}), l =
|
|
11
|
+
}), l = t), s;
|
|
11
12
|
}
|
|
12
|
-
i.on("message", (
|
|
13
|
+
i.on("message", (t) => {
|
|
13
14
|
try {
|
|
14
|
-
const
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
const o = j(t.tsconfigPath);
|
|
16
|
+
let e = o.getSourceFile(t.sourceFilePath);
|
|
17
|
+
e || (e = o.addSourceFileAtPath(t.sourceFilePath));
|
|
18
|
+
const c = [], p = [];
|
|
19
|
+
t.routerNames.forEach((a) => {
|
|
20
|
+
const h = new RegExp(`${a}\\.(?:${I})`);
|
|
21
|
+
e.forEachChild((n) => {
|
|
22
|
+
if (!h.test(n.getText()))
|
|
23
|
+
return;
|
|
24
|
+
if (t.filterEndpointPaths) {
|
|
25
|
+
const f = E(n) ?? "";
|
|
26
|
+
if (!t.filterEndpointPaths.some((P) => f.includes(P)))
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const d = performance.now(), { endpoint: r, sectionTimings: m } = F(n, t.sourceFilePath);
|
|
30
|
+
p.push({
|
|
31
|
+
method: r.method,
|
|
32
|
+
path: r.path,
|
|
33
|
+
timing: performance.now() - d,
|
|
34
|
+
sectionTimings: m
|
|
35
|
+
}), c.push(r);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
const u = {
|
|
39
|
+
taskId: t.taskId,
|
|
40
|
+
endpoints: c,
|
|
41
|
+
endpointTimings: p
|
|
34
42
|
};
|
|
35
|
-
i.postMessage(
|
|
36
|
-
} catch (
|
|
37
|
-
const
|
|
38
|
-
taskId:
|
|
39
|
-
error: String(
|
|
43
|
+
i.postMessage(u);
|
|
44
|
+
} catch (o) {
|
|
45
|
+
const e = {
|
|
46
|
+
taskId: t.taskId,
|
|
47
|
+
error: String(o)
|
|
40
48
|
};
|
|
41
|
-
i.postMessage(
|
|
49
|
+
i.postMessage(e);
|
|
42
50
|
}
|
|
43
51
|
});
|
|
44
52
|
//# sourceMappingURL=analyzerWorker.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzerWorker.mjs","sources":["../../../src/openapi/analyzerModule/analyzerWorker.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"analyzerWorker.mjs","sources":["../../../src/openapi/analyzerModule/analyzerWorker.ts"],"sourcesContent":["import { Project } from 'ts-morph'\nimport { parentPort } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { resolveEndpointPath } from './nodeParsers'\nimport { parseEndpoint } from './parseEndpoint'\nimport { EndpointTiming, WorkerResult, WorkerTask } from './workerPool'\n\nconst OPERATIONS = ['get', 'post', 'put', 'delete', 'del', 'patch']\nconst OPERATIONS_PATTERN = OPERATIONS.join('|')\n\nlet project: Project | null = null\nlet currentTsconfigPath: string | null = null\n\nfunction getProject(tsconfigPath: string): Project {\n\tif (!project || currentTsconfigPath !== tsconfigPath) {\n\t\tproject = new Project({\n\t\t\ttsConfigFilePath: tsconfigPath,\n\t\t\tskipFileDependencyResolution: true,\n\t\t})\n\t\tcurrentTsconfigPath = tsconfigPath\n\t}\n\treturn project\n}\n\nparentPort!.on('message', (task: WorkerTask) => {\n\ttry {\n\t\tconst proj = getProject(task.tsconfigPath)\n\n\t\tlet sourceFile = proj.getSourceFile(task.sourceFilePath)\n\t\tif (!sourceFile) {\n\t\t\tsourceFile = proj.addSourceFileAtPath(task.sourceFilePath)\n\t\t}\n\n\t\tconst endpoints: EndpointData[] = []\n\t\tconst endpointTimings: EndpointTiming[] = []\n\n\t\ttask.routerNames.forEach((routerName) => {\n\t\t\tconst routerPattern = new RegExp(`${routerName}\\\\.(?:${OPERATIONS_PATTERN})`)\n\t\t\tsourceFile!.forEachChild((node) => {\n\t\t\t\tif (!routerPattern.test(node.getText())) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (task.filterEndpointPaths) {\n\t\t\t\t\tconst endpointPath = resolveEndpointPath(node) ?? ''\n\t\t\t\t\tif (!task.filterEndpointPaths.some((p) => endpointPath.includes(p))) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst t1 = performance.now()\n\t\t\t\tconst { endpoint, sectionTimings } = parseEndpoint(node, task.sourceFilePath)\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\n\t\tconst result: WorkerResult = {\n\t\t\ttaskId: task.taskId,\n\t\t\tendpoints,\n\t\t\tendpointTimings,\n\t\t}\n\t\tparentPort!.postMessage(result)\n\t} catch (err) {\n\t\tconst result: WorkerResult = {\n\t\t\ttaskId: task.taskId,\n\t\t\terror: String(err),\n\t\t}\n\t\tparentPort!.postMessage(result)\n\t}\n})\n"],"names":["OPERATIONS","OPERATIONS_PATTERN","project","currentTsconfigPath","getProject","tsconfigPath","Project","parentPort","task","proj","sourceFile","endpoints","endpointTimings","routerName","routerPattern","node","endpointPath","resolveEndpointPath","p","t1","endpoint","sectionTimings","parseEndpoint","result","err"],"mappings":";;;;AAQA,MAAMA,IAAa,CAAC,OAAO,QAAQ,OAAO,UAAU,OAAO,OAAO,GAC5DC,IAAqBD,EAAW,KAAK,GAAG;AAE9C,IAAIE,IAA0B,MAC1BC,IAAqC;AAEzC,SAASC,EAAWC,GAA+B;AAC9C,UAAA,CAACH,KAAWC,MAAwBE,OACvCH,IAAU,IAAII,EAAQ;AAAA,IACrB,kBAAkBD;AAAA,IAClB,8BAA8B;AAAA,EAAA,CAC9B,GACqBF,IAAAE,IAEhBH;AACR;AAEAK,EAAY,GAAG,WAAW,CAACC,MAAqB;AAC3C,MAAA;AACG,UAAAC,IAAOL,EAAWI,EAAK,YAAY;AAEzC,QAAIE,IAAaD,EAAK,cAAcD,EAAK,cAAc;AACvD,IAAKE,MACSA,IAAAD,EAAK,oBAAoBD,EAAK,cAAc;AAG1D,UAAMG,IAA4B,CAAC,GAC7BC,IAAoC,CAAC;AAEtC,IAAAJ,EAAA,YAAY,QAAQ,CAACK,MAAe;AACxC,YAAMC,IAAgB,IAAI,OAAO,GAAGD,CAAU,SAASZ,CAAkB,GAAG;AAChE,MAAAS,EAAA,aAAa,CAACK,MAAS;AAClC,YAAI,CAACD,EAAc,KAAKC,EAAK,QAAS,CAAA;AACrC;AAGD,YAAIP,EAAK,qBAAqB;AACvB,gBAAAQ,IAAeC,EAAoBF,CAAI,KAAK;AAC9C,cAAA,CAACP,EAAK,oBAAoB,KAAK,CAACU,MAAMF,EAAa,SAASE,CAAC,CAAC;AACjE;AAAA,QACD;AAGK,cAAAC,IAAK,YAAY,IAAI,GACrB,EAAE,UAAAC,GAAU,gBAAAC,MAAmBC,EAAcP,GAAMP,EAAK,cAAc;AAC5E,QAAAI,EAAgB,KAAK;AAAA,UACpB,QAAQQ,EAAS;AAAA,UACjB,MAAMA,EAAS;AAAA,UACf,QAAQ,YAAY,IAAA,IAAQD;AAAA,UAC5B,gBAAAE;AAAA,QAAA,CACA,GACDV,EAAU,KAAKS,CAAQ;AAAA,MAAA,CACvB;AAAA,IAAA,CACD;AAED,UAAMG,IAAuB;AAAA,MAC5B,QAAQf,EAAK;AAAA,MACb,WAAAG;AAAA,MACA,iBAAAC;AAAA,IACD;AACA,IAAAL,EAAY,YAAYgB,CAAM;AAAA,WACtBC,GAAK;AACb,UAAMD,IAAuB;AAAA,MAC5B,QAAQf,EAAK;AAAA,MACb,OAAO,OAAOgB,CAAG;AAAA,IAClB;AACA,IAAAjB,EAAY,YAAYgB,CAAM;AAAA,EAAA;AAEhC,CAAC;"}
|
|
@@ -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 l=require("os"),u=require("worker_threads");class d{constructor(e,t){this.queue=[],this.pending=new Map;const s=Math.max(1,Math.min(l.cpus().length-1,8)),o=t===void 0?s:Math.max(1,Math.min(s,t));this.workers=Array.from({length:o},()=>{const i=new u.Worker(e);return i.on("message",r=>{const n=this.pending.get(r.taskId);n&&(this.pending.delete(r.taskId),n(r)),this.idle.push(i),this.flush()}),i.on("error",r=>{for(const[n,h]of this.pending){h({taskId:n,error:String(r)}),this.pending.delete(n);break}this.idle.push(i),this.flush()}),i}),this.idle=[...this.workers]}run(e){return new Promise(t=>{if(this.idle.length>0){const s=this.idle.pop();this.pending.set(e.taskId,t),s.postMessage(e)}else this.queue.push({task:e,resolve:t})})}runAll(e){return Promise.all(e.map(t=>this.run(t)))}terminate(){this.workers.forEach(e=>e.terminate())}flush(){for(;this.queue.length>0&&this.idle.length>0;){const{task:e,resolve:t}=this.queue.shift(),s=this.idle.pop();this.pending.set(e.taskId,t),s.postMessage(e)}}}exports.WorkerPool=d;
|
|
2
2
|
//# sourceMappingURL=workerPool.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workerPool.cjs","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"sourcesContent":["import os from 'os'\nimport { Worker } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { SectionTiming } from './parseEndpoint'\n\nexport type WorkerTask = {\n\ttaskId: string\n\ttsconfigPath: string\n\tsourceFilePath: string\n\
|
|
1
|
+
{"version":3,"file":"workerPool.cjs","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"sourcesContent":["import os from 'os'\nimport { Worker } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { SectionTiming } from './parseEndpoint'\n\nexport type WorkerTask = {\n\ttaskId: string\n\ttsconfigPath: string\n\tsourceFilePath: string\n\trouterNames: string[]\n\tfilterEndpointPaths?: string[]\n}\n\nexport type EndpointTiming = {\n\tmethod: string\n\tpath: string\n\ttiming: number\n\tsectionTimings: SectionTiming[]\n}\n\nexport type WorkerResultSuccess = {\n\ttaskId: string\n\tendpoints: EndpointData[]\n\tendpointTimings: EndpointTiming[]\n}\n\nexport type WorkerResultError = {\n\ttaskId: string\n\terror: string\n}\n\nexport type WorkerResult = WorkerResultSuccess | WorkerResultError\n\nexport class WorkerPool {\n\tprivate workers: Worker[]\n\tprivate idle: Worker[]\n\tprivate queue: Array<{ task: WorkerTask; resolve: (r: WorkerResult) => void }> = []\n\tprivate pending = new Map<string, (r: WorkerResult) => void>()\n\n\tconstructor(workerUrl: URL, maxWorkers?: number) {\n\t\tconst cpuBound = Math.max(1, Math.min(os.cpus().length - 1, 8))\n\t\t// Never spin up more workers than there are files to analyze: each extra worker would\n\t\t// just pay the ts-morph Project cold-start (full TS program load + type-checker warmup)\n\t\t// for nothing while contending for CPU with the workers that actually have work.\n\t\tconst size = maxWorkers === undefined ? cpuBound : Math.max(1, Math.min(cpuBound, maxWorkers))\n\t\tthis.workers = Array.from({ length: size }, () => {\n\t\t\tconst worker = new Worker(workerUrl)\n\t\t\tworker.on('message', (result: WorkerResult) => {\n\t\t\t\tconst resolve = this.pending.get(result.taskId)\n\t\t\t\tif (resolve) {\n\t\t\t\t\tthis.pending.delete(result.taskId)\n\t\t\t\t\tresolve(result)\n\t\t\t\t}\n\t\t\t\tthis.idle.push(worker)\n\t\t\t\tthis.flush()\n\t\t\t})\n\t\t\tworker.on('error', (err) => {\n\t\t\t\t// Find any pending task for this worker and reject it\n\t\t\t\t// (worker crashed — shouldn't happen, but handle gracefully)\n\t\t\t\tfor (const [taskId, resolve] of this.pending) {\n\t\t\t\t\tresolve({ taskId, error: String(err) })\n\t\t\t\t\tthis.pending.delete(taskId)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tthis.idle.push(worker)\n\t\t\t\tthis.flush()\n\t\t\t})\n\t\t\treturn worker\n\t\t})\n\t\tthis.idle = [...this.workers]\n\t}\n\n\trun(task: WorkerTask): Promise<WorkerResult> {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (this.idle.length > 0) {\n\t\t\t\tconst worker = this.idle.pop()!\n\t\t\t\tthis.pending.set(task.taskId, resolve)\n\t\t\t\tworker.postMessage(task)\n\t\t\t} else {\n\t\t\t\tthis.queue.push({ task, resolve })\n\t\t\t}\n\t\t})\n\t}\n\n\trunAll(tasks: WorkerTask[]): Promise<WorkerResult[]> {\n\t\treturn Promise.all(tasks.map((t) => this.run(t)))\n\t}\n\n\tterminate() {\n\t\tthis.workers.forEach((w) => w.terminate())\n\t}\n\n\tprivate flush() {\n\t\twhile (this.queue.length > 0 && this.idle.length > 0) {\n\t\t\tconst { task, resolve } = this.queue.shift()!\n\t\t\tconst worker = this.idle.pop()!\n\t\t\tthis.pending.set(task.taskId, resolve)\n\t\t\tworker.postMessage(task)\n\t\t}\n\t}\n}\n"],"names":["WorkerPool","workerUrl","maxWorkers","cpuBound","os","size","worker","Worker","result","resolve","err","taskId","task","tasks","w"],"mappings":"kIAkCO,MAAMA,CAAW,CAMvB,YAAYC,EAAgBC,EAAqB,CAHjD,KAAQ,MAAyE,CAAC,EAC1E,KAAA,YAAc,IAGrB,MAAMC,EAAW,KAAK,IAAI,EAAG,KAAK,IAAIC,EAAG,KAAA,EAAO,OAAS,EAAG,CAAC,CAAC,EAIxDC,EAAOH,IAAe,OAAYC,EAAW,KAAK,IAAI,EAAG,KAAK,IAAIA,EAAUD,CAAU,CAAC,EAC7F,KAAK,QAAU,MAAM,KAAK,CAAE,OAAQG,CAAA,EAAQ,IAAM,CAC3C,MAAAC,EAAS,IAAIC,EAAA,OAAON,CAAS,EAC5B,OAAAK,EAAA,GAAG,UAAYE,GAAyB,CAC9C,MAAMC,EAAU,KAAK,QAAQ,IAAID,EAAO,MAAM,EAC1CC,IACE,KAAA,QAAQ,OAAOD,EAAO,MAAM,EACjCC,EAAQD,CAAM,GAEV,KAAA,KAAK,KAAKF,CAAM,EACrB,KAAK,MAAM,CAAA,CACX,EACMA,EAAA,GAAG,QAAUI,GAAQ,CAG3B,SAAW,CAACC,EAAQF,CAAO,IAAK,KAAK,QAAS,CAC7CA,EAAQ,CAAE,OAAAE,EAAQ,MAAO,OAAOD,CAAG,EAAG,EACjC,KAAA,QAAQ,OAAOC,CAAM,EAC1B,KAAA,CAEI,KAAA,KAAK,KAAKL,CAAM,EACrB,KAAK,MAAM,CAAA,CACX,EACMA,CAAA,CACP,EACD,KAAK,KAAO,CAAC,GAAG,KAAK,OAAO,CAAA,CAG7B,IAAIM,EAAyC,CACrC,OAAA,IAAI,QAASH,GAAY,CAC3B,GAAA,KAAK,KAAK,OAAS,EAAG,CACnB,MAAAH,EAAS,KAAK,KAAK,IAAI,EAC7B,KAAK,QAAQ,IAAIM,EAAK,OAAQH,CAAO,EACrCH,EAAO,YAAYM,CAAI,CAAA,MAEvB,KAAK,MAAM,KAAK,CAAE,KAAAA,EAAM,QAAAH,EAAS,CAClC,CACA,CAAA,CAGF,OAAOI,EAA8C,CAC7C,OAAA,QAAQ,IAAIA,EAAM,IAAK,GAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAA,CAGjD,WAAY,CACX,KAAK,QAAQ,QAASC,GAAMA,EAAE,WAAW,CAAA,CAGlC,OAAQ,CACf,KAAO,KAAK,MAAM,OAAS,GAAK,KAAK,KAAK,OAAS,GAAG,CACrD,KAAM,CAAE,KAAAF,EAAM,QAAAH,CAAA,EAAY,KAAK,MAAM,MAAM,EACrCH,EAAS,KAAK,KAAK,IAAI,EAC7B,KAAK,QAAQ,IAAIM,EAAK,OAAQH,CAAO,EACrCH,EAAO,YAAYM,CAAI,CAAA,CACxB,CAEF"}
|
|
@@ -5,14 +5,19 @@ export type WorkerTask = {
|
|
|
5
5
|
taskId: string;
|
|
6
6
|
tsconfigPath: string;
|
|
7
7
|
sourceFilePath: string;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
routerNames: string[];
|
|
9
|
+
filterEndpointPaths?: string[];
|
|
10
|
+
};
|
|
11
|
+
export type EndpointTiming = {
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
timing: number;
|
|
15
|
+
sectionTimings: SectionTiming[];
|
|
10
16
|
};
|
|
11
17
|
export type WorkerResultSuccess = {
|
|
12
18
|
taskId: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
timing: number;
|
|
19
|
+
endpoints: EndpointData[];
|
|
20
|
+
endpointTimings: EndpointTiming[];
|
|
16
21
|
};
|
|
17
22
|
export type WorkerResultError = {
|
|
18
23
|
taskId: string;
|
|
@@ -24,7 +29,7 @@ export declare class WorkerPool {
|
|
|
24
29
|
private idle;
|
|
25
30
|
private queue;
|
|
26
31
|
private pending;
|
|
27
|
-
constructor(workerUrl: URL);
|
|
32
|
+
constructor(workerUrl: URL, maxWorkers?: number);
|
|
28
33
|
run(task: WorkerTask): Promise<WorkerResult>;
|
|
29
34
|
runAll(tasks: WorkerTask[]): Promise<WorkerResult[]>;
|
|
30
35
|
terminate(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workerPool.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,MAAM,MAAM,UAAU,GAAG;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,
|
|
1
|
+
{"version":3,"file":"workerPool.d.ts","sourceRoot":"","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAE/C,MAAM,MAAM,UAAU,GAAG;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,aAAa,EAAE,CAAA;CAC/B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,eAAe,EAAE,cAAc,EAAE,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,iBAAiB,CAAA;AAElE,qBAAa,UAAU;IACtB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,KAAK,CAAsE;IACnF,OAAO,CAAC,OAAO,CAA+C;gBAElD,SAAS,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,MAAM;IAiC/C,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAY5C,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAIpD,SAAS;IAIT,OAAO,CAAC,KAAK;CAQb"}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Worker as
|
|
3
|
-
class
|
|
4
|
-
constructor(e) {
|
|
1
|
+
import l from "os";
|
|
2
|
+
import { Worker as d } from "worker_threads";
|
|
3
|
+
class g {
|
|
4
|
+
constructor(e, s) {
|
|
5
5
|
this.queue = [], this.pending = /* @__PURE__ */ new Map();
|
|
6
|
-
const t = Math.max(1, Math.min(
|
|
7
|
-
this.workers = Array.from({ length:
|
|
8
|
-
const
|
|
9
|
-
return
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
}),
|
|
13
|
-
for (const [
|
|
14
|
-
|
|
6
|
+
const t = Math.max(1, Math.min(l.cpus().length - 1, 8)), o = s === void 0 ? t : Math.max(1, Math.min(t, s));
|
|
7
|
+
this.workers = Array.from({ length: o }, () => {
|
|
8
|
+
const i = new d(e);
|
|
9
|
+
return i.on("message", (r) => {
|
|
10
|
+
const n = this.pending.get(r.taskId);
|
|
11
|
+
n && (this.pending.delete(r.taskId), n(r)), this.idle.push(i), this.flush();
|
|
12
|
+
}), i.on("error", (r) => {
|
|
13
|
+
for (const [n, h] of this.pending) {
|
|
14
|
+
h({ taskId: n, error: String(r) }), this.pending.delete(n);
|
|
15
15
|
break;
|
|
16
16
|
}
|
|
17
|
-
this.idle.push(
|
|
18
|
-
}),
|
|
17
|
+
this.idle.push(i), this.flush();
|
|
18
|
+
}), i;
|
|
19
19
|
}), this.idle = [...this.workers];
|
|
20
20
|
}
|
|
21
21
|
run(e) {
|
|
22
|
-
return new Promise((
|
|
22
|
+
return new Promise((s) => {
|
|
23
23
|
if (this.idle.length > 0) {
|
|
24
|
-
const
|
|
25
|
-
this.pending.set(e.taskId,
|
|
24
|
+
const t = this.idle.pop();
|
|
25
|
+
this.pending.set(e.taskId, s), t.postMessage(e);
|
|
26
26
|
} else
|
|
27
|
-
this.queue.push({ task: e, resolve:
|
|
27
|
+
this.queue.push({ task: e, resolve: s });
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
runAll(e) {
|
|
31
|
-
return Promise.all(e.map((
|
|
31
|
+
return Promise.all(e.map((s) => this.run(s)));
|
|
32
32
|
}
|
|
33
33
|
terminate() {
|
|
34
34
|
this.workers.forEach((e) => e.terminate());
|
|
35
35
|
}
|
|
36
36
|
flush() {
|
|
37
37
|
for (; this.queue.length > 0 && this.idle.length > 0; ) {
|
|
38
|
-
const { task: e, resolve:
|
|
39
|
-
this.pending.set(e.taskId,
|
|
38
|
+
const { task: e, resolve: s } = this.queue.shift(), t = this.idle.pop();
|
|
39
|
+
this.pending.set(e.taskId, s), t.postMessage(e);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
export {
|
|
44
|
-
|
|
44
|
+
g as WorkerPool
|
|
45
45
|
};
|
|
46
46
|
//# sourceMappingURL=workerPool.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workerPool.mjs","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"sourcesContent":["import os from 'os'\nimport { Worker } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { SectionTiming } from './parseEndpoint'\n\nexport type WorkerTask = {\n\ttaskId: string\n\ttsconfigPath: string\n\tsourceFilePath: string\n\
|
|
1
|
+
{"version":3,"file":"workerPool.mjs","sources":["../../../src/openapi/analyzerModule/workerPool.ts"],"sourcesContent":["import os from 'os'\nimport { Worker } from 'worker_threads'\n\nimport { EndpointData } from '../types'\nimport { SectionTiming } from './parseEndpoint'\n\nexport type WorkerTask = {\n\ttaskId: string\n\ttsconfigPath: string\n\tsourceFilePath: string\n\trouterNames: string[]\n\tfilterEndpointPaths?: string[]\n}\n\nexport type EndpointTiming = {\n\tmethod: string\n\tpath: string\n\ttiming: number\n\tsectionTimings: SectionTiming[]\n}\n\nexport type WorkerResultSuccess = {\n\ttaskId: string\n\tendpoints: EndpointData[]\n\tendpointTimings: EndpointTiming[]\n}\n\nexport type WorkerResultError = {\n\ttaskId: string\n\terror: string\n}\n\nexport type WorkerResult = WorkerResultSuccess | WorkerResultError\n\nexport class WorkerPool {\n\tprivate workers: Worker[]\n\tprivate idle: Worker[]\n\tprivate queue: Array<{ task: WorkerTask; resolve: (r: WorkerResult) => void }> = []\n\tprivate pending = new Map<string, (r: WorkerResult) => void>()\n\n\tconstructor(workerUrl: URL, maxWorkers?: number) {\n\t\tconst cpuBound = Math.max(1, Math.min(os.cpus().length - 1, 8))\n\t\t// Never spin up more workers than there are files to analyze: each extra worker would\n\t\t// just pay the ts-morph Project cold-start (full TS program load + type-checker warmup)\n\t\t// for nothing while contending for CPU with the workers that actually have work.\n\t\tconst size = maxWorkers === undefined ? cpuBound : Math.max(1, Math.min(cpuBound, maxWorkers))\n\t\tthis.workers = Array.from({ length: size }, () => {\n\t\t\tconst worker = new Worker(workerUrl)\n\t\t\tworker.on('message', (result: WorkerResult) => {\n\t\t\t\tconst resolve = this.pending.get(result.taskId)\n\t\t\t\tif (resolve) {\n\t\t\t\t\tthis.pending.delete(result.taskId)\n\t\t\t\t\tresolve(result)\n\t\t\t\t}\n\t\t\t\tthis.idle.push(worker)\n\t\t\t\tthis.flush()\n\t\t\t})\n\t\t\tworker.on('error', (err) => {\n\t\t\t\t// Find any pending task for this worker and reject it\n\t\t\t\t// (worker crashed — shouldn't happen, but handle gracefully)\n\t\t\t\tfor (const [taskId, resolve] of this.pending) {\n\t\t\t\t\tresolve({ taskId, error: String(err) })\n\t\t\t\t\tthis.pending.delete(taskId)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tthis.idle.push(worker)\n\t\t\t\tthis.flush()\n\t\t\t})\n\t\t\treturn worker\n\t\t})\n\t\tthis.idle = [...this.workers]\n\t}\n\n\trun(task: WorkerTask): Promise<WorkerResult> {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (this.idle.length > 0) {\n\t\t\t\tconst worker = this.idle.pop()!\n\t\t\t\tthis.pending.set(task.taskId, resolve)\n\t\t\t\tworker.postMessage(task)\n\t\t\t} else {\n\t\t\t\tthis.queue.push({ task, resolve })\n\t\t\t}\n\t\t})\n\t}\n\n\trunAll(tasks: WorkerTask[]): Promise<WorkerResult[]> {\n\t\treturn Promise.all(tasks.map((t) => this.run(t)))\n\t}\n\n\tterminate() {\n\t\tthis.workers.forEach((w) => w.terminate())\n\t}\n\n\tprivate flush() {\n\t\twhile (this.queue.length > 0 && this.idle.length > 0) {\n\t\t\tconst { task, resolve } = this.queue.shift()!\n\t\t\tconst worker = this.idle.pop()!\n\t\t\tthis.pending.set(task.taskId, resolve)\n\t\t\tworker.postMessage(task)\n\t\t}\n\t}\n}\n"],"names":["WorkerPool","workerUrl","maxWorkers","cpuBound","os","size","worker","Worker","result","resolve","err","taskId","task","tasks","t","w"],"mappings":";;AAkCO,MAAMA,EAAW;AAAA,EAMvB,YAAYC,GAAgBC,GAAqB;AAHjD,SAAQ,QAAyE,CAAC,GAC1E,KAAA,8BAAc,IAAuC;AAG5D,UAAMC,IAAW,KAAK,IAAI,GAAG,KAAK,IAAIC,EAAG,KAAA,EAAO,SAAS,GAAG,CAAC,CAAC,GAIxDC,IAAOH,MAAe,SAAYC,IAAW,KAAK,IAAI,GAAG,KAAK,IAAIA,GAAUD,CAAU,CAAC;AAC7F,SAAK,UAAU,MAAM,KAAK,EAAE,QAAQG,EAAA,GAAQ,MAAM;AAC3C,YAAAC,IAAS,IAAIC,EAAON,CAAS;AAC5B,aAAAK,EAAA,GAAG,WAAW,CAACE,MAAyB;AAC9C,cAAMC,IAAU,KAAK,QAAQ,IAAID,EAAO,MAAM;AAC9C,QAAIC,MACE,KAAA,QAAQ,OAAOD,EAAO,MAAM,GACjCC,EAAQD,CAAM,IAEV,KAAA,KAAK,KAAKF,CAAM,GACrB,KAAK,MAAM;AAAA,MAAA,CACX,GACMA,EAAA,GAAG,SAAS,CAACI,MAAQ;AAG3B,mBAAW,CAACC,GAAQF,CAAO,KAAK,KAAK,SAAS;AAC7C,UAAAA,EAAQ,EAAE,QAAAE,GAAQ,OAAO,OAAOD,CAAG,GAAG,GACjC,KAAA,QAAQ,OAAOC,CAAM;AAC1B;AAAA,QAAA;AAEI,aAAA,KAAK,KAAKL,CAAM,GACrB,KAAK,MAAM;AAAA,MAAA,CACX,GACMA;AAAA,IAAA,CACP,GACD,KAAK,OAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EAAA;AAAA,EAG7B,IAAIM,GAAyC;AACrC,WAAA,IAAI,QAAQ,CAACH,MAAY;AAC3B,UAAA,KAAK,KAAK,SAAS,GAAG;AACnB,cAAAH,IAAS,KAAK,KAAK,IAAI;AAC7B,aAAK,QAAQ,IAAIM,EAAK,QAAQH,CAAO,GACrCH,EAAO,YAAYM,CAAI;AAAA,MAAA;AAEvB,aAAK,MAAM,KAAK,EAAE,MAAAA,GAAM,SAAAH,GAAS;AAAA,IAClC,CACA;AAAA,EAAA;AAAA,EAGF,OAAOI,GAA8C;AAC7C,WAAA,QAAQ,IAAIA,EAAM,IAAI,CAACC,MAAM,KAAK,IAAIA,CAAC,CAAC,CAAC;AAAA,EAAA;AAAA,EAGjD,YAAY;AACX,SAAK,QAAQ,QAAQ,CAACC,MAAMA,EAAE,WAAW;AAAA,EAAA;AAAA,EAGlC,QAAQ;AACf,WAAO,KAAK,MAAM,SAAS,KAAK,KAAK,KAAK,SAAS,KAAG;AACrD,YAAM,EAAE,MAAAH,GAAM,SAAAH,EAAA,IAAY,KAAK,MAAM,MAAM,GACrCH,IAAS,KAAK,KAAK,IAAI;AAC7B,WAAK,QAAQ,IAAIM,EAAK,QAAQH,CAAO,GACrCH,EAAO,YAAYM,CAAI;AAAA,IAAA;AAAA,EACxB;AAEF;"}
|
package/package.json
CHANGED
|
@@ -213,45 +213,24 @@ export const analyzeMultipleSourceFiles = async (
|
|
|
213
213
|
return cached.flatMap((f) => f.endpoints)
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// Build one task per
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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.
|
|
220
219
|
type FileTask = { task: WorkerTask; fileName: string }
|
|
221
|
-
const allTasks: FileTask[] =
|
|
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)
|
|
222
233
|
|
|
223
|
-
for (const { file } of uncached) {
|
|
224
|
-
for (const routerName of file.routers.named) {
|
|
225
|
-
const routerPattern = new RegExp(`${routerName}\\.(?:${operationsPattern})`)
|
|
226
|
-
let endpointIndex = 0
|
|
227
|
-
file.sourceFile.forEachChild((node) => {
|
|
228
|
-
const nodeText = node.getText()
|
|
229
|
-
if (routerPattern.test(nodeText)) {
|
|
230
|
-
if (
|
|
231
|
-
!filterEndpointPaths ||
|
|
232
|
-
filterEndpointPaths.some((p) => resolveEndpointPath(node)?.includes(p))
|
|
233
|
-
) {
|
|
234
|
-
allTasks.push({
|
|
235
|
-
fileName: file.fileName,
|
|
236
|
-
task: {
|
|
237
|
-
taskId: crypto.randomUUID(),
|
|
238
|
-
tsconfigPath: config.tsconfigPath,
|
|
239
|
-
sourceFilePath: file.sourceFile.getFilePath(),
|
|
240
|
-
routerName,
|
|
241
|
-
endpointIndex,
|
|
242
|
-
},
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
endpointIndex++
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Dispatch all tasks to the worker pool
|
|
252
|
-
const pool = new WorkerPool(resolveWorkerUrl())
|
|
253
|
-
|
|
254
|
-
type EndpointTiming = { method: string; path: string; timing: number; sectionTimings: SectionTiming[] }
|
|
255
234
|
type FileResult = {
|
|
256
235
|
endpoints: EndpointData[]
|
|
257
236
|
fileName: string
|
|
@@ -266,7 +245,7 @@ export const analyzeMultipleSourceFiles = async (
|
|
|
266
245
|
pool.terminate()
|
|
267
246
|
}
|
|
268
247
|
|
|
269
|
-
//
|
|
248
|
+
// Each result maps 1:1 to a file task.
|
|
270
249
|
const byFile = new Map<string, FileResult>()
|
|
271
250
|
for (const { file } of uncached) {
|
|
272
251
|
byFile.set(file.fileName, { endpoints: [], fileName: file.fileName, timing: 0, endpointTimings: [] })
|
|
@@ -282,14 +261,9 @@ export const analyzeMultipleSourceFiles = async (
|
|
|
282
261
|
continue
|
|
283
262
|
}
|
|
284
263
|
|
|
285
|
-
fileResult.endpoints
|
|
286
|
-
fileResult.
|
|
287
|
-
fileResult.endpointTimings.
|
|
288
|
-
method: result.endpoint.method,
|
|
289
|
-
path: result.endpoint.path,
|
|
290
|
-
timing: result.timing,
|
|
291
|
-
sectionTimings: result.sectionTimings,
|
|
292
|
-
})
|
|
264
|
+
fileResult.endpoints = result.endpoints
|
|
265
|
+
fileResult.endpointTimings = result.endpointTimings
|
|
266
|
+
fileResult.timing = result.endpointTimings.reduce((sum, t) => sum + t.timing, 0)
|
|
293
267
|
}
|
|
294
268
|
|
|
295
269
|
// Write cache for each uncached file
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Project } from 'ts-morph'
|
|
2
2
|
import { parentPort } from 'worker_threads'
|
|
3
3
|
|
|
4
|
+
import { EndpointData } from '../types'
|
|
5
|
+
import { resolveEndpointPath } from './nodeParsers'
|
|
4
6
|
import { parseEndpoint } from './parseEndpoint'
|
|
5
|
-
import { WorkerResult, WorkerTask } from './workerPool'
|
|
7
|
+
import { EndpointTiming, WorkerResult, WorkerTask } from './workerPool'
|
|
6
8
|
|
|
7
9
|
const OPERATIONS = ['get', 'post', 'put', 'delete', 'del', 'patch']
|
|
8
10
|
const OPERATIONS_PATTERN = OPERATIONS.join('|')
|
|
@@ -30,37 +32,39 @@ parentPort!.on('message', (task: WorkerTask) => {
|
|
|
30
32
|
sourceFile = proj.addSourceFileAtPath(task.sourceFilePath)
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
let targetNode: Node<ts.Node> | undefined
|
|
35
|
+
const endpoints: EndpointData[] = []
|
|
36
|
+
const endpointTimings: EndpointTiming[] = []
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
38
|
+
task.routerNames.forEach((routerName) => {
|
|
39
|
+
const routerPattern = new RegExp(`${routerName}\\.(?:${OPERATIONS_PATTERN})`)
|
|
40
|
+
sourceFile!.forEachChild((node) => {
|
|
41
|
+
if (!routerPattern.test(node.getText())) {
|
|
42
|
+
return
|
|
42
43
|
}
|
|
43
|
-
index++
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
54
|
-
}
|
|
45
|
+
if (task.filterEndpointPaths) {
|
|
46
|
+
const endpointPath = resolveEndpointPath(node) ?? ''
|
|
47
|
+
if (!task.filterEndpointPaths.some((p) => endpointPath.includes(p))) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const t1 = performance.now()
|
|
53
|
+
const { endpoint, sectionTimings } = parseEndpoint(node, task.sourceFilePath)
|
|
54
|
+
endpointTimings.push({
|
|
55
|
+
method: endpoint.method,
|
|
56
|
+
path: endpoint.path,
|
|
57
|
+
timing: performance.now() - t1,
|
|
58
|
+
sectionTimings,
|
|
59
|
+
})
|
|
60
|
+
endpoints.push(endpoint)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
58
63
|
|
|
59
64
|
const result: WorkerResult = {
|
|
60
65
|
taskId: task.taskId,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
timing: performance.now() - t1,
|
|
66
|
+
endpoints,
|
|
67
|
+
endpointTimings,
|
|
64
68
|
}
|
|
65
69
|
parentPort!.postMessage(result)
|
|
66
70
|
} catch (err) {
|
|
@@ -8,15 +8,21 @@ export type WorkerTask = {
|
|
|
8
8
|
taskId: string
|
|
9
9
|
tsconfigPath: string
|
|
10
10
|
sourceFilePath: string
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
routerNames: string[]
|
|
12
|
+
filterEndpointPaths?: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type EndpointTiming = {
|
|
16
|
+
method: string
|
|
17
|
+
path: string
|
|
18
|
+
timing: number
|
|
19
|
+
sectionTimings: SectionTiming[]
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export type WorkerResultSuccess = {
|
|
16
23
|
taskId: string
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
timing: number
|
|
24
|
+
endpoints: EndpointData[]
|
|
25
|
+
endpointTimings: EndpointTiming[]
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export type WorkerResultError = {
|
|
@@ -32,8 +38,12 @@ export class WorkerPool {
|
|
|
32
38
|
private queue: Array<{ task: WorkerTask; resolve: (r: WorkerResult) => void }> = []
|
|
33
39
|
private pending = new Map<string, (r: WorkerResult) => void>()
|
|
34
40
|
|
|
35
|
-
constructor(workerUrl: URL) {
|
|
36
|
-
const
|
|
41
|
+
constructor(workerUrl: URL, maxWorkers?: number) {
|
|
42
|
+
const cpuBound = Math.max(1, Math.min(os.cpus().length - 1, 8))
|
|
43
|
+
// Never spin up more workers than there are files to analyze: each extra worker would
|
|
44
|
+
// just pay the ts-morph Project cold-start (full TS program load + type-checker warmup)
|
|
45
|
+
// for nothing while contending for CPU with the workers that actually have work.
|
|
46
|
+
const size = maxWorkers === undefined ? cpuBound : Math.max(1, Math.min(cpuBound, maxWorkers))
|
|
37
47
|
this.workers = Array.from({ length: size }, () => {
|
|
38
48
|
const worker = new Worker(workerUrl)
|
|
39
49
|
worker.on('message', (result: WorkerResult) => {
|