@grove-dev/astro 0.1.5 → 0.2.5

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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +4 -25
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +56 -22
  5. package/dist/index.js.map +1 -1
  6. package/dist/layouts/index.d.ts +25 -0
  7. package/dist/layouts/index.d.ts.map +1 -0
  8. package/dist/layouts/index.js +25 -0
  9. package/dist/layouts/index.js.map +1 -0
  10. package/dist/lib/display.d.ts +33 -0
  11. package/dist/lib/display.d.ts.map +1 -0
  12. package/dist/lib/display.js +104 -0
  13. package/dist/lib/display.js.map +1 -0
  14. package/dist/lib/format.d.ts +30 -0
  15. package/dist/lib/format.d.ts.map +1 -0
  16. package/dist/lib/format.js +73 -0
  17. package/dist/lib/format.js.map +1 -0
  18. package/dist/lib/index.d.ts +26 -0
  19. package/dist/lib/index.d.ts.map +1 -0
  20. package/dist/lib/index.js +26 -0
  21. package/dist/lib/index.js.map +1 -0
  22. package/dist/lib/lenses.d.ts +96 -0
  23. package/dist/lib/lenses.d.ts.map +1 -0
  24. package/dist/lib/lenses.js +181 -0
  25. package/dist/lib/lenses.js.map +1 -0
  26. package/dist/lib/repo.d.ts +30 -0
  27. package/dist/lib/repo.d.ts.map +1 -0
  28. package/dist/lib/repo.js +44 -0
  29. package/dist/lib/repo.js.map +1 -0
  30. package/dist/lib/scores.d.ts +38 -0
  31. package/dist/lib/scores.d.ts.map +1 -0
  32. package/dist/lib/scores.js +83 -0
  33. package/dist/lib/scores.js.map +1 -0
  34. package/dist/lib/search.d.ts +199 -0
  35. package/dist/lib/search.d.ts.map +1 -0
  36. package/dist/lib/search.js +357 -0
  37. package/dist/lib/search.js.map +1 -0
  38. package/dist/lib/taxonomy-counts.d.ts +25 -0
  39. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  40. package/dist/lib/taxonomy-counts.js +41 -0
  41. package/dist/lib/taxonomy-counts.js.map +1 -0
  42. package/package.json +7 -7
  43. package/src/components/AppsIndexRow.astro +204 -0
  44. package/src/components/AppsPagination.astro +126 -0
  45. package/src/components/CategoryGrid.astro +86 -17
  46. package/src/components/ContributorsGrid.astro +149 -0
  47. package/src/components/DecisionRow.astro +89 -0
  48. package/src/components/ExploreByCategory.astro +67 -0
  49. package/src/components/ExploreByStack.astro +72 -0
  50. package/src/components/Hero.astro +246 -0
  51. package/src/components/Icon.astro +223 -0
  52. package/src/components/ItemCard.astro +306 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RecordSection.astro +88 -0
  56. package/src/components/RefinePanel.astro +197 -0
  57. package/src/components/ScoreBars.astro +92 -22
  58. package/src/components/SmartLensTabs.astro +71 -0
  59. package/src/components/StackGrid.astro +132 -0
  60. package/src/components/WhyThisExists.astro +116 -0
  61. package/src/index.ts +61 -14
  62. package/src/layouts/BaseLayout.astro +227 -15
  63. package/src/layouts/Container.astro +57 -0
  64. package/src/layouts/Footer.astro +235 -0
  65. package/src/layouts/Header.astro +159 -0
  66. package/src/layouts/SectionHeader.astro +58 -0
  67. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  68. package/src/layouts/ThemeToggle.astro +110 -0
  69. package/src/layouts/astro-shim.d.ts +29 -0
  70. package/src/layouts/index.ts +24 -0
  71. package/src/lib/display.ts +118 -0
  72. package/src/lib/format.ts +65 -0
  73. package/src/lib/index.ts +26 -0
  74. package/src/lib/lenses.ts +241 -0
  75. package/src/lib/repo.ts +44 -0
  76. package/src/lib/scores.ts +94 -0
  77. package/src/lib/search.ts +481 -0
  78. package/src/lib/taxonomy-counts.ts +60 -0
  79. package/src/styles.css +366 -638
  80. package/templates/default/astro.config.mjs +8 -6
  81. package/templates/default/content/pages/about.md +66 -0
  82. package/templates/default/content/pages/methodology.md +103 -0
  83. package/templates/default/data/generated/records.full.json +8 -0
  84. package/templates/default/data/generated/records.index.json +7 -0
  85. package/templates/default/data/generated/records.json +8 -0
  86. package/templates/default/data/generated/site-config.json +31 -0
  87. package/templates/default/data/records/.gitkeep +0 -0
  88. package/templates/default/grove.config.ts +36 -0
  89. package/templates/default/package.json +6 -5
  90. package/templates/default/public/llms-full.txt +14 -0
  91. package/templates/default/public/llms.txt +11 -0
  92. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  93. package/templates/default/src/components/layout/Footer.astro +16 -134
  94. package/templates/default/src/components/layout/Header.astro +25 -126
  95. package/templates/default/src/data/records.ts +193 -0
  96. package/templates/default/src/lib/markdown.ts +171 -0
  97. package/templates/default/src/pages/about.astro +95 -429
  98. package/templates/default/src/pages/index.astro +263 -61
  99. package/templates/default/src/pages/projects/[slug].astro +480 -0
  100. package/templates/default/src/pages/projects/index.astro +193 -0
  101. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  102. package/templates/default/src/pages/submit.astro +122 -411
  103. package/templates/default/src/styles/global.css +42 -68
  104. package/src/components/ActiveFilterChips.astro +0 -20
  105. package/src/components/DirectoryFilters.astro +0 -100
  106. package/src/components/DirectoryHero.astro +0 -32
  107. package/src/components/HealthBadge.astro +0 -12
  108. package/src/components/LensTabs.astro +0 -26
  109. package/src/components/MethodologyPanel.astro +0 -20
  110. package/src/components/Pagination.astro +0 -24
  111. package/src/components/ProjectDetail.astro +0 -166
  112. package/src/components/SubmitDraft.astro +0 -137
  113. package/templates/default/src/components/AppCard.astro +0 -248
  114. package/templates/default/src/components/AppSection.astro +0 -58
  115. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  116. package/templates/default/src/components/AppsPagination.astro +0 -147
  117. package/templates/default/src/components/CategoryGrid.astro +0 -80
  118. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  119. package/templates/default/src/components/DecisionRow.astro +0 -219
  120. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  121. package/templates/default/src/components/ExploreByStack.astro +0 -102
  122. package/templates/default/src/components/Hero.astro +0 -176
  123. package/templates/default/src/components/MinimalAbout.astro +0 -57
  124. package/templates/default/src/components/OriginalCollection.astro +0 -144
  125. package/templates/default/src/components/RefinePanel.astro +0 -272
  126. package/templates/default/src/components/ScoreBars.astro +0 -72
  127. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  128. package/templates/default/src/components/StackGrid.astro +0 -95
  129. package/templates/default/src/components/WhyThisExists.astro +0 -81
  130. package/templates/default/src/components/icons/Icon.astro +0 -148
  131. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  132. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  133. package/templates/default/src/data/apps.ts +0 -149
  134. package/templates/default/src/data/categories.ts +0 -27
  135. package/templates/default/src/data/config.ts +0 -28
  136. package/templates/default/src/data/contributors.ts +0 -96
  137. package/templates/default/src/data/stacks.ts +0 -47
  138. package/templates/default/src/data/stats.ts +0 -65
  139. package/templates/default/src/data/types.ts +0 -248
  140. package/templates/default/src/lib/apps-search.ts +0 -228
  141. package/templates/default/src/lib/format.ts +0 -36
  142. package/templates/default/src/lib/lenses.ts +0 -98
  143. package/templates/default/src/lib/repo.ts +0 -12
  144. package/templates/default/src/lib/scores.ts +0 -51
  145. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  146. package/templates/default/src/pages/apps/[slug].astro +0 -709
  147. package/templates/default/src/pages/apps/index.astro +0 -669
  148. package/templates/default/src/pages/contributors.astro +0 -142
