msync
Version:
Easily manage building and syncing multiple node-modules in a flexibly defined workspace.
155 lines (154 loc) • 5.75 kB
JavaScript
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { constants, elapsed, exec, filter as filterUtil, fs, listr, loadSettings, log, } from '../common';
import * as listCommand from './ls.cmd';
import * as syncCommand from './sync.cmd';
export const name = 'build';
export const description = 'Builds and syncs all typescript modules in order.';
export const args = {
'-i': 'Include ignored modules.',
'-w': 'Sync on changes to files.',
'-v': 'Verbose mode. Prints all error details.',
};
export async function cmd(args) {
const options = (args && args.options) || {};
const watch = options.w || false;
const includeIgnored = options.i || false;
const verbose = options.v || false;
await build({ includeIgnored, watch, verbose });
}
export async function build(options = {}) {
const { includeIgnored = false, watch = false, verbose = false } = options;
const settings = await loadSettings();
if (!settings) {
log.warn.yellow(constants.CONFIG_NOT_FOUND_ERROR);
return;
}
const modules = settings.modules
.filter(pkg => filterUtil.includeIgnored(pkg, includeIgnored))
.filter(pkg => pkg.isTypeScript);
if (watch) {
return buildWatch(modules, includeIgnored, verbose);
}
else {
return buildOnce(modules);
}
}
const tscCommand = async (pkg) => {
const path = fs.join(pkg.dir, 'node_modules/typescript/bin/tsc');
return (await fs.pathExists(path)) ? path : 'tsc';
};
export async function buildOnce(modules) {
const startedAt = new Date();
const tasks = modules.map(pkg => {
return {
title: `${log.magenta(pkg.name)} ${log.gray('=> sync')}`,
task: async () => {
const tsc = await tscCommand(pkg);
const cmd = `cd ${pkg.dir} && ${tsc}`;
await exec.cmd.run(cmd, { silent: true });
await syncCommand.sync({
includeIgnored: false,
updateVersions: false,
silent: true,
});
},
};
});
try {
const taskList = listr(tasks, { concurrent: false, exitOnError: false });
await taskList.run();
log.info.gray('', elapsed(startedAt));
log.info();
}
catch (error) {
}
}
export async function buildWatch(modules, includeIgnored, verbose) {
log.info.magenta('\nBuild watching:');
listCommand.printTable(modules, { includeIgnored });
log.info();
const state = {};
const updates$ = new Subject();
updates$.pipe(debounceTime(100)).subscribe(() => {
log.clear();
const items = Object.keys(state)
.sort()
.map(key => ({ key, value: state[key] }));
items.forEach(({ key, value }) => {
const hasErrors = value.errors.length > 0;
const bullet = hasErrors ? log.red('✘') : value.isBuilding ? log.gray('✎') : log.green('✔');
log.info(`${bullet} ${log.cyan(key)} ${value.message}`);
});
const errors = items.filter(({ value }) => value.errors.length > 0);
if (verbose && errors.length > 0) {
log.info();
errors.forEach(({ key, value }) => {
value.errors.forEach(error => {
log
.table()
.add([log.yellow(key), formatError(error)])
.log();
});
});
}
});
modules.forEach(async (pkg) => {
const tsc = await tscCommand(pkg);
const cmd = `cd ${pkg.dir} && ${tsc} --watch`;
exec.cmd.run(cmd).output$.subscribe(e => {
let text = e.text;
const isBuilding = text.includes('Starting compilation in watch') ||
text.includes('Starting incremental compilation');
const isError = text.includes('error') && !text.includes('Found 0 errors.');
const isSuccess = text.includes('Found 0 errors.');
const isBuilt = text.includes('Watching for file changes.');
text = text.replace(/\n*$/, '');
const key = pkg.name;
let obj = state[key] || { count: 0, errors: [] };
obj.isBuilding = isBuilding;
if (isBuilding || isBuilt) {
const count = isBuilding ? obj.count + 1 : obj.count;
const status = isBuilding ? 'Building...' : 'Built';
const countStatus = isBuilding ? log.gray(count) : log.green(count);
const message = log.gray(`${status} (${countStatus})`);
obj = Object.assign(Object.assign({}, obj), { count, message });
}
if (isError) {
if (!isBuilt) {
obj.errors = [...obj.errors, text];
}
const error = obj.errors.length === 1 ? 'Error' : 'Errors';
obj.message = log.red(`${obj.errors.length} ${error}`);
}
if (isSuccess) {
obj.errors = [];
}
state[key] = obj;
updates$.next();
});
});
}
const formatError = (error) => {
const MAX = 80;
const lines = [];
error.split('\n').forEach(line => {
line = line.length <= MAX ? line : splitLines(line);
lines.push(line);
});
return lines.join('\n');
};
const splitLines = (line) => {
const MAX = 80;
const words = [];
let count = 0;
line.split('').forEach(word => {
count += word.length;
if (count > MAX) {
word = `${word}\n`;
count = 0;
}
words.push(word);
});
return words.join('');
};