@enspirit/emb
Version:
A replacement for our Makefile-for-monorepos
120 lines (119 loc) • 5.13 kB
JavaScript
import { getContext } from '../../../index.js';
import { input } from '@inquirer/prompts';
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
import { PassThrough } from 'node:stream';
import { ContainerExecOperation } from '../../../docker/index.js';
import { EMBCollection, findRunOrder } from '../../index.js';
import { ExecuteLocalCommandOperation } from '../index.js';
export var ExecutorType;
(function (ExecutorType) {
ExecutorType["container"] = "container";
ExecutorType["local"] = "local";
})(ExecutorType || (ExecutorType = {}));
export class RunTasksOperation {
async run(params) {
const { monorepo } = getContext();
// First ensure the selection is valid (user can use task IDs or names)
const collection = new EMBCollection(monorepo.tasks, {
idField: 'id',
depField: 'pre',
});
const ordered = findRunOrder(params.tasks, collection, {
onAmbiguous: params.allMatching ? 'runAll' : 'error',
});
const hasInteractiveTasks = ordered.find((t) => t.interactive === true);
if (hasInteractiveTasks) {
monorepo.setTaskRenderer('silent');
}
const manager = monorepo.taskManager();
await manager.run(ordered.map((task) => {
return {
rendererOptions: {
persistentOutput: true,
},
task: async (context, listrTask) => {
if (!task.script) {
return;
}
const vars = await monorepo.expand(task.vars || {});
const executor = params.executor ?? (await this.defaultExecutorFor(task));
await this.ensureExecutorValid(executor, task);
// Handle tasks that require confirmation
if (task.confirm) {
const expected = await monorepo.expand(task.confirm.expect || 'yes', vars);
const message = await monorepo.expand(task.confirm.message, vars);
const res = await listrTask
.prompt(ListrInquirerPromptAdapter)
.run(input, {
message: `${message} (type '${expected}' to continue)`,
});
if (res !== expected) {
throw new Error('Task canceled');
}
}
const tee = new PassThrough();
const logFile = await monorepo.store.createWriteStream(`logs/tasks/${task.id}.logs`);
tee.pipe(listrTask.stdout());
tee.pipe(logFile);
switch (executor) {
case ExecutorType.container: {
return this.runDocker(task, tee);
}
case ExecutorType.local: {
return this.runLocal(task, tee);
}
default: {
throw new Error(`Unsuported executor type: ${executor}`);
}
}
},
title: `Running ${task.id}`,
};
}));
return ordered;
}
async runDocker(task, out) {
const { monorepo, compose } = getContext();
const containerID = await compose.getContainer(task.component);
return monorepo.run(new ContainerExecOperation(task.interactive ? undefined : out), {
container: containerID,
script: task.script,
interactive: task.interactive || false,
env: await monorepo.expand(task.vars || {}),
});
}
async runLocal(task, _out) {
const { monorepo } = getContext();
const cwd = task.component
? monorepo.join(monorepo.component(task.component).rootDir)
: monorepo.rootDir;
return monorepo.run(new ExecuteLocalCommandOperation(), {
script: task.script,
workingDir: cwd,
interactive: task.interactive,
env: await monorepo.expand(task.vars || {}),
});
}
async defaultExecutorFor(task) {
const available = await this.availableExecutorsFor(task);
if (available.length === 0) {
throw new Error('No available executor found for task');
}
return available[0];
}
async ensureExecutorValid(executor, task) {
const available = await this.availableExecutorsFor(task);
if (!available.includes(executor)) {
throw new Error(`Unsuported executor type: ${executor}`);
}
}
async availableExecutorsFor(task) {
const { compose } = getContext();
if (task.executors) {
return task.executors;
}
return task.component && (await compose.isService(task.component))
? [ExecutorType.container, ExecutorType.local]
: [ExecutorType.local];
}
}