@@ -1,66 +1,268 @@
1
1
  ---
2
- import { siteConfig } from "../data/config";
3
- import BaseLayout from "../components/layout/BaseLayout.astro";
4
- import Hero from "../components/Hero.astro";
5
- import WhyThisExists from "../components/WhyThisExists.astro";
6
- import AppSection from "../components/AppSection.astro";
7
- import StackGrid from "../components/StackGrid.astro";
8
- import CategoryGrid from "../components/CategoryGrid.astro";
9
- import ContributorsGrid from "../components/ContributorsGrid.astro";
10
- import OriginalCollection from "../components/OriginalCollection.astro";
11
-
12
- import { newApps, hotApps, matureApps } from "../data/apps";
13
- import { stacks } from "../data/stacks";
14
- import { categories } from "../data/categories";
15
- import { contributors } from "../data/contributors";
16
- import { stats } from "../data/stats";
17
-
18
- const hotList = hotApps().slice(0, 6);
19
- const newList = newApps().slice(0, 6);
20
- const matureList = matureApps().slice(0, 6);
21
-
22
- const title = `${siteConfig.name} — A directory of real open-source applications`;
2
+ /**
3
+ * Home page — the showcase's front door.
4
+ *
5
+ * Sections (in order):
6
+ * 1. Hero — brand + search + stats row
7
+ * 2. WhyThisExists — 3 short value-prop points
8
+ * 3. RecordSection "Trending" — hot lens (stars >= 1000 OR pushed <= 30d)
9
+ * 4. RecordSection "Recently added" — new lens (pushed <= 7d OR curated "new")
10
+ * 5. RecordSection "Established" — mature lens (tier=curated, pushed > 365d)
11
+ * 6. StackGrid — browse by stack
12
+ * 7. CategoryGrid — browse by category
13
+ * 8. ContributorsGrid — top contributors (when available)
14
+ * 9. OriginalCollection — origin-card link to the source repo
15
+ *
16
+ * Every section is defensive: with 0 records (fresh scaffold) it
17
+ * renders a graceful empty state instead of throwing.
18
+ *
19
+ * V1 source-of-truth types are from `@grove-dev/core`. The slim
20
+ * `IndexProjectRecord` shape is what the home page consumes (no
21
+ * `as ProjectRecord` casts); the full `Resource` only flows into
22
+ * the detail page.
23
+ */
24
+ import siteConfig from "../../data/generated/site-config.json";
25
+ import { records, fullRecords, projects, indexSlug, itemLabel } from "../data/records";
26
+ import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
27
+ import Hero from "@grove-dev/astro/components/Hero.astro";
28
+ import WhyThisExists from "@grove-dev/astro/components/WhyThisExists.astro";
29
+ import RecordSection from "@grove-dev/astro/components/RecordSection.astro";
30
+ import StackGrid from "@grove-dev/astro/components/StackGrid.astro";
31
+ import CategoryGrid from "@grove-dev/astro/components/CategoryGrid.astro";
32
+ import ContributorsGrid from "@grove-dev/astro/components/ContributorsGrid.astro";
33
+ import OriginalCollection from "@grove-dev/astro/components/OriginalCollection.astro";
34
+ import { countByCategory, countByStack } from "@grove-dev/astro";
35
+ import { existsSync, readFileSync } from "node:fs";
36
+ import { fileURLToPath } from "node:url";
37
+ import { dirname, resolve } from "node:path";
38
+ import type { IndexProjectRecord } from "@grove-dev/core";
39
+
40
+ const slug = indexSlug();
41
+ const projectKind = projects;
42
+
43
+ // ── Hero stats ──────────────────────────────────────────────────
44
+ const totalApps = fullRecords.filter((r) => r.kind === "project").length;
45
+ const totalCategories = new Set(projectKind.map((r) => r.category).filter(Boolean))
46
+ .size;
47
+ const totalStacks = new Set(
48
+ projectKind.flatMap((r) => [r.stack, ...(r.stacks ?? [])]).filter(Boolean),
49
+ ).size;
50
+ const totalContributors = new Set(
51
+ projectKind
52
+ .map((r) => {
53
+ const url = r.repoUrl ?? r.links?.github ?? "";
54
+ const m = /github\.com\/([^/]+)\//.exec(url);
55
+ return m?.[1];
56
+ })
57
+ .filter(Boolean),
58
+ ).size;
59
+ const totalStars = projectKind.reduce(
60
+ (acc, r) => acc + (r.github?.stars ?? 0),
61
+ 0,
62
+ );
63
+
64
+ // ── Lens sectioning (Hot / New / Mature) ────────────────────────
65
+ const DAY_MS = 86_400_000;
66
+ function daysSince(iso: string | null | undefined): number {
67
+ if (!iso) return Infinity;
68
+ const d = new Date(iso);
69
+ if (Number.isNaN(d.valueOf())) return Infinity;
70
+ return (Date.now() - d.valueOf()) / DAY_MS;
71
+ }
72
+
73
+ const hot = projectKind
74
+ .filter((r: IndexProjectRecord) => {
75
+ const stars = r.github?.stars ?? 0;
76
+ const pushedAt = r.github?.pushedAt ?? null;
77
+ const pushed = daysSince(pushedAt);
78
+ const labelledHot = r.curation?.labels?.includes("hot") ?? false;
79
+ return labelledHot || stars >= 1000 || pushed <= 30;
80
+ })
81
+ .slice(0, 6);
82
+
83
+ const newItems = projectKind
84
+ .filter((r: IndexProjectRecord) => {
85
+ const pushedAt = r.github?.pushedAt ?? null;
86
+ const pushed = daysSince(pushedAt);
87
+ const labelledNew = r.curation?.labels?.includes("new") ?? false;
88
+ return pushed <= 7 || labelledNew;
89
+ })
90
+ .slice(0, 6);
91
+
92
+ const mature = projectKind
93
+ .filter((r: IndexProjectRecord) => {
94
+ const tier = r.health?.tier;
95
+ const pushedAt = r.github?.pushedAt ?? null;
96
+ const pushed = daysSince(pushedAt);
97
+ return tier === "curated" && pushed > 365;
98
+ })
99
+ .slice(0, 6);
100
+
101
+ // ── Stack / category aggregates ─────────────────────────────────
102
+ const stackCounts = countByStack(projectKind);
103
+ const stacks = [...stackCounts.entries()]
104
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
105
+ .slice(0, 12)
106
+ .map(([name, count]) => ({ name, slug: name, count }));
107
+
108
+ const categoryCounts = countByCategory(projectKind);
109
+ const categories = [...categoryCounts.entries()]
110
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
111
+ .map(([name, count]) => ({ name, slug: name, count }));
112
+
113
+ // ── Contributors (best-effort, from records) ────────────────────
114
+ const here = dirname(fileURLToPath(import.meta.url));
115
+ const contributorsPath = [
116
+ resolve(here, "..", "..", "data", "generated", "contributors.json"),
117
+ resolve(process.cwd(), "data", "generated", "contributors.json"),
118
+ ].find((p) => existsSync(p));
119
+
120
+ interface ContributorsFile {
121
+ contributors?: Array<{
122
+ username: string;
123
+ name?: string;
124
+ avatarUrl?: string;
125
+ profileUrl?: string;
126
+ contributions?: number;
127
+ }>;
128
+ }
129
+
130
+ let contributors: ContributorsFile["contributors"] = [];
131
+ if (contributorsPath) {
132
+ try {
133
+ const parsed = JSON.parse(readFileSync(contributorsPath, "utf8")) as ContributorsFile;
134
+ contributors = parsed.contributors ?? [];
135
+ } catch {
136
+ contributors = [];
137
+ }
138
+ }
139
+ const hasContributors = contributors && contributors.length > 0;
140
+
141
+ // ── OriginalCollection (repo-stats.json if present) ─────────────
142
+ interface RepoStats {
143
+ originalRepo?: string;
144
+ stars?: number;
145
+ forks?: number;
146
+ contributors?: number;
147
+ description?: string;
148
+ }
149
+ let repoStats: RepoStats = {};
150
+ const repoStatsPath = [
151
+ resolve(here, "..", "..", "data", "generated", "repo-stats.json"),
152
+ resolve(process.cwd(), "data", "generated", "repo-stats.json"),
153
+ ].find((p) => existsSync(p));
154
+ if (repoStatsPath) {
155
+ try {
156
+ repoStats = JSON.parse(readFileSync(repoStatsPath, "utf8")) as RepoStats;
157
+ } catch {
158
+ repoStats = {};
159
+ }
160
+ }
161
+
162
+ const title = `${siteConfig.name} — ${siteConfig.tagline}`;
23
163
  const description =
