jazelle/rules/execute-command.js (144 lines of code) (raw):
// @flow
const {
existsSync: exists,
readFileSync: read,
readdirSync: readdir,
realpathSync: realpath,
statSync: stat,
} = require('fs');
const {execSync: exec} = require('child_process');
const {dirname, join, relative} = require('path');
const root = process.cwd();
const [node, , main, , command, distPaths, gen, out, ...args] = process.argv;
const dists = distPaths.split('|');
const {scripts = {}} = JSON.parse(read(`${main}/package.json`, 'utf8'));
if (out) {
runCommands(command, args);
const dirs = dists.map(dist => `"${dist}"`).join(' ');
for (const dist of dists) {
if (!exists(join(main, dist))) {
exec(`mkdir -p "${dist}"`, {cwd: main});
}
}
exec(`tar czf "${out}" ${dirs}`, {cwd: main});
} else {
runCommands(command, args);
// handle `gen_srcs`:
// - if the command generates file (e.g. jest snapshots), copy them back to the source dir
// - if the files already exist, they are updated through Bazel's symlink and the copy is not needed
generateSources({root, main, regexes: gen.split('|').filter(Boolean)});
}
function runCommands(command, args) {
if (command.startsWith('yarn ')) {
runCommand(command.substr(5), args);
return;
}
if (command === 'run') {
command = args.shift();
}
runCommand(scripts[`pre${command}`]);
runCommand(scripts[command], args);
runCommand(scripts[`post${command}`]);
}
function runCommand(command, args = []) {
if (command) {
const nodeDir = dirname(node);
const params = args.map(arg => `'${arg}'`).join(' ');
// prioritize hermetic Node version over system version
const binPath = `:${root}/node_modules/.bin`;
if (process.env.NODE_PRESERVE_SYMLINKS) {
const bins = readdir('node_modules/.bin');
const items = command.split(' ');
let matchingBin = null;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (bins.includes(item)) {
if (
read(join('node_modules/.bin', item), 'utf-8')
.split('\n')[0]
.trim()
.endsWith('node')
) {
matchingBin = item;
break;
}
}
}
if (matchingBin) {
const realBin = realpath(join('node_modules/.bin', matchingBin));
let pathToUse = join(
process.cwd(),
'node_modules',
realBin.split('node_modules').pop()
);
if (exists(pathToUse)) {
command = command.replace(
matchingBin,
`node --preserve-symlinks-main ${pathToUse}`
);
}
}
}
const script = `export PATH=${nodeDir}${binPath}:$PATH; ${command} ${params}`;
try {
exec(script, {cwd: main, env: process.env, stdio: 'inherit'});
} catch (e) {
process.exit(1);
}
}
}
function generateSources({root, main, regexes}) {
const dir = dirname(relative(root, `${main}/package.json`));
const realDir = dirname(realpath(`${main}/package.json`));
const relSandbox = relative(root, dir);
const real = realDir.replace(`/${relSandbox}`, '');
for (const regex of regexes) {
const sandboxed = find({root, regex: new RegExp(regex)});
for (const item of sandboxed) {
const rel = relative(root, item);
const sandboxedPath = `${root}/${rel}`;
const gensrcPath = `${real}/${rel}`;
const copy = `cp -rf ${sandboxedPath} ${gensrcPath}`;
if (!exists(gensrcPath)) {
exec(copy, {cwd: root});
}
deleteExtraneousFiles({sandboxedPath, gensrcPath});
}
}
}
function deleteExtraneousFiles({sandboxedPath, gensrcPath}) {
const sandboxed = ls(sandboxedPath);
const generated = ls(gensrcPath);
const extraneous = [];
for (const item of generated) {
if (!sandboxed.includes(item)) {
extraneous.push(item);
}
}
if (extraneous.length > 0) {
exec(`rm -rf ${extraneous.join(' ')}`, {cwd: gensrcPath});
}
}
function ls(dir) {
try {
return exec('ls', {cwd: dir, encoding: 'utf8'})
.trim()
.split('\n')
.filter(Boolean);
} catch (e) {
return [];
}
}
function* find({root, regex}) {
const dirs = readdir(root);
for (const dir of dirs) {
const path = `${root}/${dir}`;
const s = getStat(path);
if (path.match(regex)) yield path;
if (s.isDirectory()) yield* find({root: path, regex});
}
}
function getStat(path) {
try {
return stat(path);
} catch (e) {
return {
isDirectory: () => false,
};
}
}