sb-babel-cli
Version:
A smarter babel-cli
204 lines (203 loc) • 9.27 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const os_1 = __importDefault(require("os"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const make_dir_1 = __importDefault(require("make-dir"));
const chokidar_1 = __importDefault(require("chokidar"));
const anymatch_1 = __importDefault(require("anymatch"));
const debounce_1 = __importDefault(require("lodash/debounce"));
const child_process_1 = __importDefault(require("child_process"));
const sb_promise_queue_1 = require("sb-promise-queue");
const iterate_1 = __importDefault(require("./iterate"));
const helpers_1 = require("./helpers");
async function main(cliConfig) {
var _a;
const config = await helpers_1.loadConfigFromRoot(cliConfig.rootDirectory, cliConfig);
const resolvedSourceDirectory = path_1.default.resolve(config.rootDirectory, config.sourceDirectory);
const resolvedOutputDirectory = path_1.default.resolve(config.rootDirectory, (_a = config.outputDirectory) !== null && _a !== void 0 ? _a : '');
if (config.printConfig) {
console.log('CLI Config', JSON.stringify(config, null, 2));
console.log('Resolved Config (with config merged from Manifest)', JSON.stringify(config, null, 2));
console.log('resolvedSourceDirectory', resolvedSourceDirectory);
console.log('resolvedOutputDirectory', resolvedOutputDirectory);
process.exit(1);
}
if (!config.watch && config.execute) {
console.error('ERROR: --execute is not supported without --watch');
process.exit(1);
}
if (!config.outputDirectory) {
console.log('ERROR: You must specify output directory');
process.exit(1);
}
config.sourceDirectory = resolvedSourceDirectory;
config.outputDirectory = resolvedOutputDirectory;
// Sort the longest extensions to the shortest. This will help with replace
config.extensions.sort((a, b) => b.length - a.length);
let restartId = 0;
let lastRestartId = 0;
let spawnedProcess = null;
const babelCore = helpers_1.getBabelCore(config.sourceDirectory);
const contentHash = `${config.sourceDirectory}-${config.outputFileExtension}`;
const contentHashCache = await helpers_1.getCacheDB(contentHash, !config.resetCache, config.cacheDirectory);
const transformationQueue = new sb_promise_queue_1.PromiseQueue({ concurrency: os_1.default.cpus().length });
const getOutputFilePath = (filePath) => {
const foundExt = config.extensions.find((ext) => filePath.endsWith(ext));
if (foundExt != null) {
return `${filePath.slice(0, -1 * foundExt.length)}${config.outputFileExtension}`;
}
return filePath;
};
const incrementRestartId = () => {
restartId = (restartId + 1) % 1024;
};
function log(...items) {
if (config.silent) {
return;
}
if (config.execute) {
console.log(`${chalk_1.default.yellow('[sb-babel-cli]')}`, ...items);
}
else {
console.log(...items);
}
}
async function processFile(sourceFile, outputFile, sourceFileContents, stats) {
if (!config.extensions.includes(path_1.default.extname(sourceFile)))
return;
const transformed = await babelCore.transformAsync(sourceFileContents, {
root: config.rootDirectory,
filename: sourceFile,
sourceMaps: config.sourceMaps === 'inline' ? 'inline' : config.sourceMaps,
});
if (transformed == null || transformed.code == null) {
return;
}
await make_dir_1.default(path_1.default.dirname(outputFile));
const mapFile = `${outputFile}.map`;
let outputContents = transformed.code;
if (config.sourceMaps === true) {
outputContents += `\n\n//# sourceMappingURL=${path_1.default.basename(mapFile)}`;
}
await Promise.all([
fs_1.default.promises.writeFile(outputFile, outputContents, {
mode: stats.mode,
}),
// Write source maps if option is given.
config.sourceMaps && config.sourceMaps !== 'inline'
? fs_1.default.promises.writeFile(mapFile, JSON.stringify(transformed.map, null, 2))
: null,
]);
log(path_1.default.relative(config.rootDirectory, sourceFile), '->', path_1.default.relative(config.rootDirectory, outputFile));
contentHashCache.set(helpers_1.getSha1(sourceFile), helpers_1.getSha1(sourceFileContents)).write();
}
const execute = debounce_1.default(function () {
if (!config.execute) {
return;
}
if (lastRestartId !== 0 && lastRestartId === restartId) {
return;
}
lastRestartId = restartId;
if (spawnedProcess == null) {
log(chalk_1.default.yellow('to restart at any time, enter `rs`'));
}
log(chalk_1.default.green(`starting 'node ${config.execute}'`));
if (spawnedProcess != null) {
spawnedProcess.kill('SIGINT');
spawnedProcess = null;
}
try {
spawnedProcess = child_process_1.default.spawn(process.execPath, config.nodeArgs.concat([config.execute]).concat(config.programArgs), {
stdio: 'inherit',
});
}
catch (err) {
helpers_1.logError(err);
}
}, config.executeDelay);
await iterate_1.default({
config,
getOutputFilePath,
async callback(sourceFile, outputFile, stats) {
const cachedHash = await contentHashCache.get(helpers_1.getSha1(sourceFile)).value();
const sourceFileContents = await fs_1.default.promises.readFile(sourceFile, 'utf8');
let outputFileExists = false;
try {
await fs_1.default.promises.access(outputFile, fs_1.default.constants.R_OK);
outputFileExists = true;
}
catch (_) {
/* No Op */
}
if (outputFileExists && cachedHash === helpers_1.getSha1(sourceFileContents)) {
if (!config.execute) {
log(path_1.default.relative(config.rootDirectory, sourceFile), 'is unchanged');
}
return;
}
transformationQueue.add(() => processFile(sourceFile, outputFile, sourceFileContents, stats)).catch(helpers_1.logError);
},
});
if (!config.watch) {
await transformationQueue.waitTillIdle();
return;
}
if (config.execute && process.stdin) {
if (typeof process.stdin.unref === 'function') {
process.stdin.unref();
}
process.stdin.on('data', function (chunk) {
if (chunk.toString().trim() === 'rs') {
incrementRestartId();
execute();
}
});
}
const watcher = chokidar_1.default.watch(config.sourceDirectory, {
ignored: config.ignored ? config.ignored : null,
alwaysStat: true,
ignoreInitial: true,
});
watcher.on('add', function (givenFileName, stats) {
const fileName = path_1.default.relative(config.sourceDirectory, givenFileName);
const sourceFile = path_1.default.join(config.sourceDirectory, fileName);
const outputFile = path_1.default.join(config.outputDirectory, fileName);
transformationQueue
.add(async () => processFile(sourceFile, getOutputFilePath(outputFile), await fs_1.default.promises.readFile(sourceFile, 'utf8'), stats))
.catch(helpers_1.logError)
.then(() => {
if (!config.ignoredForRestart || !anymatch_1.default(config.ignoredForRestart, helpers_1.posixifyPath(sourceFile))) {
incrementRestartId();
}
});
});
watcher.on('change', function (givenFileName, stats) {
const fileName = path_1.default.relative(config.sourceDirectory, givenFileName);
const sourceFile = path_1.default.join(config.sourceDirectory, fileName);
const outputFile = path_1.default.join(config.outputDirectory, fileName);
transformationQueue
.add(async () => processFile(sourceFile, getOutputFilePath(outputFile), await fs_1.default.promises.readFile(sourceFile, 'utf8'), stats))
.catch(helpers_1.logError)
.then(() => {
if (!config.ignoredForRestart || !anymatch_1.default(config.ignoredForRestart, helpers_1.posixifyPath(sourceFile))) {
incrementRestartId();
}
});
});
watcher.on('unlink', function (givenFileName) {
const fileName = path_1.default.relative(config.sourceDirectory, givenFileName);
const outputFile = path_1.default.join(config.outputDirectory, fileName);
fs_1.default.promises.unlink(outputFile).catch(function () {
/* No Op */
});
});
transformationQueue.onIdle(execute);
execute();
}
exports.default = main;