@shivaedev/pr-watch 0.0.0 → 0.0.1

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -1
  4. package/dist/GitHub.d.ts +42 -0
  5. package/dist/GitHub.d.ts.map +1 -0
  6. package/dist/GitHub.js +97 -0
  7. package/dist/GitHub.js.map +1 -0
  8. package/dist/GitHubHttp.d.ts +12 -0
  9. package/dist/GitHubHttp.d.ts.map +1 -0
  10. package/dist/GitHubHttp.js +51 -0
  11. package/dist/GitHubHttp.js.map +1 -0
  12. package/dist/StateFile.d.ts +43 -0
  13. package/dist/StateFile.d.ts.map +1 -0
  14. package/dist/StateFile.js +33 -0
  15. package/dist/StateFile.js.map +1 -0
  16. package/dist/Watcher.d.ts +61 -0
  17. package/dist/Watcher.d.ts.map +1 -0
  18. package/dist/Watcher.js +82 -0
  19. package/dist/Watcher.js.map +1 -0
  20. package/dist/arguments.d.ts +21 -0
  21. package/dist/arguments.d.ts.map +1 -0
  22. package/dist/arguments.js +84 -0
  23. package/dist/arguments.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +34 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/confirmQueue.d.ts +28 -0
  29. package/dist/confirmQueue.d.ts.map +1 -0
  30. package/dist/confirmQueue.js +39 -0
  31. package/dist/confirmQueue.js.map +1 -0
  32. package/dist/events.d.ts +3 -0
  33. package/dist/events.d.ts.map +1 -0
  34. package/dist/events.js +32 -0
  35. package/dist/events.js.map +1 -0
  36. package/dist/httpResponse.d.ts +15 -0
  37. package/dist/httpResponse.d.ts.map +1 -0
  38. package/dist/httpResponse.js +31 -0
  39. package/dist/httpResponse.js.map +1 -0
  40. package/dist/model.d.ts +46 -0
  41. package/dist/model.d.ts.map +1 -0
  42. package/dist/model.js +23 -0
  43. package/dist/model.js.map +1 -0
  44. package/dist/program.d.ts +4 -0
  45. package/dist/program.d.ts.map +1 -0
  46. package/dist/program.js +30 -0
  47. package/dist/program.js.map +1 -0
  48. package/dist/quota.d.ts +10 -0
  49. package/dist/quota.d.ts.map +1 -0
  50. package/dist/quota.js +45 -0
  51. package/dist/quota.js.map +1 -0
  52. package/docs/north-star.md +7 -0
  53. package/docs/roadmap.md +18 -0
  54. package/package.json +70 -4
  55. package/src/GitHub.ts +102 -0
  56. package/src/GitHubHttp.ts +55 -0
  57. package/src/StateFile.ts +38 -0
  58. package/src/Watcher.ts +92 -0
  59. package/src/arguments.ts +83 -0
  60. package/src/cli.ts +48 -0
  61. package/src/confirmQueue.ts +52 -0
  62. package/src/events.ts +33 -0
  63. package/src/httpResponse.ts +37 -0
  64. package/src/model.ts +32 -0
  65. package/src/program.ts +34 -0
  66. package/src/quota.ts +52 -0
