refactor(ultraplan-local): extract atomicWriteJson to lib/util
Three changes in one commit: 1. NEW lib/util/atomic-write.mjs — exports atomicWriteJson(path, obj), the canonical tmp+rename pattern. Reused by pre-compact-flush.mjs and (in subsequent steps) by the new session-state writer. 2. NEW tests/lib/atomic-write.test.mjs — 4 unit tests covering round-trip, no-orphan-tmp, overwrite-atomic, pretty-print formatting. 3. REFACTOR hooks/scripts/pre-compact-flush.mjs — replace the inline atomicWrite() with the imported atomicWriteJson(). Also fixes a pre-existing syntax error (leading whitespace + stray --resume token outside the comment block) that silently broke the hook from v3.1.0 onward — PreCompact runtime is fail-open and swallowed the error. File reformatted with standard zero-indent JS. 163 → 167 tests, 0 fail. Step 2 of /ultracontinue v3.3.0 (project 2026-05-01-ultracontinue).
This commit is contained in:
parent
bdddf52873
commit
655c8d46f8
3 changed files with 216 additions and 146 deletions
61
plugins/ultraplan-local/tests/lib/atomic-write.test.mjs
Normal file
61
plugins/ultraplan-local/tests/lib/atomic-write.test.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// tests/lib/atomic-write.test.mjs
|
||||
// Unit tests for lib/util/atomic-write.mjs
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync, readFileSync, existsSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { atomicWriteJson } from '../../lib/util/atomic-write.mjs';
|
||||
|
||||
test('atomicWriteJson — writes valid JSON and round-trips', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'aw-test-'));
|
||||
try {
|
||||
const path = join(dir, 'state.json');
|
||||
const obj = { schema_version: 1, status: 'in_progress', items: [1, 2, 3] };
|
||||
atomicWriteJson(path, obj);
|
||||
const read = JSON.parse(readFileSync(path, 'utf-8'));
|
||||
assert.deepEqual(read, obj);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('atomicWriteJson — leaves no .tmp orphan after success', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'aw-test-'));
|
||||
try {
|
||||
const path = join(dir, 'state.json');
|
||||
atomicWriteJson(path, { ok: true });
|
||||
assert.equal(existsSync(path), true);
|
||||
assert.equal(existsSync(path + '.tmp'), false);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('atomicWriteJson — overwrites existing file atomically', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'aw-test-'));
|
||||
try {
|
||||
const path = join(dir, 'state.json');
|
||||
writeFileSync(path, '{"old":true}');
|
||||
atomicWriteJson(path, { new: true });
|
||||
const read = JSON.parse(readFileSync(path, 'utf-8'));
|
||||
assert.deepEqual(read, { new: true });
|
||||
assert.equal(existsSync(path + '.tmp'), false);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('atomicWriteJson — pretty-prints with 2-space indent', () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'aw-test-'));
|
||||
try {
|
||||
const path = join(dir, 'state.json');
|
||||
atomicWriteJson(path, { a: 1, b: { c: 2 } });
|
||||
const text = readFileSync(path, 'utf-8');
|
||||
assert.match(text, /\n {2}"a": 1/);
|
||||
assert.match(text, /\n {4}"c": 2/);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue