@realiizlabs/admin 0.8.1 → 0.9.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.
@@ -198,11 +198,27 @@ async function getPullRequestStatus(ctx, number, log = console) {
198
198
  return { state: "pending" };
199
199
  }
200
200
  if (combined.state === "success" && pr.mergeable_state !== "blocked") {
201
- const preview = combined.statuses.find((s) => s.state === "success" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url);
202
- return preview?.target_url ? { state: "green", previewUrl: preview.target_url } : { state: "green" };
201
+ const previewUrl = await previewUrlFor(ctx, pr.head.sha, combined);
202
+ return previewUrl ? { state: "green", previewUrl } : { state: "green" };
203
203
  }
204
204
  return { state: "pending" };
205
205
  }
206
+ var DASHBOARD_HOST = /^https:\/\/(www\.)?(vercel\.com|app\.netlify\.com)\//i;
207
+ async function previewUrlFor(ctx, sha, combined) {
208
+ const deployments = await ghFetch(ctx, "GET", `${repoPath(ctx)}/deployments?sha=${sha}&per_page=10`);
209
+ if (deployments.ok && Array.isArray(deployments.json)) {
210
+ for (const d of deployments.json.filter((d2) => !/^prod/i.test(d2.environment))) {
211
+ const statuses = await ghFetch(ctx, "GET", `${repoPath(ctx)}/deployments/${d.id}/statuses?per_page=10`);
212
+ if (!statuses.ok || !Array.isArray(statuses.json)) continue;
213
+ const live = statuses.json.find((s) => s.state === "success" && s.environment_url && !DASHBOARD_HOST.test(s.environment_url));
214
+ if (live?.environment_url) return live.environment_url;
215
+ }
216
+ }
217
+ const status = combined.statuses.find(
218
+ (s) => s.state === "success" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url && !DASHBOARD_HOST.test(s.target_url)
219
+ );
220
+ return status?.target_url ?? null;
221
+ }
206
222
 
207
223
  // src/git/merge.ts
208
224
  async function mergePullRequest(ctx, number, opts = {}) {
@@ -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":[],"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, ghFetchOrThrow, repoPath } 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 */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n await ghFetchOrThrow(\n ctx,\n \"DELETE\",\n `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`,\n );\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}\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 { 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 // A successful deploy-preview status carries the preview's URL — the client can look before publishing.\n const preview = combined.statuses.find((s) => s.state === \"success\" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url);\n return preview?.target_url ? { state: \"green\", previewUrl: preview.target_url } : { state: \"green\" };\n }\n\n return { state: \"pending\" };\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/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, ghFetchOrThrow, repoPath } 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 */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n await ghFetchOrThrow(\n ctx,\n \"DELETE\",\n `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`,\n );\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}\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.js CHANGED
@@ -196,11 +196,27 @@ async function getPullRequestStatus(ctx, number, log = console) {
196
196
  return { state: "pending" };
197
197
  }
198
198
  if (combined.state === "success" && pr.mergeable_state !== "blocked") {
199
- const preview = combined.statuses.find((s) => s.state === "success" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url);
200
- return preview?.target_url ? { state: "green", previewUrl: preview.target_url } : { state: "green" };
199
+ const previewUrl = await previewUrlFor(ctx, pr.head.sha, combined);
200
+ return previewUrl ? { state: "green", previewUrl } : { state: "green" };
201
201
  }
202
202
  return { state: "pending" };
203
203
  }
