@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
110 lines (92 loc) • 4.35 kB
JavaScript
const { resolve, join, sep } = require('path')
const { readdirSync } = require('fs')
const suffixes = [ '.csn', '.cds', sep+'index.csn', sep+'index.cds', sep+'csn.json' ]
/**
* 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
*/
module.exports = exports = function cds_resolve (model, o={}) { // NOSONAR
if (!model || model === '--') return
if (model._resolved) return model
if (model === '*') return _resolve_all(o,this)
if (Array.isArray(model)) {
const resolved = [... new Set(model)] .reduce ((prev,next) => prev.concat (this.resolve(next,o)||[]), [])
return o.dry || o === false ? [...new Set(resolved.flat())] : _resolved (resolved)
}
if (model.endsWith('/*')) return _resolve_subdirs_in(model,o,this)
const cwd = o.root || this.root, local = resolve (cwd,model)
const context = _paths(cwd,o,this), {cached} = context
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 = this.home + id.slice(8)
// fetch file with .cds/.csn suffix as is
if (/\.(csn|cds)$/.test(id)) try {
return cached[id] = _resolved ([ _resolve (id,context) ])
} catch {/* ignored */}
// try to resolve file with one of the suffixes
for (let tail of o.suffixes || suffixes) try {
return cached[id] = _resolved ([ _resolve (id+tail,context) ])
} catch {/* ignored */}
// fetch all in a directory
if (o.all !== false) try {
const files = readdirSync(local), all=[], unique={}
for (let f of files) if (f.endsWith('.csn')) {
all.push (unique[f.slice(0,-4)] = join(local,f))
}
for (let f of files) if (f.endsWith('.cds')) {
unique[f.slice(0,-4)] || all.push (join(local,f))
}
return cached[id] = _resolved (all)
} 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,context) ])
} catch {/* ignored */}
}
exports.cache = {}
const _required = (cds,env=cds.env) => Object.values(env.requires) .map (r => r.model) .filter(x=>x)
const _resolve = require('module')._resolveFilename
function _resolve_all (o,cds) {
const {roots} = o.env || cds.env; if (o.dry || o === false) return [ ...roots, ...new Set(_required(cds).flat()) ]
const cache = o.cache || exports.cache
const cached = cache['*']; if (cached) return cached
cache['*'] = [] // important to avoid endless recursion on '*'
const sources = cds.resolve (roots,o) || []
if (!(sources.length === 1 && sources[0].endsWith('csn.json'))) // REVISIT: why is that? -> pre-compiled gen/csn.json?
sources.push (...cds.resolve (_required(cds,o.env),o)||[])
return cache['*'] = _resolved (sources)
}
function _resolve_subdirs_in (pattern='fts/*',o,cds) {
const cache = o.cache || exports.cache
const cached = cache[pattern]; if (cached && !o.dry && o !== false) return cached
const folder = pattern.slice(0,-2), dir = resolve (o.root || cds.root, folder)
try {
const dirs = readdirSync(dir) .filter (e => cds.utils.isdir(dir+sep+e)) .map (e => folder+sep+e+sep)
if (o.dry || o === false) return dirs
return cache[pattern] = cds.resolve (dirs,o) || undefined
} catch(e) {
if (e.code === 'ENOENT')
return cache[pattern] = undefined
}
}
function _paths (dir,o,cds) {
const cache = o.cache || exports.cache
const cached = cache[dir]; if (cached) return cached
const a = dir.split(sep), n = a.length, paths = [ dir ]
const { cdsc: { moduleLookupDirectories }} = o.env ?? cds.env
for (const mld of moduleLookupDirectories) { // node_modules/ usually, more for Java
paths.push(...a.map ((_,i,a)=> a.slice(0,n-i).join(sep)+sep+mld))
}
return cache[dir] = { paths, cached:{} }
}
function _resolved (array) {
if (!array || !array.length) return
return Object.defineProperty ([...new Set (array)], '_resolved', {value:true})
}