UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

167 lines (125 loc) 5.57 kB
const resolve = module.exports = exports = cds_resolve const cds = require('..'), {fs,path} = cds.utils /** * Resolves given model references to an array of absolute filenames. * For the model references, all these are accepted: * - with suffix or without → will append `.csn|cds`, `/index.csn|cds` * - absolute refs like `@sap/cds/common` * - local refs with leading `.` or without, e.g. `srv/cat-service` * - directory names → will fetch all contained `.csn` and `.cds` files * - arrays of any of the above * @returns and array of absolute filenames */ function cds_resolve (refs,o) { if (refs?._resolved) return refs //> already resolved let o2 = resolve.options(o) if (Array.isArray(refs)) return resolve.many (refs.flat(),o2) else return resolve.single (refs,o2) } resolve.locations = (model='*', o) => resolve(model, { dry:true, ...o }) resolve.options = function (options) { const o = options === false ? { dry:true } : {...options} const cwd = o.root ??= cds.root // return cached prior result, if any const caches = o.cache || exports.cache if (cwd in caches) return { ...caches[cwd], ...o } else caches[cwd] = { paths: [ cwd ], cached:{} } // prepare module lookup paths const paths = [ cwd ] const node_modules = (o.env||cds.env).cdsc.moduleLookupDirectories const a = cwd.split(path.sep), n = a.length for (let each of node_modules) paths.push ( ...a.map ((_,i,a)=> a.slice(0,n-i).join(path.sep) + path.sep + each) ) // cache and return const cache = caches[cwd] = { paths, cached:{} } return { ...o, ...cache } } resolve.all = function (o) { const _required = env => Object.values(env.requires) .map (r => r.model) .filter(x=>x) .flat() const env = o.env || cds.env // resolve(...,false) => return cds.env.roots + all required models if (o.dry) return [ ...env.roots, ...new Set(_required(env)) ] // return cached prior result, if any const {cached} = o; if ('*' in cached) return cached['*'] else cached['*'] = [] // important to avoid endless recursion on '*' // resolve all roots, plus all cds.required.models (unless csn.json already there) const files = resolve.many (env.roots,o) || [] const is_csn_json = files.length === 1 && files[0].endsWith('csn.json') if (!is_csn_json) files.push (...resolve.many (_required(env),o)||[]) // cache and return resolved files return cached['*'] = _resolved (files) } resolve.many = function (models, o) { const resolved = _distinct(models) .reduce ((p,n) => p.concat (resolve.single(n,o)||[]), []) return o.dry ? _distinct(resolved.flat()) : _resolved (resolved) } resolve.single = function (model,o) { if (!model || model === '--') return if (model == '*') return resolve.all(o) // handle subdirectory patterns like 'app/*', 'srv/*', 'fts/*' if (model.endsWith('/*')) return resolve.nested (model,o) // check cache, and return if already resolved const {cached} = o, local = path.resolve (o.root, model) let id = model.startsWith('.') ? local : model if (id in cached) return cached[id] // expand @sap/cds by cds.home if (id.startsWith('@sap/cds/')) id = cds.home + id.slice(8) // fetch file with .cds/.csn suffix as is if (/\.(csn|cds)$/.test(id)) try { return cached[id] = _resolved ([ _resolve (id,o) ]) } catch {/* ignored */} // try to resolve file with one of the suffixes for (let tail of o.suffixes || exports.suffixes) try { return cached[id] = _resolved ([ _resolve (id+tail,o) ]) } catch {/* ignored */} // fetch all in a folder if (o.all !== false) try { return cached[id] = _resolved (resolve.folder (local,o)) } catch {/* ignored */} // fetch file without suffix if (o.any !== false && !id.endsWith('/')) try { // NOTE: this also finds .js files! return cached[id] = _resolved ([ _resolve (id,o) ]) } catch {/* ignored */} // not found return cached[id] = null } resolve.folder = function (folder) { const files = fs.readdirSync(folder), all=[], unique={} for (let f of files) if (f.endsWith('.csn')) { all.push (unique[f.slice(0,-4)] = path.join(folder,f)) } for (let f of files) if (f.endsWith('.cds')) { unique[f.slice(0,-4)] || all.push (path.join(folder,f)) } return all } resolve.nested = function (pattern,o) { // return cached prior result, if any const {cached} = o; if (!o.dry && pattern in cached) return cached[pattern] // fetch all subdirectories matching the pattern const folder = pattern.slice(0,-2), dir = path.resolve (o.root, folder) try { var nested = fs.readdirSync(dir) .filter (d => is_dir(path.join(dir,d))) .map (d => path.join(folder,d,path.sep)) } catch(e) { if (e.code === 'ENOENT') return cached[pattern] = null } // don't resolve, if resolve(...,false) if (o.dry) return nested // resolve files for those dirs const files = nested?.map (d => resolve.single (d,o)).flat() return cached[pattern] = _resolved (files.filter(x=>x)) } resolve.module = function (module_name, o = resolve.options()) { try { return _resolve (module_name,o) } catch { return null } } exports.suffixes = [ // always .csn before .cds to prefer compiled files '.csn', '.cds', path.sep+'index.csn', path.sep+'index.cds', path.sep+'csn.json' ] exports.cache = {} const _resolve = require('module')._resolveFilename const _resolved = files => !files?.length ? null : Object.defineProperty (_distinct(files), '_resolved', {value:true}) const _distinct = files => [...new Set (files)] const is_dir = file => fs.statSync(file).isDirectory()