@grove-dev/astro 0.3.1 → 0.3.4
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 +23 -11
- 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 +35 -7
- package/src/components/IndexRow.astro +2 -2
- package/src/components/ItemCard.astro +2 -7
- package/src/components/RefinePanel.astro +7 -7
- package/src/components/SmartLensTabs.astro +9 -5
- 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 +30 -8
- 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 +436 -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,436 @@
|
|
|
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
|
+
projectStackIds,
|
|
23
|
+
statusDisplay,
|
|
24
|
+
totalPages,
|
|
25
|
+
} from "@grove-dev/core";
|
|
26
|
+
import {
|
|
27
|
+
entityBySlug,
|
|
28
|
+
fullItems,
|
|
29
|
+
fullProjects,
|
|
30
|
+
getContentHtml,
|
|
31
|
+
itemLabel,
|
|
32
|
+
itemLabelPlural,
|
|
33
|
+
items,
|
|
34
|
+
projectBySlug,
|
|
35
|
+
resourceBySlug,
|
|
36
|
+
taxonomyLabel,
|
|
37
|
+
} from "./directory.js";
|
|
38
|
+
|
|
39
|
+
export interface DirectorySiteConfig {
|
|
40
|
+
name: string;
|
|
41
|
+
tagline?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
repoUrl?: string;
|
|
44
|
+
nav?: Array<{ label: string; href: string }>;
|
|
45
|
+
facets?: string[];
|
|
46
|
+
analytics?: {
|
|
47
|
+
googleAnalyticsId?: string;
|
|
48
|
+
};
|
|
49
|
+
footer?: {
|
|
50
|
+
columns?: Array<{
|
|
51
|
+
heading: string;
|
|
52
|
+
items: Array<{ label: string; href: string; external?: boolean }>;
|
|
53
|
+
}>;
|
|
54
|
+
copyright?: string;
|
|
55
|
+
license?: string;
|
|
56
|
+
};
|
|
57
|
+
submission?: {
|
|
58
|
+
eyebrow?: string;
|
|
59
|
+
title?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
good?: string[];
|
|
62
|
+
avoid?: string[];
|
|
63
|
+
};
|
|
64
|
+
blueprintConfig?: {
|
|
65
|
+
routeSlug?: string;
|
|
66
|
+
labelSingular?: string;
|
|
67
|
+
labelPlural?: string;
|
|
68
|
+
};
|
|
69
|
+
taxonomy?: {
|
|
70
|
+
categories?: Array<{ id: string; name: string }>;
|
|
71
|
+
stacks?: Array<{ id: string; name: string }>;
|
|
72
|
+
platforms?: Array<{ id: string; name: string }>;
|
|
73
|
+
};
|
|
74
|
+
stats?: {
|
|
75
|
+
originalRepo?: string;
|
|
76
|
+
originalStars?: number;
|
|
77
|
+
originalForks?: number;
|
|
78
|
+
originalContributors?: number;
|
|
79
|
+
totalApps?: number;
|
|
80
|
+
totalCategories?: number;
|
|
81
|
+
totalStacks?: number;
|
|
82
|
+
totalOwners?: number;
|
|
83
|
+
totalStars?: number;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DirectoryContributor {
|
|
88
|
+
username: string;
|
|
89
|
+
name?: string;
|
|
90
|
+
avatarUrl?: string;
|
|
91
|
+
profileUrl?: string;
|
|
92
|
+
contributions?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function loadDirectoryContributors(root = process.cwd()): DirectoryContributor[] {
|
|
96
|
+
const path = resolve(root, "data", "generated", "contributors.json");
|
|
97
|
+
if (!existsSync(path)) return [];
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as {
|
|
100
|
+
contributors?: DirectoryContributor[];
|
|
101
|
+
};
|
|
102
|
+
return parsed.contributors ?? [];
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getHomePageModel(site: DirectorySiteConfig) {
|
|
109
|
+
const slug = site.blueprintConfig?.routeSlug ?? "projects";
|
|
110
|
+
const singular = site.blueprintConfig?.labelSingular ?? itemLabel();
|
|
111
|
+
const plural = site.blueprintConfig?.labelPlural ?? itemLabelPlural();
|
|
112
|
+
const projects = items.filter((record) => record.kind === "project");
|
|
113
|
+
const hot = filterRecords(projects, { labels: ["hot"] }).slice(0, 6);
|
|
114
|
+
const recentlyAdded = applySort(projects, "recently-added").slice(0, 6);
|
|
115
|
+
const established = filterRecords(projects, { labels: ["mature"] }).slice(0, 6);
|
|
116
|
+
|
|
117
|
+
const stackCounts = new Map<string, number>();
|
|
118
|
+
const categoryCounts = new Map<string, number>();
|
|
119
|
+
for (const record of fullItems) {
|
|
120
|
+
if (record.category) categoryCounts.set(record.category, (categoryCounts.get(record.category) ?? 0) + 1);
|
|
121
|
+
if (record.kind === "project" && record.stack) {
|
|
122
|
+
stackCounts.set(record.stack, (stackCounts.get(record.stack) ?? 0) + 1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const stacks = [...stackCounts]
|
|
126
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
127
|
+
.slice(0, 12)
|
|
128
|
+
.map(([id, count]) => ({ name: taxonomyLabel("stacks", id), slug: id, count }));
|
|
129
|
+
const categories = [...categoryCounts]
|
|
130
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
131
|
+
.map(([id, count]) => ({ name: taxonomyLabel("categories", id), slug: id, count }));
|
|
132
|
+
const contributors = loadDirectoryContributors();
|
|
133
|
+
const description = site.description ??
|
|
134
|
+
`A searchable directory of real ${plural} — organized by stack, category, platform, license, activity, and maturity.`;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
slug,
|
|
138
|
+
itemSingular: singular,
|
|
139
|
+
itemPlural: plural,
|
|
140
|
+
hot,
|
|
141
|
+
recentlyAdded,
|
|
142
|
+
established,
|
|
143
|
+
stacks,
|
|
144
|
+
categories,
|
|
145
|
+
contributors,
|
|
146
|
+
title: `${site.name} — ${site.tagline ?? ""}`.trim(),
|
|
147
|
+
description,
|
|
148
|
+
stats: {
|
|
149
|
+
originalRepo: site.stats?.originalRepo ?? "",
|
|
150
|
+
apps: site.stats?.totalApps ?? 0,
|
|
151
|
+
categories: site.stats?.totalCategories ?? 0,
|
|
152
|
+
stacks: site.stats?.totalStacks ?? 0,
|
|
153
|
+
owners: site.stats?.totalOwners ?? 0,
|
|
154
|
+
stars: site.stats?.totalStars ?? 0,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const FACET_ALIASES: Record<string, "stacks" | "platforms" | "categories" | "tags" | "licenses"> = {
|
|
160
|
+
stack: "stacks",
|
|
161
|
+
stacks: "stacks",
|
|
162
|
+
platform: "platforms",
|
|
163
|
+
platforms: "platforms",
|
|
164
|
+
category: "categories",
|
|
165
|
+
categories: "categories",
|
|
166
|
+
tag: "tags",
|
|
167
|
+
tags: "tags",
|
|
168
|
+
license: "licenses",
|
|
169
|
+
licenses: "licenses",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
function configuredFacets(site?: DirectorySiteConfig) {
|
|
173
|
+
return new Set((site?.facets ?? ["category", "tags"])
|
|
174
|
+
.map((facet) => FACET_ALIASES[facet])
|
|
175
|
+
.filter((facet): facet is NonNullable<typeof facet> => Boolean(facet)));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getDirectoryIndexModel(searchParams: URLSearchParams, site?: DirectorySiteConfig) {
|
|
179
|
+
const filters = filtersFromSearchParams(searchParams);
|
|
180
|
+
const rawFacets = buildFacets(items);
|
|
181
|
+
const enabled = configuredFacets(site);
|
|
182
|
+
const facets = {
|
|
183
|
+
stacks: enabled.has("stacks") ? rawFacets.stacks.map((option) => ({ ...option, label: taxonomyLabel("stacks", option.value) })) : [],
|
|
184
|
+
platforms: enabled.has("platforms") ? rawFacets.platforms.map((option) => ({ ...option, label: taxonomyLabel("platforms", option.value) })) : [],
|
|
185
|
+
categories: enabled.has("categories") ? rawFacets.categories.map((option) => ({ ...option, label: taxonomyLabel("categories", option.value) })) : [],
|
|
186
|
+
tags: enabled.has("tags") ? rawFacets.tags : [],
|
|
187
|
+
licenses: enabled.has("licenses") ? rawFacets.licenses : [],
|
|
188
|
+
};
|
|
189
|
+
const sort = effectiveSort(filters);
|
|
190
|
+
const sorted = applySort(filterRecords(items, filters), sort);
|
|
191
|
+
const pageCount = totalPages(sorted.length);
|
|
192
|
+
const page = Math.min(effectivePage(filters), pageCount);
|
|
193
|
+
return {
|
|
194
|
+
items,
|
|
195
|
+
total: items.length,
|
|
196
|
+
filters,
|
|
197
|
+
facets,
|
|
198
|
+
sort,
|
|
199
|
+
sorted,
|
|
200
|
+
pages: pageCount,
|
|
201
|
+
page,
|
|
202
|
+
chips: activeFilterChips(filters),
|
|
203
|
+
clientItemsJson: JSON.stringify(items).replace(/</g, "\\u003c"),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getContributorsPageModel(site: DirectorySiteConfig) {
|
|
208
|
+
const contributors = loadDirectoryContributors();
|
|
209
|
+
const sorted = [...contributors].sort((a, b) =>
|
|
210
|
+
(b.contributions ?? 0) - (a.contributions ?? 0) || a.username.localeCompare(b.username)
|
|
211
|
+
);
|
|
212
|
+
const repo = site.repoUrl?.replace(/\/$/, "");
|
|
213
|
+
return {
|
|
214
|
+
contributors: sorted,
|
|
215
|
+
total: sorted.length,
|
|
216
|
+
title: `Community — ${site.name}`,
|
|
217
|
+
description: `The people who have contributed to ${site.name} through code, curation, and review.`,
|
|
218
|
+
contributorsGraphHref: repo ? `${repo}/graphs/contributors` : null,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function getSubmissionPageModel(site: DirectorySiteConfig) {
|
|
223
|
+
const singular = site.blueprintConfig?.labelSingular ?? itemLabel();
|
|
224
|
+
const enabled = configuredFacets(site);
|
|
225
|
+
const values = (key: "category" | "stack") =>
|
|
226
|
+
[...new Set(fullProjects.map((record) => String(record[key] ?? "")).filter(Boolean))]
|
|
227
|
+
.sort((a, b) => a.localeCompare(b));
|
|
228
|
+
const categories = (site.taxonomy?.categories?.length
|
|
229
|
+
? site.taxonomy.categories.map(({ id }) => id)
|
|
230
|
+
: values("category"))
|
|
231
|
+
.map((id) => ({ id, label: taxonomyLabel("categories", id) }));
|
|
232
|
+
const stacks = (site.taxonomy?.stacks?.length
|
|
233
|
+
? site.taxonomy.stacks.map(({ id }) => id)
|
|
234
|
+
: values("stack"))
|
|
235
|
+
.map((id) => ({ id, label: taxonomyLabel("stacks", id) }));
|
|
236
|
+
const existingFullNames = fullProjects.map((record) => {
|
|
237
|
+
const url = String(record.repoUrl ?? record.links?.github ?? "");
|
|
238
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/?#]+)/i);
|
|
239
|
+
return match ? `${match[1]}/${match[2].replace(/\.git$/, "")}`.toLowerCase() : null;
|
|
240
|
+
}).filter((value): value is string => Boolean(value));
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
singular,
|
|
244
|
+
title: `Submit ${singular} - ${site.name}`,
|
|
245
|
+
description: `Submit a new ${singular} to ${site.name}.`,
|
|
246
|
+
repoUrl: site.repoUrl ?? "https://github.com/tortuvshin/grove",
|
|
247
|
+
copy: site.submission ?? {},
|
|
248
|
+
fields: {
|
|
249
|
+
category: enabled.has("categories"),
|
|
250
|
+
stack: enabled.has("stacks"),
|
|
251
|
+
platforms: enabled.has("platforms"),
|
|
252
|
+
tags: enabled.has("tags"),
|
|
253
|
+
},
|
|
254
|
+
categories,
|
|
255
|
+
stacks,
|
|
256
|
+
existingSlugs: fullProjects.map((record) => record.slug).filter(Boolean),
|
|
257
|
+
existingFullNames,
|
|
258
|
+
platformOptions: site.taxonomy?.platforms?.map(({ id, name }) => ({ id, label: name })) ??
|
|
259
|
+
["ios", "android", "web", "macos", "windows", "linux"].map((id) => ({ id, label: taxonomyLabel("platforms", id) })),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
type MonthlyCommit = number | { month: string; commits: number };
|
|
264
|
+
type ProjectExtras = {
|
|
265
|
+
github?: {
|
|
266
|
+
activity?: { monthlyCommits?: MonthlyCommit[] };
|
|
267
|
+
files?: Record<string, boolean>;
|
|
268
|
+
languages?: Record<string, number>;
|
|
269
|
+
};
|
|
270
|
+
health?: { status?: string; tier?: string };
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export function getRecordDetailModel(
|
|
274
|
+
recordSlug: string,
|
|
275
|
+
site: DirectorySiteConfig,
|
|
276
|
+
routeSlug = site.blueprintConfig?.routeSlug ?? "projects",
|
|
277
|
+
) {
|
|
278
|
+
const project = projectBySlug(recordSlug);
|
|
279
|
+
const resource = resourceBySlug(recordSlug);
|
|
280
|
+
const entity = entityBySlug(recordSlug);
|
|
281
|
+
const record = project ?? resource ?? entity;
|
|
282
|
+
if (!record) return null;
|
|
283
|
+
|
|
284
|
+
const isProject = record.kind === "project";
|
|
285
|
+
const proj: ProjectRecord | null = project ?? null;
|
|
286
|
+
const name = record.kind === "resource" ? record.title : record.name;
|
|
287
|
+
const description = record.description ?? `${name} on ${site.name}`;
|
|
288
|
+
const repoUrl = proj?.repoUrl ?? record.links?.github ?? "";
|
|
289
|
+
const homepageUrl = record.links?.website ?? "";
|
|
290
|
+
const stacks = projectStackIds(proj);
|
|
291
|
+
const platforms = proj?.platforms ?? [];
|
|
292
|
+
const github = proj?.github?.repository;
|
|
293
|
+
const stars = github?.stargazers_count ?? 0;
|
|
294
|
+
const forks = github?.forks_count ?? 0;
|
|
295
|
+
const language = github?.language ?? null;
|
|
296
|
+
const licenseSpdx = github?.license?.spdx_id ?? null;
|
|
297
|
+
const pushedAt = github?.pushed_at ?? null;
|
|
298
|
+
const ownerRepo = repoUrl ? getOwnerAndRepoFromRepoUrl(repoUrl) : null;
|
|
299
|
+
const avatarSrc = proj?.logoUrl ?? (ownerRepo ? getOwnerAvatarUrl(ownerRepo.owner, 80) : null);
|
|
300
|
+
const rawScores = proj?.scores;
|
|
301
|
+
const scores = rawScores && Object.values(rawScores).some((value) => typeof value === "number")
|
|
302
|
+
? rawScores
|
|
303
|
+
: null;
|
|
304
|
+
const healthStatus = proj?.health?.status;
|
|
305
|
+
const extras = record as ProjectExtras;
|
|
306
|
+
const monthlyCommits = (extras.github?.activity?.monthlyCommits ?? []).map(
|
|
307
|
+
(value): { month: string; commits: number } => typeof value === "number"
|
|
308
|
+
? { month: "", commits: value }
|
|
309
|
+
: { month: value.month ?? "", commits: value.commits ?? 0 },
|
|
310
|
+
);
|
|
311
|
+
const contributionLabels: Record<string, string> = {
|
|
312
|
+
readme: "README",
|
|
313
|
+
contributing: "Contributing",
|
|
314
|
+
security: "Security",
|
|
315
|
+
issueTemplates: "Issue templates",
|
|
316
|
+
pullRequestTemplate: "PR template",
|
|
317
|
+
};
|
|
318
|
+
const contributionFiles = extras.github?.files ?? {};
|
|
319
|
+
const contributionSignals = Object.keys(contributionLabels)
|
|
320
|
+
.map((key) => ({ key, label: contributionLabels[key], ok: contributionFiles[key] }))
|
|
321
|
+
.filter((signal) => signal.ok !== undefined);
|
|
322
|
+
const languages = extras.github?.languages
|
|
323
|
+
? Object.entries(extras.github.languages).sort((a, b) => b[1] - a[1]).slice(0, 5)
|
|
324
|
+
: [];
|
|
325
|
+
|
|
326
|
+
let jsonLd: Record<string, unknown>;
|
|
327
|
+
if (isProject && proj) {
|
|
328
|
+
const sameAs = [repoUrl, homepageUrl].filter(Boolean);
|
|
329
|
+
const dateCreated = record.curation?.reviewedAt;
|
|
330
|
+
jsonLd = {
|
|
331
|
+
"@context": "https://schema.org",
|
|
332
|
+
"@type": "SoftwareSourceCode",
|
|
333
|
+
name,
|
|
334
|
+
headline: name,
|
|
335
|
+
description: record.description ?? undefined,
|
|
336
|
+
url: repoUrl || homepageUrl || undefined,
|
|
337
|
+
codeRepository: repoUrl || undefined,
|
|
338
|
+
sameAs: sameAs.length ? sameAs : undefined,
|
|
339
|
+
programmingLanguage: language ?? undefined,
|
|
340
|
+
license: licenseSpdx ?? undefined,
|
|
341
|
+
applicationCategory: record.category || undefined,
|
|
342
|
+
operatingSystem: platforms.length ? platforms.join(", ") : undefined,
|
|
343
|
+
keywords: record.tags?.length ? record.tags.join(", ") : undefined,
|
|
344
|
+
dateCreated: dateCreated ?? undefined,
|
|
345
|
+
dateModified: pushedAt ?? dateCreated ?? undefined,
|
|
346
|
+
interactionStatistic: stars > 0 ? {
|
|
347
|
+
"@type": "InteractionCounter",
|
|
348
|
+
interactionType: { "@type": "LikeAction" },
|
|
349
|
+
userInteractionCount: stars,
|
|
350
|
+
} : undefined,
|
|
351
|
+
author: ownerRepo ? {
|
|
352
|
+
"@type": "Organization",
|
|
353
|
+
name: ownerRepo.owner,
|
|
354
|
+
url: `https://github.com/${ownerRepo.owner}`,
|
|
355
|
+
} : undefined,
|
|
356
|
+
isAccessibleForFree: true,
|
|
357
|
+
};
|
|
358
|
+
} else if (record.kind === "resource") {
|
|
359
|
+
const schemaTypes: Record<string, string> = {
|
|
360
|
+
article: "Article", book: "Book", course: "Course", podcast: "PodcastSeries", video: "VideoObject",
|
|
361
|
+
};
|
|
362
|
+
jsonLd = {
|
|
363
|
+
"@context": "https://schema.org",
|
|
364
|
+
"@type": schemaTypes[record.type] ?? "CreativeWork",
|
|
365
|
+
name,
|
|
366
|
+
headline: name,
|
|
367
|
+
description: record.description || undefined,
|
|
368
|
+
url: homepageUrl || undefined,
|
|
369
|
+
author: record.author ? { "@type": "Person", name: record.author } : undefined,
|
|
370
|
+
datePublished: record.publishedAt || undefined,
|
|
371
|
+
keywords: record.tags?.length ? record.tags.join(", ") : undefined,
|
|
372
|
+
isAccessibleForFree: true,
|
|
373
|
+
};
|
|
374
|
+
} else {
|
|
375
|
+
jsonLd = {
|
|
376
|
+
"@context": "https://schema.org",
|
|
377
|
+
"@type": entity?.type === "person" ? "Person" : "Organization",
|
|
378
|
+
name,
|
|
379
|
+
headline: name,
|
|
380
|
+
description: entity?.description || undefined,
|
|
381
|
+
url: homepageUrl || undefined,
|
|
382
|
+
foundingDate: entity?.founded || undefined,
|
|
383
|
+
location: entity?.location || undefined,
|
|
384
|
+
keywords: entity?.tags?.length ? entity.tags.join(", ") : undefined,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
slug: routeSlug,
|
|
390
|
+
record,
|
|
391
|
+
project: proj,
|
|
392
|
+
entity,
|
|
393
|
+
isProject,
|
|
394
|
+
name,
|
|
395
|
+
title: `${name} — ${site.name}`,
|
|
396
|
+
description,
|
|
397
|
+
itemSingular: site.blueprintConfig?.labelSingular ?? record.kind,
|
|
398
|
+
repoUrl,
|
|
399
|
+
homepageUrl,
|
|
400
|
+
stacks,
|
|
401
|
+
platforms,
|
|
402
|
+
github,
|
|
403
|
+
stars,
|
|
404
|
+
forks,
|
|
405
|
+
language,
|
|
406
|
+
licenseSpdx,
|
|
407
|
+
pushedAt,
|
|
408
|
+
starsLabel: formatStars(stars),
|
|
409
|
+
pushedLabel: formatRelative(pushedAt),
|
|
410
|
+
ownerRepo,
|
|
411
|
+
avatarSrc,
|
|
412
|
+
initials: name.replace(/[^a-z0-9]/gi, "").slice(0, 2).toUpperCase(),
|
|
413
|
+
bestFor: proj?.bestFor ?? [],
|
|
414
|
+
whyListed: proj?.whyListed ?? [],
|
|
415
|
+
caveats: proj?.caveats ?? [],
|
|
416
|
+
distribution: proj?.distribution?.channels ?? [],
|
|
417
|
+
scores,
|
|
418
|
+
curationLabels: record.curation?.labels ?? [],
|
|
419
|
+
tags: record.tags ?? [],
|
|
420
|
+
healthLabel: healthStatus ? statusDisplay(healthStatus) : null,
|
|
421
|
+
contentHtml: isProject ? getContentHtml(recordSlug) : null,
|
|
422
|
+
monthlyCommits,
|
|
423
|
+
maxMonthlyCommits: Math.max(1, ...monthlyCommits.map((item) => item.commits)),
|
|
424
|
+
contributionSignals,
|
|
425
|
+
languages,
|
|
426
|
+
totalLanguageBytes: Math.max(1, languages.reduce((sum, [, bytes]) => sum + bytes, 0)),
|
|
427
|
+
jsonLd,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export type DirectoryIndexModel = ReturnType<typeof getDirectoryIndexModel>;
|
|
432
|
+
export type DirectoryHomeModel = ReturnType<typeof getHomePageModel>;
|
|
433
|
+
export type SubmissionPageModel = ReturnType<typeof getSubmissionPageModel>;
|
|
434
|
+
export type ContributorsPageModel = ReturnType<typeof getContributorsPageModel>;
|
|
435
|
+
export type RecordDetailModel = NonNullable<ReturnType<typeof getRecordDetailModel>>;
|
|
436
|
+
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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"}
|