@mmisty/cypress-allure-adapter
Version:
cypress allure adapter to generate allure results during tests execution (Allure TestOps compatible)
848 lines (847 loc) • 35.1 kB
JavaScript
;
/**
* Allure Task Server
*
* Unified server that handles all Allure operations in a separate process:
* - Filesystem operations (mkdir, writeFile, readFile, etc.)
* - High-level Allure operations (attachVideo, moveToWatch, etc.)
*
* This prevents blocking the main Cypress process.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runServer = exports.AllureTaskServer = exports.findAvailablePort = void 0;
const http_1 = __importDefault(require("http"));
const net_1 = __importDefault(require("net"));
const path_1 = __importStar(require("path"));
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const fast_glob_1 = __importDefault(require("fast-glob"));
const allure_js_parser_1 = require("allure-js-parser");
const crypto_1 = require("crypto");
const debug_1 = __importDefault(require("debug"));
const allure_js_commons_1 = require("allure-js-commons");
const allure_operations_1 = require("./allure-operations");
const debug = (0, debug_1.default)('cypress-allure:task-server');
const debugOps = (0, debug_1.default)('cypress-allure:task-server:ops');
/**
* Operation queue with concurrency control
*/
class OperationQueue {
constructor(maxConcurrent = 10) {
this.queue = [];
this.running = 0;
this.maxConcurrent = maxConcurrent;
}
enqueue(operation) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => {
this.queue.push({ operation, resolve });
this.processNext();
});
});
}
processNext() {
return __awaiter(this, void 0, void 0, function* () {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const item = this.queue.shift();
if (!item) {
this.running--;
return;
}
try {
const result = yield executeOperation(item.operation);
item.resolve(result);
}
catch (err) {
item.resolve({
success: false,
error: err.message,
});
}
finally {
this.running--;
setImmediate(() => this.processNext());
}
});
}
get pendingCount() {
return this.queue.length;
}
get runningCount() {
return this.running;
}
}
/**
* Execute a filesystem operation
*/
function executeFsOperation(op) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
debugOps(`FS operation: ${op.type}`);
switch (op.type) {
case 'fs:mkdir': {
if ((0, fs_1.existsSync)(op.path)) {
return { success: true };
}
for (let i = 0; i < 5; i++) {
try {
yield (0, promises_1.mkdir)(op.path, { recursive: (_b = (_a = op.options) === null || _a === void 0 ? void 0 : _a.recursive) !== null && _b !== void 0 ? _b : true });
return { success: true };
}
catch (err) {
if (err.code === 'EEXIST') {
return { success: true };
}
yield new Promise(resolve => setTimeout(resolve, 50));
}
}
return { success: true };
}
case 'fs:mkdirSync': {
try {
(0, fs_1.mkdirSync)(op.path, op.options);
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
}
case 'fs:writeFile': {
const content = op.encoding === 'base64' ? Buffer.from(op.content, 'base64') : op.content;
yield (0, promises_1.writeFile)(op.path, content);
return { success: true };
}
case 'fs:appendFile': {
yield (0, promises_1.appendFile)(op.path, op.content);
return { success: true };
}
case 'fs:readFile': {
const data = yield (0, promises_1.readFile)(op.path);
return { success: true, data: data.toString('base64') };
}
case 'fs:copyFile': {
yield (0, promises_1.copyFile)(op.from, op.to);
if (op.removeSource && op.from !== op.to) {
try {
yield (0, promises_1.rm)(op.from);
}
catch (_c) {
// Ignore removal errors
}
}
return { success: true };
}
case 'fs:removeFile': {
yield (0, promises_1.rm)(op.path, { recursive: true, force: true });
return { success: true };
}
case 'fs:removeFileSync': {
try {
(0, fs_1.rmSync)(op.path, { recursive: true, force: true });
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
}
case 'fs:exists': {
try {
yield (0, promises_1.stat)(op.path);
return { success: true, data: true };
}
catch (_d) {
return { success: true, data: false };
}
}
case 'fs:existsSync': {
return { success: true, data: (0, fs_1.existsSync)(op.path) };
}
default:
return { success: false, error: `Unknown FS operation: ${op.type}` };
}
});
}
/**
* Execute an Allure high-level operation
*/
function executeAllureOperation(op) {
return __awaiter(this, void 0, void 0, function* () {
debugOps(`Allure operation: ${op.type}`);
switch (op.type) {
case 'allure:attachVideo': {
return yield attachVideoToContainers(op.allureResults, op.videoPath, op.allureAddVideoOnPass);
}
case 'allure:moveToWatch': {
return yield moveResultsToWatch(op.allureResults, op.allureResultsWatch);
}
case 'allure:attachScreenshots': {
return yield attachScreenshots(op.allureResults, op.screenshots, op.allTests);
}
case 'allure:copyScreenshot': {
return yield copyScreenshot(op.allureResults, op.screenshotPath, op.targetName);
}
case 'allure:writeTestMessage': {
return yield writeTestMessage(op.path, op.message);
}
default:
return { success: false, error: `Unknown Allure operation: ${op.type}` };
}
});
}
/**
* Execute a batch of operations
*/
function executeBatchOperation(op) {
return __awaiter(this, void 0, void 0, function* () {
const results = [];
for (const subOp of op.operations) {
const result = yield executeOperation(subOp);
results.push(result);
}
const allSuccess = results.every(r => r.success);
if (allSuccess) {
return { success: true, data: results };
}
return { success: false, error: 'Some batch operations failed' };
});
}
/**
* Main operation dispatcher
*/
function executeOperation(operation) {
return __awaiter(this, void 0, void 0, function* () {
if (operation.type === 'shutdown') {
return { success: true };
}
if (operation.type === 'health') {
return { success: true, data: { status: 'ok' } };
}
if (operation.type === 'batch') {
return executeBatchOperation(operation);
}
if (operation.type.startsWith('fs:')) {
return executeFsOperation(operation);
}
if (operation.type.startsWith('allure:')) {
return executeAllureOperation(operation);
}
return { success: false, error: `Unknown operation type: ${operation.type}` };
});
}
// ============================================================================
// High-level Allure Operations Implementation
// ============================================================================
/**
* Attach video to test containers
*/
function attachVideoToContainers(allureResults, videoPath, allureAddVideoOnPass) {
return __awaiter(this, void 0, void 0, function* () {
try {
debug(`attachVideoToContainers: ${videoPath}`);
const ext = '.mp4';
const specname = (0, path_1.basename)(videoPath, ext);
// Check video exists
try {
yield (0, promises_1.stat)(videoPath);
}
catch (_a) {
return { success: false, error: `Video does not exist: ${videoPath}` };
}
const res = (0, allure_js_parser_1.parseAllure)(allureResults);
const tests = res
.filter(t => (allureAddVideoOnPass ? true : t.status !== 'passed' && t.status !== 'skipped'))
.map(t => {
var _a;
return ({
path: (_a = t.labels.find((l) => l.name === 'path')) === null || _a === void 0 ? void 0 : _a.value,
id: t.uuid,
fullName: t.fullName,
parent: t.parent,
});
});
const testsAttach = tests.filter(t => t.path && t.path.indexOf(specname) !== -1);
const testsWithSameParent = Array.from(new Map(testsAttach.filter(test => test.parent).map(test => { var _a; return [(_a = test.parent) === null || _a === void 0 ? void 0 : _a.uuid, test]; })).values());
for (const test of testsWithSameParent) {
if (!test.parent) {
continue;
}
const containerFile = `${allureResults}/${test.parent.uuid}-container.json`;
try {
const contents = yield (0, promises_1.readFile)(containerFile);
const uuid = (0, crypto_1.randomUUID)();
const nameAttach = `${uuid}-attachment${ext}`;
const newPath = path_1.default.join(allureResults, nameAttach);
// Parse and update container
const containerJSON = JSON.parse(contents.toString());
const after = {
name: 'video',
attachments: [
{
name: `${specname}${ext}`,
type: 'video/mp4',
source: nameAttach,
},
],
parameters: [],
start: Date.now(),
stop: Date.now(),
status: 'passed',
statusDetails: {},
stage: allure_js_commons_1.Stage.FINISHED,
steps: [],
};
if (!containerJSON.afters) {
containerJSON.afters = [];
}
containerJSON.afters.push(after);
// Copy video if not exists
try {
yield (0, promises_1.stat)(newPath);
}
catch (_b) {
yield (0, promises_1.copyFile)(videoPath, newPath);
}
yield (0, promises_1.writeFile)(containerFile, JSON.stringify(containerJSON));
}
catch (err) {
debug(`Error updating container: ${err.message}`);
}
}
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
});
}
/**
* Move results to watch folder for TestOps
*/
function moveResultsToWatch(allureResults, allureResultsWatch) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (allureResults === allureResultsWatch) {
return { success: true };
}
// Ensure watch directory exists
if (!(0, fs_1.existsSync)(allureResultsWatch)) {
yield (0, promises_1.mkdir)(allureResultsWatch, { recursive: true });
}
// Helper to copy if source exists and target doesn't
const copyIfNeeded = (src_1, target_1, ...args_1) => __awaiter(this, [src_1, target_1, ...args_1], void 0, function* (src, target, removeSource = false) {
try {
yield (0, promises_1.stat)(src);
try {
yield (0, promises_1.stat)(target);
// Target exists, skip or remove source
if (removeSource && src !== target) {
yield (0, promises_1.rm)(src);
}
}
catch (_a) {
// Target doesn't exist, copy
yield (0, promises_1.copyFile)(src, target);
if (removeSource && src !== target) {
yield (0, promises_1.rm)(src);
}
}
}
catch (_b) {
// Source doesn't exist, skip
}
});
const targetPath = (src) => src.replace(allureResults, allureResultsWatch);
// Copy environment, executor, categories
yield copyIfNeeded(`${allureResults}/environment.properties`, targetPath(`${allureResults}/environment.properties`), true);
yield copyIfNeeded(`${allureResults}/executor.json`, targetPath(`${allureResults}/executor.json`), true);
yield copyIfNeeded(`${allureResults}/categories.json`, targetPath(`${allureResults}/categories.json`), true);
// Parse and move tests
const tests = (0, allure_js_parser_1.parseAllure)(allureResults);
// Scan attachments ONCE outside the loop (was inside loop - major perf bug)
const allAttachments = fast_glob_1.default.sync(`${allureResults}/*-attachment.*`);
debug(`Found ${allAttachments.length} attachments to process`);
// Track moved attachments to avoid duplicate moves
const movedAttachments = new Set();
// Get parent container UUIDs helper
const getAllParentUuids = (t) => {
const uuids = [];
let current = t.parent;
while (current) {
if (current.uuid) {
uuids.push(current.uuid);
}
current = current.parent;
}
return uuids;
};
for (const test of tests) {
const testSource = `${allureResults}/${test.uuid}-result.json`;
const testTarget = targetPath(testSource);
const containerSources = getAllParentUuids(test).map(uuid => `${allureResults}/${uuid}-container.json`);
// Find attachments referenced in test or containers
let testContents = '';
try {
testContents = (yield (0, promises_1.readFile)(testSource)).toString();
}
catch (_a) {
continue;
}
const containerContents = [];
for (const containerSource of containerSources) {
try {
containerContents.push((yield (0, promises_1.readFile)(containerSource)).toString());
}
catch (_b) {
// Skip
}
}
const testAttachments = allAttachments.filter(attachFile => {
const attachBasename = (0, path_1.basename)(attachFile);
return (testContents.includes(attachBasename) || containerContents.some(content => content.includes(attachBasename)));
});
// Move attachments (skip already moved)
for (const attachFile of testAttachments) {
if (movedAttachments.has(attachFile))
continue;
movedAttachments.add(attachFile);
const attachTarget = targetPath(attachFile);
yield copyIfNeeded(attachFile, attachTarget, true);
}
// Move test result
yield copyIfNeeded(testSource, testTarget, true);
// Move containers
for (const containerSource of containerSources) {
const containerTarget = targetPath(containerSource);
yield copyIfNeeded(containerSource, containerTarget, true);
}
}
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
});
}
/**
* Attach screenshots to test results
*/
function attachScreenshots(allureResults, screenshots, allTests) {
return __awaiter(this, void 0, void 0, function* () {
try {
for (const screenshot of screenshots) {
const uuids = allTests
.filter(t => {
var _a;
return t.status !== 'passed' &&
t.retryIndex === screenshot.testAttemptIndex &&
(0, path_1.basename)((_a = t.specRelative) !== null && _a !== void 0 ? _a : '') === screenshot.specName &&
(screenshot.testId ? t.mochaId === screenshot.testId : true);
})
.map(t => t.uuid);
if (uuids.length === 0) {
continue;
}
for (const uuid of uuids) {
const testFile = `${allureResults}/${uuid}-result.json`;
try {
const contents = yield (0, promises_1.readFile)(testFile);
const ext = path_1.default.extname(screenshot.path);
const name = path_1.default.basename(screenshot.path);
const testCon = JSON.parse(contents.toString());
const uuidNew = (0, crypto_1.randomUUID)();
const nameAttach = `${uuidNew}-attachment${ext}`;
const newPath = path_1.default.join(allureResults, nameAttach);
// Copy screenshot if not exists
try {
yield (0, promises_1.stat)(newPath);
}
catch (_a) {
yield (0, promises_1.copyFile)(screenshot.path, newPath);
}
if (!testCon.attachments) {
testCon.attachments = [];
}
testCon.attachments.push({
name: name,
type: 'image/png',
source: nameAttach,
});
yield (0, promises_1.writeFile)(testFile, JSON.stringify(testCon));
}
catch (err) {
debug(`Could not attach screenshot: ${err.message}`);
}
}
}
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
});
}
/**
* Copy a screenshot to allure results
*/
function copyScreenshot(allureResults, screenshotPath, targetName) {
return __awaiter(this, void 0, void 0, function* () {
try {
const targetPath = `${allureResults}/${targetName}`;
// Ensure directory exists
if (!(0, fs_1.existsSync)(allureResults)) {
yield (0, promises_1.mkdir)(allureResults, { recursive: true });
}
// Check source exists
try {
yield (0, promises_1.stat)(screenshotPath);
}
catch (_a) {
return { success: false, error: `Screenshot does not exist: ${screenshotPath}` };
}
// Copy if target doesn't exist
try {
yield (0, promises_1.stat)(targetPath);
}
catch (_b) {
yield (0, promises_1.copyFile)(screenshotPath, targetPath);
}
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
});
}
/**
* Write test message (for testing)
*/
function writeTestMessage(filePath, message) {
return __awaiter(this, void 0, void 0, function* () {
try {
const dirPath = (0, path_1.dirname)(filePath);
if (!(0, fs_1.existsSync)(dirPath)) {
yield (0, promises_1.mkdir)(dirPath, { recursive: true });
}
try {
yield (0, promises_1.stat)(filePath);
}
catch (_a) {
yield (0, promises_1.writeFile)(filePath, '');
}
yield (0, promises_1.appendFile)(filePath, `${message}\n`);
return { success: true };
}
catch (err) {
return { success: false, error: err.message };
}
});
}
// ============================================================================
// Server Implementation
// ============================================================================
/**
* Find an available port
*/
const findAvailablePort = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (startPort = 46000) {
return new Promise((resolve, reject) => {
const tryPort = (port, attempts = 0) => {
if (attempts > 100) {
reject(new Error('Could not find available port for task server'));
return;
}
const server = net_1.default.createServer();
server.listen(port, () => {
server.close(() => {
resolve(port);
});
});
server.on('error', () => {
tryPort(port + 1, attempts + 1);
});
};
tryPort(startPort);
});
});
exports.findAvailablePort = findAvailablePort;
/**
* Allure Task Server
*/
class AllureTaskServer {
constructor(maxConcurrentOps = 10) {
this.server = null;
this.port = null;
this.lastActivityTime = Date.now();
this.inactivityTimer = null;
this.isShuttingDown = false;
this.operationQueue = new OperationQueue(maxConcurrentOps);
}
resetInactivityTimer() {
this.lastActivityTime = Date.now();
if (this.inactivityTimer) {
clearTimeout(this.inactivityTimer);
}
this.inactivityTimer = setTimeout(() => {
const idleTime = Date.now() - this.lastActivityTime;
debug(`Inactivity timeout reached (idle for ${idleTime}ms), shutting down`);
this.forceShutdown();
}, AllureTaskServer.INACTIVITY_TIMEOUT_MS);
// Don't keep Node alive just for this timer
this.inactivityTimer.unref();
}
forceShutdown() {
if (this.isShuttingDown)
return;
this.isShuttingDown = true;
debug('Force shutdown initiated');
// Give a short time for graceful stop, then force exit
this.stop()
.then(() => {
debug('Graceful shutdown completed');
process.exit(0);
})
.catch(() => {
debug('Graceful shutdown failed, forcing exit');
process.exit(0);
});
// Force exit after timeout regardless
setTimeout(() => {
debug('Shutdown timeout, forcing exit');
process.exit(0);
}, 2000).unref();
}
start(requestedPort) {
return __awaiter(this, void 0, void 0, function* () {
if (this.server) {
return this.port;
}
this.port = requestedPort !== null && requestedPort !== void 0 ? requestedPort : (yield (0, exports.findAvailablePort)());
// Start inactivity timer
this.resetInactivityTimer();
return new Promise((resolve, reject) => {
this.server = http_1.default.createServer((req, res) => __awaiter(this, void 0, void 0, function* () {
var _a;
// Reset inactivity timer on each request
this.resetInactivityTimer();
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
if (req.method === 'GET' && req.url === allure_operations_1.SERVER_HEALTH_PATH) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
pending: this.operationQueue.pendingCount,
running: this.operationQueue.runningCount,
}));
return;
}
if (req.method !== 'POST' || !((_a = req.url) === null || _a === void 0 ? void 0 : _a.startsWith(allure_operations_1.SERVER_PATH))) {
res.writeHead(404);
res.end('Not found');
return;
}
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => __awaiter(this, void 0, void 0, function* () {
try {
const operation = JSON.parse(body);
if (operation.type === 'shutdown') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
setTimeout(() => {
this.forceShutdown();
}, 100);
return;
}
const result = yield this.operationQueue.enqueue(operation);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result));
}
catch (err) {
debug(`Error processing request: ${err.message}`);
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: err.message }));
}
}));
}));
this.server.on('error', err => {
debug(`Task server error: ${err.message}`);
reject(err);
});
this.server.listen(this.port, () => {
debug(`Task server started on port ${this.port}`);
resolve(this.port);
});
});
});
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
// Clear inactivity timer
if (this.inactivityTimer) {
clearTimeout(this.inactivityTimer);
this.inactivityTimer = null;
}
return new Promise(resolve => {
if (!this.server) {
resolve();
return;
}
const startTime = Date.now();
const waitForPending = () => __awaiter(this, void 0, void 0, function* () {
while (this.operationQueue.pendingCount > 0 || this.operationQueue.runningCount > 0) {
// Add timeout to prevent hanging forever
if (Date.now() - startTime > AllureTaskServer.SHUTDOWN_TIMEOUT_MS) {
debug(`Shutdown timeout reached, ${this.operationQueue.pendingCount} pending, ${this.operationQueue.runningCount} running`);
break;
}
yield new Promise(r => setTimeout(r, 100));
}
});
waitForPending().then(() => {
this.server.close(() => {
debug('Task server stopped');
this.server = null;
this.port = null;
resolve();
});
// Force close all connections after a short delay
setTimeout(() => {
if (this.server) {
debug('Force closing server');
this.server = null;
this.port = null;
resolve();
}
}, 1000);
});
});
});
}
getPort() {
return this.port;
}
}
exports.AllureTaskServer = AllureTaskServer;
// Inactivity timeout - auto-shutdown if no requests for this duration
AllureTaskServer.INACTIVITY_TIMEOUT_MS = 120000; // 2 minutes
// Max wait time for pending operations during shutdown
AllureTaskServer.SHUTDOWN_TIMEOUT_MS = 10000; // 10 seconds
/**
* Entry point when running as standalone script
*/
const runServer = () => __awaiter(void 0, void 0, void 0, function* () {
const args = process.argv.slice(2);
let port;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--port' && args[i + 1]) {
port = parseInt(args[i + 1], 10);
}
}
const server = new AllureTaskServer();
let isShuttingDown = false;
const shutdown = (reason) => __awaiter(void 0, void 0, void 0, function* () {
if (isShuttingDown)
return;
isShuttingDown = true;
debug(`Shutdown initiated: ${reason}`);
// Set a hard timeout to force exit
const forceExitTimer = setTimeout(() => {
debug('Force exit timeout reached');
process.exit(0);
}, 5000);
forceExitTimer.unref();
try {
yield server.stop();
debug('Graceful shutdown completed');
}
catch (err) {
debug(`Shutdown error: ${err.message}`);
}
process.exit(0);
});
try {
const actualPort = yield server.start(port);
process.stdout.write(`ALLURE_SERVER_PORT:${actualPort}\n`);
// Handle various shutdown signals
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('disconnect', () => shutdown('IPC disconnect'));
// Monitor parent process - if stdin closes, parent likely died
process.stdin.on('end', () => {
debug('Parent process stdin closed');
shutdown('stdin closed');
});
process.stdin.on('close', () => {
debug('Parent process stdin close event');
shutdown('stdin close');
});
// Resume stdin to receive events (required for 'end' event)
process.stdin.resume();
debug('Task server running, waiting for requests...');
}
catch (err) {
// eslint-disable-next-line no-console
console.error(`Failed to start task server: ${err.message}`);
process.exit(1);
}
});
exports.runServer = runServer;
if (require.main === module) {
(0, exports.runServer)();
}