hereby
Version:
A simple task runner
90 lines • 3.46 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import pc from "picocolors";
import { Task } from "../index.js";
import { findUp, UserError } from "./utils.js";
const herebyfileRegExp = /^herebyfile\.m?js$/i;
export function findHerebyfile(dir) {
const result = findUp(dir, (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const matching = entries.filter((e) => herebyfileRegExp.test(e.name));
if (matching.length > 1) {
throw new UserError(`Found more than one Herebyfile: ${matching.map((e) => e.name).join(", ")}`);
}
if (matching.length === 1) {
const candidate = matching[0];
if (!candidate.isFile()) {
throw new UserError(`${candidate.name} is not a file.`);
}
return path.join(dir, candidate.name);
}
if (entries.some((e) => e.name === "package.json")) {
return false; // TODO: Is this actually desirable? What about monorepos?
}
return undefined;
});
if (result) {
return result;
}
throw new UserError("Unable to find Herebyfile.");
}
export async function loadHerebyfile(herebyfilePath) {
// Note: calling pathToFileURL is required on Windows to disambiguate URLs
// from drive letters.
const herebyfile = await import(pathToFileURL(herebyfilePath).toString());
const exportedTasks = new Set();
let defaultTask;
for (const [key, value] of Object.entries(herebyfile)) {
if (value instanceof Task) {
if (key === "default") {
defaultTask = value;
}
else if (exportedTasks.has(value)) {
throw new UserError(`Task "${pc.blue(value.options.name)}" has been exported twice.`);
}
else {
exportedTasks.add(value);
}
}
}
if (defaultTask) {
exportedTasks.add(defaultTask);
}
if (exportedTasks.size === 0) {
throw new UserError("No tasks found. Did you forget to export your tasks?");
}
// We check this here by walking the DAG, as some dependencies may not be
// exported and therefore would not be seen by the above loop.
checkTaskInvariants(exportedTasks);
const tasks = new Map([...exportedTasks].map((task) => [task.options.name, task]));
return { tasks, defaultTask };
}
function checkTaskInvariants(tasks) {
const checkedTasks = new Set();
const taskStack = new Set();
const seenNames = new Set();
checkTaskInvariantsWorker(tasks);
function checkTaskInvariantsWorker(tasks) {
for (const task of tasks) {
if (checkedTasks.has(task)) {
continue;
}
if (taskStack.has(task)) {
throw new UserError(`Task "${pc.blue(task.options.name)}" references itself.`);
}
const name = task.options.name;
if (seenNames.has(name)) {
throw new UserError(`Task "${pc.blue(name)}" was declared twice.`);
}
seenNames.add(name);
if (task.options.dependencies) {
taskStack.add(task);
checkTaskInvariantsWorker(task.options.dependencies);
taskStack.delete(task);
}
checkedTasks.add(task);
}
}
}
//# sourceMappingURL=loadHerebyfile.js.map