@plugjs/plug
Version:
PlugJS Build System ===================
190 lines (189 loc) • 6.59 kB
JavaScript
// build.ts
import { BuildFailure, assert } from "./asserts.mjs";
import { runAsync } from "./async.mjs";
import { $grn, $gry, $ms, $p, $plur, $t, $ylw, NOTICE, getLogger, log, logOptions } from "./logging.mjs";
import { Context, ContextPromises, PipeImpl } from "./pipe.mjs";
import { findCaller } from "./utils/caller.mjs";
import { getSingleton } from "./utils/singleton.mjs";
var buildMarker = Symbol.for("plugjs:plug:types:Build");
var taskCallMarker = Symbol.for("plugjs:plug:types:TaskCall");
function isTaskCall(something) {
return something[taskCallMarker] === taskCallMarker;
}
function merge(a, b) {
return Object.assign(/* @__PURE__ */ Object.create(null), a, b);
}
function makeState(state) {
const {
cache = /* @__PURE__ */ new Map(),
fails = /* @__PURE__ */ new Set(),
stack = [],
tasks = {},
props = {}
} = state;
return { cache, fails, stack, tasks, props };
}
var lastIdKey = Symbol.for("plugjs:plug:singleton:taskId");
var taskId = getSingleton(lastIdKey, () => ({ id: 0 }));
var TaskImpl = class {
constructor(name, buildFile, _def, _tasks, _props) {
this.name = name;
this.buildFile = buildFile;
this._def = _def;
this.tasks = _tasks;
this.props = _props;
}
before = [];
after = [];
id = ++taskId.id;
props;
tasks;
async invoke(state, taskName) {
assert(!state.stack.includes(this), `Recursion detected calling ${$t(taskName)}`);
const cached = state.cache.get(this);
if (cached) return cached;
state = makeState({
props: merge(this.props, state.props),
tasks: merge(this.tasks, state.tasks),
stack: [...state.stack, this],
cache: state.cache,
fails: state.fails
});
const context = new Context(this.buildFile, taskName);
const build2 = new Proxy({}, {
get: (_, name) => {
if (name in state.tasks) {
return () => {
const promise2 = state.tasks[name].invoke(state, name);
return new PipeImpl(context, promise2);
};
} else if (name in state.props) {
return state.props[name];
}
}
});
for (const before of this.before) await before.invoke(state, before.name);
context.log.info("Running...");
const now = Date.now();
const promise = runAsync(context, async () => {
return await this._def.call(build2) || void 0;
}).then(async (result) => {
const level = taskName.startsWith("_") ? "info" : "notice";
context.log[level](`Success ${$ms(Date.now() - now)}`);
return result;
}).catch((error) => {
state.fails.add(this);
context.log.error(`Failure ${$ms(Date.now() - now)}`, error);
throw BuildFailure.fail();
}).finally(async () => {
await ContextPromises.wait(context);
}).then(async (result) => {
for (const after of this.after) await after.invoke(state, after.name);
return result;
});
state.cache.set(this, promise);
return promise;
}
};
function plugjs(def) {
const buildFile = findCaller(plugjs);
const tasks = {};
const props = {};
for (const [key, val] of Object.entries(def)) {
let len = 0;
if (isTaskCall(val)) {
tasks[key] = val.task;
len = key.length;
} else if (typeof val === "string") {
props[key] = val;
} else if (typeof val === "function") {
tasks[key] = new TaskImpl(key, buildFile, val, tasks, props);
len = key.length;
}
if (logOptions.level >= NOTICE && key.startsWith("_")) continue;
if (len > logOptions.taskLength) logOptions.taskLength = len;
}
const start = async function start2(callback, overrideProps = {}) {
const state = makeState({ tasks, props: merge(props, overrideProps) });
const logger = getLogger();
logger.notice("Starting...");
const now = Date.now();
try {
const result = await callback(state);
logger.notice(`Build successful ${$ms(Date.now() - now)}`);
return result;
} catch (error) {
if (state.fails.size) {
logger.error("");
logger.error($plur(state.fails.size, "task", "tasks"), "failed:");
state.fails.forEach((task) => logger.error($gry("*"), $t(task.name)));
logger.error("");
}
throw logger.fail(`Build failed ${$ms(Date.now() - now)}`, error);
}
};
const invoke = async function invoke2(taskNames, overrideProps = {}) {
await start(async (state) => {
for (const name of taskNames) {
const task = tasks[name];
assert(task, `Task ${$t(name)} not found in build ${$p(buildFile)}`);
await task.invoke(state, name);
}
}, overrideProps);
};
const callables = {};
for (const [name, task] of Object.entries(tasks)) {
const callable = async (overrideProps) => start(async (state) => task.invoke(state, name), overrideProps);
callables[name] = Object.defineProperties(callable, {
[taskCallMarker]: { value: taskCallMarker },
"task": { value: task },
"name": { value: name }
});
}
const compiled = merge(props, callables);
Object.defineProperty(compiled, buildMarker, { value: invoke });
return compiled;
}
var build = function(def) {
log.warn(`Use of deprecated ${$ylw("build")} entry point, please use ${$grn("plugjs")}`);
return plugjs(def);
};
function isBuild(build2) {
return build2 && typeof build2[buildMarker] === "function";
}
function invokeTasks(build2, tasks, props) {
if (isBuild(build2)) {
return build2[buildMarker](tasks, props);
} else {
throw new TypeError("Invalid build instance");
}
}
function hookBefore(build2, taskName, hooks) {
const taskCall = build2[taskName];
assert(isTaskCall(taskCall), `Task "${$t(taskName)}" not found in build`);
for (const hook of hooks) {
const beforeHook = build2[hook];
assert(isTaskCall(beforeHook), `Task "${$t(hook)}" to hook before "${$t(taskName)}" not found in build`);
if (taskCall.task.before.includes(beforeHook.task)) continue;
taskCall.task.before.push(beforeHook.task);
}
}
function hookAfter(build2, taskName, hooks) {
const taskCall = build2[taskName];
assert(isTaskCall(taskCall), `Task "${$t(taskName)}" not found in build`);
for (const hook of hooks) {
const afterHook = build2[hook];
assert(isTaskCall(afterHook), `Task "${$t(hook)}" to hook after "${$t(taskName)}" not found in build`);
if (taskCall.task.after.includes(afterHook.task)) continue;
taskCall.task.after.push(afterHook.task);
}
}
export {
build,
hookAfter,
hookBefore,
invokeTasks,
isBuild,
plugjs
};
//# sourceMappingURL=build.mjs.map