@grove-dev/astro 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/display.d.ts +11 -17
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +43 -43
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +6 -7
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +11 -12
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +35 -73
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +144 -126
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +11 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +12 -25
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +15 -16
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +4 -11
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +47 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +195 -180
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +7 -21
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +12 -31
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +4 -4
- package/src/components/AppsIndexRow.astro +55 -184
- package/src/components/AppsPagination.astro +45 -108
- package/src/components/CategoryGrid.astro +43 -31
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +41 -0
- package/src/components/ExploreByStack.astro +6 -0
- package/src/components/FilterGroupMenu.astro +49 -0
- package/src/components/FilterOptions.astro +44 -0
- package/src/components/Hero.astro +198 -147
- package/src/components/Icon.astro +10 -3
- package/src/components/ItemCard.astro +266 -248
- package/src/components/RecordSection.astro +68 -33
- package/src/components/RefinePanel.astro +115 -141
- package/src/components/StackGrid.astro +32 -24
- package/src/components/WhyThisExists.astro +32 -32
- package/src/layouts/BaseLayout.astro +104 -49
- package/src/layouts/Container.astro +5 -44
- package/src/layouts/Footer.astro +31 -20
- package/src/layouts/Header.astro +40 -19
- package/src/layouts/SectionHeader.astro +27 -43
- package/src/layouts/Seo.astro +38 -56
- package/src/layouts/ThemeToggle.astro +35 -48
- package/src/lib/display.ts +46 -53
- package/src/lib/format.ts +11 -12
- package/src/lib/lenses.ts +151 -172
- package/src/lib/repo.ts +16 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +222 -290
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +207 -1
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
- package/templates/default/.github/pull_request_template.md +27 -0
- package/templates/default/.github/workflows/build.yml +55 -0
- package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
- package/templates/default/.github/workflows/daily-refresh.yml +53 -0
- package/templates/default/.github/workflows/sync-contributors.yml +53 -0
- package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
- package/templates/default/.github/workflows/validate-data.yml +35 -0
- package/templates/default/data/records/coolify.yml +46 -0
- package/templates/default/data/records/gitea.yml +46 -0
- package/templates/default/data/records/hoppscotch.yml +47 -0
- package/templates/default/data/records/inventree.yml +47 -0
- package/templates/default/data/records/label-studio.yml +46 -0
- package/templates/default/data/records/logseq.yml +48 -0
- package/templates/default/data/records/private-gpt.yml +47 -0
- package/templates/default/data/records/wakapi.yml +44 -0
- package/templates/default/data/taxonomy/categories.yml +47 -0
- package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
- package/templates/default/data/taxonomy/platforms.yml +27 -0
- package/templates/default/data/taxonomy/stacks.yml +41 -0
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +5 -4
- package/templates/default/public/icons/stacks/bun.svg +1 -0
- package/templates/default/public/icons/stacks/clojure.svg +1 -0
- package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
- package/templates/default/public/icons/stacks/deno.svg +1 -0
- package/templates/default/public/icons/stacks/django.svg +1 -0
- package/templates/default/public/icons/stacks/docker.svg +1 -0
- package/templates/default/public/icons/stacks/go.svg +1 -0
- package/templates/default/public/icons/stacks/llm.svg +1 -0
- package/templates/default/public/icons/stacks/node.js.svg +1 -0
- package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
- package/templates/default/public/icons/stacks/vue.svg +1 -0
- package/templates/default/public/llms-full.txt +197 -5
- package/templates/default/public/llms.txt +107 -6
- package/templates/default/public/sitemap.xml +63 -0
- package/templates/default/scripts/build-llms.mjs +123 -0
- package/templates/default/scripts/build-records-json.mjs +27 -0
- package/templates/default/scripts/build-sitemap.mjs +20 -0
- package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
- package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
- package/templates/default/scripts/fetch-icons.mjs +137 -0
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
- package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
- package/templates/default/scripts/refresh-records-activity.mjs +24 -0
- package/templates/default/scripts/repair-license-format.mjs +72 -0
- package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
- package/templates/default/scripts/seed-from-github.mjs +62 -0
- package/templates/default/scripts/sync-contributors.mjs +145 -0
- package/templates/default/scripts/sync-github-metadata.mjs +28 -0
- package/templates/default/scripts/validate-records.mjs +27 -0
- package/templates/default/src/data/records.ts +134 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[itemSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +476 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[itemSlug].astro +301 -0
- package/templates/default/src/pages/apps/index.astro +146 -0
- package/templates/default/src/pages/contributors.astro +184 -0
- package/templates/default/src/pages/index.astro +119 -162
- package/templates/default/src/pages/submit.astro +276 -114
- package/templates/default/wrangler.jsonc +28 -0
- package/LICENSE +0 -21
- package/templates/default/.astro/content.d.ts +0 -155
- package/templates/default/.astro/settings.json +0 -5
- package/templates/default/.astro/types.d.ts +0 -1
- package/templates/default/data/generated/apps.json +0 -1
- package/templates/default/data/generated/records.full.json +0 -8
- package/templates/default/data/generated/records.index.json +0 -7
- package/templates/default/data/generated/records.json +0 -8
- package/templates/default/data/generated/repo-stats.json +0 -1
- package/templates/default/data/generated/site-config.json +0 -31
- package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
- package/templates/default/dist/about/index.html +0 -165
- package/templates/default/dist/icons/platforms/android.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
- package/templates/default/dist/icons/platforms/chrome.svg +0 -1
- package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
- package/templates/default/dist/icons/platforms/desktop.svg +0 -4
- package/templates/default/dist/icons/platforms/embedded.svg +0 -5
- package/templates/default/dist/icons/platforms/ios.svg +0 -1
- package/templates/default/dist/icons/platforms/linux.svg +0 -1
- package/templates/default/dist/icons/platforms/macos.svg +0 -1
- package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/dist/icons/platforms/web.svg +0 -5
- package/templates/default/dist/icons/platforms/windows.svg +0 -6
- package/templates/default/dist/icons/stacks/android.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
- package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
- package/templates/default/dist/icons/stacks/dart.svg +0 -1
- package/templates/default/dist/icons/stacks/firebase.svg +0 -1
- package/templates/default/dist/icons/stacks/flutter.svg +0 -1
- package/templates/default/dist/icons/stacks/graphql.svg +0 -1
- package/templates/default/dist/icons/stacks/ionic.svg +0 -4
- package/templates/default/dist/icons/stacks/java.svg +0 -1
- package/templates/default/dist/icons/stacks/javascript.svg +0 -1
- package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
- package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
- package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
- package/templates/default/dist/icons/stacks/python.svg +0 -1
- package/templates/default/dist/icons/stacks/react-native.svg +0 -1
- package/templates/default/dist/icons/stacks/react.svg +0 -1
- package/templates/default/dist/icons/stacks/rust.svg +0 -1
- package/templates/default/dist/icons/stacks/solidity.svg +0 -1
- package/templates/default/dist/icons/stacks/swift.svg +0 -1
- package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/dist/icons/stacks/typescript.svg +0 -1
- package/templates/default/dist/index.html +0 -55
- package/templates/default/dist/llms-full.txt +0 -14
- package/templates/default/dist/llms.txt +0 -11
- package/templates/default/dist/og-image.svg +0 -48
- package/templates/default/dist/projects/index.html +0 -61
- package/templates/default/dist/robots.txt +0 -53
- package/templates/default/dist/sitemap.xml +0 -21
- package/templates/default/dist/submit/index.html +0 -69
- package/templates/default/node_modules/.bin/astro +0 -21
- package/templates/default/node_modules/.bin/astro-check +0 -21
- package/templates/default/node_modules/.bin/grove +0 -21
- package/templates/default/node_modules/.bin/jiti +0 -21
- package/templates/default/node_modules/.bin/prettier +0 -21
- package/templates/default/node_modules/.bin/rollup +0 -21
- package/templates/default/node_modules/.bin/tsc +0 -21
- package/templates/default/node_modules/.bin/tsserver +0 -21
- package/templates/default/node_modules/.bin/tsx +0 -21
- package/templates/default/node_modules/.bin/vite +0 -21
- package/templates/default/node_modules/.bin/yaml +0 -21
- package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/package.json +0 -3
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
- package/templates/default/src/components/layout/BaseLayout.astro +0 -47
- package/templates/default/src/components/layout/Container.astro +0 -18
- package/templates/default/src/components/layout/Footer.astro +0 -27
- package/templates/default/src/components/layout/Header.astro +0 -43
- package/templates/default/src/lib/markdown.ts +0 -171
- package/templates/default/src/pages/projects/index.astro +0 -193
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* /apps — alternate list URL.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors `/[slug]/index.astro` (the blueprint-aware dynamic list
|
|
6
|
+
* page) at a static URL so the openapps-style `/apps` route works
|
|
7
|
+
* for project-directory blueprints without per-blueprint forks.
|
|
8
|
+
*
|
|
9
|
+
* For non-project blueprints (resource-hub → /resources,
|
|
10
|
+
* ecosystem-map → /entities), this page redirects to the matching
|
|
11
|
+
* dynamic route.
|
|
12
|
+
*/
|
|
13
|
+
import { indexSlug, itemLabelPlural, items } from "../../data/records";
|
|
14
|
+
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
|
|
15
|
+
import SmartLensTabs from "@grove-dev/astro/components/SmartLensTabs.astro";
|
|
16
|
+
import RefinePanel from "@grove-dev/astro/components/RefinePanel.astro";
|
|
17
|
+
import AppsIndexRow from "@grove-dev/astro/components/AppsIndexRow.astro";
|
|
18
|
+
import ExploreByCategory from "@grove-dev/astro/components/ExploreByCategory.astro";
|
|
19
|
+
import ExploreByStack from "@grove-dev/astro/components/ExploreByStack.astro";
|
|
20
|
+
import siteConfig from "../../../data/generated/site-config.json";
|
|
21
|
+
import {
|
|
22
|
+
activeFilterChips,
|
|
23
|
+
applySort,
|
|
24
|
+
buildFacets,
|
|
25
|
+
effectivePage,
|
|
26
|
+
effectiveSort,
|
|
27
|
+
filterApps,
|
|
28
|
+
filtersFromSearchParams,
|
|
29
|
+
paginate,
|
|
30
|
+
totalPages,
|
|
31
|
+
} from "@grove-dev/astro";
|
|
32
|
+
|
|
33
|
+
const slug = indexSlug();
|
|
34
|
+
|
|
35
|
+
// Only render the static /apps route when the active blueprint is
|
|
36
|
+
// project-directory (the openapps-style URL). For other blueprints,
|
|
37
|
+
// redirect to the dynamic `/<slug>/` route.
|
|
38
|
+
if (slug !== "projects") {
|
|
39
|
+
return Astro.redirect(`/${slug}${Astro.url.search}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const total = items.length;
|
|
43
|
+
const filters = filtersFromSearchParams(Astro.url.searchParams);
|
|
44
|
+
const facets = buildFacets(items);
|
|
45
|
+
const sort = effectiveSort(filters);
|
|
46
|
+
const requestedPage = effectivePage(filters);
|
|
47
|
+
const filtered = filterApps(items, filters);
|
|
48
|
+
const sorted = applySort(filtered, sort);
|
|
49
|
+
const pages = totalPages(sorted.length);
|
|
50
|
+
const page = Math.min(requestedPage, pages);
|
|
51
|
+
const visibleItems = paginate(sorted, page);
|
|
52
|
+
const chips = activeFilterChips(filters);
|
|
53
|
+
const itemPlural = itemLabelPlural();
|
|
54
|
+
|
|
55
|
+
const title = `${itemPlural.charAt(0).toUpperCase()}${itemPlural.slice(1)} — ${siteConfig.name}`;
|
|
56
|
+
const description = `Browse every curated ${itemPlural.replace(/s$/, "")} in the ${siteConfig.name} directory.`;
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<BaseLayout title={title} description={description} site={siteConfig}>
|
|
60
|
+
<section class="grove-section">
|
|
61
|
+
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
62
|
+
<header class="mb-6">
|
|
63
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
64
|
+
{itemPlural}
|
|
65
|
+
</span>
|
|
66
|
+
<h1 class="m-0 mt-1 text-[1.75rem] sm:text-[2.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
67
|
+
Browse the directory
|
|
68
|
+
</h1>
|
|
69
|
+
<p class="m-0 mt-2 max-w-2xl text-base leading-relaxed text-ink-700 dark:text-ink-200">
|
|
70
|
+
Search and filter every curated {itemPlural.replace(/s$/, "")}. Use the
|
|
71
|
+
lens tabs to focus on a curated angle, or refine by stack,
|
|
72
|
+
platform, license, and status.
|
|
73
|
+
</p>
|
|
74
|
+
</header>
|
|
75
|
+
|
|
76
|
+
<div class="mb-6">
|
|
77
|
+
<SmartLensTabs
|
|
78
|
+
currentParams={Astro.url.searchParams}
|
|
79
|
+
pathPrefix="/apps"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="mb-8">
|
|
84
|
+
<RefinePanel
|
|
85
|
+
facets={facets}
|
|
86
|
+
initial={filters}
|
|
87
|
+
pathPrefix="/apps"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{chips.length > 0 && (
|
|
92
|
+
<ul class="mb-6 flex flex-wrap items-center gap-1.5" aria-label="Active filters">
|
|
93
|
+
{chips.map((chip) => (
|
|
94
|
+
<li>
|
|
95
|
+
<a
|
|
96
|
+
href={chip.removeHref}
|
|
97
|
+
class="grove-badge inline-flex items-center gap-1.5 hover:border-ink-300 dark:hover:border-ink-700"
|
|
98
|
+
title={`Remove filter: ${chip.label}`}
|
|
99
|
+
>
|
|
100
|
+
<span>{chip.label}</span>
|
|
101
|
+
<span aria-hidden="true">×</span>
|
|
102
|
+
</a>
|
|
103
|
+
</li>
|
|
104
|
+
))}
|
|
105
|
+
</ul>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{visibleItems.length === 0 ? (
|
|
109
|
+
<p class="grove-card p-8 text-center text-sm text-ink-500 dark:text-ink-400">
|
|
110
|
+
No {itemPlural} match these filters. Try clearing a filter or two.
|
|
111
|
+
</p>
|
|
112
|
+
) : (
|
|
113
|
+
<ul class="m-0 grid list-none grid-cols-1 gap-3 p-0">
|
|
114
|
+
{visibleItems.map((item) => (
|
|
115
|
+
<li class="m-0 p-0">
|
|
116
|
+
<AppsIndexRow record={item as Parameters<typeof AppsIndexRow>[0]["record"]} detailHref={`/apps/${item.slug}`} />
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
<ExploreByCategory
|
|
125
|
+
categories={[
|
|
126
|
+
...new Set(
|
|
127
|
+
items
|
|
128
|
+
.filter((i) => i.kind === "project")
|
|
129
|
+
.map((i) => (i as { category?: string }).category ?? "")
|
|
130
|
+
.filter(Boolean),
|
|
131
|
+
),
|
|
132
|
+
].map((name) => ({ name, slug: name }))}
|
|
133
|
+
pathPrefix="/apps"
|
|
134
|
+
/>
|
|
135
|
+
<ExploreByStack
|
|
136
|
+
stacks={[
|
|
137
|
+
...new Set(
|
|
138
|
+
items
|
|
139
|
+
.filter((i) => i.kind === "project")
|
|
140
|
+
.flatMap((i) => [(i as { stack?: string }).stack ?? "", ...(i as { stacks?: string[] }).stacks])
|
|
141
|
+
.filter(Boolean),
|
|
142
|
+
),
|
|
143
|
+
].map((name) => ({ name, slug: name }))}
|
|
144
|
+
pathPrefix="/apps"
|
|
145
|
+
/>
|
|
146
|
+
</BaseLayout>
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Contributors page — full list with per-user contribution counts.
|
|
4
|
+
*
|
|
5
|
+
* Generic: works for any blueprint (project-directory, resource-hub,
|
|
6
|
+
* ecosystem-map) and any source of contributors. Reads from
|
|
7
|
+
* `data/generated/contributors.json` if present, otherwise renders
|
|
8
|
+
* a graceful empty state. The file is produced by the
|
|
9
|
+
* `sync-contributors` GitHub Action (see `.github/workflows/`).
|
|
10
|
+
*
|
|
11
|
+
* Falls back to deriving contributors from the records themselves
|
|
12
|
+
* (record.github.fullName → owner handle) when no JSON file exists,
|
|
13
|
+
* so a fresh scaffold still shows something useful.
|
|
14
|
+
*/
|
|
15
|
+
import siteConfig from "../../data/generated/site-config.json";
|
|
16
|
+
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
|
|
17
|
+
import { projects } from "../data/records";
|
|
18
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { dirname, resolve } from "node:path";
|
|
21
|
+
|
|
22
|
+
interface Contributor {
|
|
23
|
+
username: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
avatarUrl?: string;
|
|
26
|
+
profileUrl?: string;
|
|
27
|
+
contributions?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const contributorsPath = [
|
|
32
|
+
resolve(here, "..", "..", "data", "generated", "contributors.json"),
|
|
33
|
+
resolve(process.cwd(), "data", "generated", "contributors.json"),
|
|
34
|
+
].find((p) => existsSync(p));
|
|
35
|
+
|
|
36
|
+
let contributors: Contributor[] = [];
|
|
37
|
+
if (contributorsPath) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(contributorsPath, "utf8")) as {
|
|
40
|
+
contributors?: Contributor[];
|
|
41
|
+
};
|
|
42
|
+
contributors = parsed.contributors ?? [];
|
|
43
|
+
} catch {
|
|
44
|
+
contributors = [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback: derive owners from record.github.fullName when no JSON
|
|
49
|
+
// is present (fresh scaffold before the first sync run).
|
|
50
|
+
if (contributors.length === 0) {
|
|
51
|
+
const seen = new Map<string, Contributor>();
|
|
52
|
+
for (const r of projects) {
|
|
53
|
+
const full = r.github?.fullName;
|
|
54
|
+
if (!full || !full.includes("/")) continue;
|
|
55
|
+
const username = full.split("/")[0];
|
|
56
|
+
if (seen.has(username)) continue;
|
|
57
|
+
seen.set(username, {
|
|
58
|
+
username,
|
|
59
|
+
avatarUrl: `https://avatars.githubusercontent.com/${username}?v=4&s=80`,
|
|
60
|
+
profileUrl: `https://github.com/${username}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
contributors = [...seen.values()].sort((a, b) =>
|
|
64
|
+
a.username.localeCompare(b.username),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const total = contributors.length;
|
|
69
|
+
const sorted = [...contributors].sort((a, b) => {
|
|
70
|
+
const ac = a.contributions ?? 0;
|
|
71
|
+
const bc = b.contributions ?? 0;
|
|
72
|
+
if (bc !== ac) return bc - ac;
|
|
73
|
+
return a.username.localeCompare(b.username);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const title = `Contributors — ${siteConfig.name}`;
|
|
77
|
+
const description =
|
|
78
|
+
`Every person who has helped build ${siteConfig.name} — from a single line to long-running stewardship.`;
|
|
79
|
+
|
|
80
|
+
const repoUrl = siteConfig.repoUrl;
|
|
81
|
+
const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/contributors` : null;
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<BaseLayout title={title} description={description} site={siteConfig}>
|
|
85
|
+
<section class="grove-section">
|
|
86
|
+
<div class="grove-container-wide mx-auto px-5 py-12 sm:px-7 sm:py-16">
|
|
87
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
88
|
+
Contributors
|
|
89
|
+
</span>
|
|
90
|
+
<h1 class="m-0 mt-2 text-[2rem] sm:text-[2.5rem] font-semibold leading-[1.05] tracking-tight text-ink-900 dark:text-ink-100">
|
|
91
|
+
{total > 0 ? `Showing all ${total} contributors` : "Contributors"}
|
|
92
|
+
</h1>
|
|
93
|
+
<p class="m-0 mt-3 max-w-2xl text-base leading-relaxed text-ink-700 dark:text-ink-200">
|
|
94
|
+
{total > 0
|
|
95
|
+
? `Every person who has helped build ${siteConfig.name} — from a single line to long-running stewardship.`
|
|
96
|
+
: `Contributors will appear here once ${siteConfig.name} syncs its first set of GitHub metadata. The list is auto-generated from ${repoUrl ? repoUrl : "the project's source repository"}.`}
|
|
97
|
+
</p>
|
|
98
|
+
|
|
99
|
+
{contributorsGraphHref && (
|
|
100
|
+
<p class="m-0 mt-3 text-sm text-ink-500 dark:text-ink-400">
|
|
101
|
+
<a
|
|
102
|
+
href={contributorsGraphHref}
|
|
103
|
+
target="_blank"
|
|
104
|
+
rel="noopener noreferrer"
|
|
105
|
+
class="font-medium underline decoration-ink-300 underline-offset-2 hover:decoration-ink-500 dark:decoration-ink-700 dark:hover:decoration-ink-400"
|
|
106
|
+
>
|
|
107
|
+
GitHub contributors graph →
|
|
108
|
+
</a>
|
|
109
|
+
</p>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
</section>
|
|
113
|
+
|
|
114
|
+
{sorted.length === 0 ? (
|
|
115
|
+
<section class="grove-section">
|
|
116
|
+
<div class="grove-container mx-auto px-5 pb-16 sm:px-7">
|
|
117
|
+
<div class="grove-card flex flex-col items-center gap-3 p-8 text-center">
|
|
118
|
+
<p class="m-0 text-sm text-ink-500 dark:text-ink-400">
|
|
119
|
+
No contributors synced yet. Once the first sync runs, every
|
|
120
|
+
contributor avatar lands here.
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</section>
|
|
125
|
+
) : (
|
|
126
|
+
<section class="grove-section" aria-label="Contributors list">
|
|
127
|
+
<div class="grove-container-wide mx-auto px-5 pb-16 sm:px-7">
|
|
128
|
+
<ul
|
|
129
|
+
class="m-0 grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
|
|
130
|
+
aria-label="Contributors"
|
|
131
|
+
>
|
|
132
|
+
{sorted.map((c) => {
|
|
133
|
+
const href = c.profileUrl ?? `https://github.com/${c.username}`;
|
|
134
|
+
const avatar =
|
|
135
|
+
c.avatarUrl ??
|
|
136
|
+
`https://avatars.githubusercontent.com/${c.username}?v=4&s=80`;
|
|
137
|
+
return (
|
|
138
|
+
<li class="m-0 p-0">
|
|
139
|
+
<a
|
|
140
|
+
href={href}
|
|
141
|
+
target="_blank"
|
|
142
|
+
rel="noopener noreferrer"
|
|
143
|
+
class="group grove-card flex flex-col items-center gap-2 p-3 text-center no-underline transition-colors hover:border-ink-300 dark:hover:border-ink-700"
|
|
144
|
+
data-event="contributor_click"
|
|
145
|
+
data-event-source="contributors_page"
|
|
146
|
+
>
|
|
147
|
+
<img
|
|
148
|
+
src={avatar}
|
|
149
|
+
alt={c.name ?? c.username}
|
|
150
|
+
width="56"
|
|
151
|
+
height="56"
|
|
152
|
+
loading="lazy"
|
|
153
|
+
decoding="async"
|
|
154
|
+
class="h-14 w-14 rounded-full border border-ink-200 object-cover dark:border-ink-800"
|
|
155
|
+
/>
|
|
156
|
+
<span class="line-clamp-1 text-sm font-medium text-ink-700 group-hover:text-ink-900 dark:text-ink-300 dark:group-hover:text-ink-100">
|
|
157
|
+
{c.name ?? c.username}
|
|
158
|
+
</span>
|
|
159
|
+
<span class="text-2xs text-ink-500 dark:text-ink-400">
|
|
160
|
+
@{c.username}
|
|
161
|
+
</span>
|
|
162
|
+
{typeof c.contributions === "number" && (
|
|
163
|
+
<span class="font-mono text-2xs text-ink-400">
|
|
164
|
+
{c.contributions.toLocaleString()} contributions
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
</a>
|
|
168
|
+
</li>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</ul>
|
|
172
|
+
</div>
|
|
173
|
+
</section>
|
|
174
|
+
)}
|
|
175
|
+
</BaseLayout>
|
|
176
|
+
|
|
177
|
+
<style>
|
|
178
|
+
.line-clamp-1 {
|
|
179
|
+
display: -webkit-box;
|
|
180
|
+
-webkit-line-clamp: 1;
|
|
181
|
+
-webkit-box-orient: vertical;
|
|
182
|
+
overflow: hidden;
|
|
183
|
+
}
|
|
184
|
+
</style>
|