@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
126 lines (111 loc) • 3.82 kB
JavaScript
const os = require('node:os')
const { parse, join } = require('node:path')
const { promises: fsp } = require('node:fs')
const YAML = require('yaml')
const { read, write } = require('../cds').utils
const JSONC = require('./jsonc')
/* YAML */
async function readYAML(src, project) {
let file
try {
file = await read(src, 'utf8')
} catch (e) {
if (e.code === 'ENOENT') return undefined
throw e
}
if (project) {
const Mustache = require('mustache')
const renderedFile = Mustache.render(file, project)
return YAML.parseDocument(renderedFile)
}
return YAML.parseDocument(file)
}
async function writeYAML(dst, yaml) {
const content = YAML.stringify(yaml, { collectionStyle: 'block', lineWidth: 150, directives: false })
await write(content).to(dst)
}
async function copyRenderedYAML(src, dst, project) {
const yaml = await readYAML(src, project)
await write(yaml.toString()).to(dst)
}
/* JSON and JSONC */
async function readJSON(src, project) {
const file = await read(src, 'utf8')
if (project) {
const Mustache = require('mustache')
const renderedFile = Mustache.render(file, project)
return JSON.parse(renderedFile)
}
try {
return JSON.parse(file)
} catch (error) {
throw new Error(`${error.message}\n${file}`, { cause: error })
}
}
async function readJSONC(src) {
return JSONC.parse(await read(src, 'utf8'))
}
async function copyRenderedJSON(src, dst, project) {
const json = await readJSON(src, project)
await write(dst, json, { spaces: 2 })
}
/* General */
function isRoot(folder) {
const { root, dir } = parse(folder)
return root === dir
}
const DEFAULT_CONCURRENCY = (() => {
const cpu = os.availableParallelism()
const uv = Number(process.env.UV_THREADPOOL_SIZE) || 4
// Keep libuv busy without building huge backlogs. Clamp to [8,64].
return Math.min(64, Math.max(8, Math.min(cpu * 4, uv * 2)))
})()
/**
* Recursively finds files in a directory tree with parallel traversal.
*
* @param {string} dir - Root directory to search
* @param {Object} [options] - Search options
* @param {(file: string) => boolean} [options.filter] - Filter predicate for paths
* @param {boolean} [options.ignoreSymlinks=false] - Skip symbolic links
* @returns {Promise<string[]>} Array of matching file paths
*/
async function find(dir, { filter, ignoreSymlinks = false } = {}) {
const files = /** @type {import('fs').Dirent[]} */([])
const queue = /** @type {import('fs').Dirent[]} */([dir])
const active = new Set()
const visit = async (/** @type {string} */ d) => {
let entries = /** @type {import('fs').Dirent[]} */([])
try { entries = await fsp.readdir(d, { withFileTypes: true }) }
catch { return }
await Promise.all(entries.map(async dirent => {
const p = join(d, dirent.name)
if (filter && !filter(p)) return
if (dirent.isDirectory()) { queue.push(p); return }
if (dirent.isFile()) { files.push(p); return }
if (!ignoreSymlinks && dirent.isSymbolicLink()) {
try {
const stat = await fsp.stat(p)
stat.isDirectory() ? queue.push(p) : files.push(p)
} catch { /* ignore */ }
}
}))
}
while (queue.length || active.size) {
while (queue.length && active.size < DEFAULT_CONCURRENCY) {
const p = visit(queue.shift()).finally(() => active.delete(p))
active.add(p)
}
if (active.size) await Promise.race(active)
}
return files
}
module.exports = {
readYAML,
writeYAML,
copyRenderedYAML,
readJSON,
readJSONC,
copyRenderedJSON,
find,
isRoot
}