UNPKG

@plugjs/plug

Version:
190 lines (189 loc) 6.59 kB
// 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