@grove-dev/astro 0.2.10 → 0.2.14

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.
Files changed (237) hide show
  1. package/README.md +26 -18
  2. package/dist/lib/display.d.ts +11 -17
  3. package/dist/lib/display.d.ts.map +1 -1
  4. package/dist/lib/display.js +43 -43
  5. package/dist/lib/display.js.map +1 -1
  6. package/dist/lib/format.d.ts +6 -7
  7. package/dist/lib/format.d.ts.map +1 -1
  8. package/dist/lib/format.js +11 -12
  9. package/dist/lib/format.js.map +1 -1
  10. package/dist/lib/lenses.d.ts +35 -73
  11. package/dist/lib/lenses.d.ts.map +1 -1
  12. package/dist/lib/lenses.js +144 -126
  13. package/dist/lib/lenses.js.map +1 -1
  14. package/dist/lib/repo.d.ts +12 -22
  15. package/dist/lib/repo.d.ts.map +1 -1
  16. package/dist/lib/repo.js +13 -25
  17. package/dist/lib/repo.js.map +1 -1
  18. package/dist/lib/scores.d.ts +15 -16
  19. package/dist/lib/scores.d.ts.map +1 -1
  20. package/dist/lib/scores.js +4 -11
  21. package/dist/lib/scores.js.map +1 -1
  22. package/dist/lib/search.d.ts +44 -129
  23. package/dist/lib/search.d.ts.map +1 -1
  24. package/dist/lib/search.js +192 -180
  25. package/dist/lib/search.js.map +1 -1
  26. package/dist/lib/taxonomy-counts.d.ts +7 -21
  27. package/dist/lib/taxonomy-counts.d.ts.map +1 -1
  28. package/dist/lib/taxonomy-counts.js +12 -31
  29. package/dist/lib/taxonomy-counts.js.map +1 -1
  30. package/package.json +4 -4
  31. package/src/components/CategoryGrid.astro +43 -31
  32. package/src/components/ContributorsGrid.astro +3 -3
  33. package/src/components/CurationGrid.astro +41 -0
  34. package/src/components/ExploreByStack.astro +6 -0
  35. package/src/components/FilterGroupMenu.astro +49 -0
  36. package/src/components/FilterOptions.astro +44 -0
  37. package/src/components/Hero.astro +198 -147
  38. package/src/components/Icon.astro +10 -3
  39. package/src/components/IndexRow.astro +75 -0
  40. package/src/components/ItemCard.astro +266 -248
  41. package/src/components/Pagination.astro +63 -0
  42. package/src/components/RecordSection.astro +70 -34
  43. package/src/components/RefinePanel.astro +115 -141
  44. package/src/components/StackGrid.astro +32 -24
  45. package/src/components/WhyThisExists.astro +32 -32
  46. package/src/layouts/BaseLayout.astro +104 -49
  47. package/src/layouts/Container.astro +5 -44
  48. package/src/layouts/Footer.astro +31 -20
  49. package/src/layouts/Header.astro +40 -19
  50. package/src/layouts/SectionHeader.astro +27 -43
  51. package/src/layouts/Seo.astro +38 -56
  52. package/src/layouts/ThemeToggle.astro +35 -48
  53. package/src/lib/display.ts +46 -53
  54. package/src/lib/format.ts +11 -12
  55. package/src/lib/lenses.ts +151 -172
  56. package/src/lib/repo.ts +17 -28
  57. package/src/lib/scores.ts +15 -19
  58. package/src/lib/search.ts +218 -291
  59. package/src/lib/taxonomy-counts.ts +12 -46
  60. package/src/styles.css +209 -1
  61. package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  62. package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  63. package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
  64. package/templates/default/.github/pull_request_template.md +27 -0
  65. package/templates/default/.github/workflows/build.yml +55 -0
  66. package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
  67. package/templates/default/.github/workflows/daily-refresh.yml +53 -0
  68. package/templates/default/.github/workflows/sync-contributors.yml +53 -0
  69. package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
  70. package/templates/default/.github/workflows/validate-data.yml +35 -0
  71. package/templates/default/data/records/coolify.yml +46 -0
  72. package/templates/default/data/records/gitea.yml +46 -0
  73. package/templates/default/data/records/hoppscotch.yml +47 -0
  74. package/templates/default/data/records/inventree.yml +47 -0
  75. package/templates/default/data/records/label-studio.yml +46 -0
  76. package/templates/default/data/records/logseq.yml +48 -0
  77. package/templates/default/data/records/private-gpt.yml +47 -0
  78. package/templates/default/data/records/wakapi.yml +44 -0
  79. package/templates/default/data/taxonomy/categories.yml +47 -0
  80. package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
  81. package/templates/default/data/taxonomy/platforms.yml +27 -0
  82. package/templates/default/data/taxonomy/stacks.yml +41 -0
  83. package/templates/default/grove.config.ts +1 -1
  84. package/templates/default/package.json +5 -4
  85. package/templates/default/public/icons/stacks/bun.svg +1 -0
  86. package/templates/default/public/icons/stacks/clojure.svg +1 -0
  87. package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
  88. package/templates/default/public/icons/stacks/deno.svg +1 -0
  89. package/templates/default/public/icons/stacks/django.svg +1 -0
  90. package/templates/default/public/icons/stacks/docker.svg +1 -0
  91. package/templates/default/public/icons/stacks/go.svg +1 -0
  92. package/templates/default/public/icons/stacks/llm.svg +1 -0
  93. package/templates/default/public/icons/stacks/node.js.svg +1 -0
  94. package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
  95. package/templates/default/public/icons/stacks/vue.svg +1 -0
  96. package/templates/default/public/llms-full.txt +197 -5
  97. package/templates/default/public/llms.txt +107 -6
  98. package/templates/default/public/sitemap.xml +63 -0
  99. package/templates/default/scripts/build-llms.mjs +123 -0
  100. package/templates/default/scripts/build-records-json.mjs +27 -0
  101. package/templates/default/scripts/build-sitemap.mjs +20 -0
  102. package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
  103. package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
  104. package/templates/default/scripts/fetch-icons.mjs +137 -0
  105. package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
  106. package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
  107. package/templates/default/scripts/refresh-records-activity.mjs +24 -0
  108. package/templates/default/scripts/repair-license-format.mjs +72 -0
  109. package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
  110. package/templates/default/scripts/seed-from-github.mjs +62 -0
  111. package/templates/default/scripts/sync-contributors.mjs +145 -0
  112. package/templates/default/scripts/sync-github-metadata.mjs +28 -0
  113. package/templates/default/scripts/validate-records.mjs +27 -0
  114. package/templates/default/src/data/records.ts +138 -51
  115. package/templates/default/src/pages/404.astro +64 -0
  116. package/templates/default/src/pages/{projects/[slug].astro → [slug]/[recordSlug].astro} +236 -66
  117. package/templates/default/src/pages/[slug]/index.astro +477 -0
  118. package/templates/default/src/pages/about.astro +165 -82
  119. package/templates/default/src/pages/apps/[recordSlug].astro +33 -0
  120. package/templates/default/src/pages/contributors.astro +184 -0
  121. package/templates/default/src/pages/index.astro +119 -162
  122. package/templates/default/src/pages/submit.astro +276 -114
  123. package/templates/default/wrangler.jsonc +28 -0
  124. package/LICENSE +0 -21
  125. package/src/components/AppsIndexRow.astro +0 -204
  126. package/src/components/AppsPagination.astro +0 -126
  127. package/templates/default/.astro/content.d.ts +0 -155
  128. package/templates/default/.astro/settings.json +0 -5
  129. package/templates/default/.astro/types.d.ts +0 -1
  130. package/templates/default/data/generated/apps.json +0 -1
  131. package/templates/default/data/generated/records.full.json +0 -8
  132. package/templates/default/data/generated/records.index.json +0 -7
  133. package/templates/default/data/generated/records.json +0 -8
  134. package/templates/default/data/generated/repo-stats.json +0 -1
  135. package/templates/default/data/generated/site-config.json +0 -31
  136. package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
  137. package/templates/default/dist/about/index.html +0 -165
  138. package/templates/default/dist/icons/platforms/android.svg +0 -1
  139. package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
  140. package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
  141. package/templates/default/dist/icons/platforms/chrome.svg +0 -1
  142. package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
  143. package/templates/default/dist/icons/platforms/desktop.svg +0 -4
  144. package/templates/default/dist/icons/platforms/embedded.svg +0 -5
  145. package/templates/default/dist/icons/platforms/ios.svg +0 -1
  146. package/templates/default/dist/icons/platforms/linux.svg +0 -1
  147. package/templates/default/dist/icons/platforms/macos.svg +0 -1
  148. package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
  149. package/templates/default/dist/icons/platforms/web.svg +0 -5
  150. package/templates/default/dist/icons/platforms/windows.svg +0 -6
  151. package/templates/default/dist/icons/stacks/android.svg +0 -1
  152. package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
  153. package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
  154. package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
  155. package/templates/default/dist/icons/stacks/dart.svg +0 -1
  156. package/templates/default/dist/icons/stacks/firebase.svg +0 -1
  157. package/templates/default/dist/icons/stacks/flutter.svg +0 -1
  158. package/templates/default/dist/icons/stacks/graphql.svg +0 -1
  159. package/templates/default/dist/icons/stacks/ionic.svg +0 -4
  160. package/templates/default/dist/icons/stacks/java.svg +0 -1
  161. package/templates/default/dist/icons/stacks/javascript.svg +0 -1
  162. package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
  163. package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
  164. package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
  165. package/templates/default/dist/icons/stacks/python.svg +0 -1
  166. package/templates/default/dist/icons/stacks/react-native.svg +0 -1
  167. package/templates/default/dist/icons/stacks/react.svg +0 -1
  168. package/templates/default/dist/icons/stacks/rust.svg +0 -1
  169. package/templates/default/dist/icons/stacks/solidity.svg +0 -1
  170. package/templates/default/dist/icons/stacks/swift.svg +0 -1
  171. package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
  172. package/templates/default/dist/icons/stacks/typescript.svg +0 -1
  173. package/templates/default/dist/index.html +0 -55
  174. package/templates/default/dist/llms-full.txt +0 -14
  175. package/templates/default/dist/llms.txt +0 -11
  176. package/templates/default/dist/og-image.svg +0 -48
  177. package/templates/default/dist/projects/index.html +0 -61
  178. package/templates/default/dist/robots.txt +0 -53
  179. package/templates/default/dist/sitemap.xml +0 -21
  180. package/templates/default/dist/submit/index.html +0 -69
  181. package/templates/default/node_modules/.bin/astro +0 -21
  182. package/templates/default/node_modules/.bin/astro-check +0 -21
  183. package/templates/default/node_modules/.bin/grove +0 -21
  184. package/templates/default/node_modules/.bin/jiti +0 -21
  185. package/templates/default/node_modules/.bin/prettier +0 -21
  186. package/templates/default/node_modules/.bin/rollup +0 -21
  187. package/templates/default/node_modules/.bin/tsc +0 -21
  188. package/templates/default/node_modules/.bin/tsserver +0 -21
  189. package/templates/default/node_modules/.bin/tsx +0 -21
  190. package/templates/default/node_modules/.bin/vite +0 -21
  191. package/templates/default/node_modules/.bin/yaml +0 -21
  192. package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
  193. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
  194. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
  195. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
  196. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
  197. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
  198. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
  199. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
  200. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
  201. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
  202. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
  203. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
  204. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
  205. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
  206. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
  207. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
  208. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
  209. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
  210. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
  211. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
  212. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
  213. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
  214. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
  215. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
  216. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
  217. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
  218. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
  219. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
  220. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
  221. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
  222. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
  223. package/templates/default/node_modules/.vite/deps/package.json +0 -3
  224. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
  225. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
  226. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
  227. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
  228. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
  229. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
  230. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
  231. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
  232. package/templates/default/src/components/layout/BaseLayout.astro +0 -47
  233. package/templates/default/src/components/layout/Container.astro +0 -18
  234. package/templates/default/src/components/layout/Footer.astro +0 -27
  235. package/templates/default/src/components/layout/Header.astro +0 -43
  236. package/templates/default/src/lib/markdown.ts +0 -171
  237. package/templates/default/src/pages/projects/index.astro +0 -193
