From 843c7046b26f154da1b1f7dd1a96a2ecb41e80a3 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 30 Jan 2025 10:02:43 -0600 Subject: [PATCH 1/8] Add for status to app configuration --- src/site/SiteChecklistClient.tsx | 23 ++++++++++++++++------- src/site/SiteChecklistServer.tsx | 13 ++++++++++++- src/site/config.ts | 15 ++++++++------- src/utility/git.ts | 8 -------- src/utility/github.ts | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 23 deletions(-) delete mode 100644 src/utility/git.ts create mode 100644 src/utility/github.ts diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 96533fa0..38b8ec37 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -94,6 +94,8 @@ export default function SiteChecklistClient({ storageError, kvError, aiError, + // Git Meta + isForkedFromBaseRepo, // Component props simplifiedView, isTestingConnections, @@ -101,6 +103,8 @@ export default function SiteChecklistClient({ Partial>> & { simplifiedView?: boolean isTestingConnections?: boolean +} & { + isForkedFromBaseRepo?: boolean }) { const renderLink = (href: string, text: string, external = true) => <> @@ -665,13 +669,18 @@ export default function SiteChecklistClient({    {commitSha ? commitUrl - ? - {commitSha} - + ? <> + + {commitSha} + +   + {isForkedFromBaseRepo && + Forked} + : {commitSha} : 'Not Found'} diff --git a/src/site/SiteChecklistServer.tsx b/src/site/SiteChecklistServer.tsx index af73d43b..160be58d 100644 --- a/src/site/SiteChecklistServer.tsx +++ b/src/site/SiteChecklistServer.tsx @@ -1,6 +1,11 @@ import SiteChecklistClient from './SiteChecklistClient'; -import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; +import { + CONFIG_CHECKLIST_STATUS, + VERCEL_GIT_REPO_OWNER, + VERCEL_GIT_REPO_SLUG, +} from '@/site/config'; import { testConnectionsAction } from '@/admin/actions'; +import { isRepoForkedFromBase } from '@/utility/github'; export default async function SiteChecklistServer({ simplifiedView, @@ -8,10 +13,16 @@ export default async function SiteChecklistServer({ simplifiedView?: boolean }) { const connectionErrors = await testConnectionsAction().catch(() => ({})); + const isForkedFromBaseRepo = await isRepoForkedFromBase( + VERCEL_GIT_REPO_OWNER, + VERCEL_GIT_REPO_SLUG, + ); + return ( ); diff --git a/src/site/config.ts b/src/site/config.ts index 14caa427..f47ff042 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -14,20 +14,21 @@ export const SITE_TITLE = 'Photo Blog'; // SOURCE -const VERCEL_GIT_PROVIDER = +export const VERCEL_GIT_PROVIDER = process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER; -const VERCEL_GIT_REPO_OWNER = +export const VERCEL_GIT_REPO_OWNER = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER; -const VERCEL_GIT_REPO_SLUG = +export const VERCEL_GIT_REPO_SLUG = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG; -const VERCEL_GIT_COMMIT_MESSAGE = +export const VERCEL_GIT_COMMIT_MESSAGE = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE; -const VERCEL_GIT_COMMIT_SHA = +export const VERCEL_GIT_COMMIT_SHA = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA; -const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA +export const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA ? VERCEL_GIT_COMMIT_SHA.slice(0, 7) : undefined; -const VERCEL_GIT_COMMIT_URL = VERCEL_GIT_PROVIDER === 'github' +export const VERCEL_IS_PROVIDER_GITHUB = VERCEL_GIT_PROVIDER === 'github'; +export const VERCEL_GIT_COMMIT_URL = VERCEL_IS_PROVIDER_GITHUB // eslint-disable-next-line max-len ? `https://github.com/${VERCEL_GIT_REPO_OWNER}/${VERCEL_GIT_REPO_SLUG}/commit/${VERCEL_GIT_COMMIT_SHA}` : undefined; diff --git a/src/utility/git.ts b/src/utility/git.ts deleted file mode 100644 index 97c32e71..00000000 --- a/src/utility/git.ts +++ /dev/null @@ -1,8 +0,0 @@ -const GITHUB_API_URL = - 'https://api.github.com/repos/sambecker/exif-photo-blog/commits/main'; - -export const fetchLatestRepoCommit = async () => { - const response = await fetch(GITHUB_API_URL); - const data = await response.json(); - return data.sha.slice(0, 7); -}; diff --git a/src/utility/github.ts b/src/utility/github.ts new file mode 100644 index 00000000..53201909 --- /dev/null +++ b/src/utility/github.ts @@ -0,0 +1,32 @@ +import { VERCEL_IS_PROVIDER_GITHUB } from '@/site/config'; + +const BASE_OWNER = 'sambecker'; +const BASE_REPO = 'exif-photo-blog'; + +type RepoParams = Parameters<(owner?: string, repo?: string) => unknown>; + +const getRepoUrl = (owner = BASE_OWNER, repo = BASE_REPO) => + `https://api.github.com/repos/${owner}/${repo}`; + +const getCommitsUrl = (...args: RepoParams) => + `${getRepoUrl(...args)}/commits/main`; + +export const fetchLatestBaseRepoCommitSha = async () => { + if (VERCEL_IS_PROVIDER_GITHUB) { + const response = await fetch(getCommitsUrl()); + const data = await response.json(); + return data.sha.slice(0, 7); + } else { + return undefined; + } +}; + +export const isRepoForkedFromBase = async (...args: RepoParams) => { + if (VERCEL_IS_PROVIDER_GITHUB) { + const response = await fetch(getRepoUrl(...args)); + const data = await response.json(); + return data.fork && data.source?.full_name === `${BASE_OWNER}/${BASE_REPO}`; + } else { + return false; + } +}; From 10c7ba42404bfac46181eedd4a03ad195ebcaf52 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 30 Jan 2025 22:45:22 -0600 Subject: [PATCH 2/8] Add git sync status for forked repos --- src/admin/github/GitHubForkStatusBadge.tsx | 14 +++ .../github/GitHubForkStatusBadgeClient.tsx | 74 +++++++++++++++ .../github/GitHubForkStatusBadgeServer.tsx | 27 ++++++ src/admin/github/index.ts | 90 +++++++++++++++++++ src/app/admin/configuration/page.tsx | 8 +- src/site/SiteChecklist.tsx | 2 +- src/site/SiteChecklistClient.tsx | 47 ++++------ src/site/SiteChecklistServer.tsx | 12 +-- src/site/config.ts | 17 ++-- src/site/globals.css | 3 + src/utility/github.ts | 32 ------- 11 files changed, 244 insertions(+), 82 deletions(-) create mode 100644 src/admin/github/GitHubForkStatusBadge.tsx create mode 100644 src/admin/github/GitHubForkStatusBadgeClient.tsx create mode 100644 src/admin/github/GitHubForkStatusBadgeServer.tsx create mode 100644 src/admin/github/index.ts delete mode 100644 src/utility/github.ts diff --git a/src/admin/github/GitHubForkStatusBadge.tsx b/src/admin/github/GitHubForkStatusBadge.tsx new file mode 100644 index 00000000..f4cb64f7 --- /dev/null +++ b/src/admin/github/GitHubForkStatusBadge.tsx @@ -0,0 +1,14 @@ +import { Suspense } from 'react'; +import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient'; +import GitHubForkStatusBadgeServer from './GitHubForkStatusBadgeServer'; +import { IS_DEVELOPMENT, IS_VERCEL_GIT_PROVIDER_GITHUB } from '@/site/config'; + +export default function GitHubForkStatusBadge() { + return IS_DEVELOPMENT + ? + : IS_VERCEL_GIT_PROVIDER_GITHUB + ? + + + : null; +} diff --git a/src/admin/github/GitHubForkStatusBadgeClient.tsx b/src/admin/github/GitHubForkStatusBadgeClient.tsx new file mode 100644 index 00000000..baf90acc --- /dev/null +++ b/src/admin/github/GitHubForkStatusBadgeClient.tsx @@ -0,0 +1,74 @@ +import Spinner from '@/components/Spinner'; +import clsx from 'clsx/lite'; +import Link from 'next/link'; +import { BiLogoGithub } from 'react-icons/bi'; + +export default function GitHubForkStatusBadgeClient({ + url, + label, + style = 'mono', + title, +}: { + url?: string + label?: string + style?: 'success' | 'warning' | 'mono' + title?: string +}) { + const classNameForStyle = () => { + switch (style) { + case 'success': return clsx( + 'text-green-700 hover:text-green-700', + 'dark:text-green-400 dark:hover:text-green-400', + 'bg-green-100/75 dark:bg-green-900/50', + 'border-green-300/25', + ); + case 'warning': return clsx( + 'text-amber-700 hover:text-amber-700', + 'dark:text-amber-400 dark:hover:text-amber-400', + 'bg-amber-100/75 dark:bg-amber-900/50', + 'border-amber-300/25 dark:border-amber-900', + ); + default: return clsx( + 'text-gray-700 hover:text-gray-700', + 'dark:text-gray-300 dark:hover:text-gray-300', + 'bg-gray-100/75 dark:bg-gray-900/50', + 'border-main', + ); + } + }; + + const className = clsx( + 'inline-flex items-center gap-2', + 'border transition-colors', + url ? 'hover:underline' : 'select-none', + 'pl-[4.5px] pr-2 py-[3px]', + 'rounded-full', + classNameForStyle(), + ); + + const content = <> + {!label + ? + : } + {label ?? 'Checking'} + ; + + return url + ? + {content} + + : + {content} + ; +} diff --git a/src/admin/github/GitHubForkStatusBadgeServer.tsx b/src/admin/github/GitHubForkStatusBadgeServer.tsx new file mode 100644 index 00000000..a6cc6f3e --- /dev/null +++ b/src/admin/github/GitHubForkStatusBadgeServer.tsx @@ -0,0 +1,27 @@ +import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient'; +import { + VERCEL_GIT_REPO_OWNER, + VERCEL_GIT_REPO_SLUG, +} from '@/site/config'; +import { getGitHubMeta } from '.'; + +export default async function GitHubForkStatusBadgeServer() { + const owner = VERCEL_GIT_REPO_OWNER; + const repo = VERCEL_GIT_REPO_SLUG; + + const { + url, + label, + title, + isBehind, + } = await getGitHubMeta({ owner, repo }); + + return ( + + ); +} diff --git a/src/admin/github/index.ts b/src/admin/github/index.ts new file mode 100644 index 00000000..395c6668 --- /dev/null +++ b/src/admin/github/index.ts @@ -0,0 +1,90 @@ +const BASE_OWNER = 'sambecker'; +const BASE_REPO = 'exif-photo-blog'; + +interface RepoParams { + owner?: string + repo?: string + branch?: string +}; + +// Urls + +const getGitHubRepoUrl = ({ + owner = BASE_OWNER, + repo = BASE_REPO, +}: RepoParams = {}) => + `https://github.com/${owner}/${repo}`; + +const getGitHubApiRepoUrl = ({ + owner = BASE_OWNER, + repo = BASE_REPO, +}: RepoParams = {}) => + `https://api.github.com/repos/${owner}/${repo}`; + +const getGitHubApiCommitsUrl = (params?: RepoParams) => + `${getGitHubApiRepoUrl(params)}/commits/main`; + +// Fetching + +const getGitHubApiCompareUrl = ({ + owner, + repo, + branch = 'main', +}: RepoParams = {}) => + `${getGitHubApiRepoUrl()}/compare/main...${owner}:${repo}:${branch}`; + +const getLatestBaseRepoCommitSha = async () => { + const response = await fetch(getGitHubApiCommitsUrl()); + const data = await response.json(); + return data.sha.slice(0, 7) as string; +}; + +const getIsRepoForkedFromBase = async (params: RepoParams) => { + const response = await fetch(getGitHubApiRepoUrl(params)); + const data = await response.json(); + return data.fork && data.source?.full_name === `${BASE_OWNER}/${BASE_REPO}`; +}; + +const getGitHubCommitsBehind = async (params?: RepoParams) => { + const response = await fetch(getGitHubApiCompareUrl(params)); + const data = await response.json(); + return data.behind_by as number; +}; + +const isRepoBaseRepo = async ({ owner, repo }: RepoParams) => + owner?.toLowerCase() === BASE_OWNER && + repo?.toLowerCase() === BASE_REPO; + +export const getGitHubMeta = async (params: RepoParams) => { + const [ + url, + isForkedFromBase, + isBaseRepo, + latestBaseRepoCommitSha, + behindBy, + ] = await Promise.all([ + getGitHubRepoUrl(params), + getIsRepoForkedFromBase(params), + isRepoBaseRepo(params), + getLatestBaseRepoCommitSha(), + getGitHubCommitsBehind(params), + ]); + + const isBehind = behindBy > 0; + const label = isBehind ? `${behindBy} Behind` : 'Synced'; + const title = isBehind + // eslint-disable-next-line max-len + ? `This fork is ${behindBy} commit${behindBy === 1 ? '' : 's'} behind. Consider syncing on GitHub for the latest updates.` + : 'This fork is up to date.'; + + return { + url, + isForkedFromBase, + isBaseRepo, + latestBaseRepoCommitSha, + behindBy, + isBehind, + label, + title, + }; +}; diff --git a/src/app/admin/configuration/page.tsx b/src/app/admin/configuration/page.tsx index ce4719f3..ecb28453 100644 --- a/src/app/admin/configuration/page.tsx +++ b/src/app/admin/configuration/page.tsx @@ -1,6 +1,8 @@ import ClearCacheButton from '@/admin/ClearCacheButton'; +import GitHubForkStatusBadge from '@/admin/github/GitHubForkStatusBadge'; import Container from '@/components/Container'; import SiteGrid from '@/components/SiteGrid'; +import { IS_DEVELOPMENT, IS_VERCEL_GIT_PROVIDER_GITHUB } from '@/site/config'; import SiteChecklist from '@/site/SiteChecklist'; export default async function AdminConfigurationPage() { @@ -8,10 +10,12 @@ export default async function AdminConfigurationPage() { -
-
+
+
App Configuration
+ {(IS_VERCEL_GIT_PROVIDER_GITHUB || IS_DEVELOPMENT) && + }
diff --git a/src/site/SiteChecklist.tsx b/src/site/SiteChecklist.tsx index 1b7dab75..935d0f9c 100644 --- a/src/site/SiteChecklist.tsx +++ b/src/site/SiteChecklist.tsx @@ -11,7 +11,7 @@ export default function SiteChecklist({ return ( }> diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 38b8ec37..a716f69d 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -94,17 +94,13 @@ export default function SiteChecklistClient({ storageError, kvError, aiError, - // Git Meta - isForkedFromBaseRepo, // Component props simplifiedView, - isTestingConnections, + isAnalyzingConfiguration, }: ConfigChecklistStatus & Partial>> & { simplifiedView?: boolean - isTestingConnections?: boolean -} & { - isForkedFromBaseRepo?: boolean + isAnalyzingConfiguration?: boolean }) { const renderLink = (href: string, text: string, external = true) => <> @@ -216,11 +212,11 @@ export default function SiteChecklistClient({ icon={} > {databaseError && renderError({ connection: { provider: 'Database', error: databaseError}, @@ -247,7 +243,7 @@ export default function SiteChecklistClient({ {storageError && renderError({ connection: { provider: 'Storage', error: storageError}, @@ -302,11 +298,11 @@ export default function SiteChecklistClient({ icon={} > Store auth secret in environment variable: {!hasAuthSecret && @@ -378,11 +374,11 @@ export default function SiteChecklistClient({ optional > {aiError && renderError({ @@ -394,11 +390,11 @@ export default function SiteChecklistClient({ {renderEnvVars(['OPENAI_SECRET_KEY'])} {kvError && renderError({ @@ -669,18 +665,13 @@ export default function SiteChecklistClient({    {commitSha ? commitUrl - ? <> - - {commitSha} - -   - {isForkedFromBaseRepo && - Forked} - + ? + {commitSha} + : {commitSha} : 'Not Found'}
diff --git a/src/site/SiteChecklistServer.tsx b/src/site/SiteChecklistServer.tsx index 160be58d..e50255ab 100644 --- a/src/site/SiteChecklistServer.tsx +++ b/src/site/SiteChecklistServer.tsx @@ -1,11 +1,6 @@ import SiteChecklistClient from './SiteChecklistClient'; -import { - CONFIG_CHECKLIST_STATUS, - VERCEL_GIT_REPO_OWNER, - VERCEL_GIT_REPO_SLUG, -} from '@/site/config'; +import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; import { testConnectionsAction } from '@/admin/actions'; -import { isRepoForkedFromBase } from '@/utility/github'; export default async function SiteChecklistServer({ simplifiedView, @@ -13,16 +8,11 @@ export default async function SiteChecklistServer({ simplifiedView?: boolean }) { const connectionErrors = await testConnectionsAction().catch(() => ({})); - const isForkedFromBaseRepo = await isRepoForkedFromBase( - VERCEL_GIT_REPO_OWNER, - VERCEL_GIT_REPO_SLUG, - ); return ( ); diff --git a/src/site/config.ts b/src/site/config.ts index f47ff042..4a16fe42 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -27,19 +27,19 @@ export const VERCEL_GIT_COMMIT_SHA = export const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA ? VERCEL_GIT_COMMIT_SHA.slice(0, 7) : undefined; -export const VERCEL_IS_PROVIDER_GITHUB = VERCEL_GIT_PROVIDER === 'github'; -export const VERCEL_GIT_COMMIT_URL = VERCEL_IS_PROVIDER_GITHUB +export const IS_VERCEL_GIT_PROVIDER_GITHUB = VERCEL_GIT_PROVIDER === 'github'; +export const VERCEL_GIT_COMMIT_URL = IS_VERCEL_GIT_PROVIDER_GITHUB // eslint-disable-next-line max-len ? `https://github.com/${VERCEL_GIT_REPO_OWNER}/${VERCEL_GIT_REPO_SLUG}/commit/${VERCEL_GIT_COMMIT_SHA}` : undefined; -const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV; -const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL; -const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL; -const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL; -const VERCEL_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF; +export const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV; +export const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL; +export const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL; +export const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL; +export const VERCEL_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF; // Last resort: cannot be used reliably -const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_BRANCH +export const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_BRANCH ? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_BRANCH}-`)[0]}.vercel.app` : undefined; @@ -49,6 +49,7 @@ export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && ( !VERCEL_ENV ); +export const IS_DEVELOPMENT = process.env.NODE_ENV === 'development'; export const IS_PREVIEW = VERCEL_ENV === 'preview'; export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass'; diff --git a/src/site/globals.css b/src/site/globals.css index 999cf3c5..a949e023 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -155,6 +155,9 @@ text-red-500 dark:text-red-400 } /* Utilities: Border */ + .border-main { + @apply border-gray-200 dark:border-gray-700 + } .border-subtle { @apply border border-gray-200 dark:border-gray-800 diff --git a/src/utility/github.ts b/src/utility/github.ts deleted file mode 100644 index 53201909..00000000 --- a/src/utility/github.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { VERCEL_IS_PROVIDER_GITHUB } from '@/site/config'; - -const BASE_OWNER = 'sambecker'; -const BASE_REPO = 'exif-photo-blog'; - -type RepoParams = Parameters<(owner?: string, repo?: string) => unknown>; - -const getRepoUrl = (owner = BASE_OWNER, repo = BASE_REPO) => - `https://api.github.com/repos/${owner}/${repo}`; - -const getCommitsUrl = (...args: RepoParams) => - `${getRepoUrl(...args)}/commits/main`; - -export const fetchLatestBaseRepoCommitSha = async () => { - if (VERCEL_IS_PROVIDER_GITHUB) { - const response = await fetch(getCommitsUrl()); - const data = await response.json(); - return data.sha.slice(0, 7); - } else { - return undefined; - } -}; - -export const isRepoForkedFromBase = async (...args: RepoParams) => { - if (VERCEL_IS_PROVIDER_GITHUB) { - const response = await fetch(getRepoUrl(...args)); - const data = await response.json(); - return data.fork && data.source?.full_name === `${BASE_OWNER}/${BASE_REPO}`; - } else { - return false; - } -}; From 2479c151d9acf09b4b1150b2dc45c2e818e94ab6 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 30 Jan 2025 22:52:52 -0600 Subject: [PATCH 3/8] Add error handling for git meta --- src/admin/github/GitHubForkStatusBadgeServer.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/admin/github/GitHubForkStatusBadgeServer.tsx b/src/admin/github/GitHubForkStatusBadgeServer.tsx index a6cc6f3e..44bb8dc8 100644 --- a/src/admin/github/GitHubForkStatusBadgeServer.tsx +++ b/src/admin/github/GitHubForkStatusBadgeServer.tsx @@ -14,7 +14,16 @@ export default async function GitHubForkStatusBadgeServer() { label, title, isBehind, - } = await getGitHubMeta({ owner, repo }); + } = await getGitHubMeta({ owner, repo }) + .catch(() => { + console.log('Error getting GitHub meta', { owner, repo }); + return { + url: undefined, + label: undefined, + title: undefined, + isBehind: false, + }; + }); return ( Date: Thu, 30 Jan 2025 22:54:40 -0600 Subject: [PATCH 4/8] Use reported branch for git comparisons --- src/admin/github/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/admin/github/index.ts b/src/admin/github/index.ts index 395c6668..fe662cc8 100644 --- a/src/admin/github/index.ts +++ b/src/admin/github/index.ts @@ -1,3 +1,5 @@ +import { VERCEL_BRANCH } from '@/site/config'; + const BASE_OWNER = 'sambecker'; const BASE_REPO = 'exif-photo-blog'; @@ -46,7 +48,10 @@ const getIsRepoForkedFromBase = async (params: RepoParams) => { }; const getGitHubCommitsBehind = async (params?: RepoParams) => { - const response = await fetch(getGitHubApiCompareUrl(params)); + const response = await fetch(getGitHubApiCompareUrl({ + ...params, + branch: VERCEL_BRANCH, + })); const data = await response.json(); return data.behind_by as number; }; From c41d1783017b74fea39e2867e0559287384ed990 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 31 Jan 2025 08:49:10 -0600 Subject: [PATCH 5/8] Tweak GitHub badge styles --- src/admin/github/GitHubForkStatusBadgeClient.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/github/GitHubForkStatusBadgeClient.tsx b/src/admin/github/GitHubForkStatusBadgeClient.tsx index baf90acc..7bf89811 100644 --- a/src/admin/github/GitHubForkStatusBadgeClient.tsx +++ b/src/admin/github/GitHubForkStatusBadgeClient.tsx @@ -31,7 +31,7 @@ export default function GitHubForkStatusBadgeClient({ default: return clsx( 'text-gray-700 hover:text-gray-700', 'dark:text-gray-300 dark:hover:text-gray-300', - 'bg-gray-100/75 dark:bg-gray-900/50', + 'bg-white dark:bg-transparent', 'border-main', ); } @@ -42,7 +42,7 @@ export default function GitHubForkStatusBadgeClient({ 'border transition-colors', url ? 'hover:underline' : 'select-none', 'pl-[4.5px] pr-2 py-[3px]', - 'rounded-full', + 'rounded-full shadow-sm', classNameForStyle(), ); From ba32f4e8f7a8ae285732aed2f0fb9065b5fa9e72 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 31 Jan 2025 18:31:35 -0600 Subject: [PATCH 6/8] Refactor GitHub fork status behavior --- __tests__/html.test.ts | 6 ++- src/admin/github/GitHubForkStatusBadge.tsx | 10 ++-- .../github/GitHubForkStatusBadgeClient.tsx | 3 +- .../github/GitHubForkStatusBadgeServer.tsx | 18 ++++--- src/admin/github/index.ts | 50 ++++++++++++------- src/site/config.ts | 12 +++-- tailwind.config.js | 2 +- 7 files changed, 61 insertions(+), 40 deletions(-) diff --git a/__tests__/html.test.ts b/__tests__/html.test.ts index 9468d976..d9eda9b1 100644 --- a/__tests__/html.test.ts +++ b/__tests__/html.test.ts @@ -1,5 +1,7 @@ -import { htmlHasBrParagraphBreaks, safelyParseFormattedHtml } from '@/utility/html'; -import { parameterize } from '@/utility/string'; +import { + htmlHasBrParagraphBreaks, + safelyParseFormattedHtml, +} from '@/utility/html'; describe('HTML', () => { it('safely parses', () => { diff --git a/src/admin/github/GitHubForkStatusBadge.tsx b/src/admin/github/GitHubForkStatusBadge.tsx index f4cb64f7..e9cd6263 100644 --- a/src/admin/github/GitHubForkStatusBadge.tsx +++ b/src/admin/github/GitHubForkStatusBadge.tsx @@ -1,14 +1,12 @@ import { Suspense } from 'react'; import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient'; import GitHubForkStatusBadgeServer from './GitHubForkStatusBadgeServer'; -import { IS_DEVELOPMENT, IS_VERCEL_GIT_PROVIDER_GITHUB } from '@/site/config'; +import { IS_DEVELOPMENT } from '@/site/config'; export default function GitHubForkStatusBadge() { return IS_DEVELOPMENT ? - : IS_VERCEL_GIT_PROVIDER_GITHUB - ? - - - : null; + : + + ; } diff --git a/src/admin/github/GitHubForkStatusBadgeClient.tsx b/src/admin/github/GitHubForkStatusBadgeClient.tsx index 7bf89811..eaf5323a 100644 --- a/src/admin/github/GitHubForkStatusBadgeClient.tsx +++ b/src/admin/github/GitHubForkStatusBadgeClient.tsx @@ -38,10 +38,11 @@ export default function GitHubForkStatusBadgeClient({ }; const className = clsx( + 'opacity-0 transition-opacity animate-fade-in', 'inline-flex items-center gap-2', 'border transition-colors', url ? 'hover:underline' : 'select-none', - 'pl-[4.5px] pr-2 py-[3px]', + 'pl-[4.5px] pr-2.5 py-[3px]', 'rounded-full shadow-sm', classNameForStyle(), ); diff --git a/src/admin/github/GitHubForkStatusBadgeServer.tsx b/src/admin/github/GitHubForkStatusBadgeServer.tsx index 44bb8dc8..39669026 100644 --- a/src/admin/github/GitHubForkStatusBadgeServer.tsx +++ b/src/admin/github/GitHubForkStatusBadgeServer.tsx @@ -1,5 +1,6 @@ import GitHubForkStatusBadgeClient from './GitHubForkStatusBadgeClient'; import { + VERCEL_GIT_BRANCH, VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG, } from '@/site/config'; @@ -8,29 +9,32 @@ import { getGitHubMeta } from '.'; export default async function GitHubForkStatusBadgeServer() { const owner = VERCEL_GIT_REPO_OWNER; const repo = VERCEL_GIT_REPO_SLUG; - + const branch = VERCEL_GIT_BRANCH; + const { url, + isForkedFromBase, label, title, isBehind, - } = await getGitHubMeta({ owner, repo }) + } = await getGitHubMeta({ owner, repo, branch }) .catch(() => { - console.log('Error getting GitHub meta', { owner, repo }); + console.error('Error retrieving GitHub meta', { owner, repo, branch }); return { url: undefined, + isForkedFromBase: false, label: undefined, title: undefined, - isBehind: false, + isBehind: undefined, }; }); - return ( - - ); + : null; } diff --git a/src/admin/github/index.ts b/src/admin/github/index.ts index fe662cc8..fbad31d0 100644 --- a/src/admin/github/index.ts +++ b/src/admin/github/index.ts @@ -1,7 +1,10 @@ -import { VERCEL_BRANCH } from '@/site/config'; +import { + TEMPLATE_BASE_OWNER, + TEMPLATE_BASE_REPO, + TEMPLATE_BASE_BRANCH, +} from '@/site/config'; -const BASE_OWNER = 'sambecker'; -const BASE_REPO = 'exif-photo-blog'; +const DEFAULT_BRANCH = 'main'; interface RepoParams { owner?: string @@ -9,31 +12,42 @@ interface RepoParams { branch?: string }; -// Urls +// Website urls const getGitHubRepoUrl = ({ - owner = BASE_OWNER, - repo = BASE_REPO, + owner = TEMPLATE_BASE_OWNER, + repo = TEMPLATE_BASE_REPO, }: RepoParams = {}) => `https://github.com/${owner}/${repo}`; +export const getGitHubCompareUrl = ({ + owner, + repo, + branch = DEFAULT_BRANCH, +}: RepoParams = {}) => + // eslint-disable-next-line max-len + `${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_BASE_OWNER}:${TEMPLATE_BASE_REPO}:${TEMPLATE_BASE_BRANCH}`; + +// API urls + const getGitHubApiRepoUrl = ({ - owner = BASE_OWNER, - repo = BASE_REPO, + owner = TEMPLATE_BASE_OWNER, + repo = TEMPLATE_BASE_REPO, }: RepoParams = {}) => `https://api.github.com/repos/${owner}/${repo}`; const getGitHubApiCommitsUrl = (params?: RepoParams) => `${getGitHubApiRepoUrl(params)}/commits/main`; -// Fetching - const getGitHubApiCompareUrl = ({ owner, repo, branch = 'main', }: RepoParams = {}) => - `${getGitHubApiRepoUrl()}/compare/main...${owner}:${repo}:${branch}`; + // eslint-disable-next-line max-len + `${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${owner}:${repo}:${branch}`; + +// Requests const getLatestBaseRepoCommitSha = async () => { const response = await fetch(getGitHubApiCommitsUrl()); @@ -44,21 +58,21 @@ const getLatestBaseRepoCommitSha = async () => { const getIsRepoForkedFromBase = async (params: RepoParams) => { const response = await fetch(getGitHubApiRepoUrl(params)); const data = await response.json(); - return data.fork && data.source?.full_name === `${BASE_OWNER}/${BASE_REPO}`; + return ( + Boolean(data.fork) && + data.source?.full_name === `${TEMPLATE_BASE_OWNER}/${TEMPLATE_BASE_REPO}` + ); }; const getGitHubCommitsBehind = async (params?: RepoParams) => { - const response = await fetch(getGitHubApiCompareUrl({ - ...params, - branch: VERCEL_BRANCH, - })); + const response = await fetch(getGitHubApiCompareUrl(params)); const data = await response.json(); return data.behind_by as number; }; const isRepoBaseRepo = async ({ owner, repo }: RepoParams) => - owner?.toLowerCase() === BASE_OWNER && - repo?.toLowerCase() === BASE_REPO; + owner?.toLowerCase() === TEMPLATE_BASE_OWNER && + repo?.toLowerCase() === TEMPLATE_BASE_REPO; export const getGitHubMeta = async (params: RepoParams) => { const [ diff --git a/src/site/config.ts b/src/site/config.ts index a73b9a49..4be0edc7 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -3,23 +3,26 @@ import type { StorageType } from '@/services/storage'; import { makeUrlAbsolute, shortenUrl } from '@/utility/url'; // HARD-CODED GLOBAL CONFIGURATION - export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined; export const SHOULD_DEBUG_SQL = false; // META / SOURCE / DOMAINS - export const SITE_TITLE = process.env.NEXT_PUBLIC_SITE_TITLE || 'Photo Blog'; // SOURCE +export const TEMPLATE_BASE_OWNER = 'sambecker'; +export const TEMPLATE_BASE_REPO = 'exif-photo-blog'; +export const TEMPLATE_BASE_BRANCH = 'main'; + export const VERCEL_GIT_PROVIDER = process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER; export const VERCEL_GIT_REPO_OWNER = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER; export const VERCEL_GIT_REPO_SLUG = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG; +export const VERCEL_GIT_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF; export const VERCEL_GIT_COMMIT_MESSAGE = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE; export const VERCEL_GIT_COMMIT_SHA = @@ -37,10 +40,9 @@ export const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV; export const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL; export const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL; export const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL; -export const VERCEL_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF; // Last resort: cannot be used reliably -export const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_BRANCH - ? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_BRANCH}-`)[0]}.vercel.app` +export const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_GIT_BRANCH + ? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_GIT_BRANCH}-`)[0]}.vercel.app` : undefined; export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && ( diff --git a/tailwind.config.js b/tailwind.config.js index a728d01d..889d2572 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -29,7 +29,7 @@ module.exports = { 'rotate-pulse': 'rotate-pulse 0.75s linear infinite normal both running', 'fade-in': - 'fade-in 0.5s linear', + 'fade-in 0.5s linear both running', 'hover-drift': 'hover-drift 8s linear infinite', 'hover-wobble': From e3745e24e8c54e35aee1f54b66d1ee710e7803e0 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 31 Jan 2025 22:16:56 -0600 Subject: [PATCH 7/8] Add GitHub fork status test coverage --- __tests__/github.test.ts | 47 ++++ jest.config.mjs => jest.config.ts | 11 +- jest.setup.ts | 1 + package.json | 3 + pnpm-lock.yaml | 209 +++++++++++++++--- .../github/GitHubForkStatusBadgeServer.tsx | 16 +- src/admin/github/index.ts | 74 +++++-- 7 files changed, 298 insertions(+), 63 deletions(-) create mode 100644 __tests__/github.test.ts rename jest.config.mjs => jest.config.ts (67%) create mode 100644 jest.setup.ts diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts new file mode 100644 index 00000000..b5904a15 --- /dev/null +++ b/__tests__/github.test.ts @@ -0,0 +1,47 @@ +import { getGitHubMetaWithFallback, getGitHubPublicFork } from '@/admin/github'; +import { TEMPLATE_BASE_OWNER, TEMPLATE_BASE_REPO } from '@/site/config'; + +describe('GitHub', () => { + it('fetches base repo meta', async () => { + const meta = await getGitHubMetaWithFallback({ + owner: TEMPLATE_BASE_OWNER, + repo: TEMPLATE_BASE_REPO, + }); + expect(meta).toBeDefined(); + expect(meta.url).toBeDefined(); + expect(meta.isForkedFromBase).toEqual(false); + expect(meta.label).toBeDefined(); + expect(meta.title).toBeDefined(); + expect(meta.isBehind).toEqual(false); + expect(meta.isBaseRepo).toBe(true); + }); + it('fetches fork meta', async () => { + const fork = await getGitHubPublicFork(); + const metaFork = await getGitHubMetaWithFallback(fork); + expect(metaFork.isForkedFromBase).toEqual(true); + }); + it('handles nonexistent repos', async () => { + const meta = await getGitHubMetaWithFallback({ + owner: 'nonexistent', + repo: 'nonexistent', + }); + expect(meta).toBeDefined(); + expect(meta.url).toBeDefined(); + expect(meta.isForkedFromBase).toEqual(false); + expect(meta.label).toEqual('Unknown'); + expect(meta.title).toEqual('Unknown'); + expect(meta.isBehind).toBeUndefined(); + }); + it('handles fetch errors', async () => { + const meta = await getGitHubMetaWithFallback({ + owner: 'gibberish / / *', + repo: 'bad text for a url.com', + }); + expect(meta).toBeDefined(); + expect(meta.url).toBeDefined(); + expect(meta.isForkedFromBase).toEqual(false); + expect(meta.label).toEqual('Unknown'); + expect(meta.title).toEqual('Unknown'); + expect(meta.isBehind).toBeUndefined(); + }); +}); diff --git a/jest.config.mjs b/jest.config.ts similarity index 67% rename from jest.config.mjs rename to jest.config.ts index b063a90b..eb690b56 100644 --- a/jest.config.mjs +++ b/jest.config.ts @@ -1,4 +1,5 @@ /* eslint-disable max-len */ +import type { Config } from 'jest'; import nextJest from 'next/jest.js'; const createJestConfig = nextJest({ @@ -7,12 +8,10 @@ const createJestConfig = nextJest({ }); // Add any custom config to be passed to Jest -/** @type {import('jest').Config} */ -const config = { - // Add more setup options before each test is run - // setupFilesAfterEnv: ['/jest.setup.js'], - - testEnvironment: 'jest-environment-jsdom', +const config: Config = { + coverageProvider: 'v8', + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/jest.setup.ts'], }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..3673ac04 --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1 @@ +import 'cross-fetch/polyfill'; \ No newline at end of file diff --git a/package.json b/package.json index 4f3cf89a..a905be0a 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@next/bundle-analyzer": "15.1.6", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.10", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", @@ -57,12 +58,14 @@ "@types/sanitize-html": "^2.13.0", "autoprefixer": "10.4.20", "clsx": "^2.1.1", + "cross-fetch": "^4.1.0", "eslint": "9.18.0", "eslint-config-next": "15.1.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "8.5.1", "tailwindcss": "3.4.17", + "ts-node": "^10.9.2", "typescript": "5.7.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 890b8eb4..5b8add6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,16 +113,19 @@ importers: version: 15.1.6 '@tailwindcss/container-queries': specifier: ^0.1.1 - version: 0.1.1(tailwindcss@3.4.17) + version: 0.1.1(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))) '@tailwindcss/forms': specifier: ^0.5.10 - version: 0.5.10(tailwindcss@3.4.17) + version: 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))) + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@testing-library/react': specifier: ^16.2.0 - version: 16.2.0(@testing-library/dom@10.1.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/jest': specifier: ^29.5.14 version: 29.5.14 @@ -147,6 +150,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cross-fetch: + specifier: ^4.1.0 + version: 4.1.0 eslint: specifier: 9.18.0 version: 9.18.0(jiti@1.21.7) @@ -155,7 +161,7 @@ importers: version: 15.1.6(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.10.7) + version: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -164,7 +170,10 @@ importers: version: 8.5.1 tailwindcss: specifier: 3.4.17 - version: 3.4.17 + version: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.10.7)(typescript@5.7.3) typescript: specifier: 5.7.3 version: 5.7.3 @@ -566,6 +575,10 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -847,6 +860,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@next/bundle-analyzer@15.1.6': resolution: {integrity: sha512-hGzQyDqJzFHcHNCyTqM3o05BpVq5tGnRODccZBVJDBf5Miv/26UJPMB0wh9L9j3ylgHC+0/v8BaBnBBek1rC6Q==} @@ -1487,8 +1503,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' - '@testing-library/dom@10.1.0': - resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} '@testing-library/jest-dom@6.6.3': @@ -1514,6 +1530,18 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1819,6 +1847,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -2112,6 +2143,12 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2250,6 +2287,10 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -3217,6 +3258,9 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -3355,6 +3399,15 @@ packages: sass: optional: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -4098,6 +4151,9 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} @@ -4114,6 +4170,20 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -4233,6 +4303,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-to-istanbul@9.2.0: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} @@ -4255,6 +4328,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -4276,6 +4352,9 @@ packages: resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} engines: {node: '>=12'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -4371,6 +4450,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -5121,6 +5204,10 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@discoveryjs/json-ext@0.5.7': {} '@emnapi/runtime@1.2.0': @@ -5305,7 +5392,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -5319,7 +5406,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.10.7) + jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -5475,6 +5562,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@next/bundle-analyzer@15.1.6': dependencies: webpack-bundle-analyzer: 4.10.1 @@ -6169,16 +6261,16 @@ snapshots: dependencies: tslib: 2.8.1 - '@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.17)': + '@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))': dependencies: - tailwindcss: 3.4.17 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)': + '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)))': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.17 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) - '@testing-library/dom@10.1.0': + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.24.2 '@babel/runtime': 7.24.5 @@ -6199,10 +6291,10 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.2.0(@testing-library/dom@10.1.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.24.5 - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) optionalDependencies: @@ -6211,6 +6303,14 @@ snapshots: '@tootallnate/once@2.0.0': {} + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -6552,6 +6652,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@4.1.3: {} + arg@5.0.2: {} argparse@1.0.10: @@ -6902,13 +7004,13 @@ snapshots: cookie@0.7.1: {} - create-jest@29.7.0(@types/node@22.10.7): + create-jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.10.7) + jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -6917,6 +7019,14 @@ snapshots: - supports-color - ts-node + create-require@1.1.1: {} + + cross-fetch@4.1.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -7029,6 +7139,8 @@ snapshots: diff-sequences@29.6.3: {} + diff@4.0.2: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -7926,16 +8038,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.10.7): + jest-cli@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.10.7) + create-jest: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.10.7) + jest-config: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -7945,7 +8057,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.10.7): + jest-config@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: '@babel/core': 7.24.5 '@jest/test-sequencer': 29.7.0 @@ -7971,6 +8083,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.10.7 + ts-node: 10.9.2(@types/node@22.10.7)(typescript@5.7.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -8205,12 +8318,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.10.7): + jest@29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.10.7) + jest-cli: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -8355,6 +8468,8 @@ snapshots: dependencies: semver: 7.6.3 + make-error@1.3.6: {} + makeerror@1.0.12: dependencies: tmpl: 1.0.5 @@ -8465,6 +8580,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-int64@0.4.0: {} node-releases@2.0.14: {} @@ -8678,12 +8797,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.1 - postcss-load-config@4.0.2(postcss@8.5.1): + postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: lilconfig: 3.1.3 yaml: 2.4.2 optionalDependencies: postcss: 8.5.1 + ts-node: 10.9.2(@types/node@22.10.7)(typescript@5.7.3) postcss-nested@6.2.0(postcss@8.5.1): dependencies: @@ -9194,7 +9314,7 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@3.4.17: + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9213,7 +9333,7 @@ snapshots: postcss: 8.5.1 postcss-import: 15.1.0(postcss@8.5.1) postcss-js: 4.0.1(postcss@8.5.1) - postcss-load-config: 4.0.2(postcss@8.5.1) + postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) postcss-nested: 6.2.0(postcss@8.5.1) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -9256,6 +9376,8 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tr46@0.0.3: {} + tr46@3.0.0: dependencies: punycode: 2.3.1 @@ -9270,6 +9392,24 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.10.7 + acorn: 8.14.0 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.7.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -9391,6 +9531,8 @@ snapshots: uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} + v8-to-istanbul@9.2.0: dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -9418,6 +9560,8 @@ snapshots: dependencies: makeerror: 1.0.12 + webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} webpack-bundle-analyzer@4.10.1: @@ -9450,6 +9594,11 @@ snapshots: tr46: 3.0.0 webidl-conversions: 7.0.0 + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -9543,6 +9692,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yn@3.1.1: {} + yocto-queue@0.1.0: {} zod-to-json-schema@3.24.1(zod@3.23.8): diff --git a/src/admin/github/GitHubForkStatusBadgeServer.tsx b/src/admin/github/GitHubForkStatusBadgeServer.tsx index 39669026..afb1ebf5 100644 --- a/src/admin/github/GitHubForkStatusBadgeServer.tsx +++ b/src/admin/github/GitHubForkStatusBadgeServer.tsx @@ -4,7 +4,7 @@ import { VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG, } from '@/site/config'; -import { getGitHubMeta } from '.'; +import { getGitHubMetaWithFallback } from '.'; export default async function GitHubForkStatusBadgeServer() { const owner = VERCEL_GIT_REPO_OWNER; @@ -17,24 +17,14 @@ export default async function GitHubForkStatusBadgeServer() { label, title, isBehind, - } = await getGitHubMeta({ owner, repo, branch }) - .catch(() => { - console.error('Error retrieving GitHub meta', { owner, repo, branch }); - return { - url: undefined, - isForkedFromBase: false, - label: undefined, - title: undefined, - isBehind: undefined, - }; - }); + } = await getGitHubMetaWithFallback({ owner, repo, branch }); return isForkedFromBase ? : null; } diff --git a/src/admin/github/index.ts b/src/admin/github/index.ts index fbad31d0..129f352c 100644 --- a/src/admin/github/index.ts +++ b/src/admin/github/index.ts @@ -6,6 +6,14 @@ import { const DEFAULT_BRANCH = 'main'; +const FALLBACK_TEXT = 'Unknown'; + +// Cache all results for 2 minutes to avoid rate limiting +// GitHub API requests limited to 60 requests per hour +const FETCH_CONFIG: RequestInit = { + next: { revalidate: 120 }, +}; + interface RepoParams { owner?: string repo?: string @@ -39,6 +47,9 @@ const getGitHubApiRepoUrl = ({ const getGitHubApiCommitsUrl = (params?: RepoParams) => `${getGitHubApiRepoUrl(params)}/commits/main`; +const getGitHubApiForksUrl = (params?: RepoParams) => + `${getGitHubApiRepoUrl(params)}/forks`; + const getGitHubApiCompareUrl = ({ owner, repo, @@ -49,14 +60,14 @@ const getGitHubApiCompareUrl = ({ // Requests -const getLatestBaseRepoCommitSha = async () => { - const response = await fetch(getGitHubApiCommitsUrl()); +export const getLatestBaseRepoCommitSha = async () => { + const response = await fetch(getGitHubApiCommitsUrl(), FETCH_CONFIG); const data = await response.json(); - return data.sha.slice(0, 7) as string; + return data.sha ? data.sha.slice(0, 7) as string : undefined; }; const getIsRepoForkedFromBase = async (params: RepoParams) => { - const response = await fetch(getGitHubApiRepoUrl(params)); + const response = await fetch(getGitHubApiRepoUrl(params), FETCH_CONFIG); const data = await response.json(); return ( Boolean(data.fork) && @@ -65,7 +76,7 @@ const getIsRepoForkedFromBase = async (params: RepoParams) => { }; const getGitHubCommitsBehind = async (params?: RepoParams) => { - const response = await fetch(getGitHubApiCompareUrl(params)); + const response = await fetch(getGitHubApiCompareUrl(params), FETCH_CONFIG); const data = await response.json(); return data.behind_by as number; }; @@ -74,36 +85,69 @@ const isRepoBaseRepo = async ({ owner, repo }: RepoParams) => owner?.toLowerCase() === TEMPLATE_BASE_OWNER && repo?.toLowerCase() === TEMPLATE_BASE_REPO; -export const getGitHubMeta = async (params: RepoParams) => { +export const getGitHubPublicFork = async ( + params?: RepoParams, +): Promise => { + const response = await fetch(getGitHubApiForksUrl(params), FETCH_CONFIG); + const fork = (await response.json())[0]; + return { + owner: fork.owner.login, + repo: fork.name, + }; +}; + +const getGitHubMeta = async (params: RepoParams) => { const [ url, isForkedFromBase, isBaseRepo, - latestBaseRepoCommitSha, behindBy, ] = await Promise.all([ getGitHubRepoUrl(params), getIsRepoForkedFromBase(params), isRepoBaseRepo(params), - getLatestBaseRepoCommitSha(), getGitHubCommitsBehind(params), ]); - const isBehind = behindBy > 0; - const label = isBehind ? `${behindBy} Behind` : 'Synced'; - const title = isBehind - // eslint-disable-next-line max-len - ? `This fork is ${behindBy} commit${behindBy === 1 ? '' : 's'} behind. Consider syncing on GitHub for the latest updates.` - : 'This fork is up to date.'; + const isBehind = behindBy === undefined + ? undefined + : behindBy > 0; + + const label = isBehind === undefined + ? FALLBACK_TEXT + : isBehind + ? `${behindBy} Behind` + : 'Synced'; + + const title = isBehind === undefined + ? FALLBACK_TEXT + : isBehind + // eslint-disable-next-line max-len + ? `This fork is ${behindBy} commit${behindBy === 1 ? '' : 's'} behind. Consider syncing on GitHub for the latest updates.` + : 'This fork is up to date.'; return { url, isForkedFromBase, isBaseRepo, - latestBaseRepoCommitSha, behindBy, isBehind, label, title, }; }; + +export const getGitHubMetaWithFallback = (params: RepoParams) => + getGitHubMeta(params) + .catch(e => { + console.error('Error retrieving GitHub meta', { params, error: e }); + return { + url: undefined, + isForkedFromBase: false, + isBaseRepo: undefined, + behindBy: undefined, + isBehind: undefined, + label: FALLBACK_TEXT, + title: FALLBACK_TEXT, + }; + }); From f68cf85bfa20641d3611e042f83887a051d748d2 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 31 Jan 2025 22:22:17 -0600 Subject: [PATCH 8/8] Show GitHub badge for base repo --- src/admin/github/GitHubForkStatusBadgeServer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/admin/github/GitHubForkStatusBadgeServer.tsx b/src/admin/github/GitHubForkStatusBadgeServer.tsx index afb1ebf5..34677866 100644 --- a/src/admin/github/GitHubForkStatusBadgeServer.tsx +++ b/src/admin/github/GitHubForkStatusBadgeServer.tsx @@ -14,12 +14,13 @@ export default async function GitHubForkStatusBadgeServer() { const { url, isForkedFromBase, + isBaseRepo, label, title, isBehind, } = await getGitHubMetaWithFallback({ owner, repo, branch }); - return isForkedFromBase + return isForkedFromBase || isBaseRepo ?