claude-yes 1.31.2 → 1.32.2
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.
- package/README.md +225 -21
- package/dist/agent-yes.js +2 -0
- package/dist/amp-yes.js +2 -0
- package/dist/auggie-yes.js +2 -0
- package/dist/claude-yes.js +2 -20432
- package/dist/cli.js +18342 -10955
- package/dist/codex-yes.js +2 -20432
- package/dist/copilot-yes.js +2 -20432
- package/dist/cursor-yes.js +2 -20432
- package/dist/gemini-yes.js +2 -20432
- package/dist/grok-yes.js +2 -20432
- package/dist/index.js +16258 -13586
- package/dist/qwen-yes.js +2 -20432
- package/package.json +93 -81
- package/ts/ReadyManager.spec.ts +10 -10
- package/ts/ReadyManager.ts +1 -1
- package/ts/SUPPORTED_CLIS.ts +4 -0
- package/ts/catcher.spec.ts +69 -70
- package/ts/cli-idle.spec.ts +8 -8
- package/ts/cli.ts +18 -26
- package/ts/defineConfig.ts +4 -4
- package/ts/idleWaiter.spec.ts +9 -9
- package/ts/index.ts +474 -233
- package/ts/logger.ts +22 -0
- package/ts/parseCliArgs.spec.ts +146 -147
- package/ts/parseCliArgs.ts +127 -59
- package/ts/postbuild.ts +29 -15
- package/ts/pty-fix.ts +155 -0
- package/ts/pty.ts +19 -0
- package/ts/removeControlCharacters.spec.ts +37 -38
- package/ts/removeControlCharacters.ts +2 -1
- package/ts/runningLock.spec.ts +119 -125
- package/ts/runningLock.ts +44 -55
- package/ts/session-integration.spec.ts +34 -42
- package/ts/utils.spec.ts +35 -35
- package/ts/utils.ts +7 -7
- package/dist/cli.js.map +0 -365
- package/dist/index.js.map +0 -323
- package/ts/codex-resume.spec.ts +0 -239
- package/ts/codexSessionManager.spec.ts +0 -51
- package/ts/codexSessionManager.test.ts +0 -259
- package/ts/codexSessionManager.ts +0 -312
- package/ts/yesLog.spec.ts +0 -74
- package/ts/yesLog.ts +0 -27
package/ts/runningLock.spec.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { execSync } from
|
|
2
|
-
import { existsSync } from
|
|
3
|
-
import { mkdir, readFile, rm, writeFile } from
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
7
6
|
import {
|
|
8
7
|
acquireLock,
|
|
9
8
|
cleanStaleLocks,
|
|
@@ -11,13 +10,16 @@ import {
|
|
|
11
10
|
shouldUseLock,
|
|
12
11
|
type Task,
|
|
13
12
|
updateCurrentTaskStatus,
|
|
14
|
-
} from
|
|
13
|
+
} from "./runningLock";
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const TEST_DIR = path.join(process.cwd(), '.cache', 'test-lock');
|
|
15
|
+
// Keep lock files inside the repo to avoid $HOME permission issues in CI
|
|
16
|
+
process.env.CLAUDE_YES_HOME = path.join(process.cwd(), ".cache");
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
const LOCK_DIR = path.join(process.env.CLAUDE_YES_HOME, ".claude-yes");
|
|
19
|
+
const LOCK_FILE = path.join(LOCK_DIR, "running.lock.json");
|
|
20
|
+
const TEST_DIR = path.join(process.cwd(), ".cache", "test-lock");
|
|
21
|
+
|
|
22
|
+
describe("runningLock", () => {
|
|
21
23
|
beforeEach(async () => {
|
|
22
24
|
// Clean up before each test
|
|
23
25
|
await cleanupLockFile();
|
|
@@ -30,25 +32,25 @@ describe('runningLock', () => {
|
|
|
30
32
|
await rm(TEST_DIR, { recursive: true, force: true });
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
describe(
|
|
34
|
-
it(
|
|
35
|
+
describe("shouldUseLock", () => {
|
|
36
|
+
it("should return true for any directory", () => {
|
|
35
37
|
expect(shouldUseLock(process.cwd())).toBe(true);
|
|
36
|
-
expect(shouldUseLock(
|
|
38
|
+
expect(shouldUseLock("/tmp")).toBe(true);
|
|
37
39
|
expect(shouldUseLock(TEST_DIR)).toBe(true);
|
|
38
40
|
});
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
describe(
|
|
42
|
-
it(
|
|
43
|
-
await acquireLock(TEST_DIR,
|
|
43
|
+
describe("acquireLock and releaseLock", () => {
|
|
44
|
+
it("should acquire and release lock successfully", async () => {
|
|
45
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
44
46
|
|
|
45
47
|
// Check lock file exists and contains task
|
|
46
48
|
const lockData = await readLockFile();
|
|
47
49
|
expect(lockData.tasks).toHaveLength(1);
|
|
48
50
|
expect(lockData.tasks[0].cwd).toBe(path.resolve(TEST_DIR));
|
|
49
|
-
expect(lockData.tasks[0].task).toBe(
|
|
51
|
+
expect(lockData.tasks[0].task).toBe("Test task");
|
|
50
52
|
expect(lockData.tasks[0].pid).toBe(process.pid);
|
|
51
|
-
expect(lockData.tasks[0].status).toBe(
|
|
53
|
+
expect(lockData.tasks[0].status).toBe("running");
|
|
52
54
|
|
|
53
55
|
// Release lock
|
|
54
56
|
await releaseLock();
|
|
@@ -58,11 +60,11 @@ describe('runningLock', () => {
|
|
|
58
60
|
expect(lockDataAfter.tasks).toHaveLength(0);
|
|
59
61
|
});
|
|
60
62
|
|
|
61
|
-
it(
|
|
63
|
+
it("should create lock directory if it does not exist", async () => {
|
|
62
64
|
// Remove lock directory
|
|
63
65
|
await rm(LOCK_DIR, { recursive: true, force: true });
|
|
64
66
|
|
|
65
|
-
await acquireLock(TEST_DIR,
|
|
67
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
66
68
|
|
|
67
69
|
// Check directory and file exist
|
|
68
70
|
expect(existsSync(LOCK_DIR)).toBe(true);
|
|
@@ -71,21 +73,21 @@ describe('runningLock', () => {
|
|
|
71
73
|
await releaseLock();
|
|
72
74
|
});
|
|
73
75
|
|
|
74
|
-
it(
|
|
75
|
-
const longPrompt =
|
|
76
|
+
it("should handle prompt longer than 100 characters", async () => {
|
|
77
|
+
const longPrompt = "A".repeat(150);
|
|
76
78
|
|
|
77
79
|
await acquireLock(TEST_DIR, longPrompt);
|
|
78
80
|
|
|
79
81
|
const lockData = await readLockFile();
|
|
80
82
|
expect(lockData.tasks[0].task).toHaveLength(100);
|
|
81
|
-
expect(lockData.tasks[0].task).toBe(
|
|
83
|
+
expect(lockData.tasks[0].task).toBe("A".repeat(100));
|
|
82
84
|
|
|
83
85
|
await releaseLock();
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
it(
|
|
88
|
+
it("should include timestamp fields", async () => {
|
|
87
89
|
const before = Date.now();
|
|
88
|
-
await acquireLock(TEST_DIR,
|
|
90
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
89
91
|
const after = Date.now();
|
|
90
92
|
|
|
91
93
|
const lockData = await readLockFile();
|
|
@@ -100,15 +102,15 @@ describe('runningLock', () => {
|
|
|
100
102
|
});
|
|
101
103
|
});
|
|
102
104
|
|
|
103
|
-
describe(
|
|
104
|
-
it(
|
|
105
|
+
describe("git repository detection", () => {
|
|
106
|
+
it("should detect git root for repository", async () => {
|
|
105
107
|
// Use current directory which is a git repo
|
|
106
|
-
const gitRoot = execSync(
|
|
108
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
107
109
|
cwd: process.cwd(),
|
|
108
|
-
encoding:
|
|
110
|
+
encoding: "utf8",
|
|
109
111
|
}).trim();
|
|
110
112
|
|
|
111
|
-
await acquireLock(process.cwd(),
|
|
113
|
+
await acquireLock(process.cwd(), "Git repo task");
|
|
112
114
|
|
|
113
115
|
const lockData = await readLockFile();
|
|
114
116
|
expect(lockData.tasks[0].gitRoot).toBe(gitRoot);
|
|
@@ -116,14 +118,14 @@ describe('runningLock', () => {
|
|
|
116
118
|
await releaseLock();
|
|
117
119
|
});
|
|
118
120
|
|
|
119
|
-
it(
|
|
120
|
-
const gitRoot = execSync(
|
|
121
|
+
it("should detect same git root for subdirectory", async () => {
|
|
122
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
121
123
|
cwd: process.cwd(),
|
|
122
|
-
encoding:
|
|
124
|
+
encoding: "utf8",
|
|
123
125
|
}).trim();
|
|
124
|
-
const subdir = path.join(process.cwd(),
|
|
126
|
+
const subdir = path.join(process.cwd(), "docs");
|
|
125
127
|
|
|
126
|
-
await acquireLock(subdir,
|
|
128
|
+
await acquireLock(subdir, "Subdirectory task");
|
|
127
129
|
|
|
128
130
|
const lockData = await readLockFile();
|
|
129
131
|
expect(lockData.tasks[0].gitRoot).toBe(gitRoot);
|
|
@@ -132,13 +134,13 @@ describe('runningLock', () => {
|
|
|
132
134
|
await releaseLock();
|
|
133
135
|
});
|
|
134
136
|
|
|
135
|
-
it(
|
|
137
|
+
it("should not have gitRoot for non-git directory", async () => {
|
|
136
138
|
// Create a temporary directory outside of any git repo
|
|
137
|
-
const tempDir = path.join(
|
|
139
|
+
const tempDir = path.join("/tmp", "test-non-git-" + Date.now());
|
|
138
140
|
await mkdir(tempDir, { recursive: true });
|
|
139
141
|
|
|
140
142
|
try {
|
|
141
|
-
await acquireLock(tempDir,
|
|
143
|
+
await acquireLock(tempDir, "Non-git task");
|
|
142
144
|
|
|
143
145
|
const lockData = await readLockFile();
|
|
144
146
|
expect(lockData.tasks[0].gitRoot).toBeUndefined();
|
|
@@ -151,35 +153,35 @@ describe('runningLock', () => {
|
|
|
151
153
|
});
|
|
152
154
|
});
|
|
153
155
|
|
|
154
|
-
describe(
|
|
155
|
-
it(
|
|
156
|
-
await acquireLock(TEST_DIR,
|
|
156
|
+
describe("updateCurrentTaskStatus", () => {
|
|
157
|
+
it("should update task status", async () => {
|
|
158
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
157
159
|
|
|
158
160
|
// Update to completed
|
|
159
|
-
await updateCurrentTaskStatus(
|
|
161
|
+
await updateCurrentTaskStatus("completed");
|
|
160
162
|
|
|
161
163
|
let lockData = await readLockFile();
|
|
162
|
-
expect(lockData.tasks[0].status).toBe(
|
|
164
|
+
expect(lockData.tasks[0].status).toBe("completed");
|
|
163
165
|
|
|
164
166
|
// Update to failed
|
|
165
|
-
await updateCurrentTaskStatus(
|
|
167
|
+
await updateCurrentTaskStatus("failed");
|
|
166
168
|
|
|
167
169
|
lockData = await readLockFile();
|
|
168
|
-
expect(lockData.tasks[0].status).toBe(
|
|
170
|
+
expect(lockData.tasks[0].status).toBe("failed");
|
|
169
171
|
|
|
170
172
|
await releaseLock();
|
|
171
173
|
});
|
|
172
174
|
|
|
173
|
-
it(
|
|
175
|
+
it("should not throw when updating non-existent task", async () => {
|
|
174
176
|
// Should complete without throwing
|
|
175
|
-
await updateCurrentTaskStatus(
|
|
177
|
+
await updateCurrentTaskStatus("completed");
|
|
176
178
|
// If we got here, no error was thrown
|
|
177
179
|
expect(true).toBe(true);
|
|
178
180
|
});
|
|
179
181
|
});
|
|
180
182
|
|
|
181
|
-
describe(
|
|
182
|
-
it(
|
|
183
|
+
describe("cleanStaleLocks", () => {
|
|
184
|
+
it("should remove stale locks with invalid PIDs", async () => {
|
|
183
185
|
// Use a PID that definitely doesn't exist
|
|
184
186
|
const invalidPid = 9999999;
|
|
185
187
|
|
|
@@ -188,9 +190,9 @@ describe('runningLock', () => {
|
|
|
188
190
|
tasks: [
|
|
189
191
|
{
|
|
190
192
|
cwd: TEST_DIR,
|
|
191
|
-
task:
|
|
193
|
+
task: "Stale task",
|
|
192
194
|
pid: invalidPid,
|
|
193
|
-
status:
|
|
195
|
+
status: "running" as const,
|
|
194
196
|
startedAt: Date.now() - 60000,
|
|
195
197
|
lockedAt: Date.now() - 60000,
|
|
196
198
|
},
|
|
@@ -201,25 +203,25 @@ describe('runningLock', () => {
|
|
|
201
203
|
await writeFile(LOCK_FILE, JSON.stringify(staleLock, null, 2));
|
|
202
204
|
|
|
203
205
|
// Verify the stale lock was written
|
|
204
|
-
let rawContent = await readFile(LOCK_FILE,
|
|
206
|
+
let rawContent = await readFile(LOCK_FILE, "utf8");
|
|
205
207
|
let rawData = JSON.parse(rawContent);
|
|
206
208
|
expect(rawData.tasks).toHaveLength(1);
|
|
207
209
|
expect(rawData.tasks[0].pid).toBe(invalidPid);
|
|
208
210
|
|
|
209
211
|
// Now acquire a lock - this will trigger cleanup of stale locks
|
|
210
|
-
await acquireLock(TEST_DIR,
|
|
212
|
+
await acquireLock(TEST_DIR, "New task");
|
|
211
213
|
|
|
212
214
|
// The stale lock should be cleaned, and only our new task should remain
|
|
213
215
|
const lockData = await readLockFile();
|
|
214
216
|
expect(lockData.tasks).toHaveLength(1);
|
|
215
217
|
expect(lockData.tasks[0].pid).toBe(process.pid);
|
|
216
|
-
expect(lockData.tasks[0].task).toBe(
|
|
218
|
+
expect(lockData.tasks[0].task).toBe("New task");
|
|
217
219
|
|
|
218
220
|
await releaseLock();
|
|
219
221
|
});
|
|
220
222
|
|
|
221
|
-
it(
|
|
222
|
-
await acquireLock(TEST_DIR,
|
|
223
|
+
it("should keep valid locks with running PIDs", async () => {
|
|
224
|
+
await acquireLock(TEST_DIR, "Valid task");
|
|
223
225
|
|
|
224
226
|
// Clean stale locks (should not remove our lock)
|
|
225
227
|
await cleanStaleLocks();
|
|
@@ -231,10 +233,10 @@ describe('runningLock', () => {
|
|
|
231
233
|
await releaseLock();
|
|
232
234
|
});
|
|
233
235
|
|
|
234
|
-
it(
|
|
236
|
+
it("should handle corrupted lock file", async () => {
|
|
235
237
|
// Write invalid JSON
|
|
236
238
|
await mkdir(LOCK_DIR, { recursive: true });
|
|
237
|
-
await writeFile(LOCK_FILE,
|
|
239
|
+
await writeFile(LOCK_FILE, "invalid json{{{");
|
|
238
240
|
|
|
239
241
|
// Reading the lock file should handle corruption gracefully
|
|
240
242
|
const lockData = await readLockFile();
|
|
@@ -243,7 +245,7 @@ describe('runningLock', () => {
|
|
|
243
245
|
expect(lockData.tasks).toHaveLength(0);
|
|
244
246
|
});
|
|
245
247
|
|
|
246
|
-
it(
|
|
248
|
+
it("should handle missing lock file", async () => {
|
|
247
249
|
await rm(LOCK_FILE, { force: true });
|
|
248
250
|
|
|
249
251
|
// Reading non-existent lock file should return empty
|
|
@@ -252,23 +254,23 @@ describe('runningLock', () => {
|
|
|
252
254
|
});
|
|
253
255
|
});
|
|
254
256
|
|
|
255
|
-
describe(
|
|
256
|
-
it(
|
|
257
|
+
describe("concurrent access", () => {
|
|
258
|
+
it("should handle multiple tasks from different processes", async () => {
|
|
257
259
|
// Acquire first task
|
|
258
|
-
await acquireLock(TEST_DIR,
|
|
260
|
+
await acquireLock(TEST_DIR, "Task 1");
|
|
259
261
|
|
|
260
262
|
// Verify the task exists
|
|
261
263
|
let lockData = await readLockFile();
|
|
262
264
|
expect(lockData.tasks).toHaveLength(1);
|
|
263
|
-
expect(lockData.tasks[0].task).toBe(
|
|
265
|
+
expect(lockData.tasks[0].task).toBe("Task 1");
|
|
264
266
|
|
|
265
267
|
// Acquire a second task with the same PID (should replace the first)
|
|
266
|
-
await acquireLock(
|
|
268
|
+
await acquireLock("/tmp", "Task 2");
|
|
267
269
|
|
|
268
270
|
// Should have only one task (the latest one)
|
|
269
271
|
lockData = await readLockFile();
|
|
270
272
|
expect(lockData.tasks).toHaveLength(1);
|
|
271
|
-
expect(lockData.tasks[0].task).toBe(
|
|
273
|
+
expect(lockData.tasks[0].task).toBe("Task 2");
|
|
272
274
|
|
|
273
275
|
await releaseLock();
|
|
274
276
|
|
|
@@ -277,52 +279,47 @@ describe('runningLock', () => {
|
|
|
277
279
|
expect(finalLockData.tasks).toHaveLength(0);
|
|
278
280
|
});
|
|
279
281
|
|
|
280
|
-
it(
|
|
281
|
-
await acquireLock(TEST_DIR,
|
|
282
|
+
it("should not duplicate tasks with same PID", async () => {
|
|
283
|
+
await acquireLock(TEST_DIR, "Task 1");
|
|
282
284
|
|
|
283
285
|
// Try to acquire again with same PID
|
|
284
|
-
await acquireLock(TEST_DIR,
|
|
286
|
+
await acquireLock(TEST_DIR, "Task 2");
|
|
285
287
|
|
|
286
288
|
// Should only have one task
|
|
287
289
|
const lockData = await readLockFile();
|
|
288
290
|
expect(lockData.tasks).toHaveLength(1);
|
|
289
|
-
expect(lockData.tasks[0].task).toBe(
|
|
291
|
+
expect(lockData.tasks[0].task).toBe("Task 2"); // Latest task
|
|
290
292
|
|
|
291
293
|
await releaseLock();
|
|
292
294
|
});
|
|
293
295
|
});
|
|
294
296
|
|
|
295
|
-
describe(
|
|
296
|
-
it(
|
|
297
|
-
await acquireLock(TEST_DIR,
|
|
297
|
+
describe("lock file structure", () => {
|
|
298
|
+
it("should have all required fields", async () => {
|
|
299
|
+
await acquireLock(TEST_DIR, "Complete task");
|
|
298
300
|
|
|
299
301
|
const lockData = await readLockFile();
|
|
300
302
|
const task = lockData.tasks[0];
|
|
301
303
|
|
|
302
|
-
expect(task).toHaveProperty(
|
|
303
|
-
expect(task).toHaveProperty(
|
|
304
|
-
expect(task).toHaveProperty(
|
|
305
|
-
expect(task).toHaveProperty(
|
|
306
|
-
expect(task).toHaveProperty(
|
|
307
|
-
expect(task).toHaveProperty(
|
|
304
|
+
expect(task).toHaveProperty("cwd");
|
|
305
|
+
expect(task).toHaveProperty("task");
|
|
306
|
+
expect(task).toHaveProperty("pid");
|
|
307
|
+
expect(task).toHaveProperty("status");
|
|
308
|
+
expect(task).toHaveProperty("startedAt");
|
|
309
|
+
expect(task).toHaveProperty("lockedAt");
|
|
308
310
|
|
|
309
|
-
expect(typeof task.cwd).toBe(
|
|
310
|
-
expect(typeof task.task).toBe(
|
|
311
|
-
expect(typeof task.pid).toBe(
|
|
312
|
-
expect(typeof task.status).toBe(
|
|
313
|
-
expect(typeof task.startedAt).toBe(
|
|
314
|
-
expect(typeof task.lockedAt).toBe(
|
|
311
|
+
expect(typeof task.cwd).toBe("string");
|
|
312
|
+
expect(typeof task.task).toBe("string");
|
|
313
|
+
expect(typeof task.pid).toBe("number");
|
|
314
|
+
expect(typeof task.status).toBe("string");
|
|
315
|
+
expect(typeof task.startedAt).toBe("number");
|
|
316
|
+
expect(typeof task.lockedAt).toBe("number");
|
|
315
317
|
|
|
316
318
|
await releaseLock();
|
|
317
319
|
});
|
|
318
320
|
|
|
319
|
-
it(
|
|
320
|
-
const validStatuses: Task[
|
|
321
|
-
'running',
|
|
322
|
-
'queued',
|
|
323
|
-
'completed',
|
|
324
|
-
'failed',
|
|
325
|
-
];
|
|
321
|
+
it("should have valid status values", async () => {
|
|
322
|
+
const validStatuses: Task["status"][] = ["running", "queued", "completed", "failed"];
|
|
326
323
|
|
|
327
324
|
for (const status of validStatuses) {
|
|
328
325
|
await acquireLock(TEST_DIR, `Task with ${status}`);
|
|
@@ -336,30 +333,29 @@ describe('runningLock', () => {
|
|
|
336
333
|
});
|
|
337
334
|
});
|
|
338
335
|
|
|
339
|
-
describe(
|
|
340
|
-
it(
|
|
341
|
-
await acquireLock(TEST_DIR,
|
|
336
|
+
describe("edge cases", () => {
|
|
337
|
+
it("should handle empty task description", async () => {
|
|
338
|
+
await acquireLock(TEST_DIR, "");
|
|
342
339
|
|
|
343
340
|
const lockData = await readLockFile();
|
|
344
|
-
expect(lockData.tasks[0].task).toBe(
|
|
341
|
+
expect(lockData.tasks[0].task).toBe("");
|
|
345
342
|
|
|
346
343
|
await releaseLock();
|
|
347
344
|
});
|
|
348
345
|
|
|
349
|
-
it(
|
|
350
|
-
const specialTask =
|
|
351
|
-
'Task with "quotes" and \'apostrophes\' and \n newlines';
|
|
346
|
+
it("should handle special characters in task description", async () => {
|
|
347
|
+
const specialTask = "Task with \"quotes\" and 'apostrophes' and \n newlines";
|
|
352
348
|
|
|
353
349
|
await acquireLock(TEST_DIR, specialTask);
|
|
354
350
|
|
|
355
351
|
const lockData = await readLockFile();
|
|
356
|
-
expect(lockData.tasks[0].task).toContain(
|
|
352
|
+
expect(lockData.tasks[0].task).toContain("quotes");
|
|
357
353
|
|
|
358
354
|
await releaseLock();
|
|
359
355
|
});
|
|
360
356
|
|
|
361
|
-
it(
|
|
362
|
-
await acquireLock(TEST_DIR,
|
|
357
|
+
it("should resolve symlinks to real paths", async () => {
|
|
358
|
+
await acquireLock(TEST_DIR, "Symlink test");
|
|
363
359
|
|
|
364
360
|
const lockData = await readLockFile();
|
|
365
361
|
// Should be an absolute path
|
|
@@ -368,7 +364,7 @@ describe('runningLock', () => {
|
|
|
368
364
|
await releaseLock();
|
|
369
365
|
});
|
|
370
366
|
|
|
371
|
-
it(
|
|
367
|
+
it("should handle rapid acquire/release cycles", async () => {
|
|
372
368
|
for (let i = 0; i < 10; i++) {
|
|
373
369
|
await acquireLock(TEST_DIR, `Rapid task ${i}`);
|
|
374
370
|
await releaseLock();
|
|
@@ -380,24 +376,24 @@ describe('runningLock', () => {
|
|
|
380
376
|
});
|
|
381
377
|
});
|
|
382
378
|
|
|
383
|
-
describe(
|
|
384
|
-
it(
|
|
385
|
-
const gitRoot = execSync(
|
|
379
|
+
describe("queueing behavior", () => {
|
|
380
|
+
it("should detect when lock is held by same git repo", async () => {
|
|
381
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
386
382
|
cwd: process.cwd(),
|
|
387
|
-
encoding:
|
|
383
|
+
encoding: "utf8",
|
|
388
384
|
}).trim();
|
|
389
385
|
|
|
390
386
|
// Acquire lock at root
|
|
391
|
-
await acquireLock(gitRoot,
|
|
387
|
+
await acquireLock(gitRoot, "Root task");
|
|
392
388
|
|
|
393
389
|
// Create a lock with different PID to simulate another process
|
|
394
390
|
const lockData = await readLockFile();
|
|
395
391
|
lockData.tasks.push({
|
|
396
|
-
cwd: path.join(gitRoot,
|
|
392
|
+
cwd: path.join(gitRoot, "subdirectory"),
|
|
397
393
|
gitRoot: gitRoot,
|
|
398
|
-
task:
|
|
394
|
+
task: "Subdirectory task",
|
|
399
395
|
pid: process.pid + 1,
|
|
400
|
-
status:
|
|
396
|
+
status: "running",
|
|
401
397
|
startedAt: Date.now(),
|
|
402
398
|
lockedAt: Date.now(),
|
|
403
399
|
});
|
|
@@ -405,15 +401,13 @@ describe('runningLock', () => {
|
|
|
405
401
|
|
|
406
402
|
// Both tasks should be in the same git repo
|
|
407
403
|
const updatedLockData = await readLockFile();
|
|
408
|
-
const gitRoots = updatedLockData.tasks
|
|
409
|
-
.map((t) => t.gitRoot)
|
|
410
|
-
.filter((g) => g);
|
|
404
|
+
const gitRoots = updatedLockData.tasks.map((t) => t.gitRoot).filter((g) => g);
|
|
411
405
|
expect(new Set(gitRoots).size).toBe(1); // All same git root
|
|
412
406
|
|
|
413
407
|
await releaseLock();
|
|
414
408
|
});
|
|
415
409
|
|
|
416
|
-
it(
|
|
410
|
+
it("should allow different directories without git repos", async () => {
|
|
417
411
|
// Test that when we already have a task, acquiring a new one replaces it
|
|
418
412
|
// (since both use the same PID)
|
|
419
413
|
|
|
@@ -421,10 +415,10 @@ describe('runningLock', () => {
|
|
|
421
415
|
const lock = {
|
|
422
416
|
tasks: [
|
|
423
417
|
{
|
|
424
|
-
cwd:
|
|
425
|
-
task:
|
|
418
|
+
cwd: "/tmp",
|
|
419
|
+
task: "Tmp task",
|
|
426
420
|
pid: process.pid,
|
|
427
|
-
status:
|
|
421
|
+
status: "running" as const,
|
|
428
422
|
startedAt: Date.now(),
|
|
429
423
|
lockedAt: Date.now(),
|
|
430
424
|
},
|
|
@@ -435,27 +429,27 @@ describe('runningLock', () => {
|
|
|
435
429
|
// Verify initial state
|
|
436
430
|
let lockData = await readLockFile();
|
|
437
431
|
expect(lockData.tasks).toHaveLength(1);
|
|
438
|
-
expect(lockData.tasks[0].task).toBe(
|
|
432
|
+
expect(lockData.tasks[0].task).toBe("Tmp task");
|
|
439
433
|
|
|
440
434
|
// Acquire lock for different directory (should replace the existing task)
|
|
441
|
-
await acquireLock(TEST_DIR,
|
|
435
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
442
436
|
|
|
443
437
|
// Should only have the new task
|
|
444
438
|
lockData = await readLockFile();
|
|
445
439
|
expect(lockData.tasks).toHaveLength(1);
|
|
446
|
-
expect(lockData.tasks[0].task).toBe(
|
|
440
|
+
expect(lockData.tasks[0].task).toBe("Test task");
|
|
447
441
|
|
|
448
442
|
await releaseLock();
|
|
449
443
|
});
|
|
450
444
|
});
|
|
451
445
|
|
|
452
|
-
describe(
|
|
453
|
-
it(
|
|
446
|
+
describe("disableLock option", () => {
|
|
447
|
+
it("should respect lock file operations when disableLock is false", async () => {
|
|
454
448
|
// Clean up first
|
|
455
449
|
await rm(LOCK_FILE, { force: true });
|
|
456
450
|
|
|
457
451
|
// When disableLock is not used (default behavior), locks work normally
|
|
458
|
-
await acquireLock(TEST_DIR,
|
|
452
|
+
await acquireLock(TEST_DIR, "Test task");
|
|
459
453
|
expect(existsSync(LOCK_FILE)).toBe(true);
|
|
460
454
|
|
|
461
455
|
const lockData = await readLockFile();
|
|
@@ -481,7 +475,7 @@ async function cleanupLockFile() {
|
|
|
481
475
|
|
|
482
476
|
async function readLockFile(): Promise<{ tasks: Task[] }> {
|
|
483
477
|
try {
|
|
484
|
-
const content = await readFile(LOCK_FILE,
|
|
478
|
+
const content = await readFile(LOCK_FILE, "utf8");
|
|
485
479
|
const lockFile = JSON.parse(content);
|
|
486
480
|
// Don't clean stale locks in tests - we want to see the raw data
|
|
487
481
|
return lockFile;
|