@@ -1,99 +1,182 @@
1
1
  ---
2
2
  /**
3
- * About page — three value props (MinimalAbout) + a long-form
4
- * methodology section rendered from `content/pages/methodology.md`.
3
+ * About page — generic.
5
4
  *
6
- * The markdown body is read at build time and rendered as a single
7
- * <pre> block (we keep the template dep-free; consumers can swap in
8
- * a real markdown pipeline by overriding the page). The intent is
9
- * that the methodology is *visible on the page* even if the prose
10
- * isn't styled — the structured content stays discoverable.
5
+ * Mirrors the openapps section structure: breadcrumb, header with
6
+ * two-line intro, then a stack of cards (Why, Value, Data, Generic)
7
+ * followed by a CTA row. The copy is generic — references "directory",
8
+ * "record", "framework" so it works for any blueprint.
9
+ *
10
+ * Side-by-side comparison with the older "Why" copy:
11
+ * - Old: "A directory of real open-source apps. Started from
12
+ * the Open Source Flutter Apps collection…"
13
+ * - New: "A growing community knowledge site. A curated, health-
14
+ * aware developer directory…"
15
+ *
16
+ * The card utility classes (`grove-card`, `grove-btn-*`) match the
17
+ * rest of the framework, and the data fields it references
18
+ * (`siteConfig.tagline`, `siteConfig.repoUrl`) all flow from the
19
+ * site config — no hardcoded brand or copy.
11
20
  */
