@olierjs/fugue-cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,74 @@
1
+ import { randomUUID, createHash } from 'node:crypto';
2
+ import { mkdir, readFile, writeFile, rename, open, unlink } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ export const digest = value => `sha256:${createHash('sha256').update(typeof value === 'string' ? value : JSON.stringify(value)).digest('hex')}`;
6
+ export const readJson = async path => JSON.parse(await readFile(path, 'utf8'));
7
+
8
+ export async function saveSession(path, state) {
9
+ const temp = `${path}.${randomUUID()}.tmp`;
10
+ await writeFile(temp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
11
+ await rename(temp, path);
12
+ }
13
+
14
+ export async function createSession(output, state) {
15
+ const directory = resolve(output);
16
+ await mkdir(directory, { recursive: true });
17
+ const path = resolve(directory, 'session.json');
18
+ await writeFile(path, JSON.stringify(state, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
19
+ return path;
20
+ }
21
+
22
+ export async function loadSession(path) {
23
+ const state = await readJson(path);
24
+ if (state.version !== 1 || !state.requirementId || !state.baseUrl) throw new Error('任务文件无效。请使用脚本返回的 session 路径。');
25
+ return state;
26
+ }
27
+
28
+ export async function lockSession(path, work) {
29
+ const lock = `${path}.lock`;
30
+ try {
31
+ const handle = await open(lock, 'wx', 0o600);
32
+ await handle.writeFile(String(process.pid));
33
+ await handle.close();
34
+ } catch (error) {
35
+ if (error.code !== 'EEXIST') throw error;
36
+ const pid = Number(await readFile(lock, 'utf8'));
37
+ if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error('任务锁无法识别,请确认没有其他命令运行后清理锁文件。');
38
+ try { process.kill(pid, 0); }
39
+ catch (cause) {
40
+ if (cause.code === 'ESRCH') { await unlink(lock); return lockSession(path, work); }
41
+ throw cause;
42
+ }
43
+ throw new Error('此任务已有命令运行,请等待该命令结束。');
44
+ }
45
+ try { return await work(); }
46
+ finally { await unlink(lock); }
47
+ }
48
+
49
+ export async function writeReview(sessionPath, state, outcome, documents) {
50
+ const directory = resolve(dirname(sessionPath), `review-${randomUUID()}`);
51
+ await mkdir(directory);
52
+ const files = {};
53
+ const hashes = {};
54
+ for (const [name, text] of Object.entries(documents)) {
55
+ const path = resolve(directory, name);
56
+ await writeFile(path, text, { flag: 'wx' });
57
+ files[name] = path;
58
+ hashes[path] = digest(text);
59
+ }
60
+ const path = resolve(directory, 'review.json');
61
+ await writeFile(path, JSON.stringify({ version: 1, session: resolve(sessionPath), requirementId: state.requirementId, baseUrl: state.baseUrl, outcome, hashes }, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
62
+ return { review: path, documents: files };
63
+ }
64
+
65
+ export async function loadReview(path) {
66
+ const review = await readJson(path);
67
+ if (review.version !== 1 || !review.session || !review.outcome || !review.hashes) throw new Error('审阅凭据无效,请重新读取文档。');
68
+ const state = await loadSession(review.session);
69
+ if (review.requirementId !== state.requirementId || review.baseUrl !== state.baseUrl) throw new Error('审阅凭据与任务不匹配。');
70
+ for (const [file, hash] of Object.entries(review.hashes)) {
71
+ if (digest(await readFile(file, 'utf8')) !== hash) throw new Error('审阅文档已被修改,请重新读取正式文档后再作决定。');
72
+ }
73
+ return review;
74
+ }