@horizon36596/zenith-github 0.1.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +18 -0
  3. package/README.md +108 -0
  4. package/dist/allowlist.d.ts +14 -0
  5. package/dist/allowlist.d.ts.map +1 -0
  6. package/dist/allowlist.js +42 -0
  7. package/dist/allowlist.js.map +1 -0
  8. package/dist/auth.d.ts +69 -0
  9. package/dist/auth.d.ts.map +1 -0
  10. package/dist/auth.js +118 -0
  11. package/dist/auth.js.map +1 -0
  12. package/dist/base64.d.ts +10 -0
  13. package/dist/base64.d.ts.map +1 -0
  14. package/dist/base64.js +20 -0
  15. package/dist/base64.js.map +1 -0
  16. package/dist/client.d.ts +95 -0
  17. package/dist/client.d.ts.map +1 -0
  18. package/dist/client.js +265 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/errors.d.ts +60 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +90 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/http.d.ts +50 -0
  25. package/dist/http.d.ts.map +1 -0
  26. package/dist/http.js +150 -0
  27. package/dist/http.js.map +1 -0
  28. package/dist/index.d.ts +25 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +18 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/project.d.ts +54 -0
  33. package/dist/project.d.ts.map +1 -0
  34. package/dist/project.js +109 -0
  35. package/dist/project.js.map +1 -0
  36. package/dist/propose.d.ts +38 -0
  37. package/dist/propose.d.ts.map +1 -0
  38. package/dist/propose.js +52 -0
  39. package/dist/propose.js.map +1 -0
  40. package/dist/review.d.ts +29 -0
  41. package/dist/review.d.ts.map +1 -0
  42. package/dist/review.js +73 -0
  43. package/dist/review.js.map +1 -0
  44. package/dist/types.d.ts +65 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +3 -0
  47. package/dist/types.js.map +1 -0
  48. package/package.json +58 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Horizon (FTC 36596)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,18 @@
