@realiizlabs/admin 0.15.9 → 0.17.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/dist/git/index.cjs +67 -5
- package/dist/git/index.cjs.map +1 -1
- package/dist/git/index.d.cts +31 -3
- package/dist/git/index.d.ts +31 -3
- package/dist/git/index.js +66 -6
- package/dist/git/index.js.map +1 -1
- package/dist/shell/index.cjs +22 -17
- package/dist/shell/index.cjs.map +1 -1
- package/dist/shell/index.d.cts +1 -1
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/index.js +22 -17
- package/dist/shell/index.js.map +1 -1
- package/dist/studio/index.cjs +78 -10
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +14 -2
- package/dist/studio/index.d.ts +14 -2
- package/dist/studio/index.js +78 -10
- package/dist/studio/index.js.map +1 -1
- package/dist/studio-ui/index.cjs +34 -23
- package/dist/studio-ui/index.cjs.map +1 -1
- package/dist/studio-ui/index.d.cts +18 -4
- package/dist/studio-ui/index.d.ts +18 -4
- package/dist/studio-ui/index.js +35 -24
- package/dist/studio-ui/index.js.map +1 -1
- package/dist/{types-BP5G1myE.d.cts → types-Cy9UDz--.d.cts} +11 -4
- package/dist/{types-BP5G1myE.d.ts → types-Cy9UDz--.d.ts} +11 -4
- package/package.json +2 -2
package/dist/git/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
require('server-only');
|
|
4
|
+
var crypto = require('crypto');
|
|
4
5
|
|
|
5
6
|
// src/git/index.ts
|
|
6
7
|
|
|
@@ -43,15 +44,15 @@ var ChecksFailedError = class extends Error {
|
|
|
43
44
|
}
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
// src/git/
|
|
47
|
-
var
|
|
47
|
+
// src/git/http.ts
|
|
48
|
+
var GITHUB_API = "https://api.github.com";
|
|
48
49
|
var GITHUB_TIMEOUT_MS = 1e4;
|
|
49
|
-
async function
|
|
50
|
-
const res = await
|
|
50
|
+
async function githubRequest(method, path, authorization, body, fetchImpl = fetch) {
|
|
51
|
+
const res = await fetchImpl(GITHUB_API + path, {
|
|
51
52
|
method,
|
|
52
53
|
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
|
|
53
54
|
headers: {
|
|
54
|
-
Authorization:
|
|
55
|
+
Authorization: authorization,
|
|
55
56
|
Accept: "application/vnd.github+json",
|
|
56
57
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
57
58
|
"User-Agent": "realiizlabs-admin",
|
|
@@ -70,6 +71,65 @@ async function ghFetch(ctx, method, path, body) {
|
|
|
70
71
|
}
|
|
71
72
|
return { ok: res.ok, status: res.status, json };
|
|
72
73
|
}
|
|
74
|
+
|
|
75
|
+
// src/git/app-auth.ts
|
|
76
|
+
var JWT_TTL_S = 9 * 60;
|
|
77
|
+
var REFRESH_BEFORE_MS = 5 * 6e4;
|
|
78
|
+
var cache = /* @__PURE__ */ new Map();
|
|
79
|
+
function b64url(input) {
|
|
80
|
+
return Buffer.from(input).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
81
|
+
}
|
|
82
|
+
function appJwt(creds, now = Math.floor(Date.now() / 1e3)) {
|
|
83
|
+
const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
84
|
+
const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + JWT_TTL_S, iss: creds.appId }));
|
|
85
|
+
const signer = crypto.createSign("RSA-SHA256");
|
|
86
|
+
signer.update(`${header}.${payload}`);
|
|
87
|
+
const signature = signer.sign(creds.privateKey.replace(/\\n/g, "\n"));
|
|
88
|
+
return `${header}.${payload}.${b64url(signature)}`;
|
|
89
|
+
}
|
|
90
|
+
function forgetInstallationToken(creds, owner, repo) {
|
|
91
|
+
cache.delete(`${creds.appId}:${owner}/${repo}`);
|
|
92
|
+
}
|
|
93
|
+
async function installationToken(creds, owner, repo, fetchImpl = fetch) {
|
|
94
|
+
const key = `${creds.appId}:${owner}/${repo}`;
|
|
95
|
+
const hit = cache.get(key);
|
|
96
|
+
if (hit && hit.expiresAt - Date.now() > REFRESH_BEFORE_MS) return hit.token;
|
|
97
|
+
const bearer = `Bearer ${appJwt(creds)}`;
|
|
98
|
+
const repoPath2 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
99
|
+
const found = await githubRequest("GET", `${repoPath2}/installation`, bearer, void 0, fetchImpl);
|
|
100
|
+
if (found.status === 404) {
|
|
101
|
+
throw new GitHubError(
|
|
102
|
+
`The Realiiz Studio GitHub App isn't installed on ${owner}/${repo}. The repository's owner installs it once \u2014 ask your web team for the link.`,
|
|
103
|
+
{ status: 404, path: `${repoPath2}/installation`, body: found.json }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (!found.ok) throw new GitHubError(`GitHub ${found.status} looking up the app installation`, { status: found.status, path: `${repoPath2}/installation`, body: found.json });
|
|
107
|
+
const minted = await githubRequest(
|
|
108
|
+
"POST",
|
|
109
|
+
`/app/installations/${found.json.id}/access_tokens`,
|
|
110
|
+
bearer,
|
|
111
|
+
void 0,
|
|
112
|
+
fetchImpl
|
|
113
|
+
);
|
|
114
|
+
if (!minted.ok) throw new GitHubError(`GitHub ${minted.status} minting an installation token`, { status: minted.status, path: "/app/installations/\u2026/access_tokens", body: minted.json });
|
|
115
|
+
cache.set(key, { token: minted.json.token, expiresAt: Date.parse(minted.json.expires_at) });
|
|
116
|
+
return minted.json.token;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/git/client.ts
|
|
120
|
+
async function credentialFor(ctx) {
|
|
121
|
+
if (ctx.app) return installationToken(ctx.app, ctx.owner, ctx.repo);
|
|
122
|
+
if (ctx.token) return ctx.token;
|
|
123
|
+
throw new GitHubError(`No GitHub credential for ${ctx.owner}/${ctx.repo}: set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY, or GITHUB_CONTENT_TOKEN.`, { status: 0, path: "", body: null });
|
|
124
|
+
}
|
|
125
|
+
async function ghFetch(ctx, method, path, body) {
|
|
126
|
+
const res = await githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
127
|
+
if (res.status === 401 && ctx.app) {
|
|
128
|
+
forgetInstallationToken(ctx.app, ctx.owner, ctx.repo);
|
|
129
|
+
return githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
130
|
+
}
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
73
133
|
async function ghFetchOrThrow(ctx, method, path, body) {
|
|
74
134
|
const res = await ghFetch(ctx, method, path, body);
|
|
75
135
|
if (!res.ok) throw toError(res, path);
|
|
@@ -296,10 +356,12 @@ exports.GitHubError = GitHubError;
|
|
|
296
356
|
exports.NotFoundError = NotFoundError;
|
|
297
357
|
exports.StaleBaseError = StaleBaseError;
|
|
298
358
|
exports.TokenScopeError = TokenScopeError;
|
|
359
|
+
exports.appJwt = appJwt;
|
|
299
360
|
exports.closePullRequest = closePullRequest;
|
|
300
361
|
exports.createBranch = createBranch;
|
|
301
362
|
exports.deleteBranch = deleteBranch;
|
|
302
363
|
exports.getPullRequestStatus = getPullRequestStatus;
|
|
364
|
+
exports.installationToken = installationToken;
|
|
303
365
|
exports.listDirectory = listDirectory;
|
|
304
366
|
exports.listOpenPullRequests = listOpenPullRequests;
|
|
305
367
|
exports.mergePullRequest = mergePullRequest;
|
package/dist/git/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/git/errors.ts","../../src/git/client.ts","../../src/git/branch.ts","../../src/git/files.ts","../../src/git/pulls.ts","../../src/git/status.ts","../../src/git/merge.ts","../../src/git/reads.ts"],"names":["d"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors — so ADMIN-04 can `instanceof` rather than string-match.\n *\n * Each carries enough to log usefully without ever including the token.\n */\n\n/** Any non-2xx from GitHub that is not one of the more specific cases below. */\nexport class GitHubError extends Error {\n readonly status: number;\n readonly path: string;\n readonly body: unknown;\n\n constructor(message: string, opts: { status: number; path: string; body: unknown }) {\n super(message);\n this.name = \"GitHubError\";\n this.status = opts.status;\n this.path = opts.path;\n this.body = opts.body;\n }\n}\n\n/**\n * The pull request cannot merge because `main` moved underneath it in a way\n * that conflicts — someone else edited the same file first.\n *\n * Observed in the probe: a stale base sha does NOT fail the trees write. Tree,\n * commit, ref and PR all succeed; the conflict only shows as\n * `mergeable_state: \"dirty\"` and then a 405 \"Pull Request has merge conflicts\"\n * on merge. So this is raised from mergePullRequest, never from writeFiles.\n * The recovery is the caller's: re-read main, rebuild the change, open a new PR.\n */\nexport class StaleBaseError extends GitHubError {\n readonly pullNumber: number;\n\n constructor(pullNumber: number, opts: { status: number; path: string; body: unknown }) {\n super(`Pull request #${pullNumber} conflicts with the base branch`, opts);\n this.name = \"StaleBaseError\";\n this.pullNumber = pullNumber;\n }\n}\n\n/**\n * The token cannot do what was asked. A 403 from GitHub with a fine-grained PAT\n * almost always means a permission was not granted when the token was made —\n * a setup bug at onboarding, not a runtime condition.\n */\nexport class TokenScopeError extends GitHubError {\n static readonly REQUIRED_PERMISSIONS =\n \"Contents: Read and write, Pull requests: Read and write, Commit statuses: Read-only\";\n\n constructor(opts: { status: number; path: string; body: unknown }) {\n super(\n `GitHub refused ${opts.path} (${opts.status}). The repo token needs these fine-grained permissions: ${TokenScopeError.REQUIRED_PERMISSIONS}`,\n opts,\n );\n this.name = \"TokenScopeError\";\n }\n}\n\n/** mergePullRequest was asked to merge while checks are red. */\nexport class ChecksFailedError extends Error {\n readonly pullNumber: number;\n readonly failingCheck: string | null;\n\n constructor(pullNumber: number, failingCheck: string | null) {\n super(\n `Pull request #${pullNumber} has failing checks${failingCheck ? ` (${failingCheck})` : \"\"}`,\n );\n this.name = \"ChecksFailedError\";\n this.pullNumber = pullNumber;\n this.failingCheck = failingCheck;\n }\n}\n","/**\n * The one place that talks to api.github.com.\n *\n * ── Every call is bounded ────────────────────────────────────────────────────\n * Same pattern as 5degrees-website's HighLevel client: a module constant plus\n * AbortSignal.timeout on every fetch. That fix exists because a slow upstream\n * ran past the serverless function limit and the process was killed before its\n * own error handler could run. A publish is ~10 sequential GitHub calls, so the\n * same failure is available here. Observed probe latency was 200–1400 ms; 10 s\n * is generous, and if GitHub is slower than that we want the error, not the wait.\n *\n * ── No module state ──────────────────────────────────────────────────────────\n * The token arrives in RepoContext on every call. Nothing is read from\n * process.env here or anywhere in src/git (BUILD-ENV law).\n */\n\nimport type { RepoContext } from \"./types\";\nimport { GitHubError, TokenScopeError } from \"./errors\";\n\nconst API = \"https://api.github.com\";\nexport const GITHUB_TIMEOUT_MS = 10_000;\n\nexport interface GitHubResponse<T = unknown> {\n ok: boolean;\n status: number;\n json: T;\n}\n\n/**\n * Low-level request. Returns instead of throwing on non-2xx so callers can\n * inspect the status when a \"failure\" is a meaningful answer (405 on merge,\n * 422 on a duplicate ref). Use `ghFetchOrThrow` when it is not.\n */\nexport async function ghFetch<T = unknown>(\n ctx: RepoContext,\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: unknown,\n): Promise<GitHubResponse<T>> {\n const res = await fetch(API + path, {\n method,\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n headers: {\n Authorization: `Bearer ${ctx.token}`,\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n \"User-Agent\": \"realiizlabs-admin\",\n ...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const text = await res.text();\n let json: unknown = null;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n json = { raw: text };\n }\n }\n return { ok: res.ok, status: res.status, json: json as T };\n}\n\n/** Same as ghFetch but a non-2xx becomes a typed error. */\nexport async function ghFetchOrThrow<T = unknown>(\n ctx: RepoContext,\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: unknown,\n): Promise<T> {\n const res = await ghFetch<T>(ctx, method, path, body);\n if (!res.ok) throw toError(res, path);\n return res.json;\n}\n\nexport function toError(res: GitHubResponse, path: string): GitHubError {\n const opts = { status: res.status, path, body: res.json };\n if (res.status === 403) return new TokenScopeError(opts);\n const message =\n (res.json as { message?: string } | null)?.message ?? `GitHub ${res.status} on ${path}`;\n return new GitHubError(message, opts);\n}\n\n/** `/repos/{owner}/{repo}` prefix, built per call. */\nexport function repoPath(ctx: RepoContext): string {\n return `/repos/${encodeURIComponent(ctx.owner)}/${encodeURIComponent(ctx.repo)}`;\n}\n\nexport function baseBranch(ctx: RepoContext): string {\n return ctx.baseBranch ?? \"main\";\n}\n","/**\n * createBranch — a new branch off the base branch, as it is RIGHT NOW.\n *\n * The base sha is fetched from the refs API on every call and never stored.\n * This module is one of four writers to the same repo (github.com, GitHub\n * Desktop, Cowork are the others), so a ref that was current a minute ago is\n * routinely stale. Branching from a cached sha would silently build on old\n * content.\n */\n\nimport type { CreateBranchResult, RepoContext } from \"./types\";\nimport { baseBranch, ghFetch, ghFetchOrThrow, repoPath, toError } from \"./client\";\n\ninterface RefResponse {\n ref: string;\n object: { sha: string; type: string };\n}\n\n/** Read the current tip of a branch. Always a live call. */\nexport async function getBranchSha(ctx: RepoContext, branch: string): Promise<string> {\n const ref = await ghFetchOrThrow<RefResponse>(\n ctx,\n \"GET\",\n `${repoPath(ctx)}/git/ref/heads/${encodeURIComponent(branch)}`,\n );\n return ref.object.sha;\n}\n\nexport async function createBranch(\n ctx: RepoContext,\n branch: string,\n): Promise<CreateBranchResult> {\n const baseSha = await getBranchSha(ctx, baseBranch(ctx));\n\n await ghFetchOrThrow<RefResponse>(ctx, \"POST\", `${repoPath(ctx)}/git/refs`, {\n ref: `refs/heads/${branch}`,\n sha: baseSha,\n });\n\n return { branch, baseSha };\n}\n\n/**\n * Remove a branch. 204 on success. Used after a squash merge so the client's\n * repo does not accumulate one dead branch per publish.\n *\n * Already gone is success too: GitHub's \"automatically delete head branches\"\n * and Dependabot both remove a merged branch on their own, and answer 422\n * \"Reference does not exist\" (or 404) when we arrive second.\n */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n const path = `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`;\n const res = await ghFetch(ctx, \"DELETE\", path);\n if (res.ok || res.status === 404 || res.status === 422) return;\n throw toError(res, path);\n}\n","/**\n * writeFiles — one or more files in ONE commit on an existing branch.\n *\n * Why the trees API and not the Contents API: a post is an MDX file plus a\n * hero image. Two Contents calls make two commits, and the first triggers a\n * build of a half-finished state. One tree, one commit.\n *\n * Encoding rule, observed in the probe: a tree entry can inline utf-8 text as\n * `content`, but has no `encoding` field — a binary must be uploaded first via\n * POST /git/blobs with `encoding: \"base64\"` and referenced in the tree by sha.\n * So a two-file publish with one image is six calls, not five.\n *\n * `opts.remove` deletes paths in the same commit (tree entry with `sha: null`) —\n * how the dashboard takes a post down: one PR that removes the file.\n */\n\nimport type { FileInput, RepoContext, WriteFilesResult } from \"./types\";\nimport { getBranchSha } from \"./branch\";\nimport { ghFetchOrThrow, repoPath } from \"./client\";\n\ntype TreeEntry =\n | { path: string; mode: \"100644\"; type: \"blob\"; content: string }\n | { path: string; mode: \"100644\"; type: \"blob\"; sha: string }\n /* sha: null in a tree entry deletes the path — GitHub's documented way to remove a file via the trees API. */\n | { path: string; mode: \"100644\"; type: \"blob\"; sha: null };\n\nexport async function writeFiles(\n ctx: RepoContext,\n branch: string,\n files: FileInput[],\n message: string,\n opts: { remove?: string[] } = {},\n): Promise<WriteFilesResult> {\n const remove = opts.remove ?? [];\n if (files.length === 0 && remove.length === 0) throw new Error(\"writeFiles: no files given\");\n\n const base = repoPath(ctx);\n\n // The branch tip as of now — not the sha createBranch returned earlier.\n const parentSha = await getBranchSha(ctx, branch);\n\n // Binary files become blobs first; text is inlined.\n const tree: TreeEntry[] = [];\n for (const file of files) {\n if (file.encoding === \"base64\") {\n const blob = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/blobs`, {\n content: file.content,\n encoding: \"base64\",\n });\n tree.push({ path: file.path, mode: \"100644\", type: \"blob\", sha: blob.sha });\n } else {\n tree.push({ path: file.path, mode: \"100644\", type: \"blob\", content: file.content });\n }\n }\n\n for (const path of remove) tree.push({ path, mode: \"100644\", type: \"blob\", sha: null });\n\n const newTree = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/trees`, {\n base_tree: parentSha,\n tree,\n });\n\n const commit = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/commits`, {\n message,\n tree: newTree.sha,\n parents: [parentSha],\n });\n\n // Fast-forward the branch onto the new commit. `force: false` means a\n // concurrent write to this same branch surfaces as a 422 \"Update is not a\n // fast forward\" rather than being silently overwritten.\n await ghFetchOrThrow(\n ctx,\n \"PATCH\",\n `${base}/git/refs/heads/${encodeURIComponent(branch)}`,\n { sha: commit.sha, force: false },\n );\n\n return { branch, commitSha: commit.sha, treeSha: newTree.sha, fileCount: files.length + remove.length };\n}\n","/**\n * openPullRequest — the PR that CI runs on and the client eventually publishes.\n *\n * PR Law (site-starter AGENTS.md): nothing lands on main except through a PR.\n * This module never pushes to the base branch directly.\n */\n\nimport type { PullRequest, RepoContext } from \"./types\";\nimport { baseBranch, ghFetchOrThrow, repoPath } from \"./client\";\n\n/** The subset of GitHub's PR payload this module reads. Observed in the probe. */\nexport interface PullPayload {\n number: number;\n title: string;\n html_url: string;\n state: \"open\" | \"closed\";\n head: { sha: string; ref: string };\n base: { sha: string; ref: string };\n /** null while GitHub is still computing — usually the first ~2 s after creation. */\n mergeable: boolean | null;\n mergeable_state: \"unknown\" | \"clean\" | \"dirty\" | \"blocked\" | \"unstable\" | \"behind\" | \"draft\" | \"has_hooks\";\n merged: boolean;\n /** The description the publish action wrote; the studio reads the file path back out of it. */\n body?: string | null;\n /** Set once merged. */\n merge_commit_sha?: string | null;\n}\n\nexport async function openPullRequest(\n ctx: RepoContext,\n branch: string,\n opts: { title: string; body?: string },\n): Promise<PullRequest> {\n const pr = await ghFetchOrThrow<PullPayload>(ctx, \"POST\", `${repoPath(ctx)}/pulls`, {\n title: opts.title,\n head: branch,\n base: baseBranch(ctx),\n body: opts.body ?? \"\",\n });\n\n return { number: pr.number, html_url: pr.html_url, headSha: pr.head.sha, branch };\n}\n\n/** Live read of a PR — the source of truth for mergeability. */\nexport async function getPullRequest(ctx: RepoContext, number: number): Promise<PullPayload> {\n return ghFetchOrThrow<PullPayload>(ctx, \"GET\", `${repoPath(ctx)}/pulls/${number}`);\n}\n\nexport interface OpenPullRequest extends PullRequest {\n title: string;\n}\n\n/**\n * Open PRs whose head branch starts with `headPrefix` — the dashboard's own\n * in-flight changes (04b names them `admin/{type}/{file}-{stamp}`). One page of\n * 100 is far more than a content site ever has open at once.\n */\nexport async function listOpenPullRequests(ctx: RepoContext, opts: { headPrefix: string }): Promise<OpenPullRequest[]> {\n const prs = await ghFetchOrThrow<PullPayload[]>(ctx, \"GET\", `${repoPath(ctx)}/pulls?state=open&per_page=100`);\n return prs\n .filter((p) => p.head.ref.startsWith(opts.headPrefix))\n .map((p) => ({ number: p.number, title: p.title, html_url: p.html_url, headSha: p.head.sha, branch: p.head.ref }));\n}\n\n/**\n * Close a PR without merging — the client changed their mind. The branch is\n * the caller's to delete (deleteBranch) once the close succeeds; closing first\n * means a failed delete leaves a closed PR, never an open one pointing at nothing.\n */\nexport async function closePullRequest(ctx: RepoContext, number: number): Promise<void> {\n await ghFetchOrThrow<PullPayload>(ctx, \"PATCH\", `${repoPath(ctx)}/pulls/${number}`, { state: \"closed\" });\n}\n","/**\n * getPullRequestStatus — green, red or pending. Nothing else.\n *\n * ── Why not the check-runs endpoint ──────────────────────────────────────────\n * A fine-grained PAT cannot read it. Observed 403 on every probe run, and the\n * token permission picker has no \"Checks\" entry to grant. So status is derived\n * from two things a PAT can always read:\n *\n * 1. the PR's `mergeable_state`, which folds in required checks when branch\n * protection is on;\n * 2. the combined commit status for the head sha, which is where Vercel and\n * any status-based CI report — and the only place a failing check has a\n * name.\n *\n * The accepted limitation: a GitHub Actions failure surfaces only as\n * `blocked`/`unstable`, not by job name. That is red without a name.\n *\n * ── Two traps, both observed ─────────────────────────────────────────────────\n * `mergeable` is null and `mergeable_state` is \"unknown\" for ~2 s after a PR is\n * created. That is pending, never red.\n *\n * The combined status of ZERO statuses has `state: \"pending\"`, not \"success\".\n * A no-CI repo read naively would be pending forever. So `total_count` is\n * checked before `state`.\n */\n\nimport type { PullRequestStatus, RepoContext } from \"./types\";\nimport { ghFetch, ghFetchOrThrow, repoPath } from \"./client\";\nimport { getPullRequest } from \"./pulls\";\n\n/** Observed shape of GET /commits/{sha}/status. */\nexport interface CombinedStatus {\n state: \"success\" | \"failure\" | \"pending\" | \"error\";\n total_count: number;\n statuses: Array<{ context: string; state: string; description: string | null; target_url?: string | null }>;\n}\n\nexport async function getPullRequestStatus(\n ctx: RepoContext,\n number: number,\n log: Pick<Console, \"warn\"> = console,\n): Promise<PullRequestStatus> {\n const pr = await getPullRequest(ctx, number);\n\n if (pr.mergeable === null || pr.mergeable_state === \"unknown\") {\n return { state: \"pending\" };\n }\n\n const combined = await ghFetchOrThrow<CombinedStatus>(\n ctx,\n \"GET\",\n `${repoPath(ctx)}/commits/${pr.head.sha}/status`,\n );\n\n const failing = combined.statuses.find((s) => s.state === \"failure\" || s.state === \"error\");\n\n // dirty is a conflict, not a check failure — mergePullRequest turns it into\n // StaleBaseError. Report it as red here so a poller stops polling.\n if (pr.mergeable_state === \"dirty\") {\n return { state: \"red\", failingCheck: failing?.context ?? \"merge conflict\" };\n }\n\n if (failing || pr.mergeable_state === \"unstable\") {\n return { state: \"red\", failingCheck: failing?.context ?? null };\n }\n\n if (combined.total_count === 0) {\n if (pr.mergeable_state === \"clean\" || pr.mergeable_state === \"behind\") {\n const warning = `[admin/git] ${ctx.owner}/${ctx.repo} has no CI statuses on PR #${number} — treating as green. A client site with no CI is a setup bug.`;\n log.warn(warning);\n return { state: \"green\", warning };\n }\n // blocked with nothing reported yet: required checks have not started.\n return { state: \"pending\" };\n }\n\n if (combined.state === \"success\" && pr.mergeable_state !== \"blocked\") {\n const previewUrl = await previewUrlFor(ctx, pr.head.sha, combined);\n return previewUrl ? { state: \"green\", previewUrl } : { state: \"green\" };\n }\n\n return { state: \"pending\" };\n}\n\n/** Observed shape of GET /deployments and /deployments/{id}/statuses (only what we read). */\ninterface Deployment { id: number; environment: string }\ninterface DeploymentStatus { state: string; environment_url?: string | null }\n\n/** Dashboard pages, not sites — Vercel's commit status points at its inspector, which 404s for anyone else. */\nconst DASHBOARD_HOST = /^https:\\/\\/(www\\.)?(vercel\\.com|app\\.netlify\\.com)\\//i;\n\n/**\n * The preview site's URL, so the client can look before publishing.\n *\n * Preferred source: the GitHub Deployments API, where Vercel/Netlify record the\n * preview hostname as `environment_url`. Needs \"Deployments: read\" on the PAT;\n * a 403 just means no preview link. Fallback: a deploy-ish commit status whose\n * `target_url` is a real site (Vercel's is its dashboard, so it is skipped).\n */\nasync function previewUrlFor(ctx: RepoContext, sha: string, combined: CombinedStatus): Promise<string | null> {\n const deployments = await ghFetch<Deployment[]>(ctx, \"GET\", `${repoPath(ctx)}/deployments?sha=${sha}&per_page=10`);\n if (deployments.ok && Array.isArray(deployments.json)) {\n for (const d of deployments.json.filter((d) => !/^prod/i.test(d.environment))) {\n const statuses = await ghFetch<DeploymentStatus[]>(ctx, \"GET\", `${repoPath(ctx)}/deployments/${d.id}/statuses?per_page=10`);\n if (!statuses.ok || !Array.isArray(statuses.json)) continue;\n const live = statuses.json.find((s) => s.state === \"success\" && s.environment_url && !DASHBOARD_HOST.test(s.environment_url));\n if (live?.environment_url) return live.environment_url;\n }\n }\n const status = combined.statuses.find(\n (s) => s.state === \"success\" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url && !DASHBOARD_HOST.test(s.target_url),\n );\n return status?.target_url ?? null;\n}\n","/**\n * mergePullRequest — publish. Squash, then delete the branch.\n *\n * `requireGreen` defaults to true. The whole point of the PR flow is that CI\n * catches bad frontmatter before it freezes the site; merging red automates the\n * exact failure the flow exists to prevent.\n *\n * Pending is the common case (checks are always pending right after a push),\n * so it is returned for the caller to poll — never thrown.\n */\n\nimport type { MergeResult, RepoContext } from \"./types\";\nimport { deleteBranch } from \"./branch\";\nimport { ghFetch, repoPath, toError } from \"./client\";\nimport { ChecksFailedError, StaleBaseError } from \"./errors\";\nimport { getPullRequest } from \"./pulls\";\nimport { getPullRequestStatus } from \"./status\";\n\ninterface MergeResponse {\n sha: string;\n merged: boolean;\n message: string;\n}\n\nexport interface MergeOptions {\n /** Refuse to merge unless checks are green. Default true. */\n requireGreen?: boolean;\n /** Delete the head branch after a successful merge. Default true. */\n deleteBranch?: boolean;\n /** Squash commit title. Defaults to GitHub's (the PR title + number). */\n commitTitle?: string;\n}\n\nexport async function mergePullRequest(\n ctx: RepoContext,\n number: number,\n opts: MergeOptions = {},\n): Promise<MergeResult> {\n const requireGreen = opts.requireGreen ?? true;\n const path = `${repoPath(ctx)}/pulls/${number}/merge`;\n\n if (requireGreen) {\n const status = await getPullRequestStatus(ctx, number);\n if (status.state === \"pending\") return { merged: false, state: \"pending\" };\n if (status.state === \"red\") {\n // A conflict is not a failed check — it is a stale base. Distinguish it.\n const pr = await getPullRequest(ctx, number);\n if (pr.mergeable_state === \"dirty\") {\n throw new StaleBaseError(number, { status: 0, path, body: pr });\n }\n throw new ChecksFailedError(number, status.failingCheck);\n }\n }\n\n const res = await ghFetch<MergeResponse>(ctx, \"PUT\", path, {\n merge_method: \"squash\",\n ...(opts.commitTitle ? { commit_title: opts.commitTitle } : {}),\n });\n\n if (!res.ok) {\n // Observed: 405 { message: \"Pull Request has merge conflicts\" } for a\n // stale base. Also 405 for \"not mergeable\" generally, and 409 if the head\n // sha moved between read and merge — both are the same recovery for the\n // caller (re-read, rebuild), so both map to StaleBaseError.\n if (res.status === 405 || res.status === 409) {\n throw new StaleBaseError(number, { status: res.status, path, body: res.json });\n }\n throw toError(res, path);\n }\n\n if (opts.deleteBranch ?? true) {\n const pr = await getPullRequest(ctx, number);\n await deleteBranch(ctx, pr.head.ref);\n }\n\n return { merged: true, sha: res.json.sha };\n}\n","/**\n * Read helpers — list a folder, open a file — straight from the repo at call time.\n *\n * The dashboard lists and opens content from `main` via the Contents API, never\n * from the deployed filesystem: Vercel's bundle only contains what the tracer\n * saw, and it is a snapshot of the last deploy rather than of the repo. This\n * is also how hand-written files (github.com, Desktop, Cowork) show up — the\n * dashboard never assumes it authored anything.\n */\n\nimport type { RepoContext } from \"./types\";\nimport { baseBranch, ghFetch, repoPath, toError } from \"./client\";\nimport { GitHubError } from \"./errors\";\n\nexport interface RepoEntry {\n name: string;\n path: string;\n sha: string;\n size: number;\n type: \"file\";\n}\n\nexport interface RepoFile {\n path: string;\n sha: string;\n content: string;\n}\n\nexport class NotFoundError extends GitHubError {\n constructor(path: string) {\n super(`Not found in repo: ${path}`, { status: 404, path, body: null });\n this.name = \"NotFoundError\";\n }\n}\n\ninterface ContentsEntry {\n name: string;\n path: string;\n sha: string;\n size: number;\n type: \"file\" | \"dir\" | \"symlink\" | \"submodule\";\n content?: string;\n encoding?: string;\n}\n\nfunction contentsPath(ctx: RepoContext, path: string, ref?: string): string {\n const clean = path.replace(/^\\/+|\\/+$/g, \"\");\n const encoded = clean.split(\"/\").map(encodeURIComponent).join(\"/\");\n return `${repoPath(ctx)}/contents/${encoded}?ref=${encodeURIComponent(ref ?? baseBranch(ctx))}`;\n}\n\n/** Files directly inside `path`, sorted by name. Missing folder → []. */\nexport async function listDirectory(ctx: RepoContext, path: string, opts: { ref?: string } = {}): Promise<RepoEntry[]> {\n const url = contentsPath(ctx, path, opts.ref);\n const res = await ghFetch<ContentsEntry[] | ContentsEntry>(ctx, \"GET\", url);\n if (res.status === 404) return [];\n if (!res.ok) throw toError(res, url);\n const list = Array.isArray(res.json) ? res.json : [];\n return list\n .filter((e) => e.type === \"file\")\n .map((e) => ({ name: e.name, path: e.path, sha: e.sha, size: e.size, type: \"file\" as const }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** One file, decoded to utf-8. */\nexport async function readFile(ctx: RepoContext, path: string, opts: { ref?: string } = {}): Promise<RepoFile> {\n const url = contentsPath(ctx, path, opts.ref);\n const res = await ghFetch<ContentsEntry>(ctx, \"GET\", url);\n if (res.status === 404) throw new NotFoundError(path);\n if (!res.ok) throw toError(res, url);\n const entry = res.json;\n if (Array.isArray(entry) || entry.type !== \"file\") throw new GitHubError(`${path} is not a file`, { status: res.status, path: url, body: entry });\n if (typeof entry.content !== \"string\") {\n // GitHub omits content for files over 1 MB; MDX never gets near that.\n throw new GitHubError(`${path} is too large to read through the Contents API`, { status: res.status, path: url, body: null });\n }\n const content = decodeBase64Utf8(entry.content);\n return { path: entry.path, sha: entry.sha, content };\n}\n\n/** The Contents API wraps base64 at 60 columns; strip whitespace before decoding. */\nfunction decodeBase64Utf8(b64: string): string {\n const clean = b64.replace(/\\s+/g, \"\");\n const bin = atob(clean);\n const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));\n return new TextDecoder(\"utf-8\").decode(bytes);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/git/errors.ts","../../src/git/http.ts","../../src/git/app-auth.ts","../../src/git/client.ts","../../src/git/branch.ts","../../src/git/files.ts","../../src/git/pulls.ts","../../src/git/status.ts","../../src/git/merge.ts","../../src/git/reads.ts"],"names":["createSign","repoPath","d"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors — so ADMIN-04 can `instanceof` rather than string-match.\n *\n * Each carries enough to log usefully without ever including the token.\n */\n\n/** Any non-2xx from GitHub that is not one of the more specific cases below. */\nexport class GitHubError extends Error {\n readonly status: number;\n readonly path: string;\n readonly body: unknown;\n\n constructor(message: string, opts: { status: number; path: string; body: unknown }) {\n super(message);\n this.name = \"GitHubError\";\n this.status = opts.status;\n this.path = opts.path;\n this.body = opts.body;\n }\n}\n\n/**\n * The pull request cannot merge because `main` moved underneath it in a way\n * that conflicts — someone else edited the same file first.\n *\n * Observed in the probe: a stale base sha does NOT fail the trees write. Tree,\n * commit, ref and PR all succeed; the conflict only shows as\n * `mergeable_state: \"dirty\"` and then a 405 \"Pull Request has merge conflicts\"\n * on merge. So this is raised from mergePullRequest, never from writeFiles.\n * The recovery is the caller's: re-read main, rebuild the change, open a new PR.\n */\nexport class StaleBaseError extends GitHubError {\n readonly pullNumber: number;\n\n constructor(pullNumber: number, opts: { status: number; path: string; body: unknown }) {\n super(`Pull request #${pullNumber} conflicts with the base branch`, opts);\n this.name = \"StaleBaseError\";\n this.pullNumber = pullNumber;\n }\n}\n\n/**\n * The token cannot do what was asked. A 403 from GitHub with a fine-grained PAT\n * almost always means a permission was not granted when the token was made —\n * a setup bug at onboarding, not a runtime condition.\n */\nexport class TokenScopeError extends GitHubError {\n static readonly REQUIRED_PERMISSIONS =\n \"Contents: Read and write, Pull requests: Read and write, Commit statuses: Read-only\";\n\n constructor(opts: { status: number; path: string; body: unknown }) {\n super(\n `GitHub refused ${opts.path} (${opts.status}). The repo token needs these fine-grained permissions: ${TokenScopeError.REQUIRED_PERMISSIONS}`,\n opts,\n );\n this.name = \"TokenScopeError\";\n }\n}\n\n/** mergePullRequest was asked to merge while checks are red. */\nexport class ChecksFailedError extends Error {\n readonly pullNumber: number;\n readonly failingCheck: string | null;\n\n constructor(pullNumber: number, failingCheck: string | null) {\n super(\n `Pull request #${pullNumber} has failing checks${failingCheck ? ` (${failingCheck})` : \"\"}`,\n );\n this.name = \"ChecksFailedError\";\n this.pullNumber = pullNumber;\n this.failingCheck = failingCheck;\n }\n}\n","/**\n * The one raw request to api.github.com — bounded by a timeout, JSON-decoded,\n * never throwing on non-2xx. `client.ts` adds the credential; `app-auth.ts`\n * uses it with an app JWT to mint that credential. Nothing else calls fetch.\n */\n\nexport const GITHUB_API = \"https://api.github.com\";\nexport const GITHUB_TIMEOUT_MS = 10_000;\n\nexport interface GitHubResponse<T = unknown> {\n ok: boolean;\n status: number;\n json: T;\n}\n\nexport type GitHubMethod = \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n\nexport async function githubRequest<T = unknown>(\n method: GitHubMethod,\n path: string,\n authorization: string,\n body?: unknown,\n fetchImpl: typeof fetch = fetch,\n): Promise<GitHubResponse<T>> {\n const res = await fetchImpl(GITHUB_API + path, {\n method,\n signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),\n headers: {\n Authorization: authorization,\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n \"User-Agent\": \"realiizlabs-admin\",\n ...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const text = await res.text();\n let json: unknown = null;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n json = { raw: text };\n }\n }\n return { ok: res.ok, status: res.status, json: json as T };\n}\n","/**\n * GitHub App authentication — how Studio reaches a client's repo without anyone\n * minting a personal access token.\n *\n * The client installs the \"Realiiz Studio\" app on their repo once. Realiiz holds\n * the app's private key. Per request, this module signs a short-lived JWT with\n * that key, finds the app's installation on the repo, and exchanges the JWT for an\n * installation token scoped to that one repo (valid ~1 hour). The token is cached\n * in memory until shortly before it expires, so a publish costs one extra call at\n * most once an hour per repo, not per request.\n *\n * Only Node's built-in crypto is used — no new dependency. This module is\n * server-only by virtue of living in src/git.\n */\n\nimport { createSign } from \"node:crypto\";\nimport { GitHubError } from \"./errors\";\nimport { githubRequest } from \"./http\";\n\nexport interface AppCredentials {\n /** The GitHub App's numeric id (public). */\n appId: string;\n /** The app's private key, PEM text. May arrive with literal \"\\n\" from an env var. */\n privateKey: string;\n}\n\nconst JWT_TTL_S = 9 * 60; // GitHub allows 10 minutes; leave clock-skew room.\nconst REFRESH_BEFORE_MS = 5 * 60_000;\n\ninterface Cached { token: string; expiresAt: number }\nconst cache = new Map<string, Cached>();\n\nfunction b64url(input: Buffer | string): string {\n return Buffer.from(input).toString(\"base64\").replace(/=+$/, \"\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n}\n\n/** A signed app JWT (RS256), good for a few minutes. */\nexport function appJwt(creds: AppCredentials, now = Math.floor(Date.now() / 1000)): string {\n const header = b64url(JSON.stringify({ alg: \"RS256\", typ: \"JWT\" }));\n const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + JWT_TTL_S, iss: creds.appId }));\n const signer = createSign(\"RSA-SHA256\");\n signer.update(`${header}.${payload}`);\n const signature = signer.sign(creds.privateKey.replace(/\\\\n/g, \"\\n\"));\n return `${header}.${payload}.${b64url(signature)}`;\n}\n\n/** Forget a cached token (a 401 means it was revoked early). */\nexport function forgetInstallationToken(creds: AppCredentials, owner: string, repo: string): void {\n cache.delete(`${creds.appId}:${owner}/${repo}`);\n}\n\n/**\n * An installation token for owner/repo, from cache when fresh. Throws a plain\n * GitHubError when the app isn't installed there — the message says who fixes it.\n */\nexport async function installationToken(\n creds: AppCredentials,\n owner: string,\n repo: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string> {\n const key = `${creds.appId}:${owner}/${repo}`;\n const hit = cache.get(key);\n if (hit && hit.expiresAt - Date.now() > REFRESH_BEFORE_MS) return hit.token;\n\n const bearer = `Bearer ${appJwt(creds)}`;\n const repoPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;\n const found = await githubRequest<{ id: number }>(\"GET\", `${repoPath}/installation`, bearer, undefined, fetchImpl);\n if (found.status === 404) {\n throw new GitHubError(\n `The Realiiz Studio GitHub App isn't installed on ${owner}/${repo}. The repository's owner installs it once — ask your web team for the link.`,\n { status: 404, path: `${repoPath}/installation`, body: found.json },\n );\n }\n if (!found.ok) throw new GitHubError(`GitHub ${found.status} looking up the app installation`, { status: found.status, path: `${repoPath}/installation`, body: found.json });\n\n const minted = await githubRequest<{ token: string; expires_at: string }>(\n \"POST\", `/app/installations/${found.json.id}/access_tokens`, bearer, undefined, fetchImpl,\n );\n if (!minted.ok) throw new GitHubError(`GitHub ${minted.status} minting an installation token`, { status: minted.status, path: \"/app/installations/…/access_tokens\", body: minted.json });\n\n cache.set(key, { token: minted.json.token, expiresAt: Date.parse(minted.json.expires_at) });\n return minted.json.token;\n}\n","/**\n * The one place that talks to api.github.com with a repo credential.\n *\n * ── Every call is bounded ────────────────────────────────────────────────────\n * Same pattern as 5degrees-website's HighLevel client: a module constant plus\n * AbortSignal.timeout on every fetch (see http.ts). That fix exists because a\n * slow upstream ran past the serverless function limit and the process was\n * killed before its own error handler could run. A publish is ~10 sequential\n * GitHub calls, so the same failure is available here.\n *\n * ── Credentials ──────────────────────────────────────────────────────────────\n * RepoContext carries either GitHub App credentials (an installation token is\n * minted and cached — app-auth.ts) or a personal access token. Nothing is read\n * from process.env here or anywhere in src/git (BUILD-ENV law).\n */\n\nimport type { RepoContext } from \"./types\";\nimport { GitHubError, TokenScopeError } from \"./errors\";\nimport { forgetInstallationToken, installationToken } from \"./app-auth\";\nimport { githubRequest, type GitHubMethod, type GitHubResponse } from \"./http\";\n\nexport { GITHUB_TIMEOUT_MS, type GitHubResponse } from \"./http\";\n\n/** The bearer credential for this repo — an installation token (app) or the PAT. */\nexport async function credentialFor(ctx: RepoContext): Promise<string> {\n if (ctx.app) return installationToken(ctx.app, ctx.owner, ctx.repo);\n if (ctx.token) return ctx.token;\n throw new GitHubError(`No GitHub credential for ${ctx.owner}/${ctx.repo}: set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY, or GITHUB_CONTENT_TOKEN.`, { status: 0, path: \"\", body: null });\n}\n\n/**\n * Low-level request. Returns instead of throwing on non-2xx so callers can\n * inspect the status when a \"failure\" is a meaningful answer (405 on merge,\n * 422 on a duplicate ref). Use `ghFetchOrThrow` when it is not.\n */\nexport async function ghFetch<T = unknown>(\n ctx: RepoContext,\n method: GitHubMethod,\n path: string,\n body?: unknown,\n): Promise<GitHubResponse<T>> {\n const res = await githubRequest<T>(method, path, `Bearer ${await credentialFor(ctx)}`, body);\n // An installation token revoked early (app reinstalled, key rotated): mint a fresh one and retry once.\n if (res.status === 401 && ctx.app) {\n forgetInstallationToken(ctx.app, ctx.owner, ctx.repo);\n return githubRequest<T>(method, path, `Bearer ${await credentialFor(ctx)}`, body);\n }\n return res;\n}\n\n/** Same as ghFetch but a non-2xx becomes a typed error. */\nexport async function ghFetchOrThrow<T = unknown>(\n ctx: RepoContext,\n method: GitHubMethod,\n path: string,\n body?: unknown,\n): Promise<T> {\n const res = await ghFetch<T>(ctx, method, path, body);\n if (!res.ok) throw toError(res, path);\n return res.json;\n}\n\nexport function toError(res: GitHubResponse, path: string): GitHubError {\n const opts = { status: res.status, path, body: res.json };\n if (res.status === 403) return new TokenScopeError(opts);\n const message =\n (res.json as { message?: string } | null)?.message ?? `GitHub ${res.status} on ${path}`;\n return new GitHubError(message, opts);\n}\n\n/** `/repos/{owner}/{repo}` prefix, built per call. */\nexport function repoPath(ctx: RepoContext): string {\n return `/repos/${encodeURIComponent(ctx.owner)}/${encodeURIComponent(ctx.repo)}`;\n}\n\nexport function baseBranch(ctx: RepoContext): string {\n return ctx.baseBranch ?? \"main\";\n}\n","/**\n * createBranch — a new branch off the base branch, as it is RIGHT NOW.\n *\n * The base sha is fetched from the refs API on every call and never stored.\n * This module is one of four writers to the same repo (github.com, GitHub\n * Desktop, Cowork are the others), so a ref that was current a minute ago is\n * routinely stale. Branching from a cached sha would silently build on old\n * content.\n */\n\nimport type { CreateBranchResult, RepoContext } from \"./types\";\nimport { baseBranch, ghFetch, ghFetchOrThrow, repoPath, toError } from \"./client\";\n\ninterface RefResponse {\n ref: string;\n object: { sha: string; type: string };\n}\n\n/** Read the current tip of a branch. Always a live call. */\nexport async function getBranchSha(ctx: RepoContext, branch: string): Promise<string> {\n const ref = await ghFetchOrThrow<RefResponse>(\n ctx,\n \"GET\",\n `${repoPath(ctx)}/git/ref/heads/${encodeURIComponent(branch)}`,\n );\n return ref.object.sha;\n}\n\nexport async function createBranch(\n ctx: RepoContext,\n branch: string,\n): Promise<CreateBranchResult> {\n const baseSha = await getBranchSha(ctx, baseBranch(ctx));\n\n await ghFetchOrThrow<RefResponse>(ctx, \"POST\", `${repoPath(ctx)}/git/refs`, {\n ref: `refs/heads/${branch}`,\n sha: baseSha,\n });\n\n return { branch, baseSha };\n}\n\n/**\n * Remove a branch. 204 on success. Used after a squash merge so the client's\n * repo does not accumulate one dead branch per publish.\n *\n * Already gone is success too: GitHub's \"automatically delete head branches\"\n * and Dependabot both remove a merged branch on their own, and answer 422\n * \"Reference does not exist\" (or 404) when we arrive second.\n */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n const path = `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`;\n const res = await ghFetch(ctx, \"DELETE\", path);\n if (res.ok || res.status === 404 || res.status === 422) return;\n throw toError(res, path);\n}\n","/**\n * writeFiles — one or more files in ONE commit on an existing branch.\n *\n * Why the trees API and not the Contents API: a post is an MDX file plus a\n * hero image. Two Contents calls make two commits, and the first triggers a\n * build of a half-finished state. One tree, one commit.\n *\n * Encoding rule, observed in the probe: a tree entry can inline utf-8 text as\n * `content`, but has no `encoding` field — a binary must be uploaded first via\n * POST /git/blobs with `encoding: \"base64\"` and referenced in the tree by sha.\n * So a two-file publish with one image is six calls, not five.\n *\n * `opts.remove` deletes paths in the same commit (tree entry with `sha: null`) —\n * how the dashboard takes a post down: one PR that removes the file.\n */\n\nimport type { FileInput, RepoContext, WriteFilesResult } from \"./types\";\nimport { getBranchSha } from \"./branch\";\nimport { ghFetchOrThrow, repoPath } from \"./client\";\n\ntype TreeEntry =\n | { path: string; mode: \"100644\"; type: \"blob\"; content: string }\n | { path: string; mode: \"100644\"; type: \"blob\"; sha: string }\n /* sha: null in a tree entry deletes the path — GitHub's documented way to remove a file via the trees API. */\n | { path: string; mode: \"100644\"; type: \"blob\"; sha: null };\n\nexport async function writeFiles(\n ctx: RepoContext,\n branch: string,\n files: FileInput[],\n message: string,\n opts: { remove?: string[] } = {},\n): Promise<WriteFilesResult> {\n const remove = opts.remove ?? [];\n if (files.length === 0 && remove.length === 0) throw new Error(\"writeFiles: no files given\");\n\n const base = repoPath(ctx);\n\n // The branch tip as of now — not the sha createBranch returned earlier.\n const parentSha = await getBranchSha(ctx, branch);\n\n // Binary files become blobs first; text is inlined.\n const tree: TreeEntry[] = [];\n for (const file of files) {\n if (file.encoding === \"base64\") {\n const blob = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/blobs`, {\n content: file.content,\n encoding: \"base64\",\n });\n tree.push({ path: file.path, mode: \"100644\", type: \"blob\", sha: blob.sha });\n } else {\n tree.push({ path: file.path, mode: \"100644\", type: \"blob\", content: file.content });\n }\n }\n\n for (const path of remove) tree.push({ path, mode: \"100644\", type: \"blob\", sha: null });\n\n const newTree = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/trees`, {\n base_tree: parentSha,\n tree,\n });\n\n const commit = await ghFetchOrThrow<{ sha: string }>(ctx, \"POST\", `${base}/git/commits`, {\n message,\n tree: newTree.sha,\n parents: [parentSha],\n });\n\n // Fast-forward the branch onto the new commit. `force: false` means a\n // concurrent write to this same branch surfaces as a 422 \"Update is not a\n // fast forward\" rather than being silently overwritten.\n await ghFetchOrThrow(\n ctx,\n \"PATCH\",\n `${base}/git/refs/heads/${encodeURIComponent(branch)}`,\n { sha: commit.sha, force: false },\n );\n\n return { branch, commitSha: commit.sha, treeSha: newTree.sha, fileCount: files.length + remove.length };\n}\n","/**\n * openPullRequest — the PR that CI runs on and the client eventually publishes.\n *\n * PR Law (site-starter AGENTS.md): nothing lands on main except through a PR.\n * This module never pushes to the base branch directly.\n */\n\nimport type { PullRequest, RepoContext } from \"./types\";\nimport { baseBranch, ghFetchOrThrow, repoPath } from \"./client\";\n\n/** The subset of GitHub's PR payload this module reads. Observed in the probe. */\nexport interface PullPayload {\n number: number;\n title: string;\n html_url: string;\n state: \"open\" | \"closed\";\n head: { sha: string; ref: string };\n base: { sha: string; ref: string };\n /** null while GitHub is still computing — usually the first ~2 s after creation. */\n mergeable: boolean | null;\n mergeable_state: \"unknown\" | \"clean\" | \"dirty\" | \"blocked\" | \"unstable\" | \"behind\" | \"draft\" | \"has_hooks\";\n merged: boolean;\n /** The description the publish action wrote; the studio reads the file path back out of it. */\n body?: string | null;\n /** Set once merged. */\n merge_commit_sha?: string | null;\n}\n\nexport async function openPullRequest(\n ctx: RepoContext,\n branch: string,\n opts: { title: string; body?: string },\n): Promise<PullRequest> {\n const pr = await ghFetchOrThrow<PullPayload>(ctx, \"POST\", `${repoPath(ctx)}/pulls`, {\n title: opts.title,\n head: branch,\n base: baseBranch(ctx),\n body: opts.body ?? \"\",\n });\n\n return { number: pr.number, html_url: pr.html_url, headSha: pr.head.sha, branch };\n}\n\n/** Live read of a PR — the source of truth for mergeability. */\nexport async function getPullRequest(ctx: RepoContext, number: number): Promise<PullPayload> {\n return ghFetchOrThrow<PullPayload>(ctx, \"GET\", `${repoPath(ctx)}/pulls/${number}`);\n}\n\nexport interface OpenPullRequest extends PullRequest {\n title: string;\n}\n\n/**\n * Open PRs whose head branch starts with `headPrefix` — the dashboard's own\n * in-flight changes (04b names them `admin/{type}/{file}-{stamp}`). One page of\n * 100 is far more than a content site ever has open at once.\n */\nexport async function listOpenPullRequests(ctx: RepoContext, opts: { headPrefix: string }): Promise<OpenPullRequest[]> {\n const prs = await ghFetchOrThrow<PullPayload[]>(ctx, \"GET\", `${repoPath(ctx)}/pulls?state=open&per_page=100`);\n return prs\n .filter((p) => p.head.ref.startsWith(opts.headPrefix))\n .map((p) => ({ number: p.number, title: p.title, html_url: p.html_url, headSha: p.head.sha, branch: p.head.ref }));\n}\n\n/**\n * Close a PR without merging — the client changed their mind. The branch is\n * the caller's to delete (deleteBranch) once the close succeeds; closing first\n * means a failed delete leaves a closed PR, never an open one pointing at nothing.\n */\nexport async function closePullRequest(ctx: RepoContext, number: number): Promise<void> {\n await ghFetchOrThrow<PullPayload>(ctx, \"PATCH\", `${repoPath(ctx)}/pulls/${number}`, { state: \"closed\" });\n}\n","/**\n * getPullRequestStatus — green, red or pending. Nothing else.\n *\n * ── Why not the check-runs endpoint ──────────────────────────────────────────\n * A fine-grained PAT cannot read it. Observed 403 on every probe run, and the\n * token permission picker has no \"Checks\" entry to grant. So status is derived\n * from two things a PAT can always read:\n *\n * 1. the PR's `mergeable_state`, which folds in required checks when branch\n * protection is on;\n * 2. the combined commit status for the head sha, which is where Vercel and\n * any status-based CI report — and the only place a failing check has a\n * name.\n *\n * The accepted limitation: a GitHub Actions failure surfaces only as\n * `blocked`/`unstable`, not by job name. That is red without a name.\n *\n * ── Two traps, both observed ─────────────────────────────────────────────────\n * `mergeable` is null and `mergeable_state` is \"unknown\" for ~2 s after a PR is\n * created. That is pending, never red.\n *\n * The combined status of ZERO statuses has `state: \"pending\"`, not \"success\".\n * A no-CI repo read naively would be pending forever. So `total_count` is\n * checked before `state`.\n */\n\nimport type { PullRequestStatus, RepoContext } from \"./types\";\nimport { ghFetch, ghFetchOrThrow, repoPath } from \"./client\";\nimport { getPullRequest } from \"./pulls\";\n\n/** Observed shape of GET /commits/{sha}/status. */\nexport interface CombinedStatus {\n state: \"success\" | \"failure\" | \"pending\" | \"error\";\n total_count: number;\n statuses: Array<{ context: string; state: string; description: string | null; target_url?: string | null }>;\n}\n\nexport async function getPullRequestStatus(\n ctx: RepoContext,\n number: number,\n log: Pick<Console, \"warn\"> = console,\n): Promise<PullRequestStatus> {\n const pr = await getPullRequest(ctx, number);\n\n if (pr.mergeable === null || pr.mergeable_state === \"unknown\") {\n return { state: \"pending\" };\n }\n\n const combined = await ghFetchOrThrow<CombinedStatus>(\n ctx,\n \"GET\",\n `${repoPath(ctx)}/commits/${pr.head.sha}/status`,\n );\n\n const failing = combined.statuses.find((s) => s.state === \"failure\" || s.state === \"error\");\n\n // dirty is a conflict, not a check failure — mergePullRequest turns it into\n // StaleBaseError. Report it as red here so a poller stops polling.\n if (pr.mergeable_state === \"dirty\") {\n return { state: \"red\", failingCheck: failing?.context ?? \"merge conflict\" };\n }\n\n if (failing || pr.mergeable_state === \"unstable\") {\n return { state: \"red\", failingCheck: failing?.context ?? null };\n }\n\n if (combined.total_count === 0) {\n if (pr.mergeable_state === \"clean\" || pr.mergeable_state === \"behind\") {\n const warning = `[admin/git] ${ctx.owner}/${ctx.repo} has no CI statuses on PR #${number} — treating as green. A client site with no CI is a setup bug.`;\n log.warn(warning);\n return { state: \"green\", warning };\n }\n // blocked with nothing reported yet: required checks have not started.\n return { state: \"pending\" };\n }\n\n if (combined.state === \"success\" && pr.mergeable_state !== \"blocked\") {\n const previewUrl = await previewUrlFor(ctx, pr.head.sha, combined);\n return previewUrl ? { state: \"green\", previewUrl } : { state: \"green\" };\n }\n\n return { state: \"pending\" };\n}\n\n/** Observed shape of GET /deployments and /deployments/{id}/statuses (only what we read). */\ninterface Deployment { id: number; environment: string }\ninterface DeploymentStatus { state: string; environment_url?: string | null }\n\n/** Dashboard pages, not sites — Vercel's commit status points at its inspector, which 404s for anyone else. */\nconst DASHBOARD_HOST = /^https:\\/\\/(www\\.)?(vercel\\.com|app\\.netlify\\.com)\\//i;\n\n/**\n * The preview site's URL, so the client can look before publishing.\n *\n * Preferred source: the GitHub Deployments API, where Vercel/Netlify record the\n * preview hostname as `environment_url`. Needs \"Deployments: read\" on the PAT;\n * a 403 just means no preview link. Fallback: a deploy-ish commit status whose\n * `target_url` is a real site (Vercel's is its dashboard, so it is skipped).\n */\nasync function previewUrlFor(ctx: RepoContext, sha: string, combined: CombinedStatus): Promise<string | null> {\n const deployments = await ghFetch<Deployment[]>(ctx, \"GET\", `${repoPath(ctx)}/deployments?sha=${sha}&per_page=10`);\n if (deployments.ok && Array.isArray(deployments.json)) {\n for (const d of deployments.json.filter((d) => !/^prod/i.test(d.environment))) {\n const statuses = await ghFetch<DeploymentStatus[]>(ctx, \"GET\", `${repoPath(ctx)}/deployments/${d.id}/statuses?per_page=10`);\n if (!statuses.ok || !Array.isArray(statuses.json)) continue;\n const live = statuses.json.find((s) => s.state === \"success\" && s.environment_url && !DASHBOARD_HOST.test(s.environment_url));\n if (live?.environment_url) return live.environment_url;\n }\n }\n const status = combined.statuses.find(\n (s) => s.state === \"success\" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url && !DASHBOARD_HOST.test(s.target_url),\n );\n return status?.target_url ?? null;\n}\n","/**\n * mergePullRequest — publish. Squash, then delete the branch.\n *\n * `requireGreen` defaults to true. The whole point of the PR flow is that CI\n * catches bad frontmatter before it freezes the site; merging red automates the\n * exact failure the flow exists to prevent.\n *\n * Pending is the common case (checks are always pending right after a push),\n * so it is returned for the caller to poll — never thrown.\n */\n\nimport type { MergeResult, RepoContext } from \"./types\";\nimport { deleteBranch } from \"./branch\";\nimport { ghFetch, repoPath, toError } from \"./client\";\nimport { ChecksFailedError, StaleBaseError } from \"./errors\";\nimport { getPullRequest } from \"./pulls\";\nimport { getPullRequestStatus } from \"./status\";\n\ninterface MergeResponse {\n sha: string;\n merged: boolean;\n message: string;\n}\n\nexport interface MergeOptions {\n /** Refuse to merge unless checks are green. Default true. */\n requireGreen?: boolean;\n /** Delete the head branch after a successful merge. Default true. */\n deleteBranch?: boolean;\n /** Squash commit title. Defaults to GitHub's (the PR title + number). */\n commitTitle?: string;\n}\n\nexport async function mergePullRequest(\n ctx: RepoContext,\n number: number,\n opts: MergeOptions = {},\n): Promise<MergeResult> {\n const requireGreen = opts.requireGreen ?? true;\n const path = `${repoPath(ctx)}/pulls/${number}/merge`;\n\n if (requireGreen) {\n const status = await getPullRequestStatus(ctx, number);\n if (status.state === \"pending\") return { merged: false, state: \"pending\" };\n if (status.state === \"red\") {\n // A conflict is not a failed check — it is a stale base. Distinguish it.\n const pr = await getPullRequest(ctx, number);\n if (pr.mergeable_state === \"dirty\") {\n throw new StaleBaseError(number, { status: 0, path, body: pr });\n }\n throw new ChecksFailedError(number, status.failingCheck);\n }\n }\n\n const res = await ghFetch<MergeResponse>(ctx, \"PUT\", path, {\n merge_method: \"squash\",\n ...(opts.commitTitle ? { commit_title: opts.commitTitle } : {}),\n });\n\n if (!res.ok) {\n // Observed: 405 { message: \"Pull Request has merge conflicts\" } for a\n // stale base. Also 405 for \"not mergeable\" generally, and 409 if the head\n // sha moved between read and merge — both are the same recovery for the\n // caller (re-read, rebuild), so both map to StaleBaseError.\n if (res.status === 405 || res.status === 409) {\n throw new StaleBaseError(number, { status: res.status, path, body: res.json });\n }\n throw toError(res, path);\n }\n\n if (opts.deleteBranch ?? true) {\n const pr = await getPullRequest(ctx, number);\n await deleteBranch(ctx, pr.head.ref);\n }\n\n return { merged: true, sha: res.json.sha };\n}\n","/**\n * Read helpers — list a folder, open a file — straight from the repo at call time.\n *\n * The dashboard lists and opens content from `main` via the Contents API, never\n * from the deployed filesystem: Vercel's bundle only contains what the tracer\n * saw, and it is a snapshot of the last deploy rather than of the repo. This\n * is also how hand-written files (github.com, Desktop, Cowork) show up — the\n * dashboard never assumes it authored anything.\n */\n\nimport type { RepoContext } from \"./types\";\nimport { baseBranch, ghFetch, repoPath, toError } from \"./client\";\nimport { GitHubError } from \"./errors\";\n\nexport interface RepoEntry {\n name: string;\n path: string;\n sha: string;\n size: number;\n type: \"file\";\n}\n\nexport interface RepoFile {\n path: string;\n sha: string;\n content: string;\n}\n\nexport class NotFoundError extends GitHubError {\n constructor(path: string) {\n super(`Not found in repo: ${path}`, { status: 404, path, body: null });\n this.name = \"NotFoundError\";\n }\n}\n\ninterface ContentsEntry {\n name: string;\n path: string;\n sha: string;\n size: number;\n type: \"file\" | \"dir\" | \"symlink\" | \"submodule\";\n content?: string;\n encoding?: string;\n}\n\nfunction contentsPath(ctx: RepoContext, path: string, ref?: string): string {\n const clean = path.replace(/^\\/+|\\/+$/g, \"\");\n const encoded = clean.split(\"/\").map(encodeURIComponent).join(\"/\");\n return `${repoPath(ctx)}/contents/${encoded}?ref=${encodeURIComponent(ref ?? baseBranch(ctx))}`;\n}\n\n/** Files directly inside `path`, sorted by name. Missing folder → []. */\nexport async function listDirectory(ctx: RepoContext, path: string, opts: { ref?: string } = {}): Promise<RepoEntry[]> {\n const url = contentsPath(ctx, path, opts.ref);\n const res = await ghFetch<ContentsEntry[] | ContentsEntry>(ctx, \"GET\", url);\n if (res.status === 404) return [];\n if (!res.ok) throw toError(res, url);\n const list = Array.isArray(res.json) ? res.json : [];\n return list\n .filter((e) => e.type === \"file\")\n .map((e) => ({ name: e.name, path: e.path, sha: e.sha, size: e.size, type: \"file\" as const }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** One file, decoded to utf-8. */\nexport async function readFile(ctx: RepoContext, path: string, opts: { ref?: string } = {}): Promise<RepoFile> {\n const url = contentsPath(ctx, path, opts.ref);\n const res = await ghFetch<ContentsEntry>(ctx, \"GET\", url);\n if (res.status === 404) throw new NotFoundError(path);\n if (!res.ok) throw toError(res, url);\n const entry = res.json;\n if (Array.isArray(entry) || entry.type !== \"file\") throw new GitHubError(`${path} is not a file`, { status: res.status, path: url, body: entry });\n if (typeof entry.content !== \"string\") {\n // GitHub omits content for files over 1 MB; MDX never gets near that.\n throw new GitHubError(`${path} is too large to read through the Contents API`, { status: res.status, path: url, body: null });\n }\n const content = decodeBase64Utf8(entry.content);\n return { path: entry.path, sha: entry.sha, content };\n}\n\n/** The Contents API wraps base64 at 60 columns; strip whitespace before decoding. */\nfunction decodeBase64Utf8(b64: string): string {\n const clean = b64.replace(/\\s+/g, \"\");\n const bin = atob(clean);\n const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));\n return new TextDecoder(\"utf-8\").decode(bytes);\n}\n"]}
|
package/dist/git/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RepoContext, C as CreateBranchResult, F as FileInput, W as WriteFilesResult, P as PullRequest, a as PullRequestStatus, M as MergeResult } from '../types-
|
|
2
|
-
export { B as BinaryFile, T as TextFile } from '../types-
|
|
1
|
+
import { R as RepoContext, C as CreateBranchResult, F as FileInput, W as WriteFilesResult, P as PullRequest, a as PullRequestStatus, M as MergeResult } from '../types-Cy9UDz--.cjs';
|
|
2
|
+
export { B as BinaryFile, T as TextFile } from '../types-Cy9UDz--.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* createBranch — a new branch off the base branch, as it is RIGHT NOW.
|
|
@@ -208,4 +208,32 @@ declare function readFile(ctx: RepoContext, path: string, opts?: {
|
|
|
208
208
|
ref?: string;
|
|
209
209
|
}): Promise<RepoFile>;
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
/**
|
|
212
|
+
* GitHub App authentication — how Studio reaches a client's repo without anyone
|
|
213
|
+
* minting a personal access token.
|
|
214
|
+
*
|
|
215
|
+
* The client installs the "Realiiz Studio" app on their repo once. Realiiz holds
|
|
216
|
+
* the app's private key. Per request, this module signs a short-lived JWT with
|
|
217
|
+
* that key, finds the app's installation on the repo, and exchanges the JWT for an
|
|
218
|
+
* installation token scoped to that one repo (valid ~1 hour). The token is cached
|
|
219
|
+
* in memory until shortly before it expires, so a publish costs one extra call at
|
|
220
|
+
* most once an hour per repo, not per request.
|
|
221
|
+
*
|
|
222
|
+
* Only Node's built-in crypto is used — no new dependency. This module is
|
|
223
|
+
* server-only by virtue of living in src/git.
|
|
224
|
+
*/
|
|
225
|
+
interface AppCredentials {
|
|
226
|
+
/** The GitHub App's numeric id (public). */
|
|
227
|
+
appId: string;
|
|
228
|
+
/** The app's private key, PEM text. May arrive with literal "\n" from an env var. */
|
|
229
|
+
privateKey: string;
|
|
230
|
+
}
|
|
231
|
+
/** A signed app JWT (RS256), good for a few minutes. */
|
|
232
|
+
declare function appJwt(creds: AppCredentials, now?: number): string;
|
|
233
|
+
/**
|
|
234
|
+
* An installation token for owner/repo, from cache when fresh. Throws a plain
|
|
235
|
+
* GitHubError when the app isn't installed there — the message says who fixes it.
|
|
236
|
+
*/
|
|
237
|
+
declare function installationToken(creds: AppCredentials, owner: string, repo: string, fetchImpl?: typeof fetch): Promise<string>;
|
|
238
|
+
|
|
239
|
+
export { type AppCredentials, ChecksFailedError, CreateBranchResult, FileInput, GitHubError, type MergeOptions, MergeResult, NotFoundError, type OpenPullRequest, PullRequest, PullRequestStatus, RepoContext, type RepoEntry, type RepoFile, StaleBaseError, TokenScopeError, WriteFilesResult, appJwt, closePullRequest, createBranch, deleteBranch, getPullRequestStatus, installationToken, listDirectory, listOpenPullRequests, mergePullRequest, openPullRequest, readFile, writeFiles };
|
package/dist/git/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RepoContext, C as CreateBranchResult, F as FileInput, W as WriteFilesResult, P as PullRequest, a as PullRequestStatus, M as MergeResult } from '../types-
|
|
2
|
-
export { B as BinaryFile, T as TextFile } from '../types-
|
|
1
|
+
import { R as RepoContext, C as CreateBranchResult, F as FileInput, W as WriteFilesResult, P as PullRequest, a as PullRequestStatus, M as MergeResult } from '../types-Cy9UDz--.js';
|
|
2
|
+
export { B as BinaryFile, T as TextFile } from '../types-Cy9UDz--.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* createBranch — a new branch off the base branch, as it is RIGHT NOW.
|
|
@@ -208,4 +208,32 @@ declare function readFile(ctx: RepoContext, path: string, opts?: {
|
|
|
208
208
|
ref?: string;
|
|
209
209
|
}): Promise<RepoFile>;
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
/**
|
|
212
|
+
* GitHub App authentication — how Studio reaches a client's repo without anyone
|
|
213
|
+
* minting a personal access token.
|
|
214
|
+
*
|
|
215
|
+
* The client installs the "Realiiz Studio" app on their repo once. Realiiz holds
|
|
216
|
+
* the app's private key. Per request, this module signs a short-lived JWT with
|
|
217
|
+
* that key, finds the app's installation on the repo, and exchanges the JWT for an
|
|
218
|
+
* installation token scoped to that one repo (valid ~1 hour). The token is cached
|
|
219
|
+
* in memory until shortly before it expires, so a publish costs one extra call at
|
|
220
|
+
* most once an hour per repo, not per request.
|
|
221
|
+
*
|
|
222
|
+
* Only Node's built-in crypto is used — no new dependency. This module is
|
|
223
|
+
* server-only by virtue of living in src/git.
|
|
224
|
+
*/
|
|
225
|
+
interface AppCredentials {
|
|
226
|
+
/** The GitHub App's numeric id (public). */
|
|
227
|
+
appId: string;
|
|
228
|
+
/** The app's private key, PEM text. May arrive with literal "\n" from an env var. */
|
|
229
|
+
privateKey: string;
|
|
230
|
+
}
|
|
231
|
+
/** A signed app JWT (RS256), good for a few minutes. */
|
|
232
|
+
declare function appJwt(creds: AppCredentials, now?: number): string;
|
|
233
|
+
/**
|
|
234
|
+
* An installation token for owner/repo, from cache when fresh. Throws a plain
|
|
235
|
+
* GitHubError when the app isn't installed there — the message says who fixes it.
|
|
236
|
+
*/
|
|
237
|
+
declare function installationToken(creds: AppCredentials, owner: string, repo: string, fetchImpl?: typeof fetch): Promise<string>;
|
|
238
|
+
|
|
239
|
+
export { type AppCredentials, ChecksFailedError, CreateBranchResult, FileInput, GitHubError, type MergeOptions, MergeResult, NotFoundError, type OpenPullRequest, PullRequest, PullRequestStatus, RepoContext, type RepoEntry, type RepoFile, StaleBaseError, TokenScopeError, WriteFilesResult, appJwt, closePullRequest, createBranch, deleteBranch, getPullRequestStatus, installationToken, listDirectory, listOpenPullRequests, mergePullRequest, openPullRequest, readFile, writeFiles };
|
package/dist/git/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
|
+
import { createSign } from 'crypto';
|
|
2
3
|
|
|
3
4
|
// src/git/index.ts
|
|
4
5
|
|
|
@@ -41,15 +42,15 @@ var ChecksFailedError = class extends Error {
|
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
// src/git/
|
|
45
|
-
var
|
|
45
|
+
// src/git/http.ts
|
|
46
|
+
var GITHUB_API = "https://api.github.com";
|
|
46
47
|
var GITHUB_TIMEOUT_MS = 1e4;
|
|
47
|
-
async function
|
|
48
|
-
const res = await
|
|
48
|
+
async function githubRequest(method, path, authorization, body, fetchImpl = fetch) {
|
|
49
|
+
const res = await fetchImpl(GITHUB_API + path, {
|
|
49
50
|
method,
|
|
50
51
|
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
|
|
51
52
|
headers: {
|
|
52
|
-
Authorization:
|
|
53
|
+
Authorization: authorization,
|
|
53
54
|
Accept: "application/vnd.github+json",
|
|
54
55
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
55
56
|
"User-Agent": "realiizlabs-admin",
|
|
@@ -68,6 +69,65 @@ async function ghFetch(ctx, method, path, body) {
|
|
|
68
69
|
}
|
|
69
70
|
return { ok: res.ok, status: res.status, json };
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
// src/git/app-auth.ts
|
|
74
|
+
var JWT_TTL_S = 9 * 60;
|
|
75
|
+
var REFRESH_BEFORE_MS = 5 * 6e4;
|
|
76
|
+
var cache = /* @__PURE__ */ new Map();
|
|
77
|
+
function b64url(input) {
|
|
78
|
+
return Buffer.from(input).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
79
|
+
}
|
|
80
|
+
function appJwt(creds, now = Math.floor(Date.now() / 1e3)) {
|
|
81
|
+
const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
82
|
+
const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + JWT_TTL_S, iss: creds.appId }));
|
|
83
|
+
const signer = createSign("RSA-SHA256");
|
|
84
|
+
signer.update(`${header}.${payload}`);
|
|
85
|
+
const signature = signer.sign(creds.privateKey.replace(/\\n/g, "\n"));
|
|
86
|
+
return `${header}.${payload}.${b64url(signature)}`;
|
|
87
|
+
}
|
|
88
|
+
function forgetInstallationToken(creds, owner, repo) {
|
|
89
|
+
cache.delete(`${creds.appId}:${owner}/${repo}`);
|
|
90
|
+
}
|
|
91
|
+
async function installationToken(creds, owner, repo, fetchImpl = fetch) {
|
|
92
|
+
const key = `${creds.appId}:${owner}/${repo}`;
|
|
93
|
+
const hit = cache.get(key);
|
|
94
|
+
if (hit && hit.expiresAt - Date.now() > REFRESH_BEFORE_MS) return hit.token;
|
|
95
|
+
const bearer = `Bearer ${appJwt(creds)}`;
|
|
96
|
+
const repoPath2 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
97
|
+
const found = await githubRequest("GET", `${repoPath2}/installation`, bearer, void 0, fetchImpl);
|
|
98
|
+
if (found.status === 404) {
|
|
99
|
+
throw new GitHubError(
|
|
100
|
+
`The Realiiz Studio GitHub App isn't installed on ${owner}/${repo}. The repository's owner installs it once \u2014 ask your web team for the link.`,
|
|
101
|
+
{ status: 404, path: `${repoPath2}/installation`, body: found.json }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!found.ok) throw new GitHubError(`GitHub ${found.status} looking up the app installation`, { status: found.status, path: `${repoPath2}/installation`, body: found.json });
|
|
105
|
+
const minted = await githubRequest(
|
|
106
|
+
"POST",
|
|
107
|
+
`/app/installations/${found.json.id}/access_tokens`,
|
|
108
|
+
bearer,
|
|
109
|
+
void 0,
|
|
110
|
+
fetchImpl
|
|
111
|
+
);
|
|
112
|
+
if (!minted.ok) throw new GitHubError(`GitHub ${minted.status} minting an installation token`, { status: minted.status, path: "/app/installations/\u2026/access_tokens", body: minted.json });
|
|
113
|
+
cache.set(key, { token: minted.json.token, expiresAt: Date.parse(minted.json.expires_at) });
|
|
114
|
+
return minted.json.token;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/git/client.ts
|
|
118
|
+
async function credentialFor(ctx) {
|
|
119
|
+
if (ctx.app) return installationToken(ctx.app, ctx.owner, ctx.repo);
|
|
120
|
+
if (ctx.token) return ctx.token;
|
|
121
|
+
throw new GitHubError(`No GitHub credential for ${ctx.owner}/${ctx.repo}: set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY, or GITHUB_CONTENT_TOKEN.`, { status: 0, path: "", body: null });
|
|
122
|
+
}
|
|
123
|
+
async function ghFetch(ctx, method, path, body) {
|
|
124
|
+
const res = await githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
125
|
+
if (res.status === 401 && ctx.app) {
|
|
126
|
+
forgetInstallationToken(ctx.app, ctx.owner, ctx.repo);
|
|
127
|
+
return githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
128
|
+
}
|
|
129
|
+
return res;
|
|
130
|
+
}
|
|
71
131
|
async function ghFetchOrThrow(ctx, method, path, body) {
|
|
72
132
|
const res = await ghFetch(ctx, method, path, body);
|
|
73
133
|
if (!res.ok) throw toError(res, path);
|
|
@@ -289,6 +349,6 @@ function decodeBase64Utf8(b64) {
|
|
|
289
349
|
return new TextDecoder("utf-8").decode(bytes);
|
|
290
350
|
}
|
|
291
351
|
|
|
292
|
-
export { ChecksFailedError, GitHubError, NotFoundError, StaleBaseError, TokenScopeError, closePullRequest, createBranch, deleteBranch, getPullRequestStatus, listDirectory, listOpenPullRequests, mergePullRequest, openPullRequest, readFile, writeFiles };
|
|
352
|
+
export { ChecksFailedError, GitHubError, NotFoundError, StaleBaseError, TokenScopeError, appJwt, closePullRequest, createBranch, deleteBranch, getPullRequestStatus, installationToken, listDirectory, listOpenPullRequests, mergePullRequest, openPullRequest, readFile, writeFiles };
|
|
293
353
|
//# sourceMappingURL=index.js.map
|
|
294
354
|
//# sourceMappingURL=index.js.map
|