UNPKG

@mintlify/previewing

Version:

Preview Mintlify docs locally

77 lines (76 loc) 2.69 kB
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; };