UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

126 lines (111 loc) 3.82 kB
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 }