12
- import { readFileSync, existsSync } from "node:fs";
13
- import { fileURLToPath } from "node:url";
14
- import { dirname, resolve } from "node:path";
15
-
16
- import siteConfig from "../../data/generated/site-config.json";
17
21
  import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
18
22
  import Container from "@grove-dev/astro/layouts/Container.astro";
19
- import MinimalAbout from "@grove-dev/astro/components/MinimalAbout.astro";
23
+ import siteConfig from "../../data/generated/site-config.json";
24
+ import { indexSlug, itemLabel } from "../data/records";
20
25
 
26
+ const githubUrl = siteConfig.repoUrl;
21
27
  const title = `About — ${siteConfig.name}`;
22
- const description = `About ${siteConfig.name} ${siteConfig.tagline}`;
23
-
24
- // ── Load methodology.md from content/pages/ (build time) ──────
25
- const here = dirname(fileURLToPath(import.meta.url));
26
- const methodologyPath = [
27
- resolve(here, "..", "..", "..", "content", "pages", "methodology.md"),
28
- resolve(here, "..", "..", "..", "..", "content", "pages", "methodology.md"),
29
- resolve(process.cwd(), "content", "pages", "methodology.md"),
30
- ].find((p) => existsSync(p));
31
-
32
- let methodologyText: string | null = null;
33
- if (methodologyPath) {
34
- try {
35
- methodologyText = readFileSync(methodologyPath, "utf8");
36
- } catch {
37
- methodologyText = null;
38
- }
39
- }
28
+ const description = `${siteConfig.tagline} Built with Grove.`;
29
+ const slug = indexSlug();
30
+ const item = itemLabel();
40
31
  ---
