predev-lock-check.mjs 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { execFileSync } from "node:child_process";
  2. import { existsSync, readFileSync, rmSync } from "node:fs";
  3. import { resolve } from "node:path";
  4. const lockPath = ".next/dev/lock";
  5. const devDirPath = ".next/dev";
  6. const webpackRuntimePath = ".next/dev/server/webpack-runtime.js";
  7. const vendorChunkPath = ".next/dev/server/vendor-chunks/next.js";
  8. const projectRoot = process.cwd();
  9. function run(cmd, args) {
  10. try {
  11. return execFileSync(cmd, args, { encoding: "utf8" }).trim();
  12. } catch {
  13. return "";
  14. }
  15. }
  16. function pidExists(pid) {
  17. try {
  18. process.kill(pid, 0);
  19. return true;
  20. } catch {
  21. return false;
  22. }
  23. }
  24. function getProcessCwd(pid) {
  25. const output = run("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"]);
  26. const cwdLine = output
  27. .split("\n")
  28. .find((line) => line.startsWith("n"));
  29. return cwdLine ? cwdLine.slice(1) : "";
  30. }
  31. function getListeningPids(port) {
  32. const output = run("lsof", ["-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
  33. if (!output) return [];
  34. return output
  35. .split("\n")
  36. .map((line) => Number(line.trim()))
  37. .filter((v) => Number.isInteger(v) && v > 0);
  38. }
  39. function removeLock(reason) {
  40. rmSync(lockPath, { force: true });
  41. console.log(`Removed stale ${lockPath}: ${reason}`);
  42. }
  43. function resetDevCache(reason) {
  44. rmSync(devDirPath, { force: true, recursive: true });
  45. console.log(`Reset ${devDirPath}: ${reason}`);
  46. }
  47. if (existsSync(webpackRuntimePath) && !existsSync(vendorChunkPath)) {
  48. resetDevCache("missing vendor-chunks/next.js");
  49. process.exit(0);
  50. }
  51. if (!existsSync(lockPath)) process.exit(0);
  52. let lock;
  53. try {
  54. lock = JSON.parse(readFileSync(lockPath, "utf8"));
  55. } catch {
  56. removeLock("invalid JSON");
  57. process.exit(0);
  58. }
  59. const pid = Number(lock?.pid ?? 0);
  60. const port = Number(lock?.port ?? 3000);
  61. if (!pid) {
  62. removeLock("missing pid");
  63. process.exit(0);
  64. }
  65. if (!pidExists(pid)) {
  66. removeLock(`pid ${pid} not running`);
  67. process.exit(0);
  68. }
  69. const processCwd = getProcessCwd(pid);
  70. if (!processCwd) {
  71. removeLock(`cannot resolve cwd for pid ${pid}`);
  72. process.exit(0);
  73. }
  74. if (resolve(processCwd) !== resolve(projectRoot)) {
  75. removeLock(`pid ${pid} belongs to another directory`);
  76. process.exit(0);
  77. }
  78. const portPids = getListeningPids(port);
  79. if (portPids.length > 0 && !portPids.includes(pid)) {
  80. removeLock(`pid ${pid} is not listening on port ${port}`);
  81. }