@grove-dev/astro 0.3.1 → 0.3.2
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/README.md +9 -75
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/display.d.ts +1 -26
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +1 -103
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +1 -28
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +1 -71
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +1 -57
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +1 -198
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +1 -19
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +1 -30
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +1 -36
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +1 -75
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +1 -113
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +1 -379
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +1 -10
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +1 -21
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +21 -9
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +2 -2
- package/src/components/DirectoryIndexClient.astro +240 -0
- package/src/components/FilterGroupMenu.astro +8 -4
- package/src/components/FinalCta.astro +41 -0
- package/src/components/Hero.astro +15 -18
- package/src/components/Icon.astro +49 -7
- package/src/components/RefinePanel.astro +7 -7
- package/src/components/SmartLensTabs.astro +5 -4
- package/src/components/StackGrid.astro +1 -1
- package/src/components/SubmissionClient.astro +133 -0
- package/src/index.ts +11 -1
- package/src/layouts/BaseLayout.astro +19 -1
- package/src/layouts/Footer.astro +25 -56
- package/src/layouts/Header.astro +33 -15
- package/src/layouts/ThemeToggle.astro +7 -3
- package/src/lib/display.ts +1 -111
- package/src/lib/format.ts +1 -64
- package/src/lib/lenses.ts +1 -220
- package/src/lib/repo.ts +1 -32
- package/src/lib/scores.ts +1 -90
- package/src/lib/search.ts +1 -418
- package/src/lib/taxonomy-counts.ts +1 -26
- package/{templates/default/src/data/records.ts → src/server/directory.ts} +12 -8
- package/src/server/index.ts +2 -0
- package/src/server/models.ts +432 -0
- package/dist/template-routes.test.d.ts +0 -2
- package/dist/template-routes.test.d.ts.map +0 -1
- package/dist/template-routes.test.js +0 -63
- package/dist/template-routes.test.js.map +0 -1
- package/dist/theme.test.d.ts +0 -2
- package/dist/theme.test.d.ts.map +0 -1
- package/dist/theme.test.js +0 -60
- package/dist/theme.test.js.map +0 -1
- package/src/template-routes.test.ts +0 -103
- package/src/theme.test.ts +0 -76
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +0 -48
- package/templates/default/.github/pull_request_template.md +0 -27
- package/templates/default/.github/workflows/build.yml +0 -53
- package/templates/default/.github/workflows/cleanup-stale-records.yml +0 -46
- package/templates/default/.github/workflows/daily-refresh.yml +0 -51
- package/templates/default/.github/workflows/sync-contributors.yml +0 -51
- package/templates/default/.github/workflows/sync-github-metadata.yml +0 -56
- package/templates/default/.github/workflows/validate-data.yml +0 -33
- package/templates/default/astro.config.mjs +0 -25
- package/templates/default/content/pages/about.md +0 -66
- package/templates/default/content/pages/methodology.md +0 -103
- package/templates/default/data/records/.gitkeep +0 -0
- package/templates/default/data/records/coolify.yml +0 -46
- package/templates/default/data/records/gitea.yml +0 -46
- package/templates/default/data/records/hoppscotch.yml +0 -47
- package/templates/default/data/records/inventree.yml +0 -47
- package/templates/default/data/records/label-studio.yml +0 -46
- package/templates/default/data/records/logseq.yml +0 -48
- package/templates/default/data/records/private-gpt.yml +0 -47
- package/templates/default/data/records/wakapi.yml +0 -44
- package/templates/default/data/taxonomy/categories.yml +0 -47
- package/templates/default/data/taxonomy/distribution-channels.yml +0 -43
- package/templates/default/data/taxonomy/platforms.yml +0 -27
- package/templates/default/data/taxonomy/stacks.yml +0 -41
- package/templates/default/grove.config.ts +0 -36
- package/templates/default/package.json +0 -43
- package/templates/default/public/icons/platforms/android.svg +0 -1
- package/templates/default/public/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/public/icons/platforms/apple-light.svg +0 -1
- package/templates/default/public/icons/platforms/chrome.svg +0 -1
- package/templates/default/public/icons/platforms/chromeos.svg +0 -5
- package/templates/default/public/icons/platforms/desktop.svg +0 -4
- package/templates/default/public/icons/platforms/embedded.svg +0 -5
- package/templates/default/public/icons/platforms/ios.svg +0 -1
- package/templates/default/public/icons/platforms/linux.svg +0 -1
- package/templates/default/public/icons/platforms/macos.svg +0 -1
- package/templates/default/public/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/public/icons/platforms/web.svg +0 -5
- package/templates/default/public/icons/platforms/windows.svg +0 -6
- package/templates/default/public/icons/stacks/android.svg +0 -1
- package/templates/default/public/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/public/icons/stacks/apple-light.svg +0 -1
- package/templates/default/public/icons/stacks/bun.svg +0 -1
- package/templates/default/public/icons/stacks/capacitor.svg +0 -14
- package/templates/default/public/icons/stacks/clojure.svg +0 -1
- package/templates/default/public/icons/stacks/clojurescript.svg +0 -1
- package/templates/default/public/icons/stacks/dart.svg +0 -1
- package/templates/default/public/icons/stacks/deno.svg +0 -1
- package/templates/default/public/icons/stacks/django.svg +0 -1
- package/templates/default/public/icons/stacks/docker.svg +0 -1
- package/templates/default/public/icons/stacks/firebase.svg +0 -1
- package/templates/default/public/icons/stacks/flutter.svg +0 -1
- package/templates/default/public/icons/stacks/go.svg +0 -1
- package/templates/default/public/icons/stacks/graphql.svg +0 -1
- package/templates/default/public/icons/stacks/ionic.svg +0 -4
- package/templates/default/public/icons/stacks/java.svg +0 -1
- package/templates/default/public/icons/stacks/javascript.svg +0 -1
- package/templates/default/public/icons/stacks/kotlin.svg +0 -1
- package/templates/default/public/icons/stacks/llm.svg +0 -1
- package/templates/default/public/icons/stacks/mongodb.svg +0 -1
- package/templates/default/public/icons/stacks/node.js.svg +0 -1
- package/templates/default/public/icons/stacks/nodejs.svg +0 -1
- package/templates/default/public/icons/stacks/python.svg +0 -1
- package/templates/default/public/icons/stacks/rag.svg +0 -7
- package/templates/default/public/icons/stacks/react-native.svg +0 -1
- package/templates/default/public/icons/stacks/react.svg +0 -1
- package/templates/default/public/icons/stacks/rust.svg +0 -1
- package/templates/default/public/icons/stacks/solidity.svg +0 -1
- package/templates/default/public/icons/stacks/sveltekit.svg +0 -1
- package/templates/default/public/icons/stacks/swift.svg +0 -1
- package/templates/default/public/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/public/icons/stacks/typescript.svg +0 -1
- package/templates/default/public/icons/stacks/vue.svg +0 -1
- package/templates/default/public/llms-full.txt +0 -83
- package/templates/default/public/llms.txt +0 -11
- package/templates/default/public/og-image.svg +0 -48
- package/templates/default/public/robots.txt +0 -53
- package/templates/default/public/sitemap.xml +0 -63
- package/templates/default/src/pages/404.astro +0 -64
- package/templates/default/src/pages/[slug]/[recordSlug].astro +0 -691
- package/templates/default/src/pages/[slug]/index.astro +0 -482
- package/templates/default/src/pages/about.astro +0 -189
- package/templates/default/src/pages/apps/[recordSlug].astro +0 -34
- package/templates/default/src/pages/contributors.astro +0 -175
- package/templates/default/src/pages/index.astro +0 -233
- package/templates/default/src/pages/sitemap.xml.ts +0 -89
- package/templates/default/src/pages/submit.astro +0 -281
- package/templates/default/src/styles/global.css +0 -33
- package/templates/default/tsconfig.json +0 -9
- package/templates/default/wrangler.jsonc +0 -28
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page view-models for Grove's Astro adapter.
|
|
3
|
+
*
|
|
4
|
+
* These functions contain directory decisions and generated-data access,
|
|
5
|
+
* while consumer-owned Astro pages keep control of markup and composition.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import type { IndexFilters, IndexRecord, ProjectRecord } from "@grove-dev/core";
|
|
10
|
+
import {
|
|
11
|
+
activeFilterChips,
|
|
12
|
+
applySort,
|
|
13
|
+
buildFacets,
|
|
14
|
+
effectivePage,
|
|
15
|
+
effectiveSort,
|
|
16
|
+
filterRecords,
|
|
17
|
+
filtersFromSearchParams,
|
|
18
|
+
formatRelative,
|
|
19
|
+
formatStars,
|
|
20
|
+
getOwnerAndRepoFromRepoUrl,
|
|
21
|
+
getOwnerAvatarUrl,
|
|
22
|
+
statusDisplay,
|
|
23
|
+
totalPages,
|
|
24
|
+
} from "@grove-dev/core";
|
|
25
|
+
import {
|
|
26
|
+
entityBySlug,
|
|
27
|
+
fullItems,
|
|
28
|
+
fullProjects,
|
|
29
|
+
getContentHtml,
|
|
30
|
+
itemLabel,
|
|
31
|
+
itemLabelPlural,
|
|
32
|
+
items,
|
|
33
|
+
projectBySlug,
|
|
34
|
+
resourceBySlug,
|
|
35
|
+
taxonomyLabel,
|
|
36
|
+
} from "./directory.js";
|
|
37
|
+
|
|
38
|
+
export interface DirectorySiteConfig {
|
|
39
|
+
name: string;
|
|
40
|
+
tagline?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
repoUrl?: string;
|
|
43
|
+
nav?: Array<{ label: string; href: string }>;
|
|
44
|
+
facets?: string[];
|
|
45
|
+
footer?: {
|
|
46
|
+
columns?: Array<{
|
|
47
|
+
heading: string;
|
|
48
|
+
items: Array<{ label: string; href: string; external?: boolean }>;
|
|
49
|
+
}>;
|
|
50
|
+
copyright?: string;
|
|
51
|
+
license?: string;
|
|
52
|
+
};
|
|
53
|
+
submission?: {
|
|
54
|
+
eyebrow?: string;
|
|
55
|
+
title?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
good?: string[];
|
|
58
|
+
avoid?: string[];
|
|
59
|
+
};
|
|
60
|
+
blueprintConfig?: {
|
|
61
|
+
routeSlug?: string;
|
|
62
|
+
labelSingular?: string;
|
|
63
|
+
labelPlural?: string;
|
|
64
|
+
};
|
|
65
|
+
taxonomy?: {
|
|
66
|
+
categories?: Array<{ id: string; name: string }>;
|
|
67
|
+
stacks?: Array<{ id: string; name: string }>;
|
|
68
|
+
platforms?: Array<{ id: string; name: string }>;
|
|
69
|
+
};
|
|
70
|
+
stats?: {
|
|
71
|
+
originalRepo?: string;
|
|
72
|
+
originalStars?: number;
|
|
73
|
+
originalForks?: number;
|
|
74
|
+
originalContributors?: number;
|
|
75
|
+
totalApps?: number;
|
|
76
|
+
totalCategories?: number;
|
|
77
|
+
totalStacks?: number;
|
|
78
|
+
totalOwners?: number;
|
|
79
|
+
totalStars?: number;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DirectoryContributor {
|
|
84
|
+
username: string;
|
|
85
|
+
name?: string;
|
|
86
|
+
avatarUrl?: string;
|
|
87
|
+
profileUrl?: string;
|
|
88
|
+
contributions?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loadDirectoryContributors(root = process.cwd()): DirectoryContributor[] {
|
|
92
|
+
const path = resolve(root, "data", "generated", "contributors.json");
|
|
93
|
+
if (!existsSync(path)) return [];
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as {
|
|
96
|
+
contributors?: DirectoryContributor[];
|
|
97
|
+
};
|
|
98
|
+
return parsed.contributors ?? [];
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getHomePageModel(site: DirectorySiteConfig) {
|
|
105
|
+
const slug = site.blueprintConfig?.routeSlug ?? "projects";
|
|
106
|
+
const singular = site.blueprintConfig?.labelSingular ?? itemLabel();
|
|
107
|
+
const plural = site.blueprintConfig?.labelPlural ?? itemLabelPlural();
|
|
108
|
+
const projects = items.filter((record) => record.kind === "project");
|
|
109
|
+
const hot = filterRecords(projects, { labels: ["hot"] }).slice(0, 6);
|
|
110
|
+
const recentlyAdded = applySort(projects, "recently-added").slice(0, 6);
|
|
111
|
+
const established = filterRecords(projects, { labels: ["mature"] }).slice(0, 6);
|
|
112
|
+
|
|
113
|
+
const stackCounts = new Map<string, number>();
|
|
114
|
+
const categoryCounts = new Map<string, number>();
|
|
115
|
+
for (const record of fullItems) {
|
|
116
|
+
if (record.category) categoryCounts.set(record.category, (categoryCounts.get(record.category) ?? 0) + 1);
|
|
117
|
+
if (record.kind === "project" && record.stack) {
|
|
118
|
+
stackCounts.set(record.stack, (stackCounts.get(record.stack) ?? 0) + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const stacks = [...stackCounts]
|
|
122
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
123
|
+
.slice(0, 12)
|
|
124
|
+
.map(([id, count]) => ({ name: taxonomyLabel("stacks", id), slug: id, count }));
|
|
125
|
+
const categories = [...categoryCounts]
|
|
126
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
127
|
+
.map(([id, count]) => ({ name: taxonomyLabel("categories", id), slug: id, count }));
|
|
128
|
+
const contributors = loadDirectoryContributors();
|
|
129
|
+
const description = site.description ??
|
|
130
|
+
`A searchable directory of real ${plural} — organized by stack, category, platform, license, activity, and maturity.`;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
slug,
|
|
134
|
+
itemSingular: singular,
|
|
135
|
+
itemPlural: plural,
|
|
136
|
+
hot,
|
|
137
|
+
recentlyAdded,
|
|
138
|
+
established,
|
|
139
|
+
stacks,
|
|
140
|
+
categories,
|
|
141
|
+
contributors,
|
|
142
|
+
title: `${site.name} — ${site.tagline ?? ""}`.trim(),
|
|
143
|
+
description,
|
|
144
|
+
stats: {
|
|
145
|
+
originalRepo: site.stats?.originalRepo ?? "",
|
|
146
|
+
apps: site.stats?.totalApps ?? 0,
|
|
147
|
+
categories: site.stats?.totalCategories ?? 0,
|
|
148
|
+
stacks: site.stats?.totalStacks ?? 0,
|
|
149
|
+
owners: site.stats?.totalOwners ?? 0,
|
|
150
|
+
stars: site.stats?.totalStars ?? 0,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const FACET_ALIASES: Record<string, "stacks" | "platforms" | "categories" | "tags" | "licenses"> = {
|
|
156
|
+
stack: "stacks",
|
|
157
|
+
stacks: "stacks",
|
|
158
|
+
platform: "platforms",
|
|
159
|
+
platforms: "platforms",
|
|
160
|
+
category: "categories",
|
|
161
|
+
categories: "categories",
|
|
162
|
+
tag: "tags",
|
|
163
|
+
tags: "tags",
|
|
164
|
+
license: "licenses",
|
|
165
|
+
licenses: "licenses",
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function configuredFacets(site?: DirectorySiteConfig) {
|
|
169
|
+
return new Set((site?.facets ?? ["category", "tags"])
|
|
170
|
+
.map((facet) => FACET_ALIASES[facet])
|
|
171
|
+
.filter((facet): facet is NonNullable<typeof facet> => Boolean(facet)));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getDirectoryIndexModel(searchParams: URLSearchParams, site?: DirectorySiteConfig) {
|
|
175
|
+
const filters = filtersFromSearchParams(searchParams);
|
|
176
|
+
const rawFacets = buildFacets(items);
|
|
177
|
+
const enabled = configuredFacets(site);
|
|
178
|
+
const facets = {
|
|
179
|
+
stacks: enabled.has("stacks") ? rawFacets.stacks.map((option) => ({ ...option, label: taxonomyLabel("stacks", option.value) })) : [],
|
|
180
|
+
platforms: enabled.has("platforms") ? rawFacets.platforms.map((option) => ({ ...option, label: taxonomyLabel("platforms", option.value) })) : [],
|
|
181
|
+
categories: enabled.has("categories") ? rawFacets.categories.map((option) => ({ ...option, label: taxonomyLabel("categories", option.value) })) : [],
|
|
182
|
+
tags: enabled.has("tags") ? rawFacets.tags : [],
|
|
183
|
+
licenses: enabled.has("licenses") ? rawFacets.licenses : [],
|
|
184
|
+
};
|
|
185
|
+
const sort = effectiveSort(filters);
|
|
186
|
+
const sorted = applySort(filterRecords(items, filters), sort);
|
|
187
|
+
const pageCount = totalPages(sorted.length);
|
|
188
|
+
const page = Math.min(effectivePage(filters), pageCount);
|
|
189
|
+
return {
|
|
190
|
+
items,
|
|
191
|
+
total: items.length,
|
|
192
|
+
filters,
|
|
193
|
+
facets,
|
|
194
|
+
sort,
|
|
195
|
+
sorted,
|
|
196
|
+
pages: pageCount,
|
|
197
|
+
page,
|
|
198
|
+
chips: activeFilterChips(filters),
|
|
199
|
+
clientItemsJson: JSON.stringify(items).replace(/</g, "\\u003c"),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function getContributorsPageModel(site: DirectorySiteConfig) {
|
|
204
|
+
const contributors = loadDirectoryContributors();
|
|
205
|
+
const sorted = [...contributors].sort((a, b) =>
|
|
206
|
+
(b.contributions ?? 0) - (a.contributions ?? 0) || a.username.localeCompare(b.username)
|
|
207
|
+
);
|
|
208
|
+
const repo = site.repoUrl?.replace(/\/$/, "");
|
|
209
|
+
return {
|
|
210
|
+
contributors: sorted,
|
|
211
|
+
total: sorted.length,
|
|
212
|
+
title: `Community — ${site.name}`,
|
|
213
|
+
description: `The people who have contributed to ${site.name} through code, curation, and review.`,
|
|
214
|
+
contributorsGraphHref: repo ? `${repo}/graphs/contributors` : null,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function getSubmissionPageModel(site: DirectorySiteConfig) {
|
|
219
|
+
const singular = site.blueprintConfig?.labelSingular ?? itemLabel();
|
|
220
|
+
const enabled = configuredFacets(site);
|
|
221
|
+
const values = (key: "category" | "stack") =>
|
|
222
|
+
[...new Set(fullProjects.map((record) => String(record[key] ?? "")).filter(Boolean))]
|
|
223
|
+
.sort((a, b) => a.localeCompare(b));
|
|
224
|
+
const categories = (site.taxonomy?.categories?.length
|
|
225
|
+
? site.taxonomy.categories.map(({ id }) => id)
|
|
226
|
+
: values("category"))
|
|
227
|
+
.map((id) => ({ id, label: taxonomyLabel("categories", id) }));
|
|
228
|
+
const stacks = (site.taxonomy?.stacks?.length
|
|
229
|
+
? site.taxonomy.stacks.map(({ id }) => id)
|
|
230
|
+
: values("stack"))
|
|
231
|
+
.map((id) => ({ id, label: taxonomyLabel("stacks", id) }));
|
|
232
|
+
const existingFullNames = fullProjects.map((record) => {
|
|
233
|
+
const url = String(record.repoUrl ?? record.links?.github ?? "");
|
|
234
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/?#]+)/i);
|
|
235
|
+
return match ? `${match[1]}/${match[2].replace(/\.git$/, "")}`.toLowerCase() : null;
|
|
236
|
+
}).filter((value): value is string => Boolean(value));
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
singular,
|
|
240
|
+
title: `Submit ${singular} - ${site.name}`,
|
|
241
|
+
description: `Submit a new ${singular} to ${site.name}.`,
|
|
242
|
+
repoUrl: site.repoUrl ?? "https://github.com/tortuvshin/grove",
|
|
243
|
+
copy: site.submission ?? {},
|
|
244
|
+
fields: {
|
|
245
|
+
category: enabled.has("categories"),
|
|
246
|
+
stack: enabled.has("stacks"),
|
|
247
|
+
platforms: enabled.has("platforms"),
|
|
248
|
+
tags: enabled.has("tags"),
|
|
249
|
+
},
|
|
250
|
+
categories,
|
|
251
|
+
stacks,
|
|
252
|
+
existingSlugs: fullProjects.map((record) => record.slug).filter(Boolean),
|
|
253
|
+
existingFullNames,
|
|
254
|
+
platformOptions: site.taxonomy?.platforms?.map(({ id, name }) => ({ id, label: name })) ??
|
|
255
|
+
["ios", "android", "web", "macos", "windows", "linux"].map((id) => ({ id, label: taxonomyLabel("platforms", id) })),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
type MonthlyCommit = number | { month: string; commits: number };
|
|
260
|
+
type ProjectExtras = {
|
|
261
|
+
github?: {
|
|
262
|
+
activity?: { monthlyCommits?: MonthlyCommit[] };
|
|
263
|
+
files?: Record<string, boolean>;
|
|
264
|
+
languages?: Record<string, number>;
|
|
265
|
+
};
|
|
266
|
+
health?: { status?: string; tier?: string };
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export function getRecordDetailModel(
|
|
270
|
+
recordSlug: string,
|
|
271
|
+
site: DirectorySiteConfig,
|
|
272
|
+
routeSlug = site.blueprintConfig?.routeSlug ?? "projects",
|
|
273
|
+
) {
|
|
274
|
+
const project = projectBySlug(recordSlug);
|
|
275
|
+
const resource = resourceBySlug(recordSlug);
|
|
276
|
+
const entity = entityBySlug(recordSlug);
|
|
277
|
+
const record = project ?? resource ?? entity;
|
|
278
|
+
if (!record) return null;
|
|
279
|
+
|
|
280
|
+
const isProject = record.kind === "project";
|
|
281
|
+
const proj: ProjectRecord | null = project ?? null;
|
|
282
|
+
const name = record.kind === "resource" ? record.title : record.name;
|
|
283
|
+
const description = record.description ?? `${name} on ${site.name}`;
|
|
284
|
+
const repoUrl = proj?.repoUrl ?? record.links?.github ?? "";
|
|
285
|
+
const homepageUrl = record.links?.website ?? "";
|
|
286
|
+
const stacks = proj?.stacks ?? (proj?.stack ? [proj.stack] : []);
|
|
287
|
+
const platforms = proj?.platforms ?? [];
|
|
288
|
+
const github = proj?.github?.repository;
|
|
289
|
+
const stars = github?.stargazers_count ?? 0;
|
|
290
|
+
const forks = github?.forks_count ?? 0;
|
|
291
|
+
const language = github?.language ?? null;
|
|
292
|
+
const licenseSpdx = github?.license?.spdx_id ?? null;
|
|
293
|
+
const pushedAt = github?.pushed_at ?? null;
|
|
294
|
+
const ownerRepo = repoUrl ? getOwnerAndRepoFromRepoUrl(repoUrl) : null;
|
|
295
|
+
const avatarSrc = proj?.logoUrl ?? (ownerRepo ? getOwnerAvatarUrl(ownerRepo.owner, 80) : null);
|
|
296
|
+
const rawScores = proj?.scores;
|
|
297
|
+
const scores = rawScores && Object.values(rawScores).some((value) => typeof value === "number")
|
|
298
|
+
? rawScores
|
|
299
|
+
: null;
|
|
300
|
+
const healthStatus = proj?.health?.status;
|
|
301
|
+
const extras = record as ProjectExtras;
|
|
302
|
+
const monthlyCommits = (extras.github?.activity?.monthlyCommits ?? []).map(
|
|
303
|
+
(value): { month: string; commits: number } => typeof value === "number"
|
|
304
|
+
? { month: "", commits: value }
|
|
305
|
+
: { month: value.month ?? "", commits: value.commits ?? 0 },
|
|
306
|
+
);
|
|
307
|
+
const contributionLabels: Record<string, string> = {
|
|
308
|
+
readme: "README",
|
|
309
|
+
contributing: "Contributing",
|
|
310
|
+
security: "Security",
|
|
311
|
+
issueTemplates: "Issue templates",
|
|
312
|
+
pullRequestTemplate: "PR template",
|
|
313
|
+
};
|
|
314
|
+
const contributionFiles = extras.github?.files ?? {};
|
|
315
|
+
const contributionSignals = Object.keys(contributionLabels)
|
|
316
|
+
.map((key) => ({ key, label: contributionLabels[key], ok: contributionFiles[key] }))
|
|
317
|
+
.filter((signal) => signal.ok !== undefined);
|
|
318
|
+
const languages = extras.github?.languages
|
|
319
|
+
? Object.entries(extras.github.languages).sort((a, b) => b[1] - a[1]).slice(0, 5)
|
|
320
|
+
: [];
|
|
321
|
+
|
|
322
|
+
let jsonLd: Record<string, unknown>;
|
|
323
|
+
if (isProject && proj) {
|
|
324
|
+
const sameAs = [repoUrl, homepageUrl].filter(Boolean);
|
|
325
|
+
const dateCreated = record.curation?.reviewedAt;
|
|
326
|
+
jsonLd = {
|
|
327
|
+
"@context": "https://schema.org",
|
|
328
|
+
"@type": "SoftwareSourceCode",
|
|
329
|
+
name,
|
|
330
|
+
headline: name,
|
|
331
|
+
description: record.description ?? undefined,
|
|
332
|
+
url: repoUrl || homepageUrl || undefined,
|
|
333
|
+
codeRepository: repoUrl || undefined,
|
|
334
|
+
sameAs: sameAs.length ? sameAs : undefined,
|
|
335
|
+
programmingLanguage: language ?? undefined,
|
|
336
|
+
license: licenseSpdx ?? undefined,
|
|
337
|
+
applicationCategory: record.category || undefined,
|
|
338
|
+
operatingSystem: platforms.length ? platforms.join(", ") : undefined,
|
|
339
|
+
keywords: record.tags?.length ? record.tags.join(", ") : undefined,
|
|
340
|
+
dateCreated: dateCreated ?? undefined,
|
|
341
|
+
dateModified: pushedAt ?? dateCreated ?? undefined,
|
|
342
|
+
interactionStatistic: stars > 0 ? {
|
|
343
|
+
"@type": "InteractionCounter",
|
|
344
|
+
interactionType: { "@type": "LikeAction" },
|
|
345
|
+
userInteractionCount: stars,
|
|
346
|
+
} : undefined,
|
|
347
|
+
author: ownerRepo ? {
|
|
348
|
+
"@type": "Organization",
|
|
349
|
+
name: ownerRepo.owner,
|
|
350
|
+
url: `https://github.com/${ownerRepo.owner}`,
|
|
351
|
+
} : undefined,
|
|
352
|
+
isAccessibleForFree: true,
|
|
353
|
+
};
|
|
354
|
+
} else if (record.kind === "resource") {
|
|
355
|
+
const schemaTypes: Record<string, string> = {
|
|
356
|
+
article: "Article", book: "Book", course: "Course", podcast: "PodcastSeries", video: "VideoObject",
|
|
357
|
+
};
|
|
358
|
+
jsonLd = {
|
|
359
|
+
"@context": "https://schema.org",
|
|
360
|
+
"@type": schemaTypes[record.type] ?? "CreativeWork",
|
|
361
|
+
name,
|
|
362
|
+
headline: name,
|
|
363
|
+
description: record.description || undefined,
|
|
364
|
+
url: homepageUrl || undefined,
|
|
365
|
+
author: record.author ? { "@type": "Person", name: record.author } : undefined,
|
|
366
|
+
datePublished: record.publishedAt || undefined,
|
|
367
|
+
keywords: record.tags?.length ? record.tags.join(", ") : undefined,
|
|
368
|
+
isAccessibleForFree: true,
|
|
369
|
+
};
|
|
370
|
+
} else {
|
|
371
|
+
jsonLd = {
|
|
372
|
+
"@context": "https://schema.org",
|
|
373
|
+
"@type": entity?.type === "person" ? "Person" : "Organization",
|
|
374
|
+
name,
|
|
375
|
+
headline: name,
|
|
376
|
+
description: entity?.description || undefined,
|
|
377
|
+
url: homepageUrl || undefined,
|
|
378
|
+
foundingDate: entity?.founded || undefined,
|
|
379
|
+
location: entity?.location || undefined,
|
|
380
|
+
keywords: entity?.tags?.length ? entity.tags.join(", ") : undefined,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
slug: routeSlug,
|
|
386
|
+
record,
|
|
387
|
+
project: proj,
|
|
388
|
+
entity,
|
|
389
|
+
isProject,
|
|
390
|
+
name,
|
|
391
|
+
title: `${name} — ${site.name}`,
|
|
392
|
+
description,
|
|
393
|
+
itemSingular: site.blueprintConfig?.labelSingular ?? record.kind,
|
|
394
|
+
repoUrl,
|
|
395
|
+
homepageUrl,
|
|
396
|
+
stacks,
|
|
397
|
+
platforms,
|
|
398
|
+
github,
|
|
399
|
+
stars,
|
|
400
|
+
forks,
|
|
401
|
+
language,
|
|
402
|
+
licenseSpdx,
|
|
403
|
+
pushedAt,
|
|
404
|
+
starsLabel: formatStars(stars),
|
|
405
|
+
pushedLabel: formatRelative(pushedAt),
|
|
406
|
+
ownerRepo,
|
|
407
|
+
avatarSrc,
|
|
408
|
+
initials: name.replace(/[^a-z0-9]/gi, "").slice(0, 2).toUpperCase(),
|
|
409
|
+
bestFor: proj?.bestFor ?? [],
|
|
410
|
+
whyListed: proj?.whyListed ?? [],
|
|
411
|
+
caveats: proj?.caveats ?? [],
|
|
412
|
+
distribution: proj?.distribution?.channels ?? [],
|
|
413
|
+
scores,
|
|
414
|
+
curationLabels: record.curation?.labels ?? [],
|
|
415
|
+
tags: record.tags ?? [],
|
|
416
|
+
healthLabel: healthStatus ? statusDisplay(healthStatus) : null,
|
|
417
|
+
contentHtml: isProject ? getContentHtml(recordSlug) : null,
|
|
418
|
+
monthlyCommits,
|
|
419
|
+
maxMonthlyCommits: Math.max(1, ...monthlyCommits.map((item) => item.commits)),
|
|
420
|
+
contributionSignals,
|
|
421
|
+
languages,
|
|
422
|
+
totalLanguageBytes: Math.max(1, languages.reduce((sum, [, bytes]) => sum + bytes, 0)),
|
|
423
|
+
jsonLd,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export type DirectoryIndexModel = ReturnType<typeof getDirectoryIndexModel>;
|
|
428
|
+
export type DirectoryHomeModel = ReturnType<typeof getHomePageModel>;
|
|
429
|
+
export type SubmissionPageModel = ReturnType<typeof getSubmissionPageModel>;
|
|
430
|
+
export type ContributorsPageModel = ReturnType<typeof getContributorsPageModel>;
|
|
431
|
+
export type RecordDetailModel = NonNullable<ReturnType<typeof getRecordDetailModel>>;
|
|
432
|
+
export type { IndexFilters };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"template-routes.test.d.ts","sourceRoot":"","sources":["../src/template-routes.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { readFile, stat } from "node:fs/promises";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
const pagesDir = resolve(import.meta.dirname, "../templates/default/src/pages");
|
|
5
|
-
describe("default Astro route configuration", () => {
|
|
6
|
-
it("ships TypeScript settings that resolve package exports and Node built-ins", async () => {
|
|
7
|
-
const templateRoot = resolve(pagesDir, "../..");
|
|
8
|
-
const tsconfig = JSON.parse(await readFile(resolve(templateRoot, "tsconfig.json"), "utf8"));
|
|
9
|
-
const manifest = JSON.parse(await readFile(resolve(templateRoot, "package.json"), "utf8"));
|
|
10
|
-
expect(tsconfig.extends).toBe("astro/tsconfigs/base");
|
|
11
|
-
expect(tsconfig.compilerOptions?.moduleResolution).toBe("Bundler");
|
|
12
|
-
expect(tsconfig.compilerOptions?.types).toContain("node");
|
|
13
|
-
expect(manifest.devDependencies?.["@types/node"]).toMatch(/^\^/);
|
|
14
|
-
});
|
|
15
|
-
it("treats generated JSON as an untyped boundary before applying payload types", async () => {
|
|
16
|
-
const recordsModule = await readFile(resolve(pagesDir, "../data/records.ts"), "utf8");
|
|
17
|
-
expect(recordsModule).toContain("fullPayload as unknown as FullPayload");
|
|
18
|
-
});
|
|
19
|
-
it("derives the directory route from generated site config", async () => {
|
|
20
|
-
const listPage = await readFile(resolve(pagesDir, "[slug]/index.astro"), "utf8");
|
|
21
|
-
expect(listPage).toContain("siteConfig.blueprintConfig?.routeSlug");
|
|
22
|
-
});
|
|
23
|
-
it("does not generate the legacy apps alias when apps is canonical", async () => {
|
|
24
|
-
const aliasPage = await readFile(resolve(pagesDir, "apps/[recordSlug].astro"), "utf8");
|
|
25
|
-
expect(aliasPage).toContain('if (indexSlug() === "apps")');
|
|
26
|
-
expect(aliasPage).toContain("return []");
|
|
27
|
-
});
|
|
28
|
-
it("renders consumer-authored about Markdown through the default page", async () => {
|
|
29
|
-
const aboutPage = await readFile(resolve(pagesDir, "about.astro"), "utf8");
|
|
30
|
-
expect(aboutPage).toContain('getPageContentHtml("about")');
|
|
31
|
-
});
|
|
32
|
-
it("generates submission drafts accepted by the Grove record schema", async () => {
|
|
33
|
-
const submitPage = await readFile(resolve(pagesDir, "submit.astro"), "utf8");
|
|
34
|
-
expect(submitPage).toContain('" type: manual"');
|
|
35
|
-
expect(submitPage).not.toContain('" type: github"');
|
|
36
|
-
});
|
|
37
|
-
it("keeps generic maintenance behavior in Grove instead of consumer scripts", async () => {
|
|
38
|
-
const templateRoot = resolve(pagesDir, "../..");
|
|
39
|
-
const manifest = JSON.parse(await readFile(resolve(templateRoot, "package.json"), "utf8"));
|
|
40
|
-
const scriptsDirExists = await stat(resolve(templateRoot, "scripts"))
|
|
41
|
-
.then(() => true)
|
|
42
|
-
.catch(() => false);
|
|
43
|
-
expect(manifest.scripts?.["build:llms"]).toBe("grove llms");
|
|
44
|
-
expect(manifest.scripts?.["sync:contributors"]).toBe("grove sync contributors");
|
|
45
|
-
expect(scriptsDirExists).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
it("hydrates static list pages with URL-driven search and pagination", async () => {
|
|
48
|
-
const listPage = await readFile(resolve(pagesDir, "[slug]/index.astro"), "utf8");
|
|
49
|
-
expect(listPage).toContain('id="grove-index-data"');
|
|
50
|
-
expect(listPage).toContain("function applyClientFilters()");
|
|
51
|
-
expect(listPage).toContain("const PAGE_SIZE = 20");
|
|
52
|
-
});
|
|
53
|
-
it("uses generated taxonomy names as display labels", async () => {
|
|
54
|
-
const recordsModule = await readFile(resolve(pagesDir, "../data/records.ts"), "utf8");
|
|
55
|
-
const homePage = await readFile(resolve(pagesDir, "index.astro"), "utf8");
|
|
56
|
-
const listPage = await readFile(resolve(pagesDir, "[slug]/index.astro"), "utf8");
|
|
57
|
-
expect(recordsModule).toContain("export function taxonomyLabel");
|
|
58
|
-
expect(recordsModule).toContain("?? prettySlug(id)");
|
|
59
|
-
expect(homePage).toContain('taxonomyLabel("categories"');
|
|
60
|
-
expect(listPage).toContain("labelFacetsWithTaxonomy");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
//# sourceMappingURL=template-routes.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"template-routes.test.js","sourceRoot":"","sources":["../src/template-routes.test.ts"],"names":[],"mappings":"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"}
|
package/dist/theme.test.d.ts
DELETED
package/dist/theme.test.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme.test.d.ts","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":""}
|
package/dist/theme.test.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
const astroTheme = readFileSync(resolve(import.meta.dirname, "styles.css"), "utf8");
|
|
5
|
-
const scaffoldTheme = readFileSync(resolve(import.meta.dirname, "../templates/default/src/styles/global.css"), "utf8");
|
|
6
|
-
const tailwindMarkup = [
|
|
7
|
-
"components/ItemCard.astro",
|
|
8
|
-
"layouts/Header.astro",
|
|
9
|
-
"../templates/default/src/pages/submit.astro",
|
|
10
|
-
].map((file) => readFileSync(resolve(import.meta.dirname, file), "utf8")).join("\n");
|
|
11
|
-
const iconMarkup = readFileSync(resolve(import.meta.dirname, "components/Icon.astro"), "utf8");
|
|
12
|
-
describe("Astro theme contract", () => {
|
|
13
|
-
it("keeps Starlight's light and dark foundation values", () => {
|
|
14
|
-
expect(astroTheme).toContain("--grove-background: oklch(100% 0 0)");
|
|
15
|
-
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-950)");
|
|
16
|
-
expect(astroTheme).toContain("--grove-background: var(--color-ink-950)");
|
|
17
|
-
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-50)");
|
|
18
|
-
expect(astroTheme).toContain("--color-ink-50: oklch(98.5% 0 0)");
|
|
19
|
-
expect(astroTheme).toContain("--color-ink-950: oklch(14.5% 0 0)");
|
|
20
|
-
});
|
|
21
|
-
it("exposes runtime semantic colors to Tailwind", () => {
|
|
22
|
-
for (const token of [
|
|
23
|
-
"background",
|
|
24
|
-
"foreground",
|
|
25
|
-
"primary",
|
|
26
|
-
"secondary",
|
|
27
|
-
"muted",
|
|
28
|
-
"accent",
|
|
29
|
-
"border",
|
|
30
|
-
"card",
|
|
31
|
-
"popover",
|
|
32
|
-
]) {
|
|
33
|
-
expect(astroTheme).toContain(`--color-${token}: var(--grove-${token})`);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
it("uses the same system and mono font stacks as the docs", () => {
|
|
37
|
-
expect(astroTheme).toContain('--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,');
|
|
38
|
-
expect(astroTheme).toContain("--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,");
|
|
39
|
-
expect(astroTheme).toContain("font-family: var(--font-sans)");
|
|
40
|
-
});
|
|
41
|
-
it("does not duplicate package-owned foundation tokens in the scaffold", () => {
|
|
42
|
-
expect(scaffoldTheme).not.toContain("--color-ink-");
|
|
43
|
-
expect(scaffoldTheme).not.toContain("font-family:");
|
|
44
|
-
expect(scaffoldTheme).not.toContain("color-scheme:");
|
|
45
|
-
});
|
|
46
|
-
it("styles components in HTML with Starlight-aligned Tailwind utilities", () => {
|
|
47
|
-
expect(tailwindMarkup).toContain("rounded-[calc(var(--radius)+0.25rem)]");
|
|
48
|
-
expect(tailwindMarkup).toContain("rounded-full");
|
|
49
|
-
expect(tailwindMarkup).toContain("min-h-5");
|
|
50
|
-
expect(tailwindMarkup).toContain("h-8");
|
|
51
|
-
expect(tailwindMarkup).toContain("focus-visible:ring-3");
|
|
52
|
-
expect(astroTheme).not.toContain(".grove-card");
|
|
53
|
-
expect(astroTheme).not.toContain(".grove-btn");
|
|
54
|
-
});
|
|
55
|
-
it("falls back to initials when a consumer has no matching icon asset", () => {
|
|
56
|
-
expect(iconMarkup).toContain("data-grove-icon-fallback-initials");
|
|
57
|
-
expect(iconMarkup).toContain("onerror=");
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
//# sourceMappingURL=theme.test.js.map
|
package/dist/theme.test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme.test.js","sourceRoot":"","sources":["../src/theme.test.ts"],"names":[],"mappings":"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"}
|