41
32
 
42
33
  <BaseLayout title={title} description={description} site={siteConfig}>
43
- <Container>
34
+ <Container size="narrow">
44
35
  <article class="py-12 sm:py-16">
45
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
46
- About
47
- </span>
48
- <h1 class="mt-1 text-[1.75rem] sm:text-[2.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100 m-0">
49
- About {siteConfig.name}
50
- </h1>
51
- <p class="mt-4 max-w-[62ch] text-base text-ink-700 dark:text-ink-200 m-0">
52
- {siteConfig.name} is a community knowledge site built with{" "}
53
- <a
54
- href="https://github.com/tortuvshin/grove"
55
- target="_blank"
56
- rel="noopener noreferrer"
57
- class="text-ink-900 underline hover:text-ink-700 dark:text-ink-100 dark:hover:text-white"
58
- >
59
- Grove
60
- </a>{" "}
61
- — an open-source framework for growing useful structured knowledge.
62
- </p>
63
- </article>
64
- </Container>
36
+ <nav class="text-2xs text-ink-500 dark:text-ink-400" aria-label="Breadcrumb">
37
+ <a href="/" class="hover:text-ink-900 dark:hover:text-ink-100">Home</a>
38
+ <span class="mx-1.5 text-ink-300 dark:text-ink-700">/</span>
39
+ <span class="text-ink-700 dark:text-ink-300">About</span>
40
+ </nav>
65
41
 
