@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
92 lines (81 loc) • 3.29 kB
JavaScript
const path = require('node:path');
const { join } = path;
const { readdir } = require('node:fs').promises;
const IGNORE_DIRS = new Set(['node_modules', '.git', '.hg', '.svn', 'dist', 'target', 'build', 'out']);
/**
* Returns an AsyncIterator of those (recursive) subdirectories that match the glob pattern.
* Skips common large directories (node_modules, .git, .hg, .svn, dist, target, build, out).
* @param {string} pattern - The glob pattern to match against.
* @param {object} options
* @param {string} options.cwd - The current working directory to start the search from.
* @returns An array of relative paths that match the glob pattern.
*/
async function* glob(pattern, { cwd } = { cwd: process.cwd() }) {
if (/\{.*}/.test(pattern)) {
// We rely on the native Node.js glob support for brace expansion.
const { glob: globNative } = require('node:fs/promises');
if (!globNative) {
throw `Glob pattern '${pattern} requires native Node.js glob support to resolve, which is available in Node.js version 22, but project uses ${process.version}.`
}
return globNative(pattern, { cwd });
}
yield* (await globArray(pattern, cwd));
}
/**
* Returns those subdirectories, recursively, that match the glob pattern.
* Skips common large directories (node_modules, .git, .hg, .svn, dist, target, build, out).
* @param {string} pattern - The glob pattern to match against.
* @param {string} cwd - The current working directory to start the search from.
* @returns {Promise<string[]>} - An array of relative paths that match the glob pattern.
*/
async function globArray(pattern, cwd) {
const regex = globToRegex(pattern);
return (await subdirs(cwd))
.map(fullPath => path.relative(cwd, fullPath))
.filter(relativePath => regex.test(relativePath.replace(/\\/g, '/')));
}
/**
* Recursively returns all subdirectories of a given directory.
* Returns only non-hidden directories (those not starting with a dot).
* Skips common large directories like node_modules, .git, etc.
* @param {string} dir - The directory to search in.
* @returns {Promise<String[]>} - An array of paths to subdirectories.
*/
async function subdirs(dir) {
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return [];
}
const children = entries.filter(child =>
child.isDirectory() && !child.name.startsWith('.') && !IGNORE_DIRS.has(child.name)
);
return (await Promise.all(
children.map(async child => {
const childPath = join(dir, child.name);
const descendants = await subdirs(childPath);
return [childPath, ...descendants];
})
)).flat();
}
/**
* Converts a glob pattern to a regular expression.
* Supports the following wildcards: `? * ** [...] [!...]`.
* Patterns such as `./foo` are normalized to `foo`.
* @param {string} glob - The glob pattern to convert.
* @returns {RegExp} - The regular expression that matches the glob pattern.
*/
function globToRegex(glob) {
const regex = glob
.replace(/^\.\//, '')
.replaceAll('**', '|')
.replaceAll(/\[!([^\]]+)]/g, '[^$1]')
.replaceAll('?', '[^/]')
.replaceAll('*', '[^/]*')
.replaceAll('|', '.*');
return new RegExp(`^${regex}$`);
}
module.exports = {
glob
};