204
+ var DASHBOARD_HOST = /^https:\/\/(www\.)?(vercel\.com|app\.netlify\.com)\//i;
205
+ async function previewUrlFor(ctx, sha, combined) {
206
+ const deployments = await ghFetch(ctx, "GET", `${repoPath(ctx)}/deployments?sha=${sha}&per_page=10`);
207
+ if (deployments.ok && Array.isArray(deployments.json)) {
208
+ for (const d of deployments.json.filter((d2) => !/^prod/i.test(d2.environment))) {
209
+ const statuses = await ghFetch(ctx, "GET", `${repoPath(ctx)}/deployments/${d.id}/statuses?per_page=10`);
210
+ if (!statuses.ok || !Array.isArray(statuses.json)) continue;
211
+ const live = statuses.json.find((s) => s.state === "success" && s.environment_url && !DASHBOARD_HOST.test(s.environment_url));
212
+ if (live?.environment_url) return live.environment_url;
213
+ }
214
+ }
215
+ const status = combined.statuses.find(
216
+ (s) => s.state === "success" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url && !DASHBOARD_HOST.test(s.target_url)
217
+ );
218
+ return status?.target_url ?? null;
219
+ }
204
220
 
205
221
  // src/git/merge.ts
206
222
  async function mergePullRequest(ctx, number, opts = {}) {
@@ -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":[],"mappings":"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","file":"index.js","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, ghFetchOrThrow, repoPath } 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 */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n await ghFetchOrThrow(\n ctx,\n \"DELETE\",\n `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`,\n );\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}\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 { 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 // A successful deploy-preview status carries the preview's URL — the client can look before publishing.\n const preview = combined.statuses.find((s) => s.state === \"success\" && /vercel|netlify|deploy|preview/i.test(s.context) && s.target_url);\n return preview?.target_url ? { state: \"green\", previewUrl: preview.target_url } : { state: \"green\" };\n }\n\n return { state: \"pending\" };\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/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.js","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, ghFetchOrThrow, repoPath } 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 */\nexport async function deleteBranch(ctx: RepoContext, branch: string): Promise<void> {\n await ghFetchOrThrow(\n ctx,\n \"DELETE\",\n `${repoPath(ctx)}/git/refs/heads/${encodeURIComponent(branch)}`,\n );\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}\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"]}
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ // src/media/errors.ts
5
+ var MediaError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "MediaError";
9
+ }
10
+ };
11
+ var MESSAGES = {
12
+ type: "That file type isn\u2019t supported \u2014 use a JPG, PNG or WebP.",
13
+ size: "That picture is too big to send \u2014 pick one under 10MB.",
14
+ encode: "This browser can\u2019t prepare pictures \u2014 try Chrome, Edge or Safari 16+."
15
+ };
16
+
17
+ // src/media/resize.ts
18
+ var ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp", "image/gif"];
19
+ var MAX_INPUT_BYTES = 10 * 1024 * 1024;
20
+ function fitWithin(width, height, maxWidth) {
21
+ if (width <= maxWidth) return { width, height };
22
+ const ratio = maxWidth / width;
23
+ return { width: Math.round(width * ratio), height: Math.max(1, Math.round(height * ratio)) };
24
+ }
25
+ function checkInput(file) {
26
+ if (!ACCEPTED_TYPES.includes(file.type)) throw new MediaError(MESSAGES.type);
27
+ if (file.size > MAX_INPUT_BYTES) throw new MediaError(MESSAGES.size);
28
+ }
29
+ async function resizeImage(file, opts = {}) {
30
+ checkInput(file);
31
+ const { maxWidth = 1600, quality = 0.82, maxBytes = 600 * 1024 } = opts;
32
+ const bitmap = await decode(file);
33
+ const { width, height } = fitWithin(bitmap.width, bitmap.height, maxWidth);
34
+ const canvas = makeCanvas(width, height);
35
+ const ctx = canvas.getContext("2d");
36
+ if (!ctx) throw new MediaError(MESSAGES.encode);
37
+ ctx.drawImage(bitmap, 0, 0, width, height);
38
+ bitmap.close?.();
39
+ let q = quality;
40
+ let blob = await encode(canvas, q);
41
+ while (blob.size > maxBytes && q - 0.08 >= 0.5) {
42
+ q -= 0.08;
43
+ blob = await encode(canvas, q);
44
+ }
45
+ return { blob, width, height };
46
+ }
47
+ async function decode(file) {
48
+ try {
49
+ return await createImageBitmap(file);
50
+ } catch {
51
+ throw new MediaError(MESSAGES.encode);
52
+ }
53
+ }
54
+ function makeCanvas(width, height) {
55
+ if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(width, height);
56
+ const c = document.createElement("canvas");
57
+ c.width = width;
58
+ c.height = height;
59
+ return c;
60
+ }
61
+ async function encode(canvas, quality) {
62
+ const blob = "convertToBlob" in canvas ? await canvas.convertToBlob({ type: "image/webp", quality }).catch(() => null) : await new Promise((resolve) => canvas.toBlob(resolve, "image/webp", quality));
63
+ if (!blob || blob.type !== "image/webp") throw new MediaError(MESSAGES.encode);
64
+ return blob;
65
+ }
66
+
67
+ // src/media/paths.ts
68
+ function slugifyName(name) {
69
+ const base = name.replace(/\.[a-z0-9]+$/i, "");
70
+ const s = base.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
71
+ return s || "image";
72
+ }
73
+ function imagePathFor(kind, slug, originalName = "", opts = {}) {
74
+ const dir = (opts.publicDir ?? "/blog").replace(/\/$/, "");
75
+ switch (kind) {
76
+ case "hero":
77
+ return `${dir}/${slug}.webp`;
78
+ case "social":
79
+ return `${dir}/${slug}-social.webp`;
80
+ case "body":
81
+ return `${dir}/${slug}/${slugifyName(originalName)}.webp`;
82
+ }
83
+ }
84
+
85
+ exports.ACCEPTED_TYPES = ACCEPTED_TYPES;
86
+ exports.MAX_INPUT_BYTES = MAX_INPUT_BYTES;
87
+ exports.MEDIA_MESSAGES = MESSAGES;
88
+ exports.MediaError = MediaError;
89
+ exports.checkInput = checkInput;
90
+ exports.fitWithin = fitWithin;
91
+ exports.imagePathFor = imagePathFor;
92
+ exports.resizeImage = resizeImage;
93
+ exports.slugifyName = slugifyName;
94
+ //# sourceMappingURL=index.cjs.map
95
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/media/errors.ts","../../src/media/resize.ts","../../src/media/paths.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** A picture problem the person can act on. The message is the sentence shown under the control. */\nexport class MediaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MediaError\";\n }\n}\n\nexport const MESSAGES = {\n type: \"That file type isn’t supported — use a JPG, PNG or WebP.\",\n size: \"That picture is too big to send — pick one under 10MB.\",\n encode: \"This browser can’t prepare pictures — try Chrome, Edge or Safari 16+.\",\n} as const;\n","/**\n * resizeImage — shrink a picture in the browser to web size, as WebP.\n *\n * Phone photos are 3–8MB; a Vercel server action body is capped at 4.5MB and the\n * GitHub API wants base64. So the shrink happens here, before anything leaves the\n * browser: decode → fit inside maxWidth → draw to a canvas → encode WebP, lowering\n * quality in steps until the result is under maxBytes. GIFs lose their animation\n * (a still frame is encoded) — acceptable for a blog.\n *\n * No network, no dependencies. Throws MediaError with a plain sentence for the\n * three things that can go wrong: wrong type, too big to even try, or a browser\n * that cannot encode WebP (toBlob returns null or a non-WebP type).\n */\n\nimport { MediaError, MESSAGES } from \"./errors\";\n\nexport const ACCEPTED_TYPES = [\"image/jpeg\", \"image/png\", \"image/webp\", \"image/gif\"] as const;\nexport const MAX_INPUT_BYTES = 10 * 1024 * 1024;\n\nexport interface ResizeOptions {\n /** Longest allowed width in pixels. Default 1600. */\n maxWidth?: number;\n /** Starting WebP quality 0–1. Default 0.82. */\n quality?: number;\n /** Target ceiling for the encoded size. Default 600KB. */\n maxBytes?: number;\n}\n\nexport interface ResizedImage {\n blob: Blob;\n width: number;\n height: number;\n}\n\n/** Pure: fit (width × height) inside maxWidth, never scaling up, whole pixels. */\nexport function fitWithin(width: number, height: number, maxWidth: number): { width: number; height: number } {\n if (width <= maxWidth) return { width, height };\n const ratio = maxWidth / width;\n return { width: Math.round(width * ratio), height: Math.max(1, Math.round(height * ratio)) };\n}\n\n/** The type/size guards, separated so they can run (and be tested) without a canvas. */\nexport function checkInput(file: { type: string; size: number }): void {\n if (!(ACCEPTED_TYPES as readonly string[]).includes(file.type)) throw new MediaError(MESSAGES.type);\n if (file.size > MAX_INPUT_BYTES) throw new MediaError(MESSAGES.size);\n}\n\nexport async function resizeImage(file: File, opts: ResizeOptions = {}): Promise<ResizedImage> {\n checkInput(file);\n const { maxWidth = 1600, quality = 0.82, maxBytes = 600 * 1024 } = opts;\n\n const bitmap = await decode(file);\n const { width, height } = fitWithin(bitmap.width, bitmap.height, maxWidth);\n const canvas = makeCanvas(width, height);\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new MediaError(MESSAGES.encode);\n ctx.drawImage(bitmap, 0, 0, width, height);\n bitmap.close?.();\n\n let q = quality;\n let blob = await encode(canvas, q);\n while (blob.size > maxBytes && q - 0.08 >= 0.5) {\n q -= 0.08;\n blob = await encode(canvas, q);\n }\n return { blob, width, height };\n}\n\nasync function decode(file: File): Promise<ImageBitmap> {\n try {\n return await createImageBitmap(file);\n } catch {\n throw new MediaError(MESSAGES.encode);\n }\n}\n\ntype AnyCanvas = HTMLCanvasElement | OffscreenCanvas;\n\nfunction makeCanvas(width: number, height: number): AnyCanvas {\n if (typeof OffscreenCanvas !== \"undefined\") return new OffscreenCanvas(width, height);\n const c = document.createElement(\"canvas\");\n c.width = width;\n c.height = height;\n return c;\n}\n\nasync function encode(canvas: AnyCanvas, quality: number): Promise<Blob> {\n const blob =\n \"convertToBlob\" in canvas\n ? await canvas.convertToBlob({ type: \"image/webp\", quality }).catch(() => null)\n : await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, \"image/webp\", quality));\n if (!blob || blob.type !== \"image/webp\") throw new MediaError(MESSAGES.encode);\n return blob;\n}\n","/**\n * Where a picture lives, decided from the post's slug so the path is known before\n * anything is committed: hero → /blog/{slug}.webp, social → /blog/{slug}-social.webp,\n * body → /blog/{slug}/{name}.webp. The host commits the file under public/ + path.\n */\n\nexport type ImageKind = \"hero\" | \"social\" | \"body\";\n\nexport interface PathOptions {\n /** URL prefix the pictures are served from. Default \"/blog\". */\n publicDir?: string;\n}\n\n/** Lower-case letters, digits and single hyphens; empty in → \"image\". */\nexport function slugifyName(name: string): string {\n const base = name.replace(/\\.[a-z0-9]+$/i, \"\");\n const s = base\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return s || \"image\";\n}\n\nexport function imagePathFor(kind: ImageKind, slug: string, originalName = \"\", opts: PathOptions = {}): string {\n const dir = (opts.publicDir ?? \"/blog\").replace(/\\/$/, \"\");\n switch (kind) {\n case \"hero\":\n return `${dir}/${slug}.webp`;\n case \"social\":\n return `${dir}/${slug}-social.webp`;\n case \"body\":\n return `${dir}/${slug}/${slugifyName(originalName)}.webp`;\n }\n}\n"]}