66
- <MinimalAbout
67
- title="Why this exists"
68
- description="Three principles guide what gets listed and how it's surfaced."
69
- />
42
+ <header class="mt-5">
43
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
44
+ About
45
+ </span>
46
+ <h1 class="mt-1 m-0 text-[1.75rem] sm:text-[2.25rem] font-semibold tracking-[-0.02em] leading-[1.1] text-ink-900 dark:text-ink-100">
47
+ A growing community knowledge site.
48
+ </h1>
49
+ <p class="mt-4 m-0 text-[1rem] sm:text-[1.0625rem] leading-relaxed text-ink-700 dark:text-ink-200 max-w-[62ch]">
50
+ {siteConfig.name} is a curated, health-aware developer directory —
51
+ built so you can find, compare, run, study, and contribute to
52
+ open-source {item}s.
53
+ </p>
54
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400 max-w-[62ch]">
55
+ A growing list, forked from the original. Same shape, same
56
+ spirit, fresh data.
57
+ </p>
58
+ </header>
70
59
 
71
- <Container>
72
- <section class="py-12 sm:py-16" aria-labelledby="methodology-heading">
73
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
74
- Methodology
75
- </span>
76
- <h2
77
- id="methodology-heading"
78
- class="mt-1 text-[1.5rem] sm:text-[1.75rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100 m-0"
79
- >
80
- How records are picked, scored, and surfaced
81
- </h2>
82
- <p class="mt-3 max-w-[62ch] text-sm text-ink-500 dark:text-ink-400 m-0">
83
- The full editorial + scoring methodology lives in
84
- <code class="font-mono text-ink-900 dark:text-ink-100">content/pages/methodology.md</code>.
85
- Below is the rendered source so reviewers can audit the criteria.
86
- </p>
87
- {methodologyText ? (
88
- <pre
89
- class="mt-6 overflow-x-auto rounded-md border border-ink-200 bg-ink-50 p-4 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"
90
- ><code>{methodologyText}</code></pre>
91
- ) : (
92
- <p class="mt-6 max-w-[62ch] text-sm text-ink-500 dark:text-ink-400 m-0">
93
- The methodology file is not yet present in this scaffold.
94
- See <code class="font-mono">content/pages/methodology.md</code>.
60
+ <section class="grove-card mt-8 p-5 sm:p-6">
61
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
62
+ Why
63
+ </span>
64
+ <h2 class="mt-1 m-0 text-[1.125rem] sm:text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
65
+ A curated starting point
66
+ </h2>
67
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
68
+ GitHub search and README lists optimize for the repository
69
+ name and description. They rarely tell you whether a project
70
+ is a real {item} or a demo, which stack it uses beyond the
71
+ headline, how active it really is, or whether it has a clear
72
+ license.
73
+ </p>
74
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
75
+ {siteConfig.name} turns that flat list into a structured,
76
+ health-aware index. Each entry carries the metadata a developer
77
+ actually needs to decide whether to open the repo.
95
78
  </p>
96
- )}
97
- </section>
79
+ </section>
80
+
81
+ <section class="grove-card mt-4 p-5 sm:p-6">
82
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
83
+ Why real {item}s
84
+ </span>
85
+ <h2 class="mt-1 m-0 text-[1.125rem] sm:text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
86
+ Real, runnable source
87
+ </h2>
88
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
89
+ Every {item} links back to its source. Skim, fork, learn — no
90
+ paywalls, no broken download links.
91
+ </p>
92
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
93
+ The directory is hand-curated — no SEO spam, no affiliate
94
+ farms, no broken links.
95
+ </p>
96
+ </section>
97
+
98
+ <section class="grove-card mt-4 p-5 sm:p-6">
99
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
100
+ What you get
101
+ </span>
102
+ <h2 class="mt-1 m-0 text-[1.125rem] sm:text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
103
+ What every page gives you
104
+ </h2>
105
+ <ul class="m-0 mt-3 list-none space-y-1.5 p-0 text-[0.9375rem] text-ink-700 dark:text-ink-200">
106
+ <li class="flex items-baseline gap-2">
107
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
108
+ <span>Stack, category, and architecture at a glance</span>
109
+ </li>
110
+ <li class="flex items-baseline gap-2">
111
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
112
+ <span>Supported platforms and license</span>
113
+ </li>
114
+ <li class="flex items-baseline gap-2">
115
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
116
+ <span>Activity and status — last commit, monthly history, health signals</span>
117
+ </li>
118
+ <li class="flex items-baseline gap-2">
119
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
120
+ <span>License, stars, and curation tier</span>
121
+ </li>
122
+ <li class="flex items-baseline gap-2">
123
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
124
+ <span>Curated notes: best for, why listed, caveats</span>
125
+ </li>
126
+ <li class="flex items-baseline gap-2">
127
+ <span aria-hidden="true" class="text-ink-400 dark:text-ink-500">—</span>
128
+ <span>Filters that respect you — search by stack, platform, license, or status</span>
129
+ </li>
130
+ </ul>
131
+ </section>
132
+
133
+ <section class="grove-card mt-4 p-5 sm:p-6">
134
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
135
+ Generic by design
136
+ </span>
137
+ <h2 class="mt-1 m-0 text-[1.125rem] sm:text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
138
+ Generic by design
139
+ </h2>
140
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
141
+ The framework supports three blueprints: project-directory,
142
+ resource-hub, and ecosystem-map. Each can be configured
143
+ without touching the components — pages, schema, and
144
+ routes all derive from <code class="font-mono">grove.config.ts</code>.
145
+ </p>
146
+ </section>
147
+
148
+ <section class="grove-card mt-4 p-5 sm:p-6">
149
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
150
+ Free to fork, free to extend
151
+ </span>
152
+ <h2 class="mt-1 m-0 text-[1.125rem] sm:text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
153
+ Free to fork, free to extend
154
+ </h2>
155
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
156
+ The full data lives in this repository as plain YAML. The
157
+ static output is plain HTML. No database, no JavaScript
158
+ framework, no CDN with sticky sessions.
159
+ </p>
160
+ <p class="mt-3 m-0 text-[0.9375rem] leading-relaxed text-ink-700 dark:text-ink-200">
161
+ Anyone can suggest a new {item}, fix a typo, or improve the
162
+ metadata through a pull request.
163
+ </p>
164
+ </section>
165
+
166
+ <section class="mt-8 flex flex-wrap items-center gap-3">
167
+ <a href={`/${slug}`} class="grove-btn grove-btn-primary">Browse the directory</a>
168
+ {githubUrl && (
169
+ <a
170
+ href={githubUrl}
171
+ target="_blank"
172
+ rel="noopener noreferrer"
173
+ class="grove-btn grove-btn-outline"
174
+ >
175
+ View on GitHub
176
+ </a>
177
+ )}
178
+ <a href="/submit" class="grove-btn grove-btn-ghost">Submit a {item} →</a>
179
+ </section>
180
+ </article>
98
181
  </Container>