24
- "A searchable directory of real open-source applications organized by stack, category, platform, license, activity, and maturity.";
164
+ siteConfig.description ?? `A growing community knowledge site powered by Grove.`;
165
+
166
+ // ── Render-time helpers ─────────────────────────────────────────
167
+ function detailHrefFor(r: IndexProjectRecord): string {
168
+ return `/${slug}/${r.slug}`;
169
+ }
25
170
  ---
26
171
 
27
- <BaseLayout title={title} description={description}>
28
- <Hero />
29
-
30
- <WhyThisExists />
31
-
32
- <AppSection
33
- id="hot"
34
- title="Hot open-source apps"
35
- description="Projects gaining stars, activity, or attention recently."
36
- apps={hotList}
37
- viewAllHref="/apps?label=hot"
38
- viewAllLabel="View hot apps"
39
- />
40
-
41
- <AppSection
42
- id="new"
43
- title="New open-source apps"
44
- description="Recently added apps from the community."
45
- apps={newList}
46
- viewAllHref="/apps?label=new"
47
- viewAllLabel="View all new"
48
- />
49
-
50
- <AppSection
51
- id="mature"
52
- title="Mature open-source apps"
53
- description="Established apps with strong history, active maintainers, and real-world usage."
54
- apps={matureList}
55
- viewAllHref="/apps?label=mature"
56
- viewAllLabel="View mature apps"
57
- />
58
-
59
- <StackGrid stacks={stacks} />
60
-
61
- <CategoryGrid categories={categories} />
62
-
63
- <ContributorsGrid contributors={contributors} />
64
-
65
- <OriginalCollection stats={stats} />
172
+ <BaseLayout
173
+ title={title}
174
+ description={description}
175
+ site={siteConfig}
176
+ submitHref="/submit"
177
+ submitLabel={`Submit ${itemLabel()}`}
178
+ >
179
+ <Hero
180
+ name={siteConfig.name}
181
+ tagline={siteConfig.tagline}
182
+ description={description}
183
+ eyebrow="Curated directory"
184
+ searchPlaceholder={`Search ${slug}...`}
185
+ searchHref={`/${slug}`}
186
+ stats={{
187
+ apps: totalApps,
188
+ categories: totalCategories,
189
+ stacks: totalStacks,
190
+ contributors: totalContributors || undefined,
191
+ stars: totalStars || undefined,
192
+ }}
193
+ ctas={[
194
+ { label: `Browse ${slug}`, href: `/${slug}`, variant: "primary" },
195
+ { label: "About", href: "/about", variant: "outline" },
196
+ ]}
197
+ />
198
+
199
+ <WhyThisExists />
200
+
201
+ <RecordSection
202
+ id="hot"
203
+ title="Trending"
204
+ description="The projects getting attention right now — high-star repos and recently active codebases."
205
+ items={hot}
206
+ hrefFor={detailHrefFor}
207
+ viewAllHref={`/${slug}?lens=hot`}
208
+ eyebrow="Hot"
209
+ emptyLabel="No trending projects yet."
210
+ />
211
+
212
+ <RecordSection
213
+ id="new"
214
+ title="Recently added"
215
+ description="Fresh entries — pushed within the last week or carrying the curated 'new' label."
216
+ items={newItems}
217
+ hrefFor={detailHrefFor}
218
+ viewAllHref={`/${slug}?lens=new`}
219
+ eyebrow="New"
220
+ emptyLabel="No new projects yet."
221
+ />
222
+
223
+ <RecordSection
224
+ id="mature"
225
+ title="Established"
226
+ description="Long-running curated projects with deep history and stable architecture."
227
+ items={mature}
228
+ hrefFor={detailHrefFor}
229
+ viewAllHref={`/${slug}?lens=mature`}
230
+ eyebrow="Mature"
231
+ emptyLabel="No established projects yet."
232
+ />
233
+
234
+ {stacks.length > 0 && (
235
+ <StackGrid
236
+ stacks={stacks}
237
+ pathPrefix={`/${slug}`}
238
+ />
239
+ )}
240
+
241
+ {categories.length > 0 && (
242
+ <CategoryGrid
243
+ categories={categories}
244
+ pathPrefix={`/${slug}`}
245
+ />
246
+ )}
247
+
248
+ {hasContributors && (
249
+ <ContributorsGrid
250
+ contributors={contributors!}
251
+ total={contributors!.length}
252
+ viewAllHref={siteConfig.repoUrl || undefined}
253
+ viewAllLabel="View on GitHub"
254
+ />
255
+ )}
256
+
257
+ {repoStats.originalRepo && (
258
+ <OriginalCollection
259
+ repoUrl={repoStats.originalRepo}
260
+ stats={{
261
+ stars: repoStats.stars,
262
+ forks: repoStats.forks,
263
+ contributors: repoStats.contributors,
264
+ description: repoStats.description,
265
+ }}
266
+ />
267
+ )}
66
268
  </BaseLayout>