@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
167 lines (125 loc) • 5.57 kB
JavaScript
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()