lion/packages-node/providence-analytics/src/cli/cli-helpers.js
2023-11-08 19:01:20 +01:00

213 lines
5.9 KiB
JavaScript

/* eslint-disable no-shadow */
const pathLib = require('path');
const child_process = require('child_process'); // eslint-disable-line camelcase
const glob = require('glob');
const readPackageTree = require('../program/utils/read-package-tree-with-bower-support.js');
const { LogService } = require('../program/core/LogService.js');
const { toPosixPath } = require('../program/utils/to-posix-path.js');
/**
* @param {any[]} arr
* @returns {any[]}
*/
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
/**
* @param {string} v
* @returns {string[]}
*/
function csToArray(v) {
return v.split(',').map(v => v.trim());
}
/**
* @param {string} v like 'js,html'
* @returns {string[]} like ['.js', '.html']
*/
function extensionsFromCs(v) {
return csToArray(v).map(v => `.${v}`);
}
function setQueryMethod(m) {
const allowedMehods = ['grep', 'ast'];
if (allowedMehods.includes(m)) {
return m;
}
LogService.error(`Please provide one of the following methods: ${allowedMehods.join(', ')}`);
return undefined;
}
/**
* @param {string} t
* @returns {string[]|undefined}
*/
function pathsArrayFromCs(t, cwd = process.cwd()) {
if (!t) {
return undefined;
}
return flatten(
t.split(',').map(t => {
if (t.startsWith('/')) {
return t;
}
if (t.includes('*')) {
if (!t.endsWith('/')) {
// eslint-disable-next-line no-param-reassign
t = `${t}/`;
}
return glob.sync(t, { cwd, absolute: true }).map(toPosixPath);
}
return toPosixPath(pathLib.resolve(cwd, t.trim()));
}),
);
}
/**
* @param {string} name collection name found in eCfg
* @param {'search-target'|'reference'} collectionType collection type
* @param {{searchTargetCollections: {[repo:string]:string[]}; referenceCollections:{[repo:string]:string[]}}} [eCfg] external configuration. Usually providence.conf.js
* @param {string} [cwd]
* @returns {string[]|undefined}
*/
function pathsArrayFromCollectionName(
name,
collectionType = 'search-target',
eCfg,
cwd = process.cwd(),
) {
let collection;
if (collectionType === 'search-target') {
collection = eCfg?.searchTargetCollections;
} else if (collectionType === 'reference') {
collection = eCfg?.referenceCollections;
}
if (collection?.[name]) {
return pathsArrayFromCs(collection[name].join(','), cwd);
}
return undefined;
}
/**
* @param {string} processArgStr
* @param {object} [opts]
* @returns {Promise<{ code:string; number:string }>}
* @throws {Error}
*/
function spawnProcess(processArgStr, opts) {
const processArgs = processArgStr.split(' ');
// eslint-disable-next-line camelcase
const proc = child_process.spawn(processArgs[0], processArgs.slice(1), opts);
/** @type {string} */
let output;
proc.stdout.on('data', data => {
output += data;
LogService.debug(data.toString());
});
return new Promise((resolve, reject) => {
proc.stderr.on('data', data => {
LogService.error(data.toString());
reject(data.toString());
});
proc.on('close', code => {
resolve({ code, output });
});
});
}
/**
* When providence is called from the root of a repo and no target is provided,
* this will provide the default fallback (the project itself)
* @param {string} cwd
* @returns {string[]}
*/
function targetDefault(cwd) {
return [toPosixPath(cwd)];
}
/**
* Returns all sub projects matching condition supplied in matchFn
* @param {string[]} rootPaths all search-target project paths
* @param {string} [matchPattern] base for RegExp
* @param {('npm'|'bower')[]} [modes]
*/
async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['npm', 'bower']) {
let matchFn;
if (matchPattern) {
if (matchPattern.startsWith('/') && matchPattern.endsWith('/')) {
matchFn = (/** @type {any} */ _, /** @type {string} */ d) => {
const reString = matchPattern.slice(1, -1);
const result = new RegExp(reString).test(d);
LogService.debug(`[appendProjectDependencyPaths]: /${reString}/.test(${d} => ${result})`);
return result;
};
} else {
LogService.error(
`[appendProjectDependencyPaths] Please provide a matchPattern enclosed by '/'. Found: ${matchPattern}`,
);
}
}
/** @type {string[]} */
const depProjectPaths = [];
for (const targetPath of rootPaths) {
for (const mode of modes) {
await readPackageTree(
targetPath,
matchFn,
(/** @type {string | undefined} */ err, /** @type {{ children: any[]; }} */ tree) => {
if (err) {
throw new Error(err);
}
const paths = tree.children.map(child => child.realpath);
depProjectPaths.push(...paths);
},
mode,
);
}
}
// Write all data to {outputPath}/projectDeps.json
// const projectDeps = {};
// rootPaths.forEach(rootP => {
// depProjectPaths.filter(depP => depP.startsWith(rootP)).;
// });
return depProjectPaths.concat(rootPaths).map(toPosixPath);
}
/**
* Will install all npm and bower deps, so an analysis can be performed on them as well.
* Relevant when '--target-dependencies' is supplied.
* @param {string[]} searchTargetPaths
*/
async function installDeps(searchTargetPaths) {
for (const targetPath of searchTargetPaths) {
LogService.info(`Installing npm dependencies for ${pathLib.basename(targetPath)}`);
try {
await spawnProcess('npm i --no-progress', { cwd: targetPath });
} catch (e) {
LogService.error(e);
}
LogService.info(`Installing bower dependencies for ${pathLib.basename(targetPath)}`);
try {
await spawnProcess(`bower i --production --force-latest`, { cwd: targetPath });
} catch (e) {
LogService.error(e);
}
}
}
module.exports = {
csToArray,
extensionsFromCs,
setQueryMethod,
pathsArrayFromCs,
targetDefault,
appendProjectDependencyPaths,
spawnProcess,
installDeps,
pathsArrayFromCollectionName,
flatten,
};