@trim21/personal-pi-extensions 0.0.271 → 0.0.272
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/package.json +1 -1
- package/src/gh-readonly.ts +12 -24
- package/src/lib/seq-state.ts +34 -0
package/package.json
CHANGED
package/src/gh-readonly.ts
CHANGED
|
@@ -34,10 +34,12 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
34
34
|
import { homedir } from "node:os";
|
|
35
35
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
36
36
|
|
|
37
|
-
import {
|
|
37
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
38
38
|
import { Type } from "typebox";
|
|
39
39
|
import { Value } from "typebox/value";
|
|
40
40
|
|
|
41
|
+
import { createSeqState } from "./lib/seq-state.js";
|
|
42
|
+
|
|
41
43
|
interface GhResult {
|
|
42
44
|
stdout: string;
|
|
43
45
|
stderr: string;
|
|
@@ -380,8 +382,9 @@ export function stepsDetail(
|
|
|
380
382
|
}));
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
// 模块级串行状态:同一资源(如 CI 日志)的请求排队执行,配合函数内部的
|
|
386
|
+
// 缓存检查避免重复网络请求。闭包状态不与其他扩展共享,key 无需全局前缀。
|
|
387
|
+
const seq = createSeqState();
|
|
385
388
|
|
|
386
389
|
async function getJobLog(
|
|
387
390
|
runId: string,
|
|
@@ -393,13 +396,10 @@ async function getJobLog(
|
|
|
393
396
|
): Promise<string> {
|
|
394
397
|
const cacheDir = join(homedir(), ".cache", "pi", "ci-logs", runId);
|
|
395
398
|
const cacheFile = join(cacheDir, `${jobId}.log`);
|
|
396
|
-
const key = `${runId}:${jobId}`;
|
|
397
|
-
|
|
398
|
-
// Check in-flight dedup map
|
|
399
|
-
const inflight = inflightLogs.get(key);
|
|
400
|
-
if (inflight) return inflight;
|
|
401
399
|
|
|
402
|
-
|
|
400
|
+
// 同一 runId:jobId 的请求串行执行:后一个进入时缓存已写入,直接命中缓存,
|
|
401
|
+
// 不会重复发网络请求;串行也保证不会有两个并发写同一 cache 文件。
|
|
402
|
+
return seq.execute(`${runId}:${jobId}`, async () => {
|
|
403
403
|
// Check file cache
|
|
404
404
|
try {
|
|
405
405
|
return await readFile(cacheFile, "utf8");
|
|
@@ -424,20 +424,10 @@ async function getJobLog(
|
|
|
424
424
|
|
|
425
425
|
// Write to cache
|
|
426
426
|
await mkdir(cacheDir, { recursive: true });
|
|
427
|
-
await
|
|
428
|
-
await writeFile(cacheFile, log);
|
|
429
|
-
});
|
|
427
|
+
await writeFile(cacheFile, log);
|
|
430
428
|
|
|
431
429
|
return log;
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
const promise = fetchAndCache();
|
|
435
|
-
inflightLogs.set(key, promise);
|
|
436
|
-
try {
|
|
437
|
-
return await promise;
|
|
438
|
-
} finally {
|
|
439
|
-
inflightLogs.delete(key);
|
|
440
|
-
}
|
|
430
|
+
});
|
|
441
431
|
}
|
|
442
432
|
|
|
443
433
|
async function resolveRepo(
|
|
@@ -1061,9 +1051,7 @@ export async function writeLogFile(
|
|
|
1061
1051
|
|
|
1062
1052
|
const target = resolve(cwd ?? process.cwd(), outputFile);
|
|
1063
1053
|
await mkdir(dirname(target), { recursive: true });
|
|
1064
|
-
await
|
|
1065
|
-
await writeFile(target, content);
|
|
1066
|
-
});
|
|
1054
|
+
await writeFile(target, content);
|
|
1067
1055
|
|
|
1068
1056
|
const lines = content.split("\n").length;
|
|
1069
1057
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按 key 串行化并发任务:同一 key 的调用排队执行,前一个 settle(无论成败)
|
|
3
|
+
* 后才执行下一个。key 由调用方自定义(如资源 id),每个 `createSeqState()`
|
|
4
|
+
* 返回独立的闭包状态,不同扩展之间不串扰。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface SeqState {
|
|
8
|
+
execute<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createSeqState(): SeqState {
|
|
12
|
+
const tails = new Map<string, Promise<unknown>>();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
execute<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
16
|
+
const previous = tails.get(key) ?? Promise.resolve();
|
|
17
|
+
|
|
18
|
+
// 等前一个任务 settle 后执行本任务。previous 恒为 resolve(tail 吞掉了错误),
|
|
19
|
+
// 失败不会阻塞后续任务。
|
|
20
|
+
const run = previous.then(() => fn());
|
|
21
|
+
|
|
22
|
+
// 本任务的完成占位:永远 resolve,作为下一个任务的等待点。
|
|
23
|
+
// eslint-disable-next-line unicorn/no-useless-undefined -- 显式返回 undefined:吞掉前序错误并归一化为成功占位
|
|
24
|
+
const tail = run.catch(() => undefined);
|
|
25
|
+
|
|
26
|
+
tails.set(key, tail);
|
|
27
|
+
void tail.finally(() => {
|
|
28
|
+
if (tails.get(key) === tail) tails.delete(key);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return run;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|