diff --git a/lib/setModel.js b/lib/setModel.js index d0544d2..c61caa3 100644 --- a/lib/setModel.js +++ b/lib/setModel.js @@ -1,9 +1,10 @@ import cds from '@sap/cds' import fs from 'fs' +import path from 'path' // Ensures only one CDS model compilation is ever in-flight. // The moment setModel is called, cds.model is set to a promise. -export default async function setModel(path) { +export default async function setModel(projectPath) { if (cds.model) { // If cds.model is a promise, await it; if it's resolved, return it if (typeof cds.model.then === 'function') await cds.model @@ -11,7 +12,7 @@ export default async function setModel(path) { } // Assign a promise immediately to cds.model to prevent duplicate compilations cds.model = (async () => { - const compiled = await compileModel(path) + const compiled = await compileModel(projectPath) cds.model = compiled return compiled })() @@ -20,16 +21,16 @@ export default async function setModel(path) { } // Loads and compiles the CDS model, returns the compiled model or throws on error -async function compileModel(path) { - cds.root = path +async function compileModel(projectPath) { + cds.root = projectPath const startTime = Date.now() - const resolved = cds.resolve(path + '/*', { cache: {} }) // make sure NOT to use the cache + const resolved = cds.resolve(projectPath + '/*', { cache: {} }) // use CAP standard resolution for model compilation let compiled = await cds.load(resolved, { docs: true, locations: true }) if (!compiled || (Array.isArray(compiled) && compiled.length === 0)) { - throw new Error(`Failed to load CDS model from path: ${path}`) + throw new Error(`Failed to load CDS model from path: ${projectPath}`) } if (!compiled.definitions || Object.keys(compiled.definitions).length === 0) { - throw new Error(`Compiled CDS model is invalid or empty for path: ${path}`) + throw new Error(`Compiled CDS model is invalid or empty for path: ${projectPath}`) } compiled = cds.compile.for.nodejs(compiled) // to include drafts, show effective types const serviceInfo = cds.compile.to.serviceinfo(compiled) @@ -41,7 +42,10 @@ async function compileModel(path) { } for (const name in compiled.definitions) { - Object.defineProperty(compiled.definitions[name], 'name', { value: name, enumerable: true }) + Object.defineProperty(compiled.definitions[name], 'name', { + value: name, + enumerable: true + }) } const _entities_in = service => { @@ -81,9 +85,9 @@ async function compileModel(path) { ? parseInt(process.env.CDS_MCP_REFRESH_MS, 10) : Math.max(compileDuration * 10, 20000) changeWatcher = setInterval(async () => { - const hasChanged = await cdsFilesChanged(path) + const hasChanged = await cdsFilesChanged(projectPath) if (hasChanged) { - await refreshModel(path) + await refreshModel(projectPath) } }, intervalMs).unref() // Uses CDS_MCP_REFRESH_MS if set, otherwise defaults to 10x compile duration or 20s } @@ -91,9 +95,9 @@ async function compileModel(path) { } // Refreshes the CDS model, only replaces cds.model if compilation succeeds -async function refreshModel(path) { +async function refreshModel(projectPath) { try { - const compiled = await compileModel(path) + const compiled = await compileModel(projectPath) cds.model = compiled return compiled } catch { @@ -105,9 +109,25 @@ async function refreshModel(path) { const cache = { cdsFiles: new Map() } let changeWatcher = null -async function cdsFilesChanged(path) { - if (path.endsWith('/')) path = path.slice(0, -1) - const files = cds.resolve(path + '/*', { cache: {} }) || [] +async function cdsFilesChanged(projectPath) { + // Recursively find all .cds files under root, ignoring node_modules + async function findCdsFiles(dir) { + let results = [] + const entries = await fs.promises.readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) { + if (entry.name === 'node_modules') continue + results = results.concat(await findCdsFiles(fullPath)) + } else if (entry.isFile() && entry.name.endsWith('.cds')) { + results.push(fullPath) + } + } + return results + } + + if (projectPath.endsWith('/')) projectPath = projectPath.slice(0, -1) + const files = await findCdsFiles(projectPath) const currentTimestamps = new Map() await Promise.all( files.map(file =>