Public: anyone can read this; the crew can change it.
Sentinel: red:01m23vygyffx5hg2f5vm8r74sr was degraded 120 minutes today: trevato/forge: main is red at ca12f0f — "a fork that follows its parent > the terminal's About card says the same sente… #2101
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2075-sentinel-red-01m23vygyffx5hg2f5vm8r74sr → main 5 commits fork 1 thread · 1 unresolved
3 files
changed +54
−9
src/core/parent_releases.ts +11
| @@ -146,6 +146,17 @@ async function fetchDoc(url: string, f: typeof fetch): Promise<unknown> { | ||
| 146 | 146 | |
| 147 | 147 | const refreshing = new Map<string, Promise<void>>(); |
| 148 | 148 | |
| 149 | /** Resolves when no refresh of this cache is in flight (at once if none is). */ | |
| 150 | export async function refreshIdle(dataDir: string): Promise<void> { | |
| 151 | const file = join(dataDir, "parent-releases.json"); | |
| 152 | for ( | |
| 153 | let job = refreshing.get(file); | |
| 154 | job !== undefined; | |
| 155 | job = refreshing.get(file) | |
| 156 | ) | |
| 157 | await job; | |
| 158 | } | |
| 159 | ||
| 149 | 160 | /** Reads the parent now and stores the answer; one refresh at a time per cache. */ |
| 150 | 161 | export function refreshParentReleases( |
| 151 | 162 | dataDir: string, |
test/following.test.ts +3 −9
| @@ -8,7 +8,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; | ||
| 8 | 8 | import { mkdtemp, rm } from "node:fs/promises"; |
| 9 | 9 | import { tmpdir } from "node:os"; |
| 10 | 10 | import { join } from "node:path"; |
| 11 | import { parentUrlProblem } from "../src/core/parent_releases.ts"; | |
| 11 | import { parentUrlProblem, refreshIdle } from "../src/core/parent_releases.ts"; | |
| 12 | 12 | import { Session } from "../src/tui/session.ts"; |
| 13 | 13 | import { releasesRow } from "../src/tui/screens/about.ts"; |
| 14 | 14 | import { createApp, type App } from "../src/app.ts"; |
| @@ -967,15 +967,9 @@ describe("a landed pull moves the forks, like the push it amounts to", () => { | ||
| 967 | 967 | }); |
| 968 | 968 | }); |
| 969 | 969 | |
| 970 | /** Waits for a background refresh to rewrite the cache (or 600 ms for none). */ | |
| 970 | /** Waits for any background refresh of the cache to finish. */ | |
| 971 | 971 | async function settled(dir: string): Promise<void> { |
| 972 | const file = Bun.file(join(dir, "parent-releases.json")); | |
| 973 | const text = async () => ((await file.exists()) ? await file.text() : ""); | |
| 974 | const before = await text(); | |
| 975 | for (let i = 0; i < 24; i++) { | |
| 976 | await Bun.sleep(25); | |
| 977 | if ((await text()) !== before) return; | |
| 978 | } | |
| 972 | await refreshIdle(dir); | |
| 979 | 973 | } |
| 980 | 974 | |
| 981 | 975 | describe("a parent URL", () => { |
test/parent-releases-idle.test.ts added +40
| @@ -0,0 +1,40 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 3 | import { tmpdir } from "node:os"; | |
| 4 | import { join } from "node:path"; | |
| 5 | import { | |
| 6 | refreshIdle, | |
| 7 | refreshParentReleases, | |
| 8 | } from "../src/core/parent_releases.ts"; | |
| 9 | ||
| 10 | test("refreshIdle resolves at once with nothing in flight, and waits for a slow refresh", async () => { | |
| 11 | const dir = await mkdtemp(join(tmpdir(), "forge-idle-")); | |
| 12 | try { | |
| 13 | await refreshIdle(dir); | |
| 14 | let release!: () => void; | |
| 15 | const gate = new Promise<void>((r) => (release = r)); | |
| 16 | const slow = (async () => { | |
| 17 | await gate; | |
| 18 | return Response.json({ | |
| 19 | releases: [{ name: "1.0.0", url: "http://127.0.0.1/r" }], | |
| 20 | }); | |
| 21 | }) as unknown as typeof fetch; | |
| 22 | void refreshParentReleases(dir, "http://127.0.0.1:1", { | |
| 23 | loopback: true, | |
| 24 | fetch: slow, | |
| 25 | }); | |
| 26 | let idle = false; | |
| 27 | const waiting = refreshIdle(dir).then(() => (idle = true)); | |
| 28 | await Bun.sleep(30); | |
| 29 | expect(idle).toBe(false); | |
| 30 | release(); | |
| 31 | await waiting; | |
| 32 | expect(idle).toBe(true); | |
| 33 | const stored = JSON.parse( | |
| 34 | await Bun.file(join(dir, "parent-releases.json")).text(), | |
| 35 | ); | |
| 36 | expect(stored.ok).toBe(true); | |
| 37 | } finally { | |
| 38 | await rm(dir, { recursive: true, force: true }); | |
| 39 | } | |
| 40 | }); |
new above
offline
Nit: the doc comment could say this is for tests and shutdown. Otherwise it reads as dead production code.