@@ -0,0 +1,84 @@
1
+ import { parseArgs } from "node:util";
2
+ import { Effect } from "effect";
3
+ import { WatchError } from "./model.js";
4
+ export const help = `pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]
5
+ --state PATH Snapshot file (default .pr-watch/OWNER-REPO.json)
6
+ --interval SECONDS Poll interval (default 60; slows as quota falls)
7
+ --once Poll once and exit
8
+ --api-url URL GitHub REST base URL (default https://api.github.com)
9
+ --help Show this help
10
+ Authentication: GH_TOKEN or GITHUB_TOKEN`;
11
+ export function argumentsFor(args, token) {
12
+ return Effect.try({
13
+ catch: (cause) => new WatchError({ message: cause instanceof Error ? cause.message : "Invalid arguments" }),
14
+ try: () => {
15
+ const { values, positionals } = parseArgs({
16
+ allowPositionals: true,
17
+ args: [...args],
18
+ options: {
19
+ "api-url": { type: "string" },
20
+ fleet: { type: "boolean" },
21
+ help: { type: "boolean" },
22
+ interval: { type: "string" },
23
+ once: { type: "boolean" },
24
+ repo: { type: "string" },
25
+ state: { type: "string" },
26
+ },
27
+ });
28
+ if (values.help) {
29
+ return { help: true };
30
+ }
31
+ const repository = values.repo ?? "";
32
+ if (repository.match(/^[\w.-]+\/[\w.-]+$/u) === null) {
33
+ throw new Error("--repo OWNER/REPO is required");
34
+ }
35
+ const numbers = numbersFor(positionals, values.fleet ?? false);
36
+ const interval = intervalFor(values.interval);
37
+ const apiUrl = apiUrlFor(values["api-url"]);
38
+ return {
39
+ help: false,
40
+ once: values.once ?? false,
41
+ options: {
42
+ apiUrl,
43
+ fleet: values.fleet ?? false,
44
+ interval,
45
+ numbers,
46
+ repository,
47
+ stateFile: values.state ?? `.pr-watch/${repository.replace("/", "-")}.json`,
48
+ token,
49
+ },
50
+ };
51
+ },
52
+ });
53
+ }
54
+ function numbersFor(positionals, fleet) {
55
+ if (positionals.some((value) => value.match(/^[1-9]\d*$/u) === null)) {
56
+ throw new Error("PR numbers must be positive integers");
57
+ }
58
+ const numbers = [...new Set(positionals.map(Number))];
59
+ if (numbers.some((number) => !Number.isSafeInteger(number))) {
60
+ throw new Error("Invalid PR number");
61
+ }
62
+ if (fleet ? numbers.length > 0 : numbers.length === 0) {
63
+ throw new Error("Choose --fleet or one or more PR numbers");
64
+ }
65
+ return numbers;
66
+ }
67
+ function intervalFor(value) {
68
+ const interval = Number(value ?? 60) * 1000;
69
+ if (!Number.isFinite(interval) || interval < 1000) {
70
+ throw new Error("--interval must be at least one second");
71
+ }
72
+ return interval;
73
+ }
74
+ function apiUrlFor(value) {
75
+ const apiUrl = (value ?? "https://api.github.com").replace(/\/$/u, "");
76
+ const url = new URL(apiUrl);
77
+ const secure = url.protocol === "https:";
78
+ const loopback = url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
79
+ if (!(secure || loopback) || url.username || url.password || url.search || url.hash) {
80
+ throw new Error("--api-url requires HTTPS (or loopback HTTP)");
81
+ }
82
+ return apiUrl;
83
+ }
84
+ //# sourceMappingURL=arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arguments.js","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import process from "node:process";
3
+ import { NodeFileSystem } from "@effect/platform-node";
4
+ import { Config, Effect, Layer } from "effect";
5
+ import { FetchHttpClient } from "effect/unstable/http";
6
+ import { GitHub } from "./GitHub.js";
7
+ import { GitHubHttp } from "./GitHubHttp.js";
8
+ import { WatchError } from "./model.js";
9
+ import { program } from "./program.js";
10
+ import { StateFile } from "./StateFile.js";
11
+ 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" }))))));
13
+ function write(stream) {
14
+ return (line) => Effect.tryPromise({
15
+ catch: () => new WatchError({ message: "Output stream failed" }),
16
+ try: () => new Promise((resolve, reject) => stream.write(`${line}\n`, (error) => (error ? reject(error) : resolve()))),
17
+ });
18
+ }
19
+ const abort = new AbortController();
20
+ process.once("SIGINT", () => abort.abort());
21
+ process.once("SIGTERM", () => abort.abort());
22
+ try {
23
+ await Effect.runPromise(Effect.gen(function* () {
24
+ const token = yield* Config.string("GH_TOKEN").pipe(Config.orElse(() => Config.string("GITHUB_TOKEN")), Config.withDefault(""));
25
+ yield* program(process.argv.slice(2), token, write(process.stdout), write(process.stderr));
26
+ }).pipe(Effect.provide(services)), { signal: abort.signal });
27
+ }
28
+ catch (cause) {
29
+ if (!abort.signal.aborted) {
30
+ process.stderr.write(`${cause instanceof Error ? cause.message : "Watcher failed"}\n`);
31
+ process.exitCode = 1;
32
+ }
33
+ }
34
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { Effect } from "effect";
2
+ import { type Options, WatchError } from "./model.ts";
3
+ export declare const confirmQueue: (http: Readonly<{
4
+ readonly delay: (interval: number) => Effect.Effect<number, never, never>;
5
+ readonly get: (url: string, token: string) => Effect.Effect<import("./httpResponse.ts").CachedResponse, WatchError, never>;
6
+ readonly post: (url: string, token: string, body: unknown) => Effect.Effect<import("./httpResponse.ts").CachedResponse, WatchError, never>;
7
+ }>, options: Options, snapshot: {
8
+ readonly autoMerge: boolean;
9
+ readonly branch: string;
10
+ readonly dirty: boolean;
11
+ readonly failed: boolean;
12
+ readonly merged: boolean;
13
+ readonly number: number;
14
+ readonly queued: boolean;
15
+ readonly state: "closed" | "open";
16
+ readonly title: string;
17
+ }) => Effect.Effect<{
18
+ branch: string;
19
+ dirty: boolean;
20
+ failed: boolean;
21
+ merged: boolean;
22
+ number: number;
23
+ state: "closed" | "open";
24
+ title: string;
25
+ autoMerge: boolean;
26
+ queued: boolean;
27
+ }, WatchError, never>;
28
+ //# sourceMappingURL=confirmQueue.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;qBAoBvB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { WatchError } from "./model.js";
3
+ const QueueResponse = Schema.Struct({
4
+ data: Schema.NullOr(Schema.Struct({
5
+ repository: Schema.NullOr(Schema.Struct({
6
+ pullRequest: Schema.NullOr(Schema.Struct({
7
+ autoMergeRequest: Schema.NullOr(Schema.Struct({ enabledAt: Schema.String })),
8
+ mergeQueueEntry: Schema.NullOr(Schema.Struct({ id: Schema.String })),
9
+ state: Schema.Literals(["OPEN", "CLOSED", "MERGED"]),
10
+ })),
11
+ })),
12
+ })),
13
+ errors: Schema.optionalKey(Schema.Array(Schema.Unknown)),
14
+ });
15
+ const query = `query QueueEntry($owner: String!, $name: String!, $number: Int!) {
16
+ repository(owner: $owner, name: $name) {
17
+ pullRequest(number: $number) {
18
+ state
19
+ autoMergeRequest { enabledAt }
20
+ mergeQueueEntry { id }
21
+ }
22
+ }
23
+ }`;
24
+ export const confirmQueue = Effect.fn("GitHub.confirmQueue")(function* (http, options, snapshot) {
25
+ const url = new URL(options.apiUrl);
26
+ url.pathname = url.pathname.replace(/\/api\/v3\/?$/u, "/api/graphql");
27
+ if (!url.pathname.endsWith("/api/graphql")) {
28
+ url.pathname = `${url.pathname.replace(/\/$/u, "")}/graphql`;
29
+ }
30
+ const [owner, name] = options.repository.split("/");
31
+ const response = yield* http.post(url.href, options.token, { query, variables: { name, number: snapshot.number, owner } });
32
+ const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response.body).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub queue confirmation" })));
33
+ const pr = body.data?.repository?.pullRequest;
34
+ if ((body.errors?.length ?? 0) > 0 || pr === undefined || pr === null || pr.state !== "OPEN") {
35
+ return yield* Effect.fail(new WatchError({ message: `Queue confirmation unavailable for PR #${snapshot.number}` }));
36
+ }
37
+ return { ...snapshot, autoMerge: pr.autoMergeRequest !== null, queued: pr.mergeQueueEntry !== null || pr.autoMergeRequest !== null };
38
+ });
39
+ //# sourceMappingURL=confirmQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmQueue.js","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Snapshot } from "./model.ts";
2
+ export declare function events(before: Snapshot | undefined, after: Snapshot): readonly string[];
3
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgB3C,wBAAgB,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAgBvF"}
package/dist/events.js ADDED
@@ -0,0 +1,32 @@
1
+ function stuck(pr) {
2
+ return pr.state === "open" && pr.autoMerge && (pr.dirty || pr.failed);
3
+ }
4
+ function openEvents(before, after) {
5
+ const transitions = [
6
+ [!before.dirty && after.dirty, "conflict"],
7
+ [!before.queued && after.queued, "queued"],
8
+ [before.queued && !after.queued, "dequeued"],
9
+ [(before.autoMerge || before.queued) && !after.autoMerge && !after.queued, "auto-merge-off"],
10
+ [!before.failed && after.failed, "checks-failed"],
11
+ [!stuck(before) && stuck(after), "stuck"],
12
+ ];
13
+ return transitions.flatMap(([changed, event]) => (changed ? [event] : []));
14
+ }
15
+ export function events(before, after) {
16
+ if (before === undefined && !stuck(after)) {
17
+ return [];
18
+ }
19
+ const baseline = before ?? { ...after, autoMerge: false, dirty: false, failed: false };
20
+ const found = [];
21
+ if (!baseline.merged && after.merged) {
22
+ found.push("merged");
23
+ }
24
+ if (baseline.state === "open" && after.state === "closed" && !after.merged) {
25
+ found.push("closed");
26
+ }
27
+ if (after.state === "open") {
28
+ found.push(...openEvents(baseline, after));
29
+ }
30
+ return found.map((event) => JSON.stringify({ branch: after.branch, event, number: after.number, title: after.title }));
31
+ }
32
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,SAAS,KAAK,CAAC,EAAY;IAC1B,OAAO,EAAE,CAAC,KAAK,KAAK,MAAM,IAAI,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;AACvE,CAAC;AACD,SAAS,UAAU,CAAC,MAAgB,EAAE,KAAe;IACpD,MAAM,WAAW,GAA4C;QAC5D,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,UAAU,CAAC;QAC1C,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC1C,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC;QAC5F,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC;QACjD,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;KACzC,CAAC;IACF,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,MAA4B,EAAE,KAAe;IACnE,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACX,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,QAAQ,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACxH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { type HttpClient, type HttpClientResponse } from "effect/unstable/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/unstable/http/HttpBody").HttpBodyError | import("effect/unstable/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
@@ -0,0 +1 @@
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,sBAAsB,CAAC;AACnG,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,uLAW1I;AACD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB;;;;sBAgB1F"}
@@ -0,0 +1,31 @@
1
+ import { Effect } from "effect";
2
+ import { HttpClientRequest } from "effect/unstable/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
@@ -0,0 +1 @@
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,sBAAsB,CAAC;AACnG,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"}
@@ -0,0 +1,46 @@
1
+ import { Schema } from "effect";
2
+ declare const WatchError_base: Schema.Class<WatchError, Schema.TaggedStruct<"WatchError", {
3
+ readonly message: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ export declare class WatchError extends WatchError_base {
6
+ }
7
+ export declare const Snapshot: Schema.Struct<{
8
+ readonly autoMerge: Schema.Boolean;
9
+ readonly branch: Schema.String;
10
+ readonly dirty: Schema.Boolean;
11
+ readonly failed: Schema.Boolean;
12
+ readonly merged: Schema.Boolean;
13
+ readonly number: Schema.Int;
14
+ readonly queued: Schema.Boolean;
15
+ readonly state: Schema.Literals<readonly ["open", "closed"]>;
16
+ readonly title: Schema.String;
17
+ }>;
18
+ export type Snapshot = typeof Snapshot.Type;
19
+ export declare const State: Schema.Struct<{
20
+ readonly openNumbers: Schema.optionalKey<Schema.$Array<Schema.Int>>;
21
+ readonly prs: Schema.$Record<Schema.String, Schema.Struct<{
22
+ readonly autoMerge: Schema.Boolean;
23
+ readonly branch: Schema.String;
24
+ readonly dirty: Schema.Boolean;
25
+ readonly failed: Schema.Boolean;
26
+ readonly merged: Schema.Boolean;
27
+ readonly number: Schema.Int;
28
+ readonly queued: Schema.Boolean;
29
+ readonly state: Schema.Literals<readonly ["open", "closed"]>;
30
+ readonly title: Schema.String;
31
+ }>>;
32
+ readonly repository: Schema.String;
33
+ readonly version: Schema.Literal<1>;
34
+ }>;
35
+ export type State = typeof State.Type;
36
+ export interface Options {
37
+ readonly apiUrl: string;
38
+ readonly fleet: boolean;
39
+ readonly interval: number;
40
+ readonly numbers: readonly number[];
41
+ readonly repository: string;
42
+ readonly stateFile: string;
43
+ readonly token: string;
44
+ }
45
+ export {};
46
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +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;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB"}
package/dist/model.js ADDED
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+ export class WatchError extends Schema.TaggedError()("WatchError", {
3
+ message: Schema.String,
4
+ }) {
5
+ }
6
+ export const Snapshot = Schema.Struct({
7
+ autoMerge: Schema.Boolean,
8
+ branch: Schema.String,
9
+ dirty: Schema.Boolean,
10
+ failed: Schema.Boolean,
11
+ merged: Schema.Boolean,
12
+ number: Schema.Int,
13
+ queued: Schema.Boolean,
14
+ state: Schema.Literals(["open", "closed"]),
15
+ title: Schema.String,
16
+ });
17
+ export const State = Schema.Struct({
18
+ openNumbers: Schema.optionalKey(Schema.Array(Schema.Int)),
19
+ prs: Schema.Record(Schema.String, Snapshot),
20
+ repository: Schema.String,
21
+ version: Schema.Literal(1),
22
+ });
23
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,MAAM,OAAO,UAAW,SAAQ,MAAM,CAAC,WAAW,EAAc,CAAC,YAAY,EAAE;IAC9E,OAAO,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC;CAAG;AACL,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC,OAAO;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC,OAAO;IACrB,MAAM,EAAE,MAAM,CAAC,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC,MAAM;IACzB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;CAC1B,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import type { WatchError } from "./model.ts";
3
+ export declare const program: (args: readonly string[], token: string, emit: (line: string) => Effect.Effect<void, WatchError>, diagnostic: (message: string) => Effect.Effect<void, WatchError>) => Effect.Effect<undefined, WatchError, "@shivaedev/pr-watch/Watcher">;
4
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +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,wDAGN,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wBACjC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wEAyB/D,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { Effect } from "effect";
2
+ import { argumentsFor, help } from "./arguments.js";
3
+ import { Watcher } from "./Watcher.js";
4
+ export const program = Effect.fn("PrWatch.program")(function* (args, token, emit, diagnostic) {
5
+ const parsed = yield* argumentsFor(args, token);
6
+ if (parsed.help) {
7
+ yield* emit(help);
8
+ return;
9
+ }
10
+ const watcher = yield* Watcher;
11
+ let state = yield* watcher.load(parsed.options);
12
+ while (true) {
13
+ const result = yield* Effect.result(watcher.poll(parsed.options, state, emit, diagnostic));
14
+ if (result._tag === "Success") {
15
+ state = result.success;
16
+ }
17
+ else {
18
+ if (parsed.once) {
19
+ return yield* Effect.fail(result.failure);
20
+ }
21
+ yield* diagnostic(result.failure.message);
22
+ state = yield* watcher.load(parsed.options);
23
+ }
24
+ if (parsed.once) {
25
+ return;
26
+ }
27
+ yield* Effect.sleep(yield* watcher.delay(parsed.options.interval));
28
+ }
29
+ });
30
+ //# sourceMappingURL=program.js.map
@@ -0,0 +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,KAAa,EACb,IAAuD,EACvD,UAAgE;IAEhE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD,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"}
@@ -0,0 +1,10 @@
1
+ import type { HttpClientResponse } from "effect/unstable/http";
2
+ export interface Budget {
3
+ blockedUntil: number;
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;
10
+ //# sourceMappingURL=quota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,WAAW,MAAM;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,EAAE,SAAS,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAY5I;AACD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjG"}
package/dist/quota.js ADDED
@@ -0,0 +1,45 @@
1
+ function numeric(headers, name) {
2
+ const value = headers[name];
3
+ return value !== undefined && Number.isFinite(Number(value)) ? Number(value) : undefined;
4
+ }
5
+ export function observeBudget(budget, response, now) {
6
+ budget.remaining = numeric(response.headers, "x-ratelimit-remaining") ?? budget.remaining;
7
+ budget.limit = numeric(response.headers, "x-ratelimit-limit") ?? budget.limit;
8
+ budget.reset = (numeric(response.headers, "x-ratelimit-reset") ?? budget.reset / 1000) * 1000;
9
+ if (response.status === 429 || response.status === 403) {
10
+ const retry = response.headers["retry-after"] ?? null;
11
+ const until = retryUntil(retry, now);
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);
32
+ }
33
+ function slowdown(ratio) {
34
+ if (ratio < 0.1) {
35
+ return 10;
36
+ }
37
+ if (ratio < 0.2) {
38
+ return 5;
39
+ }
40
+ if (ratio < 0.5) {
41
+ return 2;
42
+ }
43
+ return 1;
44
+ }
45
+ //# sourceMappingURL=quota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
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.
@@ -0,0 +1,18 @@
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.