mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-25 10:21:40 +00:00
fix(website): show real contributor count on every page shell
The contributor badge rendered 0 on Playbook, Cookbook, home and custom pages, and reverted to 0 on hydration everywhere else. Two causes: - Shells that bypass PageShell (LearningArticleLayout, PlaybookIndex, PlaybookArticleBody, CookbookIndex, HomePage, TopNav, Custom) defaulted contributorsTotal to 0 instead of the site-data value. - site-data read .all-contributorsrc with node:fs at module scope. Those shells are client:load hydrated, so the read threw in the browser and the count reset to 0 after hydration. The count is now read once in astro.config.mjs and inlined through vite.define as __CONTRIBUTORS_TOTAL__, so it is a literal in both the server render and the client bundle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
This commit is contained in:
@@ -1,9 +1,30 @@
|
|||||||
import sitemap from "@astrojs/sitemap";
|
import sitemap from "@astrojs/sitemap";
|
||||||
import react from "@astrojs/react";
|
import react from "@astrojs/react";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
|
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
|
||||||
import pagefindResources from "./src/integrations/pagefind-resources";
|
import pagefindResources from "./src/integrations/pagefind-resources";
|
||||||
|
|
||||||
|
// The contributor count is read from the repo's all-contributors manifest at
|
||||||
|
// build time and inlined as a literal into both the server render and the
|
||||||
|
// client bundle. It cannot be read at module scope in src/ because the shells
|
||||||
|
// that display it are `client:load` hydrated: `node:fs` is unavailable in the
|
||||||
|
// browser, so the read would fail there and the badge would reset to 0 on
|
||||||
|
// hydration even though the server-rendered HTML had the right number.
|
||||||
|
function readContributorsTotal() {
|
||||||
|
try {
|
||||||
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rc = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(here, "..", ".all-contributorsrc"), "utf8"),
|
||||||
|
);
|
||||||
|
return Array.isArray(rc.contributors) ? rc.contributors.length : 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Playbook course content mirrored from external workshop repos is authored in
|
// Playbook course content mirrored from external workshop repos is authored in
|
||||||
// GitHub admonition syntax (`> [!NOTE]`). This remark plugin rewrites those
|
// GitHub admonition syntax (`> [!NOTE]`). This remark plugin rewrites those
|
||||||
// callouts into directives before rendering, so the same syntax used in the
|
// callouts into directives before rendering, so the same syntax used in the
|
||||||
@@ -70,6 +91,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
trailingSlash: "always",
|
trailingSlash: "always",
|
||||||
vite: {
|
vite: {
|
||||||
|
define: {
|
||||||
|
__CONTRIBUTORS_TOTAL__: JSON.stringify(readContributorsTotal()),
|
||||||
|
},
|
||||||
// @primer/react-brand's default entrypoint is CJS, so Node's ESM loader
|
// @primer/react-brand's default entrypoint is CJS, so Node's ESM loader
|
||||||
// cannot detect its named exports during SSR. The package also ships a
|
// cannot detect its named exports during SSR. The package also ships a
|
||||||
// proper ESM build; alias to it so named imports resolve in both the
|
// proper ESM build; alias to it so named imports resolve in both the
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Heading, Text } from "@primer/react-brand";
|
|||||||
import styles from "./styles/github-copilot-app.module.css";
|
import styles from "./styles/github-copilot-app.module.css";
|
||||||
import { pageHref } from "./pageHref";
|
import { pageHref } from "./pageHref";
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
import {
|
import {
|
||||||
LearningArticleLayout,
|
LearningArticleLayout,
|
||||||
type TocSection,
|
type TocSection,
|
||||||
@@ -42,7 +43,7 @@ export type CookbookSection = {
|
|||||||
export function CookbookIndex({
|
export function CookbookIndex({
|
||||||
sections,
|
sections,
|
||||||
searchIndex = [],
|
searchIndex = [],
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
}: {
|
}: {
|
||||||
sections: CookbookSection[];
|
sections: CookbookSection[];
|
||||||
searchIndex?: SearchItem[];
|
searchIndex?: SearchItem[];
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import brandDivider from "./brand-divider-copilot-sitting.webp";
|
|||||||
import { LargeFooter } from "./LargeFooter";
|
import { LargeFooter } from "./LargeFooter";
|
||||||
import { ContributorsHoverCard } from "./ContributorsHoverCard";
|
import { ContributorsHoverCard } from "./ContributorsHoverCard";
|
||||||
import { ContributorsNavButton } from "./ContributorsNavButton";
|
import { ContributorsNavButton } from "./ContributorsNavButton";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
import { LearningIcon } from "./LearningIcon";
|
import { LearningIcon } from "./LearningIcon";
|
||||||
import type { PrototypePageProps } from "./pageHref";
|
import type { PrototypePageProps } from "./pageHref";
|
||||||
import { getAwesomeCopilotNavLinks } from "./navigation";
|
import { getAwesomeCopilotNavLinks } from "./navigation";
|
||||||
@@ -137,7 +138,10 @@ export default function AwesomeCopilot({ pageHref }: PrototypePageProps) {
|
|||||||
styles={styles}
|
styles={styles}
|
||||||
inputAriaLabel="Search the library"
|
inputAriaLabel="Search the library"
|
||||||
/>
|
/>
|
||||||
<ContributorsNavButton href={pageHref("contributors")} />
|
<ContributorsNavButton
|
||||||
|
href={pageHref("contributors")}
|
||||||
|
total={siteContributorsTotal}
|
||||||
|
/>
|
||||||
<Button as="a" href={CONTRIBUTING_URL} variant="subtle" size="small">
|
<Button as="a" href={CONTRIBUTING_URL} variant="subtle" size="small">
|
||||||
Contribute
|
Contribute
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { LearningIcon } from "./LearningIcon";
|
|||||||
import { PageShell } from "./PageShell";
|
import { PageShell } from "./PageShell";
|
||||||
import { pageHref } from "./pageHref";
|
import { pageHref } from "./pageHref";
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
|
|
||||||
const REPO_URL = "https://github.com/github/awesome-copilot";
|
const REPO_URL = "https://github.com/github/awesome-copilot";
|
||||||
const CONTRIBUTING_URL =
|
const CONTRIBUTING_URL =
|
||||||
@@ -123,7 +124,7 @@ const buildResources = (counts: HomePageCounts): Resource[] => [
|
|||||||
export function HomePage({
|
export function HomePage({
|
||||||
counts,
|
counts,
|
||||||
searchIndex = [],
|
searchIndex = [],
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
}: HomePageProps) {
|
}: HomePageProps) {
|
||||||
const resources = buildResources(counts);
|
const resources = buildResources(counts);
|
||||||
const internalHref = ({ page, anchor }: { page?: string; anchor?: string }) =>
|
const internalHref = ({ page, anchor }: { page?: string; anchor?: string }) =>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
import { TopNavSearch } from "./TopNavSearch";
|
import { TopNavSearch } from "./TopNavSearch";
|
||||||
import { ContributorsNavButton } from "./ContributorsNavButton";
|
import { ContributorsNavButton } from "./ContributorsNavButton";
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
|
|
||||||
const CONTRIBUTING_URL =
|
const CONTRIBUTING_URL =
|
||||||
"https://github.com/github/awesome-copilot/blob/main/CONTRIBUTING.md";
|
"https://github.com/github/awesome-copilot/blob/main/CONTRIBUTING.md";
|
||||||
@@ -164,7 +165,7 @@ function LearningArticleLayoutBody({
|
|||||||
heroExtra,
|
heroExtra,
|
||||||
tocSections,
|
tocSections,
|
||||||
searchIndex = [],
|
searchIndex = [],
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
upNext,
|
upNext,
|
||||||
children,
|
children,
|
||||||
}: LearningArticleLayoutProps) {
|
}: LearningArticleLayoutProps) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
} from "../../lib/playbook-article";
|
} from "../../lib/playbook-article";
|
||||||
import { pageHref } from "./pageHref";
|
import { pageHref } from "./pageHref";
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
import styles from "./styles/github-copilot-app.module.css";
|
import styles from "./styles/github-copilot-app.module.css";
|
||||||
|
|
||||||
const CALLOUT_TITLES: Record<CalloutKind, string> = {
|
const CALLOUT_TITLES: Record<CalloutKind, string> = {
|
||||||
@@ -60,7 +61,7 @@ export function PlaybookArticleBody({
|
|||||||
sections,
|
sections,
|
||||||
tocSections,
|
tocSections,
|
||||||
searchIndex = [],
|
searchIndex = [],
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
}: {
|
}: {
|
||||||
/** Site path of this article, e.g. `learning-hub/agentic-workflows`. */
|
/** Site path of this article, e.g. `learning-hub/agentic-workflows`. */
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { PageShell } from "./PageShell";
|
|||||||
import { ScrambleText } from "./ScrambleText";
|
import { ScrambleText } from "./ScrambleText";
|
||||||
import { pageHref } from "./pageHref";
|
import { pageHref } from "./pageHref";
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
|
|
||||||
type Topic =
|
type Topic =
|
||||||
| "Getting started"
|
| "Getting started"
|
||||||
@@ -191,7 +192,7 @@ const recommendedCards = [
|
|||||||
export function PlaybookIndex({
|
export function PlaybookIndex({
|
||||||
articles,
|
articles,
|
||||||
searchIndex = [],
|
searchIndex = [],
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
}: {
|
}: {
|
||||||
articles: PlaybookArticle[];
|
articles: PlaybookArticle[];
|
||||||
searchIndex?: SearchItem[];
|
searchIndex?: SearchItem[];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from "@primer/react-brand";
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
import type { SearchItem } from "./searchIndex";
|
import type { SearchItem } from "./searchIndex";
|
||||||
|
import { contributorsTotal as siteContributorsTotal } from "../../lib/site-data";
|
||||||
import mobileStyles from "./styles/TopNav.module.css";
|
import mobileStyles from "./styles/TopNav.module.css";
|
||||||
import { ContributorsNavButton } from "./ContributorsNavButton";
|
import { ContributorsNavButton } from "./ContributorsNavButton";
|
||||||
import { LanguageSelect } from "./LanguageSelect";
|
import { LanguageSelect } from "./LanguageSelect";
|
||||||
@@ -26,7 +27,7 @@ export function TopNav({
|
|||||||
libraryLabel = "Resources",
|
libraryLabel = "Resources",
|
||||||
playbookLabel = "Playbook",
|
playbookLabel = "Playbook",
|
||||||
contributorsHref,
|
contributorsHref,
|
||||||
contributorsTotal = 0,
|
contributorsTotal = siteContributorsTotal,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
contributorsCurrent = false,
|
contributorsCurrent = false,
|
||||||
searchAriaLabel = "Search the library",
|
searchAriaLabel = "Search the library",
|
||||||
|
|||||||
Vendored
+5
@@ -1 +1,6 @@
|
|||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
||||||
|
/** Contributor count inlined at build time from the repo's `.all-contributorsrc`
|
||||||
|
* (see the `vite.define` entry in astro.config.mjs). Declared as a global so it
|
||||||
|
* works in both the server render and the hydrated client bundle. */
|
||||||
|
declare const __CONTRIBUTORS_TOTAL__: number;
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
* by the React components is defined in exactly one place and the shell always
|
* by the React components is defined in exactly one place and the shell always
|
||||||
* gets consistent counts and a consistent search index.
|
* gets consistent counts and a consistent search index.
|
||||||
*/
|
*/
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
import agentsData from "../../public/data/agents.json";
|
import agentsData from "../../public/data/agents.json";
|
||||||
import extensionsData from "../../public/data/extensions.json";
|
import extensionsData from "../../public/data/extensions.json";
|
||||||
import instructionsData from "../../public/data/instructions.json";
|
import instructionsData from "../../public/data/instructions.json";
|
||||||
@@ -24,15 +21,7 @@ import {
|
|||||||
const BASE = import.meta.env.BASE_URL ?? "/";
|
const BASE = import.meta.env.BASE_URL ?? "/";
|
||||||
|
|
||||||
function getContributorsTotal(): number {
|
function getContributorsTotal(): number {
|
||||||
try {
|
return typeof __CONTRIBUTORS_TOTAL__ === "number" ? __CONTRIBUTORS_TOTAL__ : 0;
|
||||||
const rcPath = path.resolve(process.cwd(), "..", ".all-contributorsrc");
|
|
||||||
const rc = JSON.parse(fs.readFileSync(rcPath, "utf8")) as {
|
|
||||||
contributors?: Array<Record<string, unknown>>;
|
|
||||||
};
|
|
||||||
return Array.isArray(rc.contributors) ? rc.contributors.length : 0;
|
|
||||||
} catch {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const agents = agentsData.items;
|
export const agents = agentsData.items;
|
||||||
|
|||||||
Reference in New Issue
Block a user