99
182
  </BaseLayout>
@@ -0,0 +1,33 @@
1
+ ---
2
+ /**
3
+ * /apps/<slug> — V0-published alias URL → V1 canonical.
4
+ *
5
+ * The canonical V1 detail URL is `/<blueprint>/<recordSlug>` (e.g.
6
+ * `/projects/coolify`). The V0 published Astro template exposed
7
+ * `/apps/<slug>` as a static alias to keep the openapps-style URL
8
+ * working for project-directory blueprints. Now that the V1
9
+ * template uses the blueprint-aware dynamic route everywhere,
10
+ * this page is a pure redirect: any `/apps/<slug>` request is
11
+ * sent to the blueprint-canonical path.
12
+ */
13
+ import { indexSlug, fullRecords, projectBySlug, recordBySlug } from "../../data/records";
14
+
15
+ export async function getStaticPaths() {
16
+ return fullRecords.map((r) => ({ params: { recordSlug: r.slug } }));
17
+ }
18
+
19
+ const slug = indexSlug();
20
+ const recordSlug = Astro.params.recordSlug ?? "";
21
+
22
+ if (slug !== "projects") {
23
+ return Astro.redirect(`/${slug}/${recordSlug}`);
24
+ }
25
+
26
+ const record = recordBySlug(recordSlug);
27
+ if (!record) {
28
+ return Astro.redirect(`/${slug}`);
29
+ }
30
+
31
+ // Canonical V1 URL.
32
+ return Astro.redirect(`/${slug}/${recordSlug}`, 301);
33
+ ---
@@ -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>