@shivaedev/pr-watch 0.1.1 → 0.2.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.
- package/CHANGELOG.md +16 -0
- package/README.md +3 -7
- package/dist/GitHub.d.ts.map +1 -1
- package/dist/GitHub.js +5 -19
- package/dist/GitHub.js.map +1 -1
- package/dist/GitHubHttp.d.ts +4 -5
- package/dist/GitHubHttp.d.ts.map +1 -1
- package/dist/GitHubHttp.js +26 -29
- package/dist/GitHubHttp.js.map +1 -1
- package/dist/arguments.d.ts +1 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/arguments.js +1 -2
- package/dist/arguments.js.map +1 -1
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/confirmQueue.d.ts +3 -2
- package/dist/confirmQueue.d.ts.map +1 -1
- package/dist/confirmQueue.js +2 -2
- package/dist/confirmQueue.js.map +1 -1
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/program.d.ts +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -2
- package/dist/program.js.map +1 -1
- package/dist/quota.d.ts +3 -9
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +10 -30
- package/dist/quota.js.map +1 -1
- package/package.json +15 -16
- package/src/GitHub.ts +5 -19
- package/src/GitHubHttp.ts +38 -30
- package/src/arguments.ts +1 -2
- package/src/cli.ts +12 -12
- package/src/confirmQueue.ts +2 -2
- package/src/model.ts +0 -1
- package/src/program.ts +1 -2
- package/src/quota.ts +16 -37
- package/dist/httpResponse.d.ts +0 -15
- package/dist/httpResponse.d.ts.map +0 -1
- package/dist/httpResponse.js +0 -31
- package/dist/httpResponse.js.map +0 -1
- package/docs/north-star.md +0 -7
- package/docs/roadmap.md +0 -18
- package/src/httpResponse.ts +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-10-25
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Send GitHub reads, queue-confirmation writes, conditional requests and Link pagination through effect-github. Keep polling slowdown and cooldown scheduling in the watcher, using shared resource observations and rate-limit retry instants. Permission failures retain the saved snapshot without imposing a quota cooldown.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- Provide effect-github’s `GitHub.layer` and `GitHubToken.fromEnv` when composing `GitHubHttp.layer`. Remove `Options.token` and the token arguments to `argumentsFor`, `program` and `GitHubHttp` methods; configure authentication through `GitHubToken` instead. `GitHubHttp.get` and `post` return decoded bodies, and `list` collects paginated bodies. Replace `httpResponse` helpers and `quota.observeBudget`/`Budget` with effect-github; `pollDelay` now accepts its rate-limit observations and an optional retry instant.
|
|
12
|
+
|
|
13
|
+
## 0.1.2 - 2026-10-24
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Require Effect and its companion packages at exactly 4.0.5. Generated applications use the same versions.
|
|
18
|
+
|
|
3
19
|
## 0.1.1 - 2026-10-18
|
|
4
20
|
|
|
5
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Each successful observation compares a PR with its saved snapshot. The first obs
|
|
|
23
23
|
Install with Node 24 or later and the repository's Effect peer version:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
npm install @shivaedev/pr-watch effect@4.0.
|
|
26
|
+
npm install @shivaedev/pr-watch effect@4.0.5 @effect/platform-node@4.0.5 @effect/platform-node-shared@4.0.5
|
|
27
27
|
export GH_TOKEN=YOUR_READ_ONLY_TOKEN
|
|
28
28
|
pr-watch --repo acme/project 42 57 --state ./watch-state.json
|
|
29
29
|
pr-watch --repo acme/project --fleet --interval 60 --state ./fleet-state.json
|
|
@@ -59,7 +59,7 @@ At a 60-second interval, for N open PRs, one page of checks per PR and one page
|
|
|
59
59
|
|
|
60
60
|
These are request counts, not necessarily charged quota. With authentication, a successful ETag conditional request returning 304 costs zero primary rate-limit quota. Every REST endpoint sends `If-None-Match` after receiving an ETag and reuses its successful cached body on 304. Initial requests, changed responses and endpoints without ETags consume quota normally. Caches are in memory, so a restart makes fresh requests. Secondary limits still apply.
|
|
61
61
|
|
|
62
|
-
The minimum interval doubles below 50% remaining quota, increases fivefold below 20% and tenfold below 10%. At low quota, the delay also spreads the observed round's request count over the remaining reset window.
|
|
62
|
+
The minimum interval doubles below 50% remaining quota, increases fivefold below 20% and tenfold below 10%. At low quota, the delay also spreads the observed round's request count over the remaining reset window. Rate-limited requests defer polling until effect-github’s `retryAt`, derived from `Retry-After` or the primary reset instant; exhausted primary quota waits until reset. Permission failures remain failed observations without a quota cooldown. No further request starts during a known cooldown.
|
|
63
63
|
|
|
64
64
|
### Restart and delivery limits
|
|
65
65
|
|
|
@@ -67,8 +67,4 @@ The state file records discovered open PR numbers and the last successfully comp
|
|
|
67
67
|
|
|
68
68
|
Transitions that reverse between observations, including while the watcher is down, are not recovered. For example, leaving and rejoining the merge queue before the next observation produces no `dequeued` event. Stdout delivery is at-least-once across a crash: a crash after output but before the state replacement can repeat a line. There is no event history or acknowledged output protocol.
|
|
69
69
|
|
|
70
|
-
The exported `StateFile.layer` requires Effect `FileSystem
|
|
71
|
-
|
|
72
|
-
### Evidence
|
|
73
|
-
|
|
74
|
-
Synthetic HTTP tests cover conditional 304 responses, all eight events, already-stuck PRs at startup, terminal pruning across restarts, queue entry with a cleared auto-merge request, confirmed queue drops and rebuilds, manual auto-merge disablement, native IO injection and typed failures, checks independent of PR ETags, fleet discovery and pagination, quota slowdown, failed requests, and clean restart comparison. A CLI subprocess test verifies that pending mergeability diagnostics stay on stderr while stdout contains JSON event lines. The packed consumer runs the installed executable against a synthetic server. These tests establish the REST and GraphQL handling and installed package behavior; they do not establish live GitHub or Monitor adoption.
|
|
70
|
+
The exported `StateFile.layer` requires Effect `FileSystem`. `GitHubHttp.layer` requires effect-github’s `GitHub` service, which owns authentication, conditional representations, Link pagination and rate-limit observations. Provide `GitHub.layer` with Effect `HttpClient` and `GitHubToken.fromEnv`; the token Layer reads `GH_TOKEN`, then `GITHUB_TOKEN`, and allows unauthenticated access when neither exists. Watcher `Options` contain polling and repository settings; authentication belongs to the Layer. The CLI supplies these Layers, `NodeFileSystem.layer` and a `FetchHttpClient` layer that refuses redirects. HTTP and file failures remain typed `WatchError` values.
|
package/dist/GitHub.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHub.d.ts","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAkBrE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"GitHub.d.ts","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAkBrE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEjB,CAAC"}
|
package/dist/GitHub.js
CHANGED
|
@@ -27,21 +27,7 @@ export const GitHub = defineService({
|
|
|
27
27
|
return Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub response" })));
|
|
28
28
|
}
|
|
29
29
|
function pages(options, path) {
|
|
30
|
-
return
|
|
31
|
-
const bodies = [];
|
|
32
|
-
const visited = new Set();
|
|
33
|
-
let url = `${options.apiUrl}/repos/${options.repository}${path}`;
|
|
34
|
-
while (url !== undefined) {
|
|
35
|
-
if (visited.has(url)) {
|
|
36
|
-
return yield* Effect.fail(new WatchError({ message: "Repeated pagination URL" }));
|
|
37
|
-
}
|
|
38
|
-
visited.add(url);
|
|
39
|
-
const response = yield* http.get(url, options.token);
|
|
40
|
-
bodies.push(response.body);
|
|
41
|
-
url = response.next;
|
|
42
|
-
}
|
|
43
|
-
return bodies;
|
|
44
|
-
});
|
|
30
|
+
return http.list(`${options.apiUrl}/repos/${options.repository}${path}`);
|
|
45
31
|
}
|
|
46
32
|
return {
|
|
47
33
|
confirmQueue: (options, snapshot) => confirmQueue(http, options, snapshot),
|
|
@@ -60,8 +46,8 @@ export const GitHub = defineService({
|
|
|
60
46
|
}));
|
|
61
47
|
}),
|
|
62
48
|
snapshot: (options, number, queued) => Effect.gen(function* () {
|
|
63
|
-
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}
|
|
64
|
-
const pr = yield* decode(Pull, response
|
|
49
|
+
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}`);
|
|
50
|
+
const pr = yield* decode(Pull, response);
|
|
65
51
|
if (pr.number !== number) {
|
|
66
52
|
return yield* Effect.fail(new WatchError({ message: "GitHub returned the wrong PR" }));
|
|
67
53
|
}
|
|
@@ -72,8 +58,8 @@ export const GitHub = defineService({
|
|
|
72
58
|
if (pr.state === "open") {
|
|
73
59
|
const bodies = yield* pages(options, `/commits/${pr.head.sha}/check-runs?per_page=100&filter=latest`);
|
|
74
60
|
const checks = yield* Effect.forEach(bodies, (body) => decode(Checks, body));
|
|
75
|
-
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status
|
|
76
|
-
const status = yield* decode(Status, statusResponse
|
|
61
|
+
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status`);
|
|
62
|
+
const status = yield* decode(Status, statusResponse);
|
|
77
63
|
failed =
|
|
78
64
|
checks.some((page) => page.checkRuns.some((check) => check.status === "completed" && failures.has(check.conclusion ?? "")))
|
|
79
65
|
|| ["failure", "error"].includes(status.state);
|
package/dist/GitHub.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHub.js","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAA+B,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;IAChI,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/D,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC,MAAM;IAC7B,MAAM,EAAE,MAAM,CAAC,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;AAC3F,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAClD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;AACzD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACjG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AACtH,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/G,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;IACnC,EAAE,EAAE,4BAA4B;IAChC,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACjB,SAAS,MAAM,CAA6B,MAAe,EAAE,IAAa;YACzE,OAAO,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC,CAAC,CAAC;QACrI,CAAC;QACD,SAAS,KAAK,CAAC,OAAgB,EAAE,IAAY;YAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"GitHub.js","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"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"}
|
package/dist/GitHubHttp.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { HttpClient } from "effect/http";
|
|
3
|
-
import { type CachedResponse } from "./httpResponse.ts";
|
|
4
2
|
import { WatchError } from "./model.ts";
|
|
5
3
|
export declare const GitHubHttp: import("effect/Context").Service<"@shivaedev/pr-watch/GitHubHttp", Readonly<{
|
|
6
4
|
readonly delay: (interval: number) => Effect.Effect<number, never, never>;
|
|
7
|
-
readonly get: (url: string
|
|
8
|
-
readonly
|
|
5
|
+
readonly get: (url: string) => Effect.Effect<unknown, WatchError, never>;
|
|
6
|
+
readonly list: (url: string) => Effect.Effect<unknown[], WatchError, never>;
|
|
7
|
+
readonly post: (url: string, body: unknown) => Effect.Effect<unknown, WatchError, never>;
|
|
9
8
|
}>> & {
|
|
10
|
-
readonly layer: import("effect/Layer").Layer<"@shivaedev/pr-watch/GitHubHttp", never,
|
|
9
|
+
readonly layer: import("effect/Layer").Layer<"@shivaedev/pr-watch/GitHubHttp", never, "@shivaedev/effect-github/GitHub">;
|
|
11
10
|
};
|
|
12
11
|
//# sourceMappingURL=GitHubHttp.d.ts.map
|
package/dist/GitHubHttp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubHttp.d.ts","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"GitHubHttp.d.ts","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAIjE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,eAAO,MAAM,UAAU;;;;;;;CAuDrB,CAAC"}
|
package/dist/GitHubHttp.js
CHANGED
|
@@ -1,51 +1,48 @@
|
|
|
1
|
-
import { Clock, Effect } from "effect";
|
|
2
|
-
import {
|
|
1
|
+
import { Clock, DateTime, Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { GitHubRateLimited } from "@shivaedev/effect-github/errors.ts";
|
|
3
|
+
import { GitHub } from "@shivaedev/effect-github/GitHub.ts";
|
|
3
4
|
import { defineService } from "@shivaedev/effect-service/defineService.ts";
|
|
4
|
-
import { decodeResponse, fetchResponse } from "./httpResponse.js";
|
|
5
5
|
import { WatchError } from "./model.js";
|
|
6
|
-
import {
|
|
6
|
+
import { pollDelay, quotaRetryAt } from "./quota.js";
|
|
7
7
|
export const GitHubHttp = defineService({
|
|
8
8
|
id: "@shivaedev/pr-watch/GitHubHttp",
|
|
9
9
|
initialize: Effect.gen(function* () {
|
|
10
|
-
return {
|
|
11
|
-
budget: { blockedUntil: 0, limit: 5000, remaining: 5000, reset: 0 },
|
|
12
|
-
cache: new Map(),
|
|
13
|
-
client: yield* HttpClient.HttpClient,
|
|
14
|
-
requests: 0,
|
|
15
|
-
};
|
|
10
|
+
return { github: yield* GitHub, requests: 0, retryAt: 0 };
|
|
16
11
|
}),
|
|
17
12
|
methods: (state) => {
|
|
18
|
-
function request(
|
|
13
|
+
function request(operation) {
|
|
19
14
|
return Effect.gen(function* () {
|
|
20
15
|
const now = yield* Clock.currentTimeMillis;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const cached = body === undefined ? state.cache.get(url) : undefined;
|
|
25
|
-
state.requests += 1;
|
|
26
|
-
const response = yield* fetchResponse(state.client, url, token, cached, body);
|
|
27
|
-
observeBudget(state.budget, response, yield* Clock.currentTimeMillis);
|
|
28
|
-
if (response.status === 304 && cached !== undefined) {
|
|
29
|
-
return cached;
|
|
16
|
+
const observations = yield* state.github.observations();
|
|
17
|
+
if (quotaRetryAt(observations, state.retryAt) > now) {
|
|
18
|
+
return yield* new WatchError({ message: "GitHub quota cooldown" });
|
|
30
19
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
return yield* operation;
|
|
21
|
+
}).pipe(Effect.scoped, Effect.timeout("30 seconds"), Effect.tapError((cause) => Effect.sync(() => {
|
|
22
|
+
if (cause instanceof GitHubRateLimited) {
|
|
23
|
+
state.retryAt = DateTime.toEpochMillis(cause.retryAt);
|
|
34
24
|
}
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
})), Effect.mapError(() => new WatchError({ message: "GitHub request failed" })));
|
|
26
|
+
}
|
|
27
|
+
function counted(operation) {
|
|
28
|
+
return Effect.suspend(() => {
|
|
29
|
+
state.requests += 1;
|
|
30
|
+
return operation;
|
|
31
|
+
});
|
|
37
32
|
}
|
|
38
33
|
return {
|
|
39
34
|
delay: (interval) => Effect.gen(function* () {
|
|
40
35
|
const now = yield* Clock.currentTimeMillis;
|
|
41
|
-
const
|
|
36
|
+
const observations = yield* state.github.observations();
|
|
37
|
+
const delay = pollDelay(observations, interval, Math.max(1, state.requests), now, state.retryAt);
|
|
42
38
|
state.requests = 0;
|
|
43
39
|
return delay;
|
|
44
40
|
}),
|
|
45
|
-
get: (url
|
|
46
|
-
|
|
41
|
+
get: (url) => request(counted(state.github.read(url)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
42
|
+
list: (url) => request(Stream.unwrap(state.github.list(url)).pipe(Stream.mapEffect((response) => counted(response.decode(Schema.Unknown))), Stream.runCollect)),
|
|
43
|
+
post: (url, body) => request(counted(state.github.write("POST", url, body)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
47
44
|
};
|
|
48
45
|
},
|
|
49
|
-
requires: [
|
|
46
|
+
requires: [GitHub],
|
|
50
47
|
});
|
|
51
48
|
//# sourceMappingURL=GitHubHttp.js.map
|
package/dist/GitHubHttp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubHttp.js","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"GitHubHttp.js","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"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"}
|
package/dist/arguments.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { WatchError } from "./model.ts";
|
|
3
3
|
export declare const help = "pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]\n --state PATH Snapshot file (default .pr-watch/OWNER-REPO.json)\n --interval SECONDS Poll interval (default 60; slows as quota falls)\n --once Poll once and exit\n --api-url URL GitHub REST base URL (default https://api.github.com)\n --help Show this help\nAuthentication: GH_TOKEN or GITHUB_TOKEN";
|
|
4
|
-
export declare function argumentsFor(args: readonly string[]
|
|
4
|
+
export declare function argumentsFor(args: readonly string[]): Effect.Effect<{
|
|
5
5
|
help: true;
|
|
6
6
|
once?: never;
|
|
7
7
|
options?: never;
|
|
@@ -15,7 +15,6 @@ export declare function argumentsFor(args: readonly string[], token: string): Ef
|
|
|
15
15
|
numbers: readonly number[];
|
|
16
16
|
repository: string;
|
|
17
17
|
stateFile: string;
|
|
18
|
-
token: string;
|
|
19
18
|
};
|
|
20
19
|
}, WatchError, never>;
|
|
21
20
|
//# sourceMappingURL=arguments.d.ts.map
|
package/dist/arguments.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,eAAO,MAAM,IAAI,4YAMwB,CAAC;AAC1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,eAAO,MAAM,IAAI,4YAMwB,CAAC;AAC1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE;;;;;;;;;;;;;;;sBAyCnD"}
|
package/dist/arguments.js
CHANGED
|
@@ -8,7 +8,7 @@ export const help = `pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]
|
|
|
8
8
|
--api-url URL GitHub REST base URL (default https://api.github.com)
|
|
9
9
|
--help Show this help
|
|
10
10
|
Authentication: GH_TOKEN or GITHUB_TOKEN`;
|
|
11
|
-
export function argumentsFor(args
|
|
11
|
+
export function argumentsFor(args) {
|
|
12
12
|
return Effect.try({
|
|
13
13
|
catch: (cause) => new WatchError({ message: cause instanceof Error ? cause.message : "Invalid arguments" }),
|
|
14
14
|
try: () => {
|
|
@@ -45,7 +45,6 @@ export function argumentsFor(args, token) {
|
|
|
45
45
|
numbers,
|
|
46
46
|
repository,
|
|
47
47
|
stateFile: values.state ?? `.pr-watch/${repository.replace("/", "-")}.json`,
|
|
48
|
-
token,
|
|
49
48
|
},
|
|
50
49
|
};
|
|
51
50
|
},
|
package/dist/arguments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arguments.js","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;yCAMqB,CAAC;AAC1C,MAAM,UAAU,YAAY,CAAC,IAAuB
|
|
1
|
+
{"version":3,"file":"arguments.js","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
4
|
-
import {
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
5
|
import { FetchHttpClient } from "effect/http";
|
|
6
|
+
import { GitHub as GitHubClient } from "@shivaedev/effect-github/GitHub.ts";
|
|
7
|
+
import { GitHubToken } from "@shivaedev/effect-github/GitHubToken.ts";
|
|
6
8
|
import { GitHub } from "./GitHub.js";
|
|
7
9
|
import { GitHubHttp } from "./GitHubHttp.js";
|
|
8
10
|
import { WatchError } from "./model.js";
|
|
9
11
|
import { program } from "./program.js";
|
|
10
12
|
import { StateFile } from "./StateFile.js";
|
|
11
13
|
import { Watcher } from "./Watcher.js";
|
|
12
|
-
const services = Watcher.layer.pipe(Layer.provide(Layer.mergeAll(StateFile.layer, GitHub.layer.pipe(Layer.provide(GitHubHttp.layer)))), Layer.provide(Layer.mergeAll(NodeFileSystem.layer, FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ redirect: "error" }))))));
|
|
14
|
+
const services = Watcher.layer.pipe(Layer.provide(Layer.mergeAll(StateFile.layer, GitHub.layer.pipe(Layer.provide(GitHubHttp.layer.pipe(Layer.provide(GitHubClient.layer), Layer.provide(GitHubToken.fromEnv)))))), Layer.provide(Layer.mergeAll(NodeFileSystem.layer, FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ redirect: "error" }))))));
|
|
13
15
|
function write(stream) {
|
|
14
16
|
return (line) => Effect.tryPromise({
|
|
15
17
|
catch: () => new WatchError({ message: "Output stream failed" }),
|
|
@@ -20,10 +22,9 @@ const abort = new AbortController();
|
|
|
20
22
|
process.once("SIGINT", () => abort.abort());
|
|
21
23
|
process.once("SIGTERM", () => abort.abort());
|
|
22
24
|
try {
|
|
23
|
-
await Effect.runPromise(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}).pipe(Effect.provide(services)), { signal: abort.signal });
|
|
25
|
+
await Effect.runPromise(program(process.argv.slice(2), write(process.stdout), write(process.stderr)).pipe(Effect.provide(services)), {
|
|
26
|
+
signal: abort.signal,
|
|
27
|
+
});
|
|
27
28
|
}
|
|
28
29
|
catch (cause) {
|
|
29
30
|
if (!abort.signal.aborted) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/confirmQueue.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { type Options, WatchError } from "./model.ts";
|
|
3
3
|
export declare const confirmQueue: (http: Readonly<{
|
|
4
4
|
readonly delay: (interval: number) => Effect.Effect<number, never, never>;
|
|
5
|
-
readonly get: (url: string
|
|
6
|
-
readonly
|
|
5
|
+
readonly get: (url: string) => Effect.Effect<unknown, WatchError, never>;
|
|
6
|
+
readonly list: (url: string) => Effect.Effect<unknown[], WatchError, never>;
|
|
7
|
+
readonly post: (url: string, body: unknown) => Effect.Effect<unknown, WatchError, never>;
|
|
7
8
|
}>, options: Options, snapshot: {
|
|
8
9
|
readonly autoMerge: boolean;
|
|
9
10
|
readonly branch: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmQueue.d.ts","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAU,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AA6BrE,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"confirmQueue.d.ts","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAU,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AA6BrE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;qBAoBvB,CAAC"}
|
package/dist/confirmQueue.js
CHANGED
|
@@ -28,8 +28,8 @@ export const confirmQueue = Effect.fn("GitHub.confirmQueue")(function* (http, op
|
|
|
28
28
|
url.pathname = `${url.pathname.replace(/\/$/u, "")}/graphql`;
|
|
29
29
|
}
|
|
30
30
|
const [owner, name] = options.repository.split("/");
|
|
31
|
-
const response = yield* http.post(url.href,
|
|
32
|
-
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response
|
|
31
|
+
const response = yield* http.post(url.href, { query, variables: { name, number: snapshot.number, owner } });
|
|
32
|
+
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub queue confirmation" })));
|
|
33
33
|
const pr = body.data?.repository?.pullRequest;
|
|
34
34
|
if ((body.errors?.length ?? 0) > 0 || pr === undefined || pr === null || pr.state !== "OPEN") {
|
|
35
35
|
return yield* Effect.fail(new WatchError({ message: `Queue confirmation unavailable for PR #${snapshot.number}` }));
|
package/dist/confirmQueue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmQueue.js","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAA+B,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,CAClB,MAAM,CAAC,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,CACxB,MAAM,CAAC,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC,MAAM,CACzB,MAAM,CAAC,MAAM,CAAC;gBACb,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC5E,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBACpE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;aACpD,CAAC,CACF;SACD,CAAC,CACF;KACD,CAAC,CACF;IACD,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;CACxD,CAAC,CAAC;AACH,MAAM,KAAK,GAAG;;;;;;;;EAQZ,CAAC;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,EACrE,IAA8C,EAC9C,OAAgB,EAChB,QAAkB;IAElB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IACtE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC;IAC9D,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"confirmQueue.js","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"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"}
|
package/dist/model.d.ts
CHANGED
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAChC,qBAAa,UAAW,SAAQ,eAE9B;CAAG;AACL,eAAO,MAAM,QAAQ;;;;;;;;;;EAUnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;EAKhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AACtC,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAChC,qBAAa,UAAW,SAAQ,eAE9B;CAAG;AACL,eAAO,MAAM,QAAQ;;;;;;;;;;EAUnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;EAKhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AACtC,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B"}
|
package/dist/program.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { WatchError } from "./model.ts";
|
|
3
|
-
export declare const program: (args: readonly string[],
|
|
3
|
+
export declare const program: (args: readonly string[], emit: (line: string) => Effect.Effect<void, WatchError>, diagnostic: (message: string) => Effect.Effect<void, WatchError>) => Effect.Effect<undefined, WatchError, "@shivaedev/pr-watch/Watcher">;
|
|
4
4
|
//# sourceMappingURL=program.d.ts.map
|
package/dist/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,OAAO,yCAEN,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wBACjC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wEAyB/D,CAAC"}
|
package/dist/program.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { argumentsFor, help } from "./arguments.js";
|
|
3
3
|
import { Watcher } from "./Watcher.js";
|
|
4
|
-
export const program = Effect.fn("PrWatch.program")(function* (args,
|
|
5
|
-
const parsed = yield* argumentsFor(args
|
|
4
|
+
export const program = Effect.fn("PrWatch.program")(function* (args, emit, diagnostic) {
|
|
5
|
+
const parsed = yield* argumentsFor(args);
|
|
6
6
|
if (parsed.help) {
|
|
7
7
|
yield* emit(help);
|
|
8
8
|
return;
|
package/dist/program.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAC5D,IAAuB,EACvB,
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAC5D,IAAuB,EACvB,IAAuD,EACvD,UAAgE;IAEhE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACR,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;IAC/B,IAAI,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC;aAAM,CAAC;YACP,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,CAAC;YACD,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC1C,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO;QACR,CAAC;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,CAAC;AACF,CAAC,CAAC,CAAC"}
|
package/dist/quota.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
limit: number;
|
|
5
|
-
remaining: number;
|
|
6
|
-
reset: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function observeBudget(budget: Budget, response: Pick<HttpClientResponse.HttpClientResponse, "headers" | "status">, now: number): void;
|
|
9
|
-
export declare function pollDelay(budget: Budget, interval: number, requests: number, now: number): number;
|
|
1
|
+
import type { RateLimitObservation } from "@shivaedev/effect-github/rateLimits.ts";
|
|
2
|
+
export declare function quotaRetryAt(observations: readonly RateLimitObservation[], retryAt?: number): number;
|
|
3
|
+
export declare function pollDelay(observations: readonly RateLimitObservation[], interval: number, requests: number, now: number, retryAt?: number): number;
|
|
10
4
|
//# sourceMappingURL=quota.d.ts.map
|
package/dist/quota.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAEnF,wBAAgB,YAAY,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,OAAO,SAAI,GAAG,MAAM,CAE/F;AAED,wBAAgB,SAAS,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,MAAM,CAW7I"}
|
package/dist/quota.js
CHANGED
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return
|
|
1
|
+
import { DateTime } from "effect";
|
|
2
|
+
export function quotaRetryAt(observations, retryAt = 0) {
|
|
3
|
+
return Math.max(retryAt, ...observations.filter((budget) => budget.remaining === 0).map((budget) => DateTime.toEpochMillis(budget.reset)));
|
|
4
4
|
}
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, Number.isFinite(until) ? until : now + 60_000);
|
|
13
|
-
}
|
|
14
|
-
if (budget.remaining === 0) {
|
|
15
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, budget.reset);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export function pollDelay(budget, interval, requests, now) {
|
|
19
|
-
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
20
|
-
const multiplier = slowdown(ratio);
|
|
21
|
-
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, budget.reset - now) * requests) / budget.remaining) : 0;
|
|
22
|
-
return Math.max(interval * multiplier, paced, budget.blockedUntil - now);
|
|
23
|
-
}
|
|
24
|
-
function retryUntil(retry, now) {
|
|
25
|
-
if (retry === null) {
|
|
26
|
-
return now + 60_000;
|
|
27
|
-
}
|
|
28
|
-
if (Number.isFinite(Number(retry))) {
|
|
29
|
-
return now + Number(retry) * 1000;
|
|
30
|
-
}
|
|
31
|
-
return Date.parse(retry);
|
|
5
|
+
export function pollDelay(observations, interval, requests, now, retryAt = 0) {
|
|
6
|
+
return Math.max(interval, quotaRetryAt(observations, retryAt) - now, ...observations.map((budget) => {
|
|
7
|
+
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
8
|
+
const reset = DateTime.toEpochMillis(budget.reset);
|
|
9
|
+
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, reset - now) * requests) / budget.remaining) : 0;
|
|
10
|
+
return Math.max(interval * slowdown(ratio), paced);
|
|
11
|
+
}));
|
|
32
12
|
}
|
|
33
13
|
function slowdown(ratio) {
|
|
34
14
|
if (ratio < 0.1) {
|
package/dist/quota.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,MAAM,UAAU,YAAY,CAAC,YAA6C,EAAE,OAAO,GAAG,CAAC;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC5I,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,YAA6C,EAAE,QAAgB,EAAE,QAAgB,EAAE,GAAW,EAAE,OAAO,GAAG,CAAC;IACpI,OAAO,IAAI,CAAC,GAAG,CACd,QAAQ,EACR,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,GAAG,EACzC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5H,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AACD,SAAS,QAAQ,CAAC,KAAa;IAC9B,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/pr-watch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "One REST watcher for pull request transitions across a repository",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/pr-watch#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -39,30 +39,29 @@
|
|
|
39
39
|
"!src/**/*.test.ts",
|
|
40
40
|
"!src/**/*.spec.ts",
|
|
41
41
|
"!src/**/test-support",
|
|
42
|
-
"docs",
|
|
43
42
|
"CHANGELOG.md",
|
|
44
43
|
"README.md"
|
|
45
44
|
],
|
|
46
45
|
"dependencies": {
|
|
47
|
-
"@shivaedev/effect-
|
|
46
|
+
"@shivaedev/effect-github": "0.2.0",
|
|
47
|
+
"@shivaedev/effect-service": "0.4.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@effect/platform-node": "4.0.
|
|
51
|
-
"@effect/platform-node-shared": "4.0.
|
|
52
|
-
"@effect/vitest": "4.0.
|
|
53
|
-
"@shivaedev/effect-test": "0.
|
|
54
|
-
"@shivaedev/quality": "0.
|
|
55
|
-
"@shivaedev/test-story": "0.
|
|
50
|
+
"@effect/platform-node": "4.0.5",
|
|
51
|
+
"@effect/platform-node-shared": "4.0.5",
|
|
52
|
+
"@effect/vitest": "4.0.5",
|
|
53
|
+
"@shivaedev/effect-test": "0.8.1",
|
|
54
|
+
"@shivaedev/quality": "0.38.0",
|
|
55
|
+
"@shivaedev/test-story": "0.19.0",
|
|
56
56
|
"@types/node": "24.19.2",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"vitest": "5.0.3"
|
|
57
|
+
"effect": "4.0.5",
|
|
58
|
+
"typescript": "7.0.2",
|
|
59
|
+
"vitest": "5.0.5"
|
|
61
60
|
},
|
|
62
61
|
"peerDependencies": {
|
|
63
|
-
"@effect/platform-node": "4.0.
|
|
64
|
-
"@effect/platform-node-shared": "4.0.
|
|
65
|
-
"effect": "4.0.
|
|
62
|
+
"@effect/platform-node": "4.0.5",
|
|
63
|
+
"@effect/platform-node-shared": "4.0.5",
|
|
64
|
+
"effect": "4.0.5"
|
|
66
65
|
},
|
|
67
66
|
"engines": {
|
|
68
67
|
"node": ">=24"
|
package/src/GitHub.ts
CHANGED
|
@@ -28,21 +28,7 @@ export const GitHub = defineService({
|
|
|
28
28
|
return Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub response" })));
|
|
29
29
|
}
|
|
30
30
|
function pages(options: Options, path: string) {
|
|
31
|
-
return
|
|
32
|
-
const bodies: unknown[] = [];
|
|
33
|
-
const visited = new Set<string>();
|
|
34
|
-
let url: string | undefined = `${options.apiUrl}/repos/${options.repository}${path}`;
|
|
35
|
-
while (url !== undefined) {
|
|
36
|
-
if (visited.has(url)) {
|
|
37
|
-
return yield* Effect.fail(new WatchError({ message: "Repeated pagination URL" }));
|
|
38
|
-
}
|
|
39
|
-
visited.add(url);
|
|
40
|
-
const response: Effect.Success<ReturnType<typeof http.get>> = yield* http.get(url, options.token);
|
|
41
|
-
bodies.push(response.body);
|
|
42
|
-
url = response.next;
|
|
43
|
-
}
|
|
44
|
-
return bodies;
|
|
45
|
-
});
|
|
31
|
+
return http.list(`${options.apiUrl}/repos/${options.repository}${path}`);
|
|
46
32
|
}
|
|
47
33
|
return {
|
|
48
34
|
confirmQueue: (options: Options, snapshot: Snapshot) => confirmQueue(http, options, snapshot),
|
|
@@ -66,8 +52,8 @@ export const GitHub = defineService({
|
|
|
66
52
|
}),
|
|
67
53
|
snapshot: (options: Options, number: number, queued: ReadonlySet<number>) =>
|
|
68
54
|
Effect.gen(function* () {
|
|
69
|
-
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}
|
|
70
|
-
const pr = yield* decode(Pull, response
|
|
55
|
+
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}`);
|
|
56
|
+
const pr = yield* decode(Pull, response);
|
|
71
57
|
if (pr.number !== number) {
|
|
72
58
|
return yield* Effect.fail(new WatchError({ message: "GitHub returned the wrong PR" }));
|
|
73
59
|
}
|
|
@@ -78,8 +64,8 @@ export const GitHub = defineService({
|
|
|
78
64
|
if (pr.state === "open") {
|
|
79
65
|
const bodies = yield* pages(options, `/commits/${pr.head.sha}/check-runs?per_page=100&filter=latest`);
|
|
80
66
|
const checks = yield* Effect.forEach(bodies, (body) => decode(Checks, body));
|
|
81
|
-
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status
|
|
82
|
-
const status = yield* decode(Status, statusResponse
|
|
67
|
+
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status`);
|
|
68
|
+
const status = yield* decode(Status, statusResponse);
|
|
83
69
|
failed =
|
|
84
70
|
checks.some((page) => page.checkRuns.some((check) => check.status === "completed" && failures.has(check.conclusion ?? "")))
|
|
85
71
|
|| ["failure", "error"].includes(status.state);
|
package/src/GitHubHttp.ts
CHANGED
|
@@ -1,55 +1,63 @@
|
|
|
1
|
-
import { Clock, Effect } from "effect";
|
|
2
|
-
import {
|
|
1
|
+
import { Clock, DateTime, Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { GitHubRateLimited } from "@shivaedev/effect-github/errors.ts";
|
|
3
|
+
import { GitHub } from "@shivaedev/effect-github/GitHub.ts";
|
|
3
4
|
import { defineService } from "@shivaedev/effect-service/defineService.ts";
|
|
4
|
-
import { type CachedResponse, decodeResponse, fetchResponse } from "./httpResponse.ts";
|
|
5
5
|
import { WatchError } from "./model.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { pollDelay, quotaRetryAt } from "./quota.ts";
|
|
7
|
+
|
|
7
8
|
export const GitHubHttp = defineService({
|
|
8
9
|
id: "@shivaedev/pr-watch/GitHubHttp",
|
|
9
10
|
initialize: Effect.gen(function* () {
|
|
10
|
-
return {
|
|
11
|
-
budget: { blockedUntil: 0, limit: 5000, remaining: 5000, reset: 0 } satisfies Budget,
|
|
12
|
-
cache: new Map<string, CachedResponse>(),
|
|
13
|
-
client: yield* HttpClient.HttpClient,
|
|
14
|
-
requests: 0,
|
|
15
|
-
};
|
|
11
|
+
return { github: yield* GitHub, requests: 0, retryAt: 0 };
|
|
16
12
|
}),
|
|
17
13
|
methods: (state) => {
|
|
18
|
-
function request
|
|
14
|
+
function request<T, E, R>(operation: Effect.Effect<T, E, R>) {
|
|
19
15
|
return Effect.gen(function* () {
|
|
20
16
|
const now = yield* Clock.currentTimeMillis;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const cached = body === undefined ? state.cache.get(url) : undefined;
|
|
25
|
-
state.requests += 1;
|
|
26
|
-
const response = yield* fetchResponse(state.client, url, token, cached, body);
|
|
27
|
-
observeBudget(state.budget, response, yield* Clock.currentTimeMillis);
|
|
28
|
-
if (response.status === 304 && cached !== undefined) {
|
|
29
|
-
return cached;
|
|
17
|
+
const observations = yield* state.github.observations();
|
|
18
|
+
if (quotaRetryAt(observations, state.retryAt) > now) {
|
|
19
|
+
return yield* new WatchError({ message: "GitHub quota cooldown" });
|
|
30
20
|
}
|
|
31
|
-
|
|
32
|
-
if (body === undefined) {
|
|
33
|
-
state.cache.set(url, result);
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
21
|
+
return yield* operation;
|
|
36
22
|
}).pipe(
|
|
37
23
|
Effect.scoped,
|
|
38
24
|
Effect.timeout("30 seconds"),
|
|
39
|
-
Effect.
|
|
25
|
+
Effect.tapError((cause) =>
|
|
26
|
+
Effect.sync(() => {
|
|
27
|
+
if (cause instanceof GitHubRateLimited) {
|
|
28
|
+
state.retryAt = DateTime.toEpochMillis(cause.retryAt);
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
Effect.mapError(() => new WatchError({ message: "GitHub request failed" })),
|
|
40
33
|
);
|
|
41
34
|
}
|
|
35
|
+
function counted<T, E, R>(operation: Effect.Effect<T, E, R>) {
|
|
36
|
+
return Effect.suspend(() => {
|
|
37
|
+
state.requests += 1;
|
|
38
|
+
return operation;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
42
41
|
return {
|
|
43
42
|
delay: (interval: number) =>
|
|
44
43
|
Effect.gen(function* () {
|
|
45
44
|
const now = yield* Clock.currentTimeMillis;
|
|
46
|
-
const
|
|
45
|
+
const observations = yield* state.github.observations();
|
|
46
|
+
const delay = pollDelay(observations, interval, Math.max(1, state.requests), now, state.retryAt);
|
|
47
47
|
state.requests = 0;
|
|
48
48
|
return delay;
|
|
49
49
|
}),
|
|
50
|
-
get: (url: string
|
|
51
|
-
|
|
50
|
+
get: (url: string) => request(counted(state.github.read(url)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
51
|
+
list: (url: string) =>
|
|
52
|
+
request(
|
|
53
|
+
Stream.unwrap(state.github.list(url)).pipe(
|
|
54
|
+
Stream.mapEffect((response) => counted(response.decode(Schema.Unknown))),
|
|
55
|
+
Stream.runCollect,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
post: (url: string, body: unknown) =>
|
|
59
|
+
request(counted(state.github.write("POST", url, body)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
52
60
|
};
|
|
53
61
|
},
|
|
54
|
-
requires: [
|
|
62
|
+
requires: [GitHub],
|
|
55
63
|
});
|
package/src/arguments.ts
CHANGED
|
@@ -8,7 +8,7 @@ export const help = `pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]
|
|
|
8
8
|
--api-url URL GitHub REST base URL (default https://api.github.com)
|
|
9
9
|
--help Show this help
|
|
10
10
|
Authentication: GH_TOKEN or GITHUB_TOKEN`;
|
|
11
|
-
export function argumentsFor(args: readonly string[]
|
|
11
|
+
export function argumentsFor(args: readonly string[]) {
|
|
12
12
|
return Effect.try({
|
|
13
13
|
catch: (cause) => new WatchError({ message: cause instanceof Error ? cause.message : "Invalid arguments" }),
|
|
14
14
|
try: () => {
|
|
@@ -45,7 +45,6 @@ export function argumentsFor(args: readonly string[], token: string) {
|
|
|
45
45
|
numbers,
|
|
46
46
|
repository,
|
|
47
47
|
stateFile: values.state ?? `.pr-watch/${repository.replace("/", "-")}.json`,
|
|
48
|
-
token,
|
|
49
48
|
} satisfies Options,
|
|
50
49
|
};
|
|
51
50
|
},
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
4
|
-
import {
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
5
|
import { FetchHttpClient } from "effect/http";
|
|
6
|
+
import { GitHub as GitHubClient } from "@shivaedev/effect-github/GitHub.ts";
|
|
7
|
+
import { GitHubToken } from "@shivaedev/effect-github/GitHubToken.ts";
|
|
6
8
|
import { GitHub } from "./GitHub.ts";
|
|
7
9
|
import { GitHubHttp } from "./GitHubHttp.ts";
|
|
8
10
|
import { WatchError } from "./model.ts";
|
|
@@ -11,7 +13,12 @@ import { StateFile } from "./StateFile.ts";
|
|
|
11
13
|
import { Watcher } from "./Watcher.ts";
|
|
12
14
|
|
|
13
15
|
const services = Watcher.layer.pipe(
|
|
14
|
-
Layer.provide(
|
|
16
|
+
Layer.provide(
|
|
17
|
+
Layer.mergeAll(
|
|
18
|
+
StateFile.layer,
|
|
19
|
+
GitHub.layer.pipe(Layer.provide(GitHubHttp.layer.pipe(Layer.provide(GitHubClient.layer), Layer.provide(GitHubToken.fromEnv)))),
|
|
20
|
+
),
|
|
21
|
+
),
|
|
15
22
|
Layer.provide(
|
|
16
23
|
Layer.mergeAll(
|
|
17
24
|
NodeFileSystem.layer,
|
|
@@ -30,16 +37,9 @@ const abort = new AbortController();
|
|
|
30
37
|
process.once("SIGINT", () => abort.abort());
|
|
31
38
|
process.once("SIGTERM", () => abort.abort());
|
|
32
39
|
try {
|
|
33
|
-
await Effect.runPromise(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Config.orElse(() => Config.String("GITHUB_TOKEN")),
|
|
37
|
-
Config.withDefault(""),
|
|
38
|
-
);
|
|
39
|
-
yield* program(process.argv.slice(2), token, write(process.stdout), write(process.stderr));
|
|
40
|
-
}).pipe(Effect.provide(services)),
|
|
41
|
-
{ signal: abort.signal },
|
|
42
|
-
);
|
|
40
|
+
await Effect.runPromise(program(process.argv.slice(2), write(process.stdout), write(process.stderr)).pipe(Effect.provide(services)), {
|
|
41
|
+
signal: abort.signal,
|
|
42
|
+
});
|
|
43
43
|
} catch (cause) {
|
|
44
44
|
if (!abort.signal.aborted) {
|
|
45
45
|
process.stderr.write(`${cause instanceof Error ? cause.message : "Watcher failed"}\n`);
|
package/src/confirmQueue.ts
CHANGED
|
@@ -40,8 +40,8 @@ export const confirmQueue = Effect.fn("GitHub.confirmQueue")(function* (
|
|
|
40
40
|
url.pathname = `${url.pathname.replace(/\/$/u, "")}/graphql`;
|
|
41
41
|
}
|
|
42
42
|
const [owner, name] = options.repository.split("/");
|
|
43
|
-
const response = yield* http.post(url.href,
|
|
44
|
-
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response
|
|
43
|
+
const response = yield* http.post(url.href, { query, variables: { name, number: snapshot.number, owner } });
|
|
44
|
+
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response).pipe(
|
|
45
45
|
Effect.mapError(() => new WatchError({ message: "Invalid GitHub queue confirmation" })),
|
|
46
46
|
);
|
|
47
47
|
const pr = body.data?.repository?.pullRequest;
|
package/src/model.ts
CHANGED
package/src/program.ts
CHANGED
|
@@ -4,11 +4,10 @@ import type { WatchError } from "./model.ts";
|
|
|
4
4
|
import { Watcher } from "./Watcher.ts";
|
|
5
5
|
export const program = Effect.fn("PrWatch.program")(function* (
|
|
6
6
|
args: readonly string[],
|
|
7
|
-
token: string,
|
|
8
7
|
emit: (line: string) => Effect.Effect<void, WatchError>,
|
|
9
8
|
diagnostic: (message: string) => Effect.Effect<void, WatchError>,
|
|
10
9
|
) {
|
|
11
|
-
const parsed = yield* argumentsFor(args
|
|
10
|
+
const parsed = yield* argumentsFor(args);
|
|
12
11
|
if (parsed.help) {
|
|
13
12
|
yield* emit(help);
|
|
14
13
|
return;
|
package/src/quota.ts
CHANGED
|
@@ -1,42 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DateTime } from "effect";
|
|
2
|
+
import type { RateLimitObservation } from "@shivaedev/effect-github/rateLimits.ts";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
limit: number;
|
|
6
|
-
remaining: number;
|
|
7
|
-
reset: number;
|
|
4
|
+
export function quotaRetryAt(observations: readonly RateLimitObservation[], retryAt = 0): number {
|
|
5
|
+
return Math.max(retryAt, ...observations.filter((budget) => budget.remaining === 0).map((budget) => DateTime.toEpochMillis(budget.reset)));
|
|
8
6
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
if (budget.remaining === 0) {
|
|
23
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, budget.reset);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export function pollDelay(budget: Budget, interval: number, requests: number, now: number): number {
|
|
27
|
-
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
28
|
-
const multiplier = slowdown(ratio);
|
|
29
|
-
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, budget.reset - now) * requests) / budget.remaining) : 0;
|
|
30
|
-
return Math.max(interval * multiplier, paced, budget.blockedUntil - now);
|
|
31
|
-
}
|
|
32
|
-
function retryUntil(retry: string | null, now: number): number {
|
|
33
|
-
if (retry === null) {
|
|
34
|
-
return now + 60_000;
|
|
35
|
-
}
|
|
36
|
-
if (Number.isFinite(Number(retry))) {
|
|
37
|
-
return now + Number(retry) * 1000;
|
|
38
|
-
}
|
|
39
|
-
return Date.parse(retry);
|
|
7
|
+
|
|
8
|
+
export function pollDelay(observations: readonly RateLimitObservation[], interval: number, requests: number, now: number, retryAt = 0): number {
|
|
9
|
+
return Math.max(
|
|
10
|
+
interval,
|
|
11
|
+
quotaRetryAt(observations, retryAt) - now,
|
|
12
|
+
...observations.map((budget) => {
|
|
13
|
+
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
14
|
+
const reset = DateTime.toEpochMillis(budget.reset);
|
|
15
|
+
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, reset - now) * requests) / budget.remaining) : 0;
|
|
16
|
+
return Math.max(interval * slowdown(ratio), paced);
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
40
19
|
}
|
|
41
20
|
function slowdown(ratio: number): number {
|
|
42
21
|
if (ratio < 0.1) {
|
package/dist/httpResponse.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { type HttpClient, type HttpClientResponse } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.ts";
|
|
4
|
-
export interface CachedResponse {
|
|
5
|
-
readonly body: unknown;
|
|
6
|
-
readonly etag: string | null;
|
|
7
|
-
readonly next: string | undefined;
|
|
8
|
-
}
|
|
9
|
-
export declare function fetchResponse(client: HttpClient.HttpClient, url: string, token: string, cached: CachedResponse | undefined, body?: unknown): Effect.Effect<HttpClientResponse.HttpClientResponse, import("effect/http/HttpBody").HttpBodyError | import("effect/http/HttpClientError").HttpClientError, never>;
|
|
10
|
-
export declare function decodeResponse(url: string, response: HttpClientResponse.HttpClientResponse): Effect.Effect<{
|
|
11
|
-
body: import("effect/Schema").Json;
|
|
12
|
-
etag: string | null;
|
|
13
|
-
next: string | undefined;
|
|
14
|
-
}, WatchError, never>;
|
|
15
|
-
//# sourceMappingURL=httpResponse.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"httpResponse.d.ts","sourceRoot":"","sources":["../src/httpResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,KAAK,UAAU,EAAqB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AACD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,qKAW1I;AACD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB;;;;sBAgB1F"}
|
package/dist/httpResponse.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { HttpClientRequest } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.js";
|
|
4
|
-
export function fetchResponse(client, url, token, cached, body) {
|
|
5
|
-
const headers = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
|
|
6
|
-
if (token) {
|
|
7
|
-
headers.Authorization = `Bearer ${token}`;
|
|
8
|
-
}
|
|
9
|
-
if (cached?.etag) {
|
|
10
|
-
headers["If-None-Match"] = cached.etag;
|
|
11
|
-
}
|
|
12
|
-
return body === undefined
|
|
13
|
-
? client.get(url, { headers })
|
|
14
|
-
: HttpClientRequest.post(url, { headers }).pipe(HttpClientRequest.bodyJson(body), Effect.flatMap(client.execute));
|
|
15
|
-
}
|
|
16
|
-
export function decodeResponse(url, response) {
|
|
17
|
-
return Effect.gen(function* () {
|
|
18
|
-
if (response.status < 200 || response.status >= 300) {
|
|
19
|
-
return yield* Effect.fail(new WatchError({ message: `GitHub HTTP ${response.status} for ${new URL(url).pathname}` }));
|
|
20
|
-
}
|
|
21
|
-
const body = yield* response.json;
|
|
22
|
-
const next = response.headers.link?.match(/<(?<url>[^>]+)>; rel="next"/u)?.groups?.url;
|
|
23
|
-
yield* Effect.try(() => {
|
|
24
|
-
if (next !== undefined && new URL(next).origin !== new URL(url).origin) {
|
|
25
|
-
throw new Error("Cross-origin pagination refused");
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
return { body, etag: response.headers.etag ?? null, next };
|
|
29
|
-
}).pipe(Effect.mapError((cause) => (cause instanceof WatchError ? cause : new WatchError({ message: "Invalid GitHub response or pagination URL" }))));
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=httpResponse.js.map
|
package/dist/httpResponse.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"httpResponse.js","sourceRoot":"","sources":["../src/httpResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAmB,iBAAiB,EAA2B,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxC,MAAM,UAAU,aAAa,CAAC,MAA6B,EAAE,GAAW,EAAE,KAAa,EAAE,MAAkC,EAAE,IAAc;IAC1I,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,YAAY,EAAE,CAAC;IACxH,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;IAC3C,CAAC;IACD,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;QAClB,OAAO,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,KAAK,SAAS;QACxB,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;QAC9B,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AACpH,CAAC;AACD,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,QAA+C;IAC1F,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACrD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,eAAe,QAAQ,CAAC,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QACvH,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,8BAA8B,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC;QACvF,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;YACtB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;gBACxE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACpD,CAAC;QACF,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI,EAA2B,CAAC;IACrF,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC,CAAC,CAC5I,CAAC;AACH,CAAC"}
|
package/docs/north-star.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# One process for pull request observations
|
|
2
|
-
|
|
3
|
-
An orchestrator needs to see merges, conflicts, failed checks and merge queue drops across a repository. One REST watcher shares discovery and queue requests, uses conditional responses, and keeps successful snapshots across restarts.
|
|
4
|
-
|
|
5
|
-
Evidence wins over speed. A failed or rate-limited request cannot stand in for a negative observation. Check runs and commit statuses belong to the observed head; queue discovery comes from the queue refs, and a suspected drop is confirmed against the PR’s own queue entry and auto-merge state. Native Effect services own composition and interruption.
|
|
6
|
-
|
|
7
|
-
Durability means comparison between successful snapshots. It does not recover transitions that reversed between observations. Stdout is at-least-once across a crash. Event history, acknowledged delivery, PR changes and automated reactions belong outside this package.
|
package/docs/roadmap.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
## Built
|
|
4
|
-
|
|
5
|
-
- A REST executable for explicit PRs or repository-wide fleet discovery, using effect-service services.
|
|
6
|
-
- ETag response reuse, quota pacing, queue-ref observations and eight transition events, distinguishing queue entry from disabled auto-merge.
|
|
7
|
-
- Startup events for already-stuck auto-merges, terminal PR pruning and GraphQL confirmation of suspected queue drops.
|
|
8
|
-
- Native Effect filesystem and HTTP services with typed failures.
|
|
9
|
-
- Atomic snapshot persistence and comparison across clean restarts.
|
|
10
|
-
- Synthetic HTTP tests and packed executable consumer scenarios.
|
|
11
|
-
|
|
12
|
-
## Next
|
|
13
|
-
|
|
14
|
-
Work Fleet checks and delivery integrations can consume the event lines. Application adoption needs its own evidence.
|
|
15
|
-
|
|
16
|
-
## Maintainer decisions
|
|
17
|
-
|
|
18
|
-
No additional delivery protocol or event history is part of this package.
|
package/src/httpResponse.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { type HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.ts";
|
|
4
|
-
export interface CachedResponse {
|
|
5
|
-
readonly body: unknown;
|
|
6
|
-
readonly etag: string | null;
|
|
7
|
-
readonly next: string | undefined;
|
|
8
|
-
}
|
|
9
|
-
export function fetchResponse(client: HttpClient.HttpClient, url: string, token: string, cached: CachedResponse | undefined, body?: unknown) {
|
|
10
|
-
const headers: Record<string, string> = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
|
|
11
|
-
if (token) {
|
|
12
|
-
headers.Authorization = `Bearer ${token}`;
|
|
13
|
-
}
|
|
14
|
-
if (cached?.etag) {
|
|
15
|
-
headers["If-None-Match"] = cached.etag;
|
|
16
|
-
}
|
|
17
|
-
return body === undefined
|
|
18
|
-
? client.get(url, { headers })
|
|
19
|
-
: HttpClientRequest.post(url, { headers }).pipe(HttpClientRequest.bodyJson(body), Effect.flatMap(client.execute));
|
|
20
|
-
}
|
|
21
|
-
export function decodeResponse(url: string, response: HttpClientResponse.HttpClientResponse) {
|
|
22
|
-
return Effect.gen(function* () {
|
|
23
|
-
if (response.status < 200 || response.status >= 300) {
|
|
24
|
-
return yield* Effect.fail(new WatchError({ message: `GitHub HTTP ${response.status} for ${new URL(url).pathname}` }));
|
|
25
|
-
}
|
|
26
|
-
const body = yield* response.json;
|
|
27
|
-
const next = response.headers.link?.match(/<(?<url>[^>]+)>; rel="next"/u)?.groups?.url;
|
|
28
|
-
yield* Effect.try(() => {
|
|
29
|
-
if (next !== undefined && new URL(next).origin !== new URL(url).origin) {
|
|
30
|
-
throw new Error("Cross-origin pagination refused");
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
return { body, etag: response.headers.etag ?? null, next } satisfies CachedResponse;
|
|
34
|
-
}).pipe(
|
|
35
|
-
Effect.mapError((cause) => (cause instanceof WatchError ? cause : new WatchError({ message: "Invalid GitHub response or pagination URL" }))),
|
|
36
|
-
);
|
|
37
|
-
}
|