Files
makelore/electron/services/release-utility-worker.ts

91 lines
2.6 KiB
TypeScript

import { parentPort } from 'electron';
import {
prepareProjectReleaseSummary,
ProjectReleaseBuildError,
type ReleaseBuildRuntimeContext,
} from './project-release-builder';
type StartMessage = {
type: 'start';
jobId: string;
projectPath: string;
clientVersion: string;
runtimeContext: ReleaseBuildRuntimeContext;
};
type CancelMessage = {
type: 'cancel';
jobId: string;
};
type WorkerMessage = StartMessage | CancelMessage;
const controllers = new Map<string, AbortController>();
function readMessage(value: unknown): WorkerMessage | null {
const candidate = value && typeof value === 'object' && 'data' in value
? (value as { data?: unknown }).data
: value;
if (!candidate || typeof candidate !== 'object') return null;
const message = candidate as Partial<WorkerMessage>;
if (message.type !== 'start' && message.type !== 'cancel') return null;
return message as WorkerMessage;
}
function post(message: unknown): void {
parentPort?.postMessage(message);
}
async function run(message: StartMessage): Promise<void> {
const controller = new AbortController();
controllers.set(message.jobId, controller);
try {
const prepared = await prepareProjectReleaseSummary({
projectPath: message.projectPath,
clientVersion: message.clientVersion,
runtimeContext: message.runtimeContext,
signal: controller.signal,
onProgress: (progress) => post({ type: 'progress', jobId: message.jobId, progress }),
});
try {
if (controller.signal.aborted) {
throw new ProjectReleaseBuildError('LOCAL_BUILD_CANCELLED');
}
post({
type: 'complete',
jobId: message.jobId,
packageSummary: prepared.sourceArchive.summary,
contract: prepared.contract,
});
} finally {
await prepared.dispose();
}
} catch (error) {
const code = error instanceof ProjectReleaseBuildError
? error.code
: 'LOCAL_BUILD_FAILED';
post({
type: 'error',
jobId: message.jobId,
code,
message: error instanceof Error ? error.message : String(error),
});
} finally {
controllers.delete(message.jobId);
// The worker is single-job by design. Exiting here releases any native
// module state and makes the process count observable and bounded.
setImmediate(() => process.exit(0));
}
}
parentPort?.on('message', (value) => {
const message = readMessage(value);
if (!message) return;
if (message.type === 'cancel') {
controllers.get(message.jobId)?.abort();
return;
}
if (controllers.has(message.jobId)) return;
void run(message);
});