1
+ Zenith
2
+ Copyright (c) 2026 Horizon (FTC 36596)
3
+
4
+ Zenith is licensed under the MIT License; see LICENSE.
5
+
6
+ Third-party assets
7
+
8
+ - BIOBUZZ field images (apps/web/public/fields/biobuzz/*.webp) by Team Juice 16236, from the r/FTC
9
+ post "BIOBUZZ custom field images (MeepMeep compatible)". Used with the credit the author asks for.
10
+
11
+ Bundled fonts
12
+
13
+ The following fonts are bundled in apps/web/public/fonts/. Each is licensed under the SIL Open Font
14
+ License, Version 1.1. The full licence text ships next to each font file.
15
+
16
+ - Jost, licensed under the SIL Open Font License 1.1 (jost-OFL.txt).
17
+ - IBM Plex Sans, licensed under the SIL Open Font License 1.1 (ibm-plex-sans-OFL.txt).
18
+ - JetBrains Mono, licensed under the SIL Open Font License 1.1 (jetbrains-mono-OFL.txt).
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @horizon36596/zenith-github
2
+
3
+ GitHub collaboration for [Zenith](https://libraries.horizon36596.org/zenith/), the FTC autonomous
4
+ planner by Horizon (FTC 36596): propose an auto as a pull request, and load a review link, from the
5
+ browser or Node.
6
+
7
+ ```
8
+ npm install @horizon36596/zenith-github
9
+ ```
10
+
11
+ Documentation: https://libraries.horizon36596.org/zenith/. Licence: MIT.
12
+
13
+ ## Package reference
14
+
15
+ An isomorphic (browser + Node) GitHub client, built on `fetch` with no Octokit and no Node-only
16
+ APIs. It is the only package that talks to `api.github.com`; `apps/web` and `packages/cli` both
17
+ build on it. The user-facing side is the docs site's GitHub page
18
+ (https://libraries.horizon36596.org/zenith/github/).
19
+
20
+ ### Creating a token
21
+
22
+ Zenith v1 signs in with a pasted **fine-grained personal access token**.
23
+ There is no OAuth App and no device flow yet.
24
+
25
+ 1. On github.com: **Settings -> Developer settings -> Personal access tokens -> Fine-grained
26
+ tokens -> Generate new token**.
27
+ 2. **Resource owner**: the account or organisation that owns the robot repository.
28
+ 3. **Repository access**: "Only select repositories", and pick the one robot repository the token
29
+ is for (e.g. `example-team/robot`). Do not grant access to every repository the account can
30
+ see; Zenith only ever needs the one.
31
+ 4. **Permissions** (under "Repository permissions"):
32
+ - **Contents**: Read and write — reads `zenith.json` and the robot/field/waypoints/auto files,
33
+ writes commits to work branches.
34
+ - **Pull requests**: Read and write — opens and updates the PR `propose` creates, and reads PR
35
+ metadata and changed files for review mode.
36
+ - Everything else can stay "No access".
37
+ 5. Set an expiry (GitHub's fine-grained tokens require one) and generate. Paste the token into
38
+ Zenith's sign-in screen when it asks.
39
+
40
+ A fine-grained token scoped this way cannot read or write any other repository, cannot see issues,
41
+ actions, settings or anything else in the one repository it can reach, and expires on its own.
42
+
43
+ ### How the token is stored
44
+
45
+ - The token is held in memory for the life of the tab or process. It is never written to disk by
46
+ this package.
47
+ - In the browser, `BrowserTokenStore` can also mirror it to `sessionStorage` (the default) so a
48
+ page reload does not force a re-paste; that mirror is cleared when the tab closes. It is never
49
+ written to `localStorage`, which would outlive the browser session.
50
+ - In Node (the CLI), `NodeTokenStore` is a no-op: nothing persists between runs, and it is the
51
+ caller's job (an environment variable, a local file the user manages themselves, an interactive
52
+ prompt) to hand the token to `PatAuth` each time.
53
+ - `GitHubClient` sends the token as an `Authorization: Bearer` header to `apiBase` only (default
54
+ `https://api.github.com`). It is never logged, never included in error messages beyond GitHub's
55
+ own response text, and never sent to any other host.
56
+
57
+ ### Safety guarantees
58
+
59
+ These are enforced in code,
60
+ not just by convention:
61
+
62
+ - **The base branch is never written to.** `GitHubProject.save` throws `BaseBranchWriteError`
63
+ before making any network call if the target branch equals the project's base branch.
64
+ - **No force-pushes.** The client has no method that force-updates an existing ref; branches are
65
+ only ever created fresh (`branches.create`, from the base branch's current sha) or advanced by
66
+ ordinary commits (`contents.put`).
67
+ - **Path allowlist.** `GitHubProject.save` refuses (`PathNotAllowedError`) any path that is not
68
+ under `zenith.json`'s `autosDir`, `deploy.dir` or `codegen.dir` (which is where
69
+ `autosDir/.renders/` already lives). This is checked locally before any request is sent.
70
+ - **Tokens go to `api.github.com` only.** See the storage section above.
71
+ - **Merge conflicts are surfaced, not hidden.** `repos.merge` throws a typed `MergeConflictError`
72
+ naming the files involved when the work branch cannot be brought up to date with the base branch
73
+ cleanly, instead of leaving a half-merged branch behind.
74
+
75
+ ### Where the device flow plugs in later
76
+
77
+ Everything in this package that needs a token depends only on the `AuthProvider` interface:
78
+
79
+ ```ts
80
+ interface AuthProvider {
81
+ token(): Promise<string>;
82
+ }
83
+ ```
84
+
85
+ `PatAuth` is the only implementation today: it wraps a pasted token and always returns it. A later
86
+ device-flow implementation (show a code, poll `github.com` for the token, store it with the same `TokenStore`) is a second class implementing the
87
+ same interface; `GitHubClient`, `GitHubProject`, `propose` and `loadReview` take an `AuthProvider`
88
+ and never construct one, so none of them change when it arrives. Swapping sign-in methods is then a
89
+ matter of which `AuthProvider` the app hands to `GitHubClient`, not a rewrite of this package.
90
+
91
+ ### Exported API
92
+
93
+ See `src/index.ts` for the full surface. In short:
94
+
95
+ - **Auth**: `AuthProvider`, `PatAuth`, `validateToken`, `TokenStore` (`MemoryTokenStore`,
96
+ `BrowserTokenStore`, `NodeTokenStore`).
97
+ - **Client**: `GitHubClient` — `repos`, `branches`, `contents`, `git`, `pulls`, `users`.
98
+ - **Project**: `GitHubProject` — `open`, `workBranch`, `save`; `isPathAllowed` /
99
+ `assertPathAllowed`.
100
+ - **Propose**: `propose`, `RENDER_URL_PLACEHOLDER`.
101
+ - **Review**: `loadReview`, `lineAnchor`, `diffFileHash`, `diffAnchor`.
102
+ - **Errors**: `AuthError`, `NotFoundError`, `ConflictError`, `RateLimitError`,
103
+ `MergeConflictError`, `PathNotAllowedError`, `BaseBranchWriteError`, `GitHubError`.
104
+
105
+ ### Testing
106
+
107
+ `pnpm exec vitest run --project github` (or `pnpm test` for the whole workspace). Every test uses
108
+ the hand-rolled `fetch` stub in `test/fetchStub.ts`; nothing here touches the network.
@@ -0,0 +1,14 @@
1
+ import type { Link } from "@horizon36596/zenith-schema";
2
+ /**
3
+ * The write allowlist of site/docs/github.md: only `autosDir`,
4
+ * `deploy.dir` and `codegen.dir` from `zenith.json` (and, redundantly but explicitly per the
5
+ * spec, `autosDir/.renders/`, which is already inside `autosDir`).
6
+ */
7
+ export declare function isPathAllowed(link: Link, path: string): boolean;
8
+ /** Throws `PathNotAllowedError` unless `isPathAllowed` is true. */
9
+ export declare function assertPathAllowed(link: Link, path: string): void;
10
+ /** `autosDir/.renders/<name>.svg`, the fixed location `propose` commits the render SVG to. */
11
+ export declare function renderPath(link: Link, autoName: string): string;
12
+ /** `autosDir/<name>.auto.json`, the fixed location an auto file lives at. */
13
+ export declare function autoPath(link: Link, autoName: string): string;
14
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../src/allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAaxD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ/D;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D"}
@@ -0,0 +1,42 @@
1
+ import { PathNotAllowedError } from "./errors.js";
2
+ /** Strips a leading `./`, converts `\` to `/`, and drops a leading `/`. */
3
+ function normalize(path) {
4
+ return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
5
+ }
6
+ function withinDir(path, dir) {
7
+ const normalizedDir = normalize(dir).replace(/\/+$/, "");
8
+ return path === normalizedDir || path.startsWith(`${normalizedDir}/`);
9
+ }
10
+ /**
11
+ * The write allowlist of site/docs/github.md: only `autosDir`,
12
+ * `deploy.dir` and `codegen.dir` from `zenith.json` (and, redundantly but explicitly per the
13
+ * spec, `autosDir/.renders/`, which is already inside `autosDir`).
14
+ */
15
+ export function isPathAllowed(link, path) {
16
+ const normalized = normalize(path);
17
+ if (normalized.length === 0)
18
+ return false;
19
+ if (normalized.split("/").includes(".."))
20
+ return false;
21
+ if (withinDir(normalized, link.autosDir))
22
+ return true;
23
+ if (link.deploy && withinDir(normalized, link.deploy.dir))
24
+ return true;
25
+ if (link.codegen && withinDir(normalized, link.codegen.dir))
26
+ return true;
27
+ return false;
28
+ }
29
+ /** Throws `PathNotAllowedError` unless `isPathAllowed` is true. */
30
+ export function assertPathAllowed(link, path) {
31
+ if (!isPathAllowed(link, path))
32
+ throw new PathNotAllowedError(path);
33
+ }
34
+ /** `autosDir/.renders/<name>.svg`, the fixed location `propose` commits the render SVG to. */
35
+ export function renderPath(link, autoName) {
36
+ return `${normalize(link.autosDir).replace(/\/+$/, "")}/.renders/${autoName}.svg`;
37
+ }
38
+ /** `autosDir/<name>.auto.json`, the fixed location an auto file lives at. */
39
+ export function autoPath(link, autoName) {
40
+ return `${normalize(link.autosDir).replace(/\/+$/, "")}/${autoName}.auto.json`;
41
+ }
42
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../src/allowlist.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,2EAA2E;AAC3E,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,GAAW;IAC1C,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU,EAAE,IAAY;IACpD,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,iBAAiB,CAAC,IAAU,EAAE,IAAY;IACxD,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,UAAU,CAAC,IAAU,EAAE,QAAgB;IACrD,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,QAAQ,MAAM,CAAC;AACpF,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,QAAQ,CAAC,IAAU,EAAE,QAAgB;IACnD,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,QAAQ,YAAY,CAAC;AACjF,CAAC"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * How the client gets a bearer token for `api.github.com`. v1 ships one implementation
3
+ * (`PatAuth`, a pasted fine-grained PAT, site/docs/github.md); this seam is
4
+ * what lets a later device-flow implementation plug in without touching `GitHubClient`,
5
+ * `GitHubProject`, `propose` or `loadReview`.
6
+ */
7
+ export interface AuthProvider {
8
+ /** Resolves to a bearer token, or rejects (typically with `AuthError`) when none is available. */
9
+ token(): Promise<string>;
10
+ }
11
+ /** v1's only `AuthProvider`: a fine-grained personal access token pasted into the app or the CLI. */
12
+ export declare class PatAuth implements AuthProvider {
13
+ #private;
14
+ constructor(pat: string);
15
+ token(): Promise<string>;
16
+ }
17
+ /** What `validateToken` reports back about the token it was given. */
18
+ export interface TokenInfo {
19
+ login: string;
20
+ /**
21
+ * Scopes from the `x-oauth-scopes` response header. GitHub sets this header for classic PATs and
22
+ * OAuth Apps; a fine-grained PAT authenticates fine but does not echo its permissions here, so
23
+ * this is `[]` for one, not a sign that the token has no access.
24
+ */
25
+ scopes: string[];
26
+ }
27
+ /**
28
+ * Confirms the token works and reports who it belongs to, the way the app's sign-in screen and
29
+ * `zenith propose`'s preflight both want (06 §2). Throws `AuthError` on 401/403.
30
+ */
31
+ export declare function validateToken(auth: AuthProvider, apiBase?: string, fetchImpl?: typeof fetch): Promise<TokenInfo>;
32
+ /** Loads, saves and clears the pasted token. Never `localStorage` (00-decisions.md GitHub mode). */
33
+ export interface TokenStore {
34
+ load(): string | null;
35
+ save(token: string): void;
36
+ clear(): void;
37
+ }
38
+ /** Works anywhere; the token disappears when the page or process does. */
39
+ export declare class MemoryTokenStore implements TokenStore {
40
+ private token;
41
+ load(): string | null;
42
+ save(token: string): void;
43
+ clear(): void;
44
+ }
45
+ /**
46
+ * The browser store. Memory is the source of truth; when `persist` is on and `sessionStorage` is
47
+ * reachable, the token is mirrored there too, so a page reload does not force a re-paste. Falls
48
+ * back to memory-only, quietly, if storage throws (private browsing, a locked-down iframe).
49
+ * Deliberately has no `localStorage` mode: the token must not outlive the browser session.
50
+ */
51
+ export declare class BrowserTokenStore implements TokenStore {
52
+ private readonly memory;
53
+ private readonly storage;
54
+ constructor(options?: {
55
+ persist?: boolean;
56
+ storage?: Storage;
57
+ });
58
+ private static readonly KEY;
59
+ load(): string | null;
60
+ save(token: string): void;
61
+ clear(): void;
62
+ }
63
+ /** Node (the CLI) has no browser storage; the token lives only where the caller puts it. */
64
+ export declare class NodeTokenStore implements TokenStore {
65
+ load(): string | null;
66
+ save(): void;
67
+ clear(): void;
68
+ }
69
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,kGAAkG;IAClG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED,qGAAqG;AACrG,qBAAa,OAAQ,YAAW,YAAY;;gBAQ9B,GAAG,EAAE,MAAM;IAKjB,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;CAG/B;AAED,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,YAAY,EAClB,OAAO,SAA2B,EAClC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,SAAS,CAAC,CAyBpB;AAED,oGAAoG;AACpG,MAAM,WAAW,UAAU;IACzB,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,KAAK,CAAuB;IAEpC,IAAI,IAAI,MAAM,GAAG,IAAI;IAIrB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,IAAI;CAGd;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;gBAE7B,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO;IAclE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAyB;IAEpD,IAAI,IAAI,MAAM,GAAG,IAAI;IAIrB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IASzB,KAAK,IAAI,IAAI;CAQd;AAED,4FAA4F;AAC5F,qBAAa,cAAe,YAAW,UAAU;IAC/C,IAAI,IAAI,MAAM,GAAG,IAAI;IAIrB,IAAI,IAAI,IAAI;IAIZ,KAAK,IAAI,IAAI;CAGd"}
package/dist/auth.js ADDED
@@ -0,0 +1,118 @@
1
+ import { AuthError } from "./errors.js";
2
+ /** v1's only `AuthProvider`: a fine-grained personal access token pasted into the app or the CLI. */
3
+ export class PatAuth {
4
+ // A real JS private field (`#pat`), not a TypeScript `private` constructor parameter: the latter
5
+ // still compiles to an ordinary own enumerable property, so `JSON.stringify(patAuthInstance)` (or
6
+ // any code that walks `state.backend`'s object graph down to this) would print the token in the
7
+ // clear. `#pat` has no enumerable representation at
8
+ // all — `Object.keys`, `JSON.stringify` and `for...in` all skip it.
9
+ #pat;
10
+ constructor(pat) {
11
+ if (!pat.trim())
12
+ throw new AuthError("A personal access token is required.");
13
+ this.#pat = pat;
14
+ }
15
+ async token() {
16
+ return this.#pat;
17
+ }
18
+ }
19
+ /**
20
+ * Confirms the token works and reports who it belongs to, the way the app's sign-in screen and
21
+ * `zenith propose`'s preflight both want (06 §2). Throws `AuthError` on 401/403.
22
+ */
23
+ export async function validateToken(auth, apiBase = "https://api.github.com", fetchImpl = fetch) {
24
+ const token = await auth.token();
25
+ const response = await fetchImpl(`${apiBase}/user`, {
26
+ headers: {
27
+ Authorization: `Bearer ${token}`,
28
+ Accept: "application/vnd.github+json",
29
+ "X-GitHub-Api-Version": "2022-11-28",
30
+ },
31
+ });
32
+ if (response.status === 401 || response.status === 403) {
33
+ throw new AuthError(`GitHub rejected this token (${response.status}). Paste a fresh fine-grained PAT with repo contents and pull requests read/write.`, response.status);
34
+ }
35
+ if (!response.ok) {
36
+ throw new AuthError(`Could not validate the token: GitHub returned ${response.status}.`, response.status);
37
+ }
38
+ const body = (await response.json());
39
+ const scopesHeader = response.headers.get("x-oauth-scopes") ?? "";
40
+ const scopes = scopesHeader
41
+ .split(",")
42
+ .map((scope) => scope.trim())
43
+ .filter((scope) => scope.length > 0);
44
+ return { login: body.login, scopes };
45
+ }
46
+ /** Works anywhere; the token disappears when the page or process does. */
47
+ export class MemoryTokenStore {
48
+ token = null;
49
+ load() {
50
+ return this.token;
51
+ }
52
+ save(token) {
53
+ this.token = token;
54
+ }
55
+ clear() {
56
+ this.token = null;
57
+ }
58
+ }
59
+ /**
60
+ * The browser store. Memory is the source of truth; when `persist` is on and `sessionStorage` is
61
+ * reachable, the token is mirrored there too, so a page reload does not force a re-paste. Falls
62
+ * back to memory-only, quietly, if storage throws (private browsing, a locked-down iframe).
63
+ * Deliberately has no `localStorage` mode: the token must not outlive the browser session.
64
+ */
65
+ export class BrowserTokenStore {
66
+ memory = new MemoryTokenStore();
67
+ storage;
68
+ constructor(options = {}) {
69
+ const persist = options.persist ?? true;
70
+ const candidate = options.storage ?? (typeof sessionStorage === "undefined" ? null : sessionStorage);
71
+ this.storage = persist ? candidate : null;
72
+ if (this.storage) {
73
+ try {
74
+ const stored = this.storage.getItem(BrowserTokenStore.KEY);
75
+ if (stored)
76
+ this.memory.save(stored);
77
+ }
78
+ catch {
79
+ this.storage = null;
80
+ }
81
+ }
82
+ }
83
+ static KEY = "zenith.github.token";
84
+ load() {
85
+ return this.memory.load();
86
+ }
87
+ save(token) {
88
+ this.memory.save(token);
89
+ try {
90
+ this.storage?.setItem(BrowserTokenStore.KEY, token);
91
+ }
92
+ catch {
93
+ // sessionStorage can throw (quota, privacy mode); memory still has the token.
94
+ }
95
+ }
96
+ clear() {
97
+ this.memory.clear();
98
+ try {
99
+ this.storage?.removeItem(BrowserTokenStore.KEY);
100
+ }
101
+ catch {
102
+ // Nothing to clean up if storage was never reachable.
103
+ }
104
+ }
105
+ }
106
+ /** Node (the CLI) has no browser storage; the token lives only where the caller puts it. */
107
+ export class NodeTokenStore {
108
+ load() {
109
+ return null;
110
+ }
111
+ save() {
112
+ // Intentionally a no-op: see the class comment.
113
+ }
114
+ clear() {
115
+ // Intentionally a no-op: see the class comment.
116
+ }
117
+ }
118
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * UTF-8 safe base64, using only `atob`/`btoa` and `TextEncoder`/`TextDecoder` so the package stays
3
+ * isomorphic: those four are global in every evergreen browser and in Node 18+, unlike `Buffer`,
4
+ * which is Node-only. The Contents API sends and receives base64 with the file's bytes; going
5
+ * through `atob`/`btoa` directly (which only understand Latin-1) would corrupt anything outside
6
+ * that range.
7
+ */
8
+ export declare function encodeBase64Utf8(text: string): string;
9
+ export declare function decodeBase64Utf8(base64: string): string;
10
+ //# sourceMappingURL=base64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIvD"}
package/dist/base64.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * UTF-8 safe base64, using only `atob`/`btoa` and `TextEncoder`/`TextDecoder` so the package stays
3
+ * isomorphic: those four are global in every evergreen browser and in Node 18+, unlike `Buffer`,
4
+ * which is Node-only. The Contents API sends and receives base64 with the file's bytes; going
5
+ * through `atob`/`btoa` directly (which only understand Latin-1) would corrupt anything outside
6
+ * that range.
7
+ */
8
+ export function encodeBase64Utf8(text) {
9
+ const bytes = new TextEncoder().encode(text);
10
+ let binary = "";
11
+ for (const byte of bytes)
12
+ binary += String.fromCharCode(byte);
13
+ return btoa(binary);
14
+ }
15
+ export function decodeBase64Utf8(base64) {
16
+ const binary = atob(base64.replace(/\n/g, ""));
17
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
18
+ return new TextDecoder().decode(bytes);
19
+ }
20
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,95 @@
1
+ import type { Link } from "@horizon36596/zenith-schema";
2
+ import type { AuthProvider } from "./auth.js";
3
+ import { type HttpOptions } from "./http.js";
4
+ import type { Branch, ContentEntry, ContentFile, MergeResult, PullFile, PullRequest, PutResult, Repo, RepoSummary, User } from "./types.js";
5
+ export type GitHubClientOptions = HttpOptions;
6
+ /**
7
+ * Bounds a write `GitHubClient.contents.put`/`deleteFile` may make: `base` is the branch a write
8
+ * must never target, and `link` is `zenith.json`'s own allowlist of directories a path must fall
9
+ * under. Passed by `GitHubProject` on every call it makes, so the guard lives in the client method
10
+ * itself — not only in `GitHubProject.save`'s own pre-check — and a raw `GitHubClient` call that
11
+ * passes the same guard is refused exactly the same way. The guard used to sit above the client,
12
+ * which let a direct client call around it.
13
+ */
14
+ export interface ContentWriteGuard {
15
+ base: string;
16
+ link: Link;
17
+ }
18
+ /** The lighter guard `repos.merge` needs: it has no path, only a branch that must never be the
19
+ * merge's write target. */
20
+ export interface MergeWriteGuard {
21
+ base: string;
22
+ }
23
+ /**
24
+ * An isomorphic (browser + Node), `fetch`-based GitHub client: no Octokit, no Node-only APIs. One
25
+ * instance is bound to an `AuthProvider`, not to a repository — `GitHubProject` is the layer bound
26
+ * to `{owner, repo, base}` (site/docs/github.md).
27
+ */
28
+ export declare class GitHubClient {
29
+ private readonly http;
30
+ constructor(auth: AuthProvider, options?: GitHubClientOptions);
31
+ /** The most recent `x-ratelimit-*` snapshot seen, or `null` before the first request. */
32
+ get rateLimit(): import("./http.js").RateLimitSnapshot | null;
33
+ readonly repos: {
34
+ /** Repositories the authenticated account can push to, across personal and org affiliation. */
35
+ listWritable: () => Promise<RepoSummary[]>;
36
+ get: (owner: string, repo: string) => Promise<Repo>;
37
+ /**
38
+ * Merges `head` into `base` (`POST /repos/{owner}/{repo}/merges`) — the "update the work
39
+ * branch from the base branch" step of propose (06 §3 step 2). On conflict, recovers the
40
+ * files in play by comparing both branches against their merge base; see `MergeConflictError`.
41
+ * `guard`, when given, refuses the call outright when `base` (the ref the merge commit lands
42
+ * on) is the project's protected base branch (finding 12).
43
+ */
44
+ merge: (owner: string, repo: string, base: string, head: string, guard?: MergeWriteGuard) => Promise<MergeResult>;
45
+ };
46
+ readonly branches: {
47
+ list: (owner: string, repo: string) => Promise<Branch[]>;
48
+ get: (owner: string, repo: string, branch: string) => Promise<Branch>;
49
+ /** Creates `name` pointing at `from`'s current sha. Never touches an existing ref (no force-push). */
50
+ create: (owner: string, repo: string, name: string, from: {
51
+ branch: string;
52
+ }) => Promise<Branch>;
53
+ };
54
+ readonly contents: {
55
+ get: (owner: string, repo: string, path: string, ref: string) => Promise<ContentFile>;
56
+ /** `guard`, when given, refuses a write to the base branch or to a path outside `link`'s
57
+ * allowlist before any network call (finding 12). `GitHubProject.save` always passes one; a
58
+ * raw `GitHubClient` caller that cares about the same protection can pass one too. */
59
+ put: (owner: string, repo: string, path: string, text: string, message: string, branch: string, sha?: string, guard?: ContentWriteGuard) => Promise<PutResult>;
60
+ list: (owner: string, repo: string, dir: string, ref: string) => Promise<ContentEntry[]>;
61
+ /** Same guard as `put` (site/docs/github.md): the "never write base, only inside
62
+ * the allowlist" rule covers a delete exactly as much as it covers a write. */
63
+ deleteFile: (owner: string, repo: string, path: string, message: string, branch: string, sha: string, guard?: ContentWriteGuard) => Promise<void>;
64
+ };
65
+ readonly git: {
66
+ /** The commit both `base` and `head` descend from, via the compare API's `merge_base_commit`. */
67
+ mergeBase: (owner: string, repo: string, base: string, head: string) => Promise<string | null>;
68
+ };
69
+ readonly pulls: {
70
+ create: (owner: string, repo: string, input: {
71
+ title: string;
72
+ head: string;
73
+ base: string;
74
+ body: string;
75
+ }) => Promise<PullRequest>;
76
+ update: (owner: string, repo: string, number: number, input: {
77
+ title?: string;
78
+ body?: string;
79
+ state?: "open" | "closed";
80
+ }) => Promise<PullRequest>;
81
+ get: (owner: string, repo: string, number: number) => Promise<PullRequest>;
82
+ listForBranch: (owner: string, repo: string, branch: string, state?: "open" | "closed" | "all") => Promise<PullRequest[]>;
83
+ files: (owner: string, repo: string, number: number) => Promise<PullFile[]>;
84
+ requestReviewers: (owner: string, repo: string, number: number, reviewers: string[]) => Promise<void>;
85
+ };
86
+ readonly users: {
87
+ me: () => Promise<User>;
88
+ };
89
+ /** Follows `Link: rel="next"` headers to collect every page. */
90
+ private paginate;
91
+ /** Files touched on both sides since the merge base: a conservative guess at the conflict set. */
92
+ private conflictingFiles;
93
+ private comparedFiles;
94
+ }
95
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,WAAW,EACX,SAAS,EACT,IAAI,EACJ,WAAW,EACX,IAAI,EACL,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAsG9C;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;2BAC2B;AAC3B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;gBAEtB,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,mBAAwB;IAIjE,yFAAyF;IACzF,IAAI,SAAS,iDAEZ;IAED,QAAQ,CAAC,KAAK;QACZ,+FAA+F;4BACvE,OAAO,CAAC,WAAW,EAAE,CAAC;qBAQ3B,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;QAKvD;;;;;;WAMG;uBACkB,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM,UAAU,eAAe,KAAG,OAAO,CAAC,WAAW,CAAC;MAsBrH;IAEF,QAAQ,CAAC,QAAQ;sBACK,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC;qBAKzC,MAAM,QAAQ,MAAM,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;QAOzE,sGAAsG;wBAChF,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,KAAG,OAAO,CAAC,MAAM,CAAC;MAQpG;IAEF,QAAQ,CAAC,QAAQ;qBACI,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,WAAW,CAAC;QAUzF;;8FAEsF;qBAE7E,MAAM,QACP,MAAM,QACN,MAAM,QACN,MAAM,WACH,MAAM,UACP,MAAM,QACR,MAAM,UACJ,iBAAiB,KACxB,OAAO,CAAC,SAAS,CAAC;sBAoBD,MAAM,QAAQ,MAAM,OAAO,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,YAAY,EAAE,CAAC;QAO5F;uFAC+E;4BAEtE,MAAM,QACP,MAAM,QACN,MAAM,WACH,MAAM,UACP,MAAM,OACT,MAAM,UACH,iBAAiB,KACxB,OAAO,CAAC,IAAI,CAAC;MAUhB;IAEF,QAAQ,CAAC,GAAG;QACV,iGAAiG;2BACxE,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;MAWlG;IAEF,QAAQ,CAAC,KAAK;wBAEH,MAAM,QACP,MAAM,SACL;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,KACjE,OAAO,CAAC,WAAW,CAAC;wBASd,MAAM,QACP,MAAM,UACJ,MAAM,SACP;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;SAAE,KAClE,OAAO,CAAC,WAAW,CAAC;qBAQJ,MAAM,QAAQ,MAAM,UAAU,MAAM,KAAG,OAAO,CAAC,WAAW,CAAC;+BAMrE,MAAM,QACP,MAAM,UACJ,MAAM,UACP,MAAM,GAAG,QAAQ,GAAG,KAAK,KAC/B,OAAO,CAAC,WAAW,EAAE,CAAC;uBASJ,MAAM,QAAQ,MAAM,UAAU,MAAM,KAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;kCAO/C,MAAM,QAAQ,MAAM,UAAU,MAAM,aAAa,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAAC;MAOzG;IAEF,QAAQ,CAAC,KAAK;kBACE,OAAO,CAAC,IAAI,CAAC;MAI3B;IAEF,gEAAgE;YAClD,QAAQ;IAatB,kGAAkG;YACpF,gBAAgB;YAUhB,aAAa;CAM5B"}