@coralai/sps-cli 0.24.4 → 0.24.6
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/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +3 -70
- package/dist/core/config.js.map +1 -1
- package/dist/core/config.test.d.ts +2 -0
- package/dist/core/config.test.d.ts.map +1 -0
- package/dist/core/config.test.js +363 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/lock.d.ts.map +1 -1
- package/dist/core/lock.js +8 -5
- package/dist/core/lock.js.map +1 -1
- package/dist/core/lock.test.d.ts +2 -0
- package/dist/core/lock.test.d.ts.map +1 -0
- package/dist/core/lock.test.js +103 -0
- package/dist/core/lock.test.js.map +1 -0
- package/dist/core/queue.test.d.ts +2 -0
- package/dist/core/queue.test.d.ts.map +1 -0
- package/dist/core/queue.test.js +99 -0
- package/dist/core/queue.test.js.map +1 -0
- package/dist/core/shellEnv.d.ts +20 -0
- package/dist/core/shellEnv.d.ts.map +1 -0
- package/dist/core/shellEnv.js +78 -0
- package/dist/core/shellEnv.js.map +1 -0
- package/dist/core/shellEnv.test.d.ts +2 -0
- package/dist/core/shellEnv.test.d.ts.map +1 -0
- package/dist/core/shellEnv.test.js +101 -0
- package/dist/core/shellEnv.test.js.map +1 -0
- package/dist/core/state.test.d.ts +2 -0
- package/dist/core/state.test.d.ts.map +1 -0
- package/dist/core/state.test.js +322 -0
- package/dist/core/state.test.js.map +1 -0
- package/dist/manager/completion-judge.d.ts.map +1 -1
- package/dist/manager/completion-judge.js +12 -2
- package/dist/manager/completion-judge.js.map +1 -1
- package/dist/manager/completion-judge.test.d.ts +2 -0
- package/dist/manager/completion-judge.test.d.ts.map +1 -0
- package/dist/manager/completion-judge.test.js +218 -0
- package/dist/manager/completion-judge.test.js.map +1 -0
- package/dist/manager/integration-queue.test.d.ts +2 -0
- package/dist/manager/integration-queue.test.d.ts.map +1 -0
- package/dist/manager/integration-queue.test.js +195 -0
- package/dist/manager/integration-queue.test.js.map +1 -0
- package/dist/manager/resource-limiter.test.d.ts +2 -0
- package/dist/manager/resource-limiter.test.d.ts.map +1 -0
- package/dist/manager/resource-limiter.test.js +103 -0
- package/dist/manager/resource-limiter.test.js.map +1 -0
- package/dist/manager/supervisor.d.ts +0 -1
- package/dist/manager/supervisor.d.ts.map +1 -1
- package/dist/manager/supervisor.js +5 -40
- package/dist/manager/supervisor.js.map +1 -1
- package/dist/manager/supervisor.test.d.ts +2 -0
- package/dist/manager/supervisor.test.d.ts.map +1 -0
- package/dist/manager/supervisor.test.js +245 -0
- package/dist/manager/supervisor.test.js.map +1 -0
- package/dist/manager/worker-manager-impl.d.ts.map +1 -1
- package/dist/manager/worker-manager-impl.js +7 -0
- package/dist/manager/worker-manager-impl.js.map +1 -1
- package/dist/manager/worker-manager-impl.test.d.ts +2 -0
- package/dist/manager/worker-manager-impl.test.d.ts.map +1 -0
- package/dist/manager/worker-manager-impl.test.js +528 -0
- package/dist/manager/worker-manager-impl.test.js.map +1 -0
- package/dist/providers/outputParser.test.d.ts +2 -0
- package/dist/providers/outputParser.test.d.ts.map +1 -0
- package/dist/providers/outputParser.test.js +203 -0
- package/dist/providers/outputParser.test.js.map +1 -0
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +18 -0
- package/dist/test-setup.js.map +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { acquireTickLock, releaseTickLock } from './lock.js';
|
|
6
|
+
function makeTempDir() {
|
|
7
|
+
return mkdtempSync(join(tmpdir(), 'sps-lock-test-'));
|
|
8
|
+
}
|
|
9
|
+
describe('acquireTickLock', () => {
|
|
10
|
+
let tempDir;
|
|
11
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
12
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
13
|
+
it('acquires lock when no lock file exists (reason undefined)', () => {
|
|
14
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
15
|
+
const result = acquireTickLock(lockFile, 30);
|
|
16
|
+
expect(result.acquired).toBe(true);
|
|
17
|
+
expect(result.reason).toBeUndefined();
|
|
18
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
19
|
+
const content = JSON.parse(readFileSync(lockFile, 'utf-8'));
|
|
20
|
+
expect(content.pid).toBe(process.pid);
|
|
21
|
+
expect(content.startedAt).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
it('rejects when another live process holds the lock', () => {
|
|
24
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
25
|
+
// Write a lock with current PID (which is alive)
|
|
26
|
+
writeFileSync(lockFile, JSON.stringify({
|
|
27
|
+
pid: process.pid,
|
|
28
|
+
startedAt: new Date().toISOString(),
|
|
29
|
+
}) + '\n');
|
|
30
|
+
const result = acquireTickLock(lockFile, 30);
|
|
31
|
+
expect(result.acquired).toBe(false);
|
|
32
|
+
expect(result.reason).toBe('another_tick_running');
|
|
33
|
+
});
|
|
34
|
+
it('reclaims lock when PID is dead (reason stale_lock_reclaimed)', () => {
|
|
35
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
36
|
+
writeFileSync(lockFile, JSON.stringify({
|
|
37
|
+
pid: 4_000_000,
|
|
38
|
+
startedAt: new Date().toISOString(),
|
|
39
|
+
}) + '\n');
|
|
40
|
+
const result = acquireTickLock(lockFile, 30);
|
|
41
|
+
expect(result.acquired).toBe(true);
|
|
42
|
+
expect(result.reason).toBe('stale_lock_reclaimed');
|
|
43
|
+
const content = JSON.parse(readFileSync(lockFile, 'utf-8'));
|
|
44
|
+
expect(content.pid).toBe(process.pid);
|
|
45
|
+
});
|
|
46
|
+
it('reclaims lock when timed out even if PID is alive', () => {
|
|
47
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
48
|
+
// Write a lock from current process but started 60 minutes ago
|
|
49
|
+
const pastTime = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
50
|
+
writeFileSync(lockFile, JSON.stringify({
|
|
51
|
+
pid: process.pid,
|
|
52
|
+
startedAt: pastTime,
|
|
53
|
+
}) + '\n');
|
|
54
|
+
const result = acquireTickLock(lockFile, 30); // 30 min timeout, 60 min elapsed
|
|
55
|
+
expect(result.acquired).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
it('does not reclaim when within timeout', () => {
|
|
58
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
59
|
+
// Write lock from current process, started just now
|
|
60
|
+
writeFileSync(lockFile, JSON.stringify({
|
|
61
|
+
pid: process.pid,
|
|
62
|
+
startedAt: new Date().toISOString(),
|
|
63
|
+
}) + '\n');
|
|
64
|
+
const result = acquireTickLock(lockFile, 30);
|
|
65
|
+
expect(result.acquired).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('handles corrupt lock file', () => {
|
|
68
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
69
|
+
writeFileSync(lockFile, 'NOT VALID JSON\n');
|
|
70
|
+
const result = acquireTickLock(lockFile, 30);
|
|
71
|
+
expect(result.acquired).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it('creates parent directories if needed', () => {
|
|
74
|
+
const lockFile = join(tempDir, 'deep', 'nested', 'tick.lock');
|
|
75
|
+
const result = acquireTickLock(lockFile, 30);
|
|
76
|
+
expect(result.acquired).toBe(true);
|
|
77
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('releaseTickLock', () => {
|
|
81
|
+
let tempDir;
|
|
82
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
83
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
84
|
+
it('removes the lock file', () => {
|
|
85
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
86
|
+
acquireTickLock(lockFile, 30);
|
|
87
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
88
|
+
releaseTickLock(lockFile);
|
|
89
|
+
expect(existsSync(lockFile)).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
it('does nothing when lock file does not exist', () => {
|
|
92
|
+
const lockFile = join(tempDir, 'nonexistent.lock');
|
|
93
|
+
expect(() => releaseTickLock(lockFile)).not.toThrow();
|
|
94
|
+
});
|
|
95
|
+
it('acquire then release then re-acquire works', () => {
|
|
96
|
+
const lockFile = join(tempDir, 'tick.lock');
|
|
97
|
+
acquireTickLock(lockFile, 30);
|
|
98
|
+
releaseTickLock(lockFile);
|
|
99
|
+
const result = acquireTickLock(lockFile, 30);
|
|
100
|
+
expect(result.acquired).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=lock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.test.js","sourceRoot":"","sources":["../../src/core/lock.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE7D,SAAS,WAAW;IAClB,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,SAAS,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAE7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,iDAAiD;QACjD,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;YACrC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,GAAG,IAAI,CAAC,CAAC;QAEX,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;YACrC,GAAG,EAAE,SAAS;YACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,GAAG,IAAI,CAAC,CAAC;QAEX,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAEnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,+DAA+D;QAC/D,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACrE,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;YACrC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,QAAQ;SACpB,CAAC,GAAG,IAAI,CAAC,CAAC;QAEX,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,iCAAiC;QAC/E,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,oDAAoD;QACpD,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC;YACrC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,GAAG,IAAI,CAAC,CAAC;QAEX,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,aAAa,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAE5C,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,SAAS,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC1B,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC5C,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC9B,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.test.d.ts","sourceRoot":"","sources":["../../src/core/queue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { readQueue, writeQueue, removeFromQueue } from './queue.js';
|
|
6
|
+
function makeTempDir() {
|
|
7
|
+
return mkdtempSync(join(tmpdir(), 'sps-queue-test-'));
|
|
8
|
+
}
|
|
9
|
+
describe('readQueue', () => {
|
|
10
|
+
let tempDir;
|
|
11
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
12
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
13
|
+
it('returns empty array when file does not exist', () => {
|
|
14
|
+
expect(readQueue(join(tempDir, 'nonexistent.json'))).toEqual([]);
|
|
15
|
+
});
|
|
16
|
+
it('reads a valid queue file', () => {
|
|
17
|
+
const file = join(tempDir, 'queue.json');
|
|
18
|
+
writeFileSync(file, '[1, 2, 3, 4, 5]\n');
|
|
19
|
+
expect(readQueue(file)).toEqual([1, 2, 3, 4, 5]);
|
|
20
|
+
});
|
|
21
|
+
it('returns empty array for invalid JSON', () => {
|
|
22
|
+
const file = join(tempDir, 'bad.json');
|
|
23
|
+
writeFileSync(file, 'NOT JSON');
|
|
24
|
+
expect(readQueue(file)).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
it('returns empty array when file contains non-array JSON', () => {
|
|
27
|
+
const file = join(tempDir, 'obj.json');
|
|
28
|
+
writeFileSync(file, '{"not": "array"}');
|
|
29
|
+
expect(readQueue(file)).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
it('filters out non-number values', () => {
|
|
32
|
+
const file = join(tempDir, 'mixed.json');
|
|
33
|
+
writeFileSync(file, '[1, "two", 3, null, 5, true]');
|
|
34
|
+
expect(readQueue(file)).toEqual([1, 3, 5]);
|
|
35
|
+
});
|
|
36
|
+
it('handles empty array', () => {
|
|
37
|
+
const file = join(tempDir, 'empty.json');
|
|
38
|
+
writeFileSync(file, '[]');
|
|
39
|
+
expect(readQueue(file)).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('writeQueue', () => {
|
|
43
|
+
let tempDir;
|
|
44
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
45
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
46
|
+
it('writes a queue atomically', () => {
|
|
47
|
+
const file = join(tempDir, 'queue.json');
|
|
48
|
+
writeQueue(file, [10, 20, 30]);
|
|
49
|
+
const raw = JSON.parse(readFileSync(file, 'utf-8'));
|
|
50
|
+
expect(raw).toEqual([10, 20, 30]);
|
|
51
|
+
});
|
|
52
|
+
it('overwrites existing queue', () => {
|
|
53
|
+
const file = join(tempDir, 'queue.json');
|
|
54
|
+
writeQueue(file, [1, 2, 3]);
|
|
55
|
+
writeQueue(file, [4, 5]);
|
|
56
|
+
expect(readQueue(file)).toEqual([4, 5]);
|
|
57
|
+
});
|
|
58
|
+
it('writes empty array', () => {
|
|
59
|
+
const file = join(tempDir, 'queue.json');
|
|
60
|
+
writeQueue(file, []);
|
|
61
|
+
expect(readQueue(file)).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
it('does not leave temp files', () => {
|
|
64
|
+
const file = join(tempDir, 'queue.json');
|
|
65
|
+
writeQueue(file, [1]);
|
|
66
|
+
const { readdirSync } = require('node:fs');
|
|
67
|
+
const files = readdirSync(tempDir);
|
|
68
|
+
const tmpFiles = files.filter((f) => f.endsWith('.tmp'));
|
|
69
|
+
expect(tmpFiles).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('removeFromQueue', () => {
|
|
73
|
+
let tempDir;
|
|
74
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
75
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
76
|
+
it('removes a seq from the queue', () => {
|
|
77
|
+
const file = join(tempDir, 'queue.json');
|
|
78
|
+
writeQueue(file, [1, 2, 3, 4, 5]);
|
|
79
|
+
removeFromQueue(file, 3);
|
|
80
|
+
expect(readQueue(file)).toEqual([1, 2, 4, 5]);
|
|
81
|
+
});
|
|
82
|
+
it('does nothing when seq not in queue', () => {
|
|
83
|
+
const file = join(tempDir, 'queue.json');
|
|
84
|
+
writeQueue(file, [1, 2, 3]);
|
|
85
|
+
removeFromQueue(file, 99);
|
|
86
|
+
expect(readQueue(file)).toEqual([1, 2, 3]);
|
|
87
|
+
});
|
|
88
|
+
it('handles nonexistent file gracefully', () => {
|
|
89
|
+
const file = join(tempDir, 'nofile.json');
|
|
90
|
+
expect(() => removeFromQueue(file, 1)).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
it('removes duplicate occurrences', () => {
|
|
93
|
+
const file = join(tempDir, 'queue.json');
|
|
94
|
+
writeQueue(file, [1, 2, 2, 3]);
|
|
95
|
+
removeFromQueue(file, 2);
|
|
96
|
+
expect(readQueue(file)).toEqual([1, 3]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=queue.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.test.js","sourceRoot":"","sources":["../../src/core/queue.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RawEnv {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Parse a shell conf file (KEY=value / export KEY=value) into a plain object.
|
|
6
|
+
* Does NOT execute the file — only extracts simple assignments.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseShellConf(filePath: string): RawEnv;
|
|
9
|
+
/**
|
|
10
|
+
* Source a shell file via bash and capture all exported variables.
|
|
11
|
+
* Falls back to regex parsing on bash failure.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sourceShellConf(filePath: string): RawEnv;
|
|
14
|
+
/**
|
|
15
|
+
* Source multiple shell files in a single bash context.
|
|
16
|
+
* Later files can reference variables from earlier ones.
|
|
17
|
+
* Falls back to sourcing each file separately on bash failure.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sourceCombinedConf(filePaths: string[]): RawEnv;
|
|
20
|
+
//# sourceMappingURL=shellEnv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellEnv.d.ts","sourceRoot":"","sources":["../../src/core/shellEnv.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,MAAM;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBvD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAmB9D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell environment utilities — parse and source shell conf files.
|
|
3
|
+
*
|
|
4
|
+
* Shared by config.ts (project loading) and supervisor.ts (worker env).
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Parse a shell conf file (KEY=value / export KEY=value) into a plain object.
|
|
10
|
+
* Does NOT execute the file — only extracts simple assignments.
|
|
11
|
+
*/
|
|
12
|
+
export function parseShellConf(filePath) {
|
|
13
|
+
const result = {};
|
|
14
|
+
try {
|
|
15
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
16
|
+
for (const line of content.split('\n')) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
19
|
+
continue;
|
|
20
|
+
const match = trimmed.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)=["']?(.*?)["']?\s*$/);
|
|
21
|
+
if (match) {
|
|
22
|
+
result[match[1]] = match[2];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { /* file read error */ }
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Source a shell file via bash and capture all exported variables.
|
|
31
|
+
* Falls back to regex parsing on bash failure.
|
|
32
|
+
*/
|
|
33
|
+
export function sourceShellConf(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
const output = execSync(`bash -c 'set -a; source "${filePath}" 2>/dev/null; env'`, { encoding: 'utf-8', timeout: 5000 });
|
|
36
|
+
return parseEnvOutput(output);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return parseShellConf(filePath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Source multiple shell files in a single bash context.
|
|
44
|
+
* Later files can reference variables from earlier ones.
|
|
45
|
+
* Falls back to sourcing each file separately on bash failure.
|
|
46
|
+
*/
|
|
47
|
+
export function sourceCombinedConf(filePaths) {
|
|
48
|
+
const existing = filePaths.filter(p => existsSync(p));
|
|
49
|
+
if (existing.length === 0)
|
|
50
|
+
return {};
|
|
51
|
+
const sources = existing.map(p => `source "${p}" 2>/dev/null`);
|
|
52
|
+
try {
|
|
53
|
+
const output = execSync(`bash -c 'set -a; ${sources.join('; ')}; env'`, { encoding: 'utf-8', timeout: 5000 });
|
|
54
|
+
return parseEnvOutput(output);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fallback: source each file separately and merge
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const p of existing) {
|
|
60
|
+
Object.assign(result, sourceShellConf(p));
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse `env` command output into key-value pairs.
|
|
67
|
+
*/
|
|
68
|
+
function parseEnvOutput(output) {
|
|
69
|
+
const result = {};
|
|
70
|
+
for (const line of output.split('\n')) {
|
|
71
|
+
const idx = line.indexOf('=');
|
|
72
|
+
if (idx > 0) {
|
|
73
|
+
result[line.slice(0, idx)] = line.slice(idx + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=shellEnv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellEnv.js","sourceRoot":"","sources":["../../src/core/shellEnv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellEnv.test.d.ts","sourceRoot":"","sources":["../../src/core/shellEnv.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { parseShellConf, sourceShellConf, sourceCombinedConf } from './shellEnv.js';
|
|
6
|
+
function makeTempDir() {
|
|
7
|
+
return mkdtempSync(join(tmpdir(), 'sps-shellenv-test-'));
|
|
8
|
+
}
|
|
9
|
+
describe('parseShellConf', () => {
|
|
10
|
+
let tempDir;
|
|
11
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
12
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
13
|
+
it('parses KEY=value lines', () => {
|
|
14
|
+
const file = join(tempDir, 'conf');
|
|
15
|
+
writeFileSync(file, 'FOO=bar\nBAZ=qux\n');
|
|
16
|
+
const result = parseShellConf(file);
|
|
17
|
+
expect(result.FOO).toBe('bar');
|
|
18
|
+
expect(result.BAZ).toBe('qux');
|
|
19
|
+
});
|
|
20
|
+
it('parses export KEY=value', () => {
|
|
21
|
+
const file = join(tempDir, 'conf');
|
|
22
|
+
writeFileSync(file, 'export MY_VAR=hello\n');
|
|
23
|
+
expect(parseShellConf(file).MY_VAR).toBe('hello');
|
|
24
|
+
});
|
|
25
|
+
it('parses double-quoted values', () => {
|
|
26
|
+
const file = join(tempDir, 'conf');
|
|
27
|
+
writeFileSync(file, 'MY_VAR="hello world"\n');
|
|
28
|
+
expect(parseShellConf(file).MY_VAR).toBe('hello world');
|
|
29
|
+
});
|
|
30
|
+
it('parses single-quoted values', () => {
|
|
31
|
+
const file = join(tempDir, 'conf');
|
|
32
|
+
writeFileSync(file, "MY_VAR='single'\n");
|
|
33
|
+
expect(parseShellConf(file).MY_VAR).toBe('single');
|
|
34
|
+
});
|
|
35
|
+
it('skips comments and blank lines', () => {
|
|
36
|
+
const file = join(tempDir, 'conf');
|
|
37
|
+
writeFileSync(file, '# comment\n\nKEY=val\n# another\n');
|
|
38
|
+
const result = parseShellConf(file);
|
|
39
|
+
expect(Object.keys(result).filter(k => k === 'KEY')).toHaveLength(1);
|
|
40
|
+
expect(result.KEY).toBe('val');
|
|
41
|
+
});
|
|
42
|
+
it('returns empty object for nonexistent file', () => {
|
|
43
|
+
expect(parseShellConf(join(tempDir, 'no-file'))).toEqual({});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('sourceShellConf', () => {
|
|
47
|
+
let tempDir;
|
|
48
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
49
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
50
|
+
it('sources variables via bash', () => {
|
|
51
|
+
const file = join(tempDir, 'conf');
|
|
52
|
+
writeFileSync(file, 'TEST_SRC_VAR=sourced_value\n');
|
|
53
|
+
const result = sourceShellConf(file);
|
|
54
|
+
expect(result.TEST_SRC_VAR).toBe('sourced_value');
|
|
55
|
+
});
|
|
56
|
+
it('resolves variable interpolation', () => {
|
|
57
|
+
const file = join(tempDir, 'conf');
|
|
58
|
+
writeFileSync(file, 'BASE=hello\nDERIVED="${BASE}_world"\n');
|
|
59
|
+
const result = sourceShellConf(file);
|
|
60
|
+
expect(result.DERIVED).toBe('hello_world');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('sourceCombinedConf', () => {
|
|
64
|
+
let tempDir;
|
|
65
|
+
beforeEach(() => { tempDir = makeTempDir(); });
|
|
66
|
+
afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
|
|
67
|
+
it('returns empty when no files exist', () => {
|
|
68
|
+
expect(sourceCombinedConf(['/tmp/no1', '/tmp/no2'])).toEqual({});
|
|
69
|
+
});
|
|
70
|
+
it('sources a single file', () => {
|
|
71
|
+
const file = join(tempDir, 'env');
|
|
72
|
+
writeFileSync(file, 'SINGLE=yes\n');
|
|
73
|
+
const result = sourceCombinedConf([file]);
|
|
74
|
+
expect(result.SINGLE).toBe('yes');
|
|
75
|
+
});
|
|
76
|
+
it('merges multiple files with later overriding earlier', () => {
|
|
77
|
+
const env = join(tempDir, 'env');
|
|
78
|
+
const conf = join(tempDir, 'conf');
|
|
79
|
+
writeFileSync(env, 'SHARED=from_env\nENV_ONLY=env\n');
|
|
80
|
+
writeFileSync(conf, 'SHARED=from_conf\nCONF_ONLY=conf\n');
|
|
81
|
+
const result = sourceCombinedConf([env, conf]);
|
|
82
|
+
expect(result.SHARED).toBe('from_conf');
|
|
83
|
+
expect(result.ENV_ONLY).toBe('env');
|
|
84
|
+
expect(result.CONF_ONLY).toBe('conf');
|
|
85
|
+
});
|
|
86
|
+
it('later file can reference earlier file variables', () => {
|
|
87
|
+
const env = join(tempDir, 'env');
|
|
88
|
+
const conf = join(tempDir, 'conf');
|
|
89
|
+
writeFileSync(env, 'BASE_URL=https://api.example.com\n');
|
|
90
|
+
writeFileSync(conf, 'API_ENDPOINT="${BASE_URL}/v1"\n');
|
|
91
|
+
const result = sourceCombinedConf([env, conf]);
|
|
92
|
+
expect(result.API_ENDPOINT).toBe('https://api.example.com/v1');
|
|
93
|
+
});
|
|
94
|
+
it('skips nonexistent files gracefully', () => {
|
|
95
|
+
const existing = join(tempDir, 'exists');
|
|
96
|
+
writeFileSync(existing, 'EXISTS=yes\n');
|
|
97
|
+
const result = sourceCombinedConf(['/tmp/no-file-xyz', existing]);
|
|
98
|
+
expect(result.EXISTS).toBe('yes');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=shellEnv.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellEnv.test.js","sourceRoot":"","sources":["../../src/core/shellEnv.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.test.d.ts","sourceRoot":"","sources":["../../src/core/state.test.ts"],"names":[],"mappings":""}
|