@sumicom/quicksave 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,43 @@
1
1
  /**
2
2
  * Singleton lock for ensuring exactly one daemon per user.
3
3
  *
4
- * Uses an exclusive lock file at ~/.quicksave/run/service.lock.
4
+ * Uses an exclusive lock file at <quicksaveDir>/run/service.lock.
5
5
  * The lock is held via an open file descriptor — if the process crashes,
6
6
  * the OS releases the lock automatically.
7
+ *
8
+ * All paths derive from a single configurable base directory:
9
+ * - Default: ~/.quicksave
10
+ * - Override: QUICKSAVE_HOME env var, or setQuicksaveDir() for tests
7
11
  */
8
12
  import { openSync, closeSync, writeSync, readFileSync, mkdirSync, existsSync, unlinkSync, constants } from 'fs';
9
13
  import { join } from 'path';
10
14
  import { homedir } from 'os';
11
- const QUICKSAVE_DIR = join(homedir(), '.quicksave');
12
- const RUN_DIR = join(QUICKSAVE_DIR, 'run');
13
- const LOCK_FILE = join(RUN_DIR, 'service.lock');
14
- const SOCKET_PATH = join(RUN_DIR, 'service.sock');
15
+ let _quicksaveDir = process.env.QUICKSAVE_HOME || join(homedir(), '.quicksave');
16
+ /**
17
+ * Get the quicksave base directory.
18
+ * All other paths derive from this.
19
+ */
20
+ export function getQuicksaveDir() {
21
+ return _quicksaveDir;
22
+ }
23
+ /**
24
+ * Override the base directory. Primarily for tests.
25
+ * Call before any other path function to ensure consistency.
26
+ */
27
+ export function setQuicksaveDir(dir) {
28
+ _quicksaveDir = dir;
29
+ }
15
30
  export function getRunDir() {
16
- return RUN_DIR;
31
+ return join(_quicksaveDir, 'run');
17
32
  }
18
33
  export function getSocketPath() {
19
- return SOCKET_PATH;
34
+ return join(getRunDir(), 'service.sock');
20
35
  }
21
36
  export function getLockPath() {
22
- return LOCK_FILE;
37
+ return join(getRunDir(), 'service.lock');
23
38
  }
24
39
  export function getStateDir() {
25
- return join(QUICKSAVE_DIR, 'state');
40
+ return join(_quicksaveDir, 'state');
26
41
  }
27
42
  export function getSessionsDir() {
28
43
  return join(getStateDir(), 'sessions');
@@ -31,13 +46,22 @@ export function getSessionRegistryDir() {
31
46
  return join(getStateDir(), 'session-registry');
32
47
  }
33
48
  export function getLogsDir() {
34
- return join(QUICKSAVE_DIR, 'logs');
49
+ return join(_quicksaveDir, 'logs');
50
+ }
51
+ export function getDebugDir() {
52
+ return join(_quicksaveDir, 'debug');
53
+ }
54
+ export function getConfigFile() {
55
+ return join(_quicksaveDir, 'agent.json');
56
+ }
57
+ export function getCardHistoryDir() {
58
+ return join(getStateDir(), 'card-history');
35
59
  }
36
60
  /**
37
61
  * Ensure all required directories exist.
38
62
  */
39
63
  export function ensureDirectories() {
40
- for (const dir of [RUN_DIR, getStateDir(), getSessionsDir(), getSessionRegistryDir(), getLogsDir()]) {
64
+ for (const dir of [getRunDir(), getStateDir(), getSessionsDir(), getSessionRegistryDir(), getLogsDir()]) {
41
65
  if (!existsSync(dir)) {
42
66
  mkdirSync(dir, { recursive: true });
43
67
  }
@@ -52,32 +76,33 @@ export function ensureDirectories() {
52
76
  */
53
77
  export function acquireLock() {
54
78
  ensureDirectories();
79
+ const lockFile = getLockPath();
55
80
  // Check for stale lock: if lock file exists but PID is dead, remove it
56
- if (existsSync(LOCK_FILE)) {
81
+ if (existsSync(lockFile)) {
57
82
  try {
58
- const pidStr = readFileSync(LOCK_FILE, 'utf-8').trim();
83
+ const pidStr = readFileSync(lockFile, 'utf-8').trim();
59
84
  const pid = parseInt(pidStr, 10);
60
85
  if (!isNaN(pid) && !isProcessAlive(pid)) {
61
86
  // Stale lock — previous daemon crashed
62
- unlinkSync(LOCK_FILE);
87
+ unlinkSync(lockFile);
63
88
  }
64
89
  }
65
90
  catch {
66
91
  // Can't read lock file — remove it and retry
67
92
  try {
68
- unlinkSync(LOCK_FILE);
93
+ unlinkSync(lockFile);
69
94
  }
70
95
  catch { /* ignore */ }
71
96
  }
72
97
  }
73
98
  try {
74
- const fd = openSync(LOCK_FILE, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
99
+ const fd = openSync(lockFile, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
75
100
  // Write our PID so future processes can detect stale locks
76
101
  writeSync(fd, Buffer.from(String(process.pid)));
77
102
  return () => {
78
103
  try {
79
104
  closeSync(fd);
80
- unlinkSync(LOCK_FILE);
105
+ unlinkSync(lockFile);
81
106
  }
82
107
  catch {
83
108
  // Best-effort cleanup
@@ -95,7 +120,7 @@ export function acquireLock() {
95
120
  * Remove stale runtime files (lock, socket) when we detect a dead daemon.
96
121
  */
97
122
  export function cleanStaleRuntime() {
98
- for (const file of [LOCK_FILE, SOCKET_PATH]) {
123
+ for (const file of [getLockPath(), getSocketPath()]) {
99
124
  try {
100
125
  unlinkSync(file);
101
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/service/singleton.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/service/singleton.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export const IPC_VERSION = 1;
18
18
  * In dev mode (placeholder unreplaced), hashes source file mtimes so the
19
19
  * value only changes when code actually changes — avoids spurious daemon restarts.
20
20
  */
21
- const _PLACEHOLDER = '__BUILD_ID__';
21
+ const _PLACEHOLDER = '41525c7e5f3f';
22
22
  export const BUILD_ID = _PLACEHOLDER === '__BUILD' + '_ID__'
23
23
  ? devBuildId()
24
24
  : _PLACEHOLDER;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumicom/quicksave",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Quicksave agent - remote git control CLI",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "simple-git": "^3.22.0",
40
40
  "ws": "^8.16.0",
41
41
  "zod": "^4.3.6",
42
- "@sumicom/quicksave-shared": "0.6.2"
42
+ "@sumicom/quicksave-shared": "0.6.3"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.11.0",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "scripts": {
53
53
  "dev": "tsx watch src/index.ts",
54
- "build": "tsc",
54
+ "build": "tsc && node scripts/stamp-build-id.js",
55
55
  "start": "node dist/index.js",
56
56
  "typecheck": "tsc --noEmit",
57
57
  "test": "vitest run",