235 lines
6.3 KiB
JavaScript
235 lines
6.3 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs-extra');
|
|
const chokidar = require('chokidar');
|
|
const multimatch = require('multimatch');
|
|
const { exec } = require('child_process');
|
|
const { dirs, jsFiles, scssFiles, ignoreMask, copyDirs, symlinkFiles } = require('./config');
|
|
const { debounce, envCheckTrue, onSuccess, onError, getSignatures, writeSignatures, cleanUp, formatDirsForMatcher } = require('./utils');
|
|
const getJS = require('./js');
|
|
const getSass = require('./sass');
|
|
const getCopy = require('./copy');
|
|
const getSymlinks = require('./symlinks');
|
|
const colors = require('colors/safe');
|
|
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const addOmniExecPath = path.join(ROOT, 'app', 'scripts', 'add_omni_file');
|
|
let shouldAddOmni = false;
|
|
|
|
const source = [
|
|
'chrome',
|
|
'components',
|
|
'defaults',
|
|
'resource',
|
|
'scss',
|
|
'test',
|
|
'styles',
|
|
'translators',
|
|
'scss',
|
|
'chrome/**',
|
|
'components/**',
|
|
'defaults/**',
|
|
'resource/**',
|
|
'scss/**',
|
|
'test/**',
|
|
'styles/**',
|
|
'translators/**',
|
|
'scss/**'
|
|
];
|
|
|
|
const symlinks = symlinkFiles
|
|
.concat(dirs.map(d => `${d}/**`))
|
|
.concat([`!${formatDirsForMatcher(dirs)}/**/*.js`])
|
|
.concat([`!${formatDirsForMatcher(dirs)}/**/*.jsx`])
|
|
.concat([`!${formatDirsForMatcher(dirs)}/**/*.scss`])
|
|
.concat([`!${formatDirsForMatcher(copyDirs)}/**`]);
|
|
|
|
var signatures;
|
|
|
|
process.on('SIGINT', () => {
|
|
writeSignatures(signatures);
|
|
process.exit(); // eslint-disable-line no-process-exit
|
|
});
|
|
|
|
async function addOmniFiles(relPaths) {
|
|
const t1 = Date.now();
|
|
const buildDirPath = path.join(ROOT, 'build');
|
|
const wrappedPaths = relPaths.map(relPath => `"${path.relative(buildDirPath, relPath)}"`);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const cmd = `"${addOmniExecPath}" ${wrappedPaths.join(' ')}`;
|
|
exec(cmd, { cwd: buildDirPath }, (error, output) => {
|
|
if (error) {
|
|
reject(error);
|
|
}
|
|
else {
|
|
process.env.NODE_ENV === 'debug' && console.log(`Executed:\n${cmd};\nOutput:\n${output}\n`);
|
|
resolve(output);
|
|
}
|
|
});
|
|
});
|
|
|
|
const t2 = Date.now();
|
|
|
|
return {
|
|
action: 'add-omni-files',
|
|
count: relPaths.length,
|
|
totalCount: relPaths.length,
|
|
processingTime: t2 - t1
|
|
};
|
|
}
|
|
|
|
async function processFile(path) {
|
|
try {
|
|
var result = false;
|
|
if (multimatch(path, jsFiles).length && !multimatch(path, ignoreMask).length) {
|
|
result = await getJS(path, { ignore: ignoreMask }, signatures);
|
|
onSuccess(await cleanUp(signatures));
|
|
}
|
|
if (!result) {
|
|
for (var i = 0; i < scssFiles.length; i++) {
|
|
if (multimatch(path, scssFiles[i]).length) {
|
|
result = await getSass(scssFiles[i], { ignore: ignoreMask }); // eslint-disable-line no-await-in-loop
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!result && multimatch(path, copyDirs.map(d => `${d}/**`)).length) {
|
|
result = await getCopy(path, {}, signatures);
|
|
}
|
|
if (!result && multimatch(path, symlinks).length) {
|
|
result = await getSymlinks(path, { nodir: true }, signatures);
|
|
}
|
|
}
|
|
catch (err) {
|
|
onError(err);
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function processFiles(mutex) {
|
|
mutex.isLocked = true;
|
|
try {
|
|
const t1 = Date.now();
|
|
let paths = Array.from(mutex.batch);
|
|
let results = await Promise.all(paths.map(processFile));
|
|
let t2 = Date.now();
|
|
let aggrResult;
|
|
|
|
if (results.length === 1 && results[0]) {
|
|
onSuccess(results[0]);
|
|
aggrResult = results[0];
|
|
}
|
|
else if (results.length > 1) {
|
|
aggrResult = results.reduce((acc, result) => {
|
|
if (result) {
|
|
if (!(result.action in acc)) {
|
|
acc.actions[result.action] = 0;
|
|
}
|
|
acc.actions[result.action] += result?.count ?? 0;
|
|
acc.count += result?.count ?? 0;
|
|
acc.outFiles = acc.outFiles.concat(result?.outFiles ?? []);
|
|
}
|
|
return acc;
|
|
}, { actions: {}, count: 0, processingTime: t2 - t1, outFiles: [] });
|
|
|
|
onSuccess({
|
|
action: Object.keys(aggrResult.actions).length > 1 ? 'multiple' : Object.keys(aggrResult.actions)[0],
|
|
count: aggrResult.count,
|
|
processingTime: aggrResult.processingTime,
|
|
});
|
|
}
|
|
|
|
onSuccess(await cleanUp(signatures));
|
|
|
|
if (shouldAddOmni && aggrResult?.outFiles?.length) {
|
|
try {
|
|
onSuccess(await addOmniFiles(aggrResult.outFiles));
|
|
}
|
|
catch (err) {
|
|
onError(`omni update failed: ${err}`);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
mutex.isLocked = false;
|
|
mutex.batch.clear();
|
|
}
|
|
}
|
|
|
|
|
|
async function batchProcessFiles(path, mutex, debouncedProcessFiles) {
|
|
let counter = 0;
|
|
let pollInterval = 250;
|
|
let started = Date.now();
|
|
|
|
// if there's a batch processing and another batch waiting, add to it
|
|
if (mutex.isLocked && mutex.nextBatch) {
|
|
mutex.nextBatch.add(path);
|
|
return;
|
|
}
|
|
// else if there's a batch processing, create a new batch
|
|
else if (mutex.isLocked) {
|
|
mutex.nextBatch = new Set([path]);
|
|
}
|
|
while (mutex.isLocked) {
|
|
if (counter === 0) {
|
|
console.log(colors.yellow(`Waiting for previous batch to finish...`));
|
|
}
|
|
if (++counter >= 40) {
|
|
onError(`Batch processing timeout after ${counter * pollInterval}ms. ${mutex?.nextBatch?.size ?? 0} files in this batch have not been processed 😢`);
|
|
mutex.batch.clear();
|
|
mutex.nextBatch = null;
|
|
mutex.isLocked = false;
|
|
return;
|
|
}
|
|
process.env.NODE_ENV === 'debug' && console.log(`waiting ${pollInterval}ms...`);
|
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
}
|
|
if (counter > 0) {
|
|
console.log(colors.green(`Previous batch finished in ${Date.now() - started}ms. ${mutex?.nextBatch?.size ?? 0} files in the next batch.`));
|
|
}
|
|
if (mutex.nextBatch) {
|
|
mutex.batch = new Set([...mutex.nextBatch]);
|
|
mutex.nextBatch = null;
|
|
}
|
|
else {
|
|
mutex.batch.add(path);
|
|
}
|
|
debouncedProcessFiles();
|
|
}
|
|
|
|
async function getWatch() {
|
|
try {
|
|
await fs.access(addOmniExecPath, fs.constants.F_OK);
|
|
shouldAddOmni = !envCheckTrue(process.env.SKIP_OMNI);
|
|
}
|
|
catch (_) {}
|
|
|
|
let mutex = { batch: new Set(), isLocked: false };
|
|
const debouncedProcessFiles = debounce(() => processFiles(mutex));
|
|
|
|
let watcher = chokidar.watch(source, { cwd: ROOT, ignoreInitial: true })
|
|
.on('change', (path) => {
|
|
batchProcessFiles(path, mutex, debouncedProcessFiles);
|
|
})
|
|
.on('add', (path) => {
|
|
batchProcessFiles(path, mutex, debouncedProcessFiles);
|
|
})
|
|
.on('unlink', debounce(async () => {
|
|
onSuccess(await cleanUp(signatures));
|
|
}));
|
|
|
|
watcher.add(source);
|
|
console.log(`Watching files for changes (omni updates ${shouldAddOmni ? 'enabled' : 'disabled'})...`);
|
|
}
|
|
|
|
module.exports = getWatch;
|
|
|
|
if (require.main === module) {
|
|
(async () => {
|
|
signatures = await getSignatures();
|
|
getWatch();
|
|
})();
|
|
}
|