hereby
Version:
A simple task runner
94 lines • 3.38 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 { UserError } from "./utils.js";
function isHerebyfile(p) {
p = p.toLowerCase();
return p === "herebyfile.mjs" || p === "herebyfile.js";
}
export async function findHerebyfile(dir) {
const root = path.parse(dir).root;
for (; dir !== root; dir = path.dirname(dir)) {
const entries = await fs.promises.readdir(dir);
const matching = entries.filter(isHerebyfile);
if (matching.length > 1) {
throw new UserError(`Found more than one Herebyfile: ${matching.join(", ")}`);
}
if (matching.length === 1) {
const candidate = path.join(dir, matching[0]);
const stat = await fs.promises.stat(candidate);
if (!stat.isFile()) {
throw new UserError(`${matching[0]} is not a file.`);
}
return candidate;
}
if (entries.includes("package.json")) {
break;
}
}
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?");
}
const tasks = [...exportedTasks.values()];
// 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(tasks);
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