@mintlify/previewing
Version:
Preview Mintlify docs locally
77 lines (76 loc) • 2.69 kB
JavaScript
import { randomUUID } from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { DOT_MINTLIFY } from '../constants.js';
const isNodeError = (error, code) => error instanceof Error && 'code' in error && error.code === code;
const readLockOwner = (lockPath) => {
try {
const owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
if (Number.isInteger(owner.pid) && owner.pid > 0 && typeof owner.token === 'string') {
return owner;
}
}
catch { }
return undefined;
};
const isProcessRunning = (pid) => {
try {
process.kill(pid, 0);
return true;
}
catch (error) {
return !isNodeError(error, 'ESRCH');
}
};
export const acquirePreviewLock = (port, locksDirectory = path.join(DOT_MINTLIFY, 'preview-locks')) => {
fs.mkdirSync(locksDirectory, { recursive: true });
const lockPath = path.join(locksDirectory, `${port}.lock`);
const token = `${process.pid}-${randomUUID()}`;
const candidatePath = path.join(locksDirectory, `.${port}-${token}.candidate`);
fs.writeFileSync(candidatePath, JSON.stringify({ pid: process.pid, token }), { flag: 'wx' });
let acquired = false;
try {
for (let attempt = 0; attempt < 5; attempt++) {
try {
fs.linkSync(candidatePath, lockPath);
acquired = true;
break;
}
catch (error) {
if (!isNodeError(error, 'EEXIST'))
throw error;
const owner = readLockOwner(lockPath);
if (owner && isProcessRunning(owner.pid)) {
throw new Error(`local preview on port ${port} is already starting or running`);
}
const stalePath = `${lockPath}.stale-${token}`;
try {
fs.renameSync(lockPath, stalePath);
fs.rmSync(stalePath, { force: true });
}
catch (renameError) {
if (!isNodeError(renameError, 'ENOENT'))
throw renameError;
}
}
}
}
finally {
fs.rmSync(candidatePath, { force: true });
}
if (!acquired) {
throw new Error(`could not acquire local preview workspace for port ${port}`);
}
let released = false;
const release = () => {
if (released)
return;
released = true;
process.off('exit', release);
if (readLockOwner(lockPath)?.token === token) {
fs.rmSync(lockPath, { force: true });
}
};
process.once('exit', release);
return release;
};