Repository navigation
Expand file tree
/
Copy pathpidLock.ts
More file actions
49 lines (43 loc) · 1.66 KB
/
Copy pathpidLock.ts
File metadata and controls
49 lines (43 loc) · 1.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { isEnoent, isProcessAlive } from "@procni/core";
import { ProcniError } from "@procni/shared";
import { readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
interface LockInfo {
pid: number;
port: number;
startedAt: string;
}
function lockFilePath(projectDir: string): string {
return join(projectDir, "procni.lock");
}
/**
* Throws `ALREADY_RUNNING` if a live `procni dev` process already holds this
* project's lock. A lock file whose `pid` is no longer a running process
* (the previous `procni dev` crashed instead of shutting down cleanly) is
* treated as stale and ignored — this is what lets `procni dev` recover on
* its own after a crash, instead of requiring a developer to manually delete
* a leftover lock file.
*/
export async function assertNotAlreadyRunning(projectDir: string): Promise<void> {
const existing = await readLockIfExists(projectDir);
if (existing && isProcessAlive(existing.pid)) {
throw new ProcniError(
"ALREADY_RUNNING",
`procni dev is already running for this project (pid ${existing.pid}) at http://127.0.0.1:${existing.port}.`,
);
}
}
export async function writeLock(projectDir: string, info: LockInfo): Promise<void> {
await writeFile(lockFilePath(projectDir), JSON.stringify(info), "utf8");
}
export async function releaseLock(projectDir: string): Promise<void> {
await rm(lockFilePath(projectDir), { force: true });
}
async function readLockIfExists(projectDir: string): Promise<LockInfo | null> {
try {
return JSON.parse(await readFile(lockFilePath(projectDir), "utf8")) as LockInfo;
} catch (err) {
if (isEnoent(err)) return null;
throw err;
}
}