From c4d2e4c78fdfe1c50392ea5f67e038df1c3a28ff Mon Sep 17 00:00:00 2001 From: Maaren <80040706+SSoggyTacoMan@users.noreply.github.com> Date: Tue, 13 Oct 2026 08:51:32 +0200 Subject: [PATCH 1/5] fix(editor): update closed pull requests and harden branch creation --- src/components/navbar-editor.tsx | 50 +++++++++++++++--- src/lib/game-saving/github.ts | 84 ++++++++++++++++++++++++++----- tests/editor-pr-matching.test.mjs | 56 +++++++++++++++++++++ 3 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 tests/editor-pr-matching.test.mjs diff --git a/src/components/navbar-editor.tsx b/src/components/navbar-editor.tsx index 8f9b902542..b97ff0aa33 100644 --- a/src/components/navbar-editor.tsx +++ b/src/components/navbar-editor.tsx @@ -40,7 +40,7 @@ import beautifier from "js-beautify"; import { collapseRanges } from "../lib/codemirror/util"; import { foldAllTemplateLiterals, onRun} from "./big-interactive-pages/editor"; import { showKeyBinding } from '../lib/state'; -import { validateGitHubToken, forkRepository, createBranch, createCommit, fetchLatestCommitSha, createTreeAndCommit, createPullRequest, fetchForkedRepository, updateBranch, createBlobForImage, findGamePullRequest, recordGamePullRequest, fetchCommitTreeSha, updatePullRequest, EDITOR_BRANCH_PREFIX } from "../lib/game-saving/github"; +import { validateGitHubToken, forkRepository, createBranch, createCommit, fetchLatestCommitSha, createTreeAndCommit, createPullRequest, fetchForkedRepository, updateBranch, createBlobForImage, findGamePullRequest, recordGamePullRequest, fetchCommitTreeSha, updatePullRequest, EDITOR_BRANCH_PREFIX, delay } from "../lib/game-saving/github"; const saveName = throttle(500, async (gameId: string, newName: string) => { try { @@ -322,7 +322,7 @@ export default function EditorNavbar(props: EditorNavbarProps) { const publishError = useSignal(false); const publishErrorMessage = useSignal(null); const githubPRUrl = useSignal(null); - const publishOutcome = useSignal<"new" | "updated" | "unchanged">("new"); + const publishOutcome = useSignal<"new" | "updated" | "unchanged" | "reopened" | "closed_updated">("new"); const publishFormTitle = useSignal(null); const publishFormAuthor = useSignal(null); const publishFormDescription = useSignal(""); @@ -676,7 +676,7 @@ export default function EditorNavbar(props: EditorNavbarProps) { } } - const latestCommitSha = await fetchLatestCommitSha(accessToken, "hackclub", "sprig", forkedRepo.default_branch); + const latestCommitSha = await fetchLatestCommitSha(accessToken, "hackclub", "sprig", "main"); if (!latestCommitSha) { trackFailure("github_publish.failure.commit_sha"); throw new Error("Failed to fetch the latest commit SHA."); @@ -685,9 +685,20 @@ export default function EditorNavbar(props: EditorNavbarProps) { const newBranchName = `${EDITOR_BRANCH_PREFIX}${Date.now()}`; try { await createBranch(accessToken, forkedRepo.owner.login, forkedRepo.name, newBranchName, latestCommitSha); - } catch (error) { - trackFailure("github_publish.failure.branch"); - throw new Error("Failed to create branch: " + (error instanceof Error ? error.message : String(error))); + } catch (error: any) { + if (error?.status === 404 || error?.message?.includes("404")) { + console.warn("Branch creation returned 404, repository may still be provisioning. Retrying after delay..."); + await delay(3000); + try { + await createBranch(accessToken, forkedRepo.owner.login, forkedRepo.name, newBranchName, latestCommitSha); + } catch (retryError) { + trackFailure("github_publish.failure.branch"); + throw new Error("Failed to create branch: " + (retryError instanceof Error ? retryError.message : String(retryError))); + } + } else { + trackFailure("github_publish.failure.branch"); + throw new Error("Failed to create branch: " + (error instanceof Error ? error.message : String(error))); + } } repoOwner = forkedRepo.owner.login; @@ -764,7 +775,22 @@ export default function EditorNavbar(props: EditorNavbarProps) { const titleChanged = pullRequest.title !== prTitle; const normalizeBody = (body?: string | null) => (body ?? "").replace(/\r\n/g, "\n").trim(); const bodyChanged = Boolean(prBody && normalizeBody(pullRequest.body) !== normalizeBody(prBody)); - if (titleChanged || bodyChanged) { + let isReopened = false; + let closedNeedsReviewer = false; + + if (pullRequest.state === "closed") { + try { + await updatePullRequest(accessToken, "hackclub", "sprig", pullRequest.number, { + state: "open", + ...(titleChanged ? { title: prTitle } : {}), + ...(bodyChanged ? { body: prBody } : {}), + }); + isReopened = true; + } catch (err: any) { + console.warn("Could not automatically reopen closed pull request:", err); + closedNeedsReviewer = true; + } + } else if (titleChanged || bodyChanged) { try { await updatePullRequest(accessToken, "hackclub", "sprig", pullRequest.number, { title: prTitle, @@ -787,7 +813,13 @@ export default function EditorNavbar(props: EditorNavbarProps) { rememberPullRequest(pullRequest.html_url); const prUpdated = codeChanged || titleChanged || bodyChanged; - publishOutcome.value = prUpdated ? "updated" : "unchanged"; + if (isReopened) { + publishOutcome.value = "reopened"; + } else if (closedNeedsReviewer) { + publishOutcome.value = "closed_updated"; + } else { + publishOutcome.value = prUpdated ? "updated" : "unchanged"; + } reportMetric("github_publish.success"); reportMetric(prUpdated ? "github_publish.success.updated_pr" : "github_publish.success.unchanged_pr"); @@ -1186,6 +1218,8 @@ export default function EditorNavbar(props: EditorNavbarProps) { new: "Your game has been successfully published to GitHub.", updated: "Your open pull request for this game was updated with this version.", unchanged: "Your pull request is already up to date with this version.", + reopened: "Your pull request was updated and reopened on GitHub.", + closed_updated: `Your game was updated in pull request #${openPR?.pullRequest?.number}. Because it was closed by a reviewer, ask a reviewer to reopen it when you're ready!`, }[publishOutcome.value]}