@grove-dev/astro 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/display.d.ts +11 -17
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +43 -43
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +6 -7
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +11 -12
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +35 -73
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +144 -126
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +11 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +12 -25
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +15 -16
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +4 -11
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +47 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +195 -180
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +7 -21
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +12 -31
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +4 -4
- package/src/components/AppsIndexRow.astro +55 -184
- package/src/components/AppsPagination.astro +45 -108
- package/src/components/CategoryGrid.astro +43 -31
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +41 -0
- package/src/components/ExploreByStack.astro +6 -0
- package/src/components/FilterGroupMenu.astro +49 -0
- package/src/components/FilterOptions.astro +44 -0
- package/src/components/Hero.astro +198 -147
- package/src/components/Icon.astro +10 -3
- package/src/components/ItemCard.astro +266 -248
- package/src/components/RecordSection.astro +68 -33
- package/src/components/RefinePanel.astro +115 -141
- package/src/components/StackGrid.astro +32 -24
- package/src/components/WhyThisExists.astro +32 -32
- package/src/layouts/BaseLayout.astro +104 -49
- package/src/layouts/Container.astro +5 -44
- package/src/layouts/Footer.astro +31 -20
- package/src/layouts/Header.astro +40 -19
- package/src/layouts/SectionHeader.astro +27 -43
- package/src/layouts/Seo.astro +38 -56
- package/src/layouts/ThemeToggle.astro +35 -48
- package/src/lib/display.ts +46 -53
- package/src/lib/format.ts +11 -12
- package/src/lib/lenses.ts +151 -172
- package/src/lib/repo.ts +16 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +222 -290
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +207 -1
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
- package/templates/default/.github/pull_request_template.md +27 -0
- package/templates/default/.github/workflows/build.yml +55 -0
- package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
- package/templates/default/.github/workflows/daily-refresh.yml +53 -0
- package/templates/default/.github/workflows/sync-contributors.yml +53 -0
- package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
- package/templates/default/.github/workflows/validate-data.yml +35 -0
- package/templates/default/data/records/coolify.yml +46 -0
- package/templates/default/data/records/gitea.yml +46 -0
- package/templates/default/data/records/hoppscotch.yml +47 -0
- package/templates/default/data/records/inventree.yml +47 -0
- package/templates/default/data/records/label-studio.yml +46 -0
- package/templates/default/data/records/logseq.yml +48 -0
- package/templates/default/data/records/private-gpt.yml +47 -0
- package/templates/default/data/records/wakapi.yml +44 -0
- package/templates/default/data/taxonomy/categories.yml +47 -0
- package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
- package/templates/default/data/taxonomy/platforms.yml +27 -0
- package/templates/default/data/taxonomy/stacks.yml +41 -0
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +5 -4
- package/templates/default/public/icons/stacks/bun.svg +1 -0
- package/templates/default/public/icons/stacks/clojure.svg +1 -0
- package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
- package/templates/default/public/icons/stacks/deno.svg +1 -0
- package/templates/default/public/icons/stacks/django.svg +1 -0
- package/templates/default/public/icons/stacks/docker.svg +1 -0
- package/templates/default/public/icons/stacks/go.svg +1 -0
- package/templates/default/public/icons/stacks/llm.svg +1 -0
- package/templates/default/public/icons/stacks/node.js.svg +1 -0
- package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
- package/templates/default/public/icons/stacks/vue.svg +1 -0
- package/templates/default/public/llms-full.txt +197 -5
- package/templates/default/public/llms.txt +107 -6
- package/templates/default/public/sitemap.xml +63 -0
- package/templates/default/scripts/build-llms.mjs +123 -0
- package/templates/default/scripts/build-records-json.mjs +27 -0
- package/templates/default/scripts/build-sitemap.mjs +20 -0
- package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
- package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
- package/templates/default/scripts/fetch-icons.mjs +137 -0
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
- package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
- package/templates/default/scripts/refresh-records-activity.mjs +24 -0
- package/templates/default/scripts/repair-license-format.mjs +72 -0
- package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
- package/templates/default/scripts/seed-from-github.mjs +62 -0
- package/templates/default/scripts/sync-contributors.mjs +145 -0
- package/templates/default/scripts/sync-github-metadata.mjs +28 -0
- package/templates/default/scripts/validate-records.mjs +27 -0
- package/templates/default/src/data/records.ts +134 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[itemSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +476 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[itemSlug].astro +301 -0
- package/templates/default/src/pages/apps/index.astro +146 -0
- package/templates/default/src/pages/contributors.astro +184 -0
- package/templates/default/src/pages/index.astro +119 -162
- package/templates/default/src/pages/submit.astro +276 -114
- package/templates/default/wrangler.jsonc +28 -0
- package/LICENSE +0 -21
- package/templates/default/.astro/content.d.ts +0 -155
- package/templates/default/.astro/settings.json +0 -5
- package/templates/default/.astro/types.d.ts +0 -1
- package/templates/default/data/generated/apps.json +0 -1
- package/templates/default/data/generated/records.full.json +0 -8
- package/templates/default/data/generated/records.index.json +0 -7
- package/templates/default/data/generated/records.json +0 -8
- package/templates/default/data/generated/repo-stats.json +0 -1
- package/templates/default/data/generated/site-config.json +0 -31
- package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
- package/templates/default/dist/about/index.html +0 -165
- package/templates/default/dist/icons/platforms/android.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
- package/templates/default/dist/icons/platforms/chrome.svg +0 -1
- package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
- package/templates/default/dist/icons/platforms/desktop.svg +0 -4
- package/templates/default/dist/icons/platforms/embedded.svg +0 -5
- package/templates/default/dist/icons/platforms/ios.svg +0 -1
- package/templates/default/dist/icons/platforms/linux.svg +0 -1
- package/templates/default/dist/icons/platforms/macos.svg +0 -1
- package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/dist/icons/platforms/web.svg +0 -5
- package/templates/default/dist/icons/platforms/windows.svg +0 -6
- package/templates/default/dist/icons/stacks/android.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
- package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
- package/templates/default/dist/icons/stacks/dart.svg +0 -1
- package/templates/default/dist/icons/stacks/firebase.svg +0 -1
- package/templates/default/dist/icons/stacks/flutter.svg +0 -1
- package/templates/default/dist/icons/stacks/graphql.svg +0 -1
- package/templates/default/dist/icons/stacks/ionic.svg +0 -4
- package/templates/default/dist/icons/stacks/java.svg +0 -1
- package/templates/default/dist/icons/stacks/javascript.svg +0 -1
- package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
- package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
- package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
- package/templates/default/dist/icons/stacks/python.svg +0 -1
- package/templates/default/dist/icons/stacks/react-native.svg +0 -1
- package/templates/default/dist/icons/stacks/react.svg +0 -1
- package/templates/default/dist/icons/stacks/rust.svg +0 -1
- package/templates/default/dist/icons/stacks/solidity.svg +0 -1
- package/templates/default/dist/icons/stacks/swift.svg +0 -1
- package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/dist/icons/stacks/typescript.svg +0 -1
- package/templates/default/dist/index.html +0 -55
- package/templates/default/dist/llms-full.txt +0 -14
- package/templates/default/dist/llms.txt +0 -11
- package/templates/default/dist/og-image.svg +0 -48
- package/templates/default/dist/projects/index.html +0 -61
- package/templates/default/dist/robots.txt +0 -53
- package/templates/default/dist/sitemap.xml +0 -21
- package/templates/default/dist/submit/index.html +0 -69
- package/templates/default/node_modules/.bin/astro +0 -21
- package/templates/default/node_modules/.bin/astro-check +0 -21
- package/templates/default/node_modules/.bin/grove +0 -21
- package/templates/default/node_modules/.bin/jiti +0 -21
- package/templates/default/node_modules/.bin/prettier +0 -21
- package/templates/default/node_modules/.bin/rollup +0 -21
- package/templates/default/node_modules/.bin/tsc +0 -21
- package/templates/default/node_modules/.bin/tsserver +0 -21
- package/templates/default/node_modules/.bin/tsx +0 -21
- package/templates/default/node_modules/.bin/vite +0 -21
- package/templates/default/node_modules/.bin/yaml +0 -21
- package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/package.json +0 -3
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
- package/templates/default/src/components/layout/BaseLayout.astro +0 -47
- package/templates/default/src/components/layout/Container.astro +0 -18
- package/templates/default/src/components/layout/Footer.astro +0 -27
- package/templates/default/src/components/layout/Header.astro +0 -43
- package/templates/default/src/lib/markdown.ts +0 -171
- package/templates/default/src/pages/projects/index.astro +0 -193
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Home page —
|
|
3
|
+
* Home page — generic.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
5
|
+
* Mirrors the openapps structure: Hero → WhyThisExists → three
|
|
6
|
+
* lens sections (hot / new / mature) → StackGrid → CategoryGrid →
|
|
7
|
+
* ContributorsGrid → OriginalCollection.
|
|
15
8
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `IndexProjectRecord` shape is what the home page consumes (no
|
|
21
|
-
* `as ProjectRecord` casts); the full `Resource` only flows into
|
|
22
|
-
* the detail page.
|
|
9
|
+
* All data derives from `data/generated/records.{index,full}.json`
|
|
10
|
+
* and `data/generated/site-config.json`. Nothing here is hardcoded
|
|
11
|
+
* to a specific blueprint — section copy uses singular/plural
|
|
12
|
+
* labels from `itemLabel()`/`itemLabelPlural()`.
|
|
23
13
|
*/
|
|
24
|
-
import siteConfig from "../../data/generated/site-config.json";
|
|
25
|
-
import { records, fullRecords, projects, indexSlug, itemLabel } from "../data/records";
|
|
26
14
|
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
|
|
27
15
|
import Hero from "@grove-dev/astro/components/Hero.astro";
|
|
28
16
|
import WhyThisExists from "@grove-dev/astro/components/WhyThisExists.astro";
|
|
@@ -31,35 +19,16 @@ import StackGrid from "@grove-dev/astro/components/StackGrid.astro";
|
|
|
31
19
|
import CategoryGrid from "@grove-dev/astro/components/CategoryGrid.astro";
|
|
32
20
|
import ContributorsGrid from "@grove-dev/astro/components/ContributorsGrid.astro";
|
|
33
21
|
import OriginalCollection from "@grove-dev/astro/components/OriginalCollection.astro";
|
|
34
|
-
|
|
22
|
+
|
|
23
|
+
import { items, fullRecords, fullItems } from "../data/records";
|
|
35
24
|
import { existsSync, readFileSync } from "node:fs";
|
|
36
25
|
import { fileURLToPath } from "node:url";
|
|
37
26
|
import { dirname, resolve } from "node:path";
|
|
38
|
-
import
|
|
39
|
-
|
|
40
|
-
const slug = indexSlug();
|
|
41
|
-
const projectKind = projects;
|
|
27
|
+
import siteConfig from "../../data/generated/site-config.json";
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
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
|
-
);
|
|
29
|
+
const slug = (siteConfig.blueprintConfig?.routeSlug ?? "projects") as string;
|
|
30
|
+
const itemSingular = (siteConfig.blueprintConfig?.labelSingular ?? "project") as string;
|
|
31
|
+
const itemPlural = (siteConfig.blueprintConfig?.labelPlural ?? "projects") as string;
|
|
63
32
|
|
|
64
33
|
// ── Lens sectioning (Hot / New / Mature) ────────────────────────
|
|
65
34
|
const DAY_MS = 86_400_000;
|
|
@@ -69,54 +38,66 @@ function daysSince(iso: string | null | undefined): number {
|
|
|
69
38
|
if (Number.isNaN(d.valueOf())) return Infinity;
|
|
70
39
|
return (Date.now() - d.valueOf()) / DAY_MS;
|
|
71
40
|
}
|
|
41
|
+
function starsOf(r: typeof items[number]): number {
|
|
42
|
+
return (r as { github?: { stars?: number } }).github?.stars ?? 0;
|
|
43
|
+
}
|
|
44
|
+
function pushedAtOf(r: typeof items[number]): string | null {
|
|
45
|
+
return (r as { github?: { pushedAt?: string | null } }).github?.pushedAt ?? null;
|
|
46
|
+
}
|
|
47
|
+
function reviewedAtOf(r: typeof items[number]): string | null {
|
|
48
|
+
return r.curation?.reviewedAt ?? null;
|
|
49
|
+
}
|
|
50
|
+
function labelsOf(r: typeof items[number]): string[] {
|
|
51
|
+
return r.curation?.labels ?? [];
|
|
52
|
+
}
|
|
53
|
+
function tierOf(r: typeof items[number]): string | undefined {
|
|
54
|
+
return (r as { health?: { tier?: string } }).health?.tier;
|
|
55
|
+
}
|
|
72
56
|
|
|
73
|
-
const
|
|
74
|
-
.filter((r
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const labelledHot = r.curation?.labels?.includes("hot") ?? false;
|
|
79
|
-
return labelledHot || stars >= 1000 || pushed <= 30;
|
|
57
|
+
const hotList = items
|
|
58
|
+
.filter((r) => r.kind === "project")
|
|
59
|
+
.filter((r) => {
|
|
60
|
+
const labelledHot = labelsOf(r).includes("hot");
|
|
61
|
+
return labelledHot || starsOf(r) >= 1000 || daysSince(pushedAtOf(r)) <= 30;
|
|
80
62
|
})
|
|
81
63
|
.slice(0, 6);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.filter((r
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const labelledNew = r.curation?.labels?.includes("new") ?? false;
|
|
88
|
-
return pushed <= 7 || labelledNew;
|
|
64
|
+
const newList = items
|
|
65
|
+
.filter((r) => r.kind === "project")
|
|
66
|
+
.filter((r) => {
|
|
67
|
+
const labelledNew = labelsOf(r).includes("new");
|
|
68
|
+
return daysSince(reviewedAtOf(r)) <= 21 || labelledNew;
|
|
89
69
|
})
|
|
90
70
|
.slice(0, 6);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.filter((r
|
|
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
|
-
})
|
|
71
|
+
const matureList = items
|
|
72
|
+
.filter((r) => r.kind === "project")
|
|
73
|
+
.filter((r) => labelsOf(r).includes("mature") || tierOf(r) === "curated")
|
|
99
74
|
.slice(0, 6);
|
|
100
75
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
const
|
|
76
|
+
// Stacks and categories derived from items
|
|
77
|
+
const stackMap = new Map<string, number>();
|
|
78
|
+
const catMap = new Map<string, number>();
|
|
79
|
+
for (const r of fullItems) {
|
|
80
|
+
const cat = (r as { category?: string }).category;
|
|
81
|
+
if (cat) catMap.set(cat, (catMap.get(cat) ?? 0) + 1);
|
|
82
|
+
const s1 = (r as { stack?: string }).stack;
|
|
83
|
+
if (s1) stackMap.set(s1, (stackMap.get(s1) ?? 0) + 1);
|
|
84
|
+
const s2 = (r as { stacks?: string[] }).stacks ?? [];
|
|
85
|
+
for (const s of s2) stackMap.set(s, (stackMap.get(s) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
const stacks = [...stackMap.entries()]
|
|
104
88
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
105
89
|
.slice(0, 12)
|
|
106
90
|
.map(([name, count]) => ({ name, slug: name, count }));
|
|
107
|
-
|
|
108
|
-
const categoryCounts = countByCategory(projectKind);
|
|
109
|
-
const categories = [...categoryCounts.entries()]
|
|
91
|
+
const categories = [...catMap.entries()]
|
|
110
92
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
111
93
|
.map(([name, count]) => ({ name, slug: name, count }));
|
|
112
94
|
|
|
113
|
-
//
|
|
95
|
+
// Contributors (best-effort from data/generated/contributors.json)
|
|
114
96
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
115
97
|
const contributorsPath = [
|
|
116
98
|
resolve(here, "..", "..", "data", "generated", "contributors.json"),
|
|
117
99
|
resolve(process.cwd(), "data", "generated", "contributors.json"),
|
|
118
100
|
].find((p) => existsSync(p));
|
|
119
|
-
|
|
120
101
|
interface ContributorsFile {
|
|
121
102
|
contributors?: Array<{
|
|
122
103
|
username: string;
|
|
@@ -126,7 +107,6 @@ interface ContributorsFile {
|
|
|
126
107
|
contributions?: number;
|
|
127
108
|
}>;
|
|
128
109
|
}
|
|
129
|
-
|
|
130
110
|
let contributors: ContributorsFile["contributors"] = [];
|
|
131
111
|
if (contributorsPath) {
|
|
132
112
|
try {
|
|
@@ -136,63 +116,52 @@ if (contributorsPath) {
|
|
|
136
116
|
contributors = [];
|
|
137
117
|
}
|
|
138
118
|
}
|
|
139
|
-
const hasContributors = contributors && contributors.length > 0;
|
|
140
119
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
contributors
|
|
147
|
-
|
|
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
|
-
}
|
|
120
|
+
const stats = {
|
|
121
|
+
originalRepo: siteConfig.repoUrl ?? "",
|
|
122
|
+
apps: siteConfig.stats?.totalApps ?? 0,
|
|
123
|
+
categories: siteConfig.stats?.totalCategories ?? 0,
|
|
124
|
+
stacks: siteConfig.stats?.totalStacks ?? 0,
|
|
125
|
+
contributors: siteConfig.stats?.totalOwners ?? 0,
|
|
126
|
+
stars: siteConfig.stats?.totalStars ?? 0,
|
|
127
|
+
};
|
|
161
128
|
|
|
162
129
|
const title = `${siteConfig.name} — ${siteConfig.tagline}`;
|
|
163
130
|
const description =
|
|
164
|
-
siteConfig.description ??
|
|
165
|
-
|
|
166
|
-
// ── Render-time helpers ─────────────────────────────────────────
|
|
167
|
-
function detailHrefFor(r: IndexProjectRecord): string {
|
|
168
|
-
return `/${slug}/${r.slug}`;
|
|
169
|
-
}
|
|
131
|
+
siteConfig.description ??
|
|
132
|
+
`A searchable directory of real ${itemPlural} — organized by stack, category, platform, license, activity, and maturity.`;
|
|
170
133
|
---
|
|
171
134
|
|
|
172
|
-
<BaseLayout
|
|
173
|
-
title={title}
|
|
174
|
-
description={description}
|
|
175
|
-
site={siteConfig}
|
|
176
|
-
submitHref="/submit"
|
|
177
|
-
submitLabel={`Submit ${itemLabel()}`}
|
|
178
|
-
>
|
|
135
|
+
<BaseLayout title={title} description={description} site={siteConfig}>
|
|
179
136
|
<Hero
|
|
180
|
-
name={
|
|
181
|
-
tagline=
|
|
137
|
+
name={`Find ${itemPlural} you can actually`}
|
|
138
|
+
tagline="run, study, and contribute to."
|
|
182
139
|
description={description}
|
|
183
|
-
eyebrow=
|
|
184
|
-
|
|
185
|
-
searchHref={`/${slug}`}
|
|
140
|
+
eyebrow={`A directory of real curated ${itemPlural}`}
|
|
141
|
+
eyebrowBadge="Beta"
|
|
186
142
|
stats={{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
143
|
+
totalApps: stats.apps,
|
|
144
|
+
totalCategories: stats.categories,
|
|
145
|
+
totalStacks: stats.stacks,
|
|
146
|
+
totalOwners: stats.contributors,
|
|
147
|
+
totalStars: stats.stars,
|
|
148
|
+
originalRepo: stats.originalRepo,
|
|
192
149
|
}}
|
|
150
|
+
itemsLabel={itemPlural}
|
|
151
|
+
itemLabel={itemSingular}
|
|
152
|
+
searchHref={`/${slug}`}
|
|
153
|
+
searchPlaceholder={`Search ${itemPlural}, stacks, categories, licenses, platforms...`}
|
|
154
|
+
quickFilters={[
|
|
155
|
+
{ label: `Trending ${itemPlural}`, href: `/${slug}?label=hot` },
|
|
156
|
+
{ label: "Recently added", href: `/${slug}?label=new` },
|
|
157
|
+
{ label: "Established", href: `/${slug}?label=mature` },
|
|
158
|
+
]}
|
|
193
159
|
ctas={[
|
|
194
|
-
{ label: `Browse ${
|
|
195
|
-
{ label:
|
|
160
|
+
{ label: `Browse ${itemPlural}`, href: `/${slug}`, variant: "primary" },
|
|
161
|
+
{ label: `Submit ${itemSingular} via GitHub`, href: "/submit", variant: "outline" },
|
|
162
|
+
...(siteConfig.repoUrl
|
|
163
|
+
? [{ label: "View source", href: siteConfig.repoUrl, variant: "outline" as const }]
|
|
164
|
+
: []),
|
|
196
165
|
]}
|
|
197
166
|
/>
|
|
198
167
|
|
|
@@ -200,68 +169,56 @@ function detailHrefFor(r: IndexProjectRecord): string {
|
|
|
200
169
|
|
|
201
170
|
<RecordSection
|
|
202
171
|
id="hot"
|
|
203
|
-
title="Trending"
|
|
204
|
-
description=
|
|
205
|
-
items={hot}
|
|
206
|
-
hrefFor={detailHrefFor}
|
|
207
|
-
viewAllHref={`/${slug}?lens=hot`}
|
|
172
|
+
title="Trending now"
|
|
173
|
+
description={`${itemPlural.charAt(0).toUpperCase() + itemPlural.slice(1)} gaining stars, activity, or attention recently.`}
|
|
208
174
|
eyebrow="Hot"
|
|
209
|
-
|
|
175
|
+
items={hotList}
|
|
176
|
+
hrefFor={(r) => `/${slug}/${r.slug}`}
|
|
177
|
+
viewAllHref={`/${slug}?label=hot`}
|
|
178
|
+
viewAllLabel={`View all trending ${itemPlural}`}
|
|
210
179
|
/>
|
|
211
180
|
|
|
212
181
|
<RecordSection
|
|
213
182
|
id="new"
|
|
214
183
|
title="Recently added"
|
|
215
|
-
description=
|
|
216
|
-
items={newItems}
|
|
217
|
-
hrefFor={detailHrefFor}
|
|
218
|
-
viewAllHref={`/${slug}?lens=new`}
|
|
184
|
+
description={`${itemPlural.charAt(0).toUpperCase() + itemPlural.slice(1)} added to the directory in the last few weeks.`}
|
|
219
185
|
eyebrow="New"
|
|
220
|
-
|
|
186
|
+
items={newList}
|
|
187
|
+
hrefFor={(r) => `/${slug}/${r.slug}`}
|
|
188
|
+
viewAllHref={`/${slug}?label=new`}
|
|
189
|
+
viewAllLabel={`View all recently added`}
|
|
221
190
|
/>
|
|
222
191
|
|
|
223
192
|
<RecordSection
|
|
224
193
|
id="mature"
|
|
225
194
|
title="Established"
|
|
226
|
-
description=
|
|
227
|
-
items={mature}
|
|
228
|
-
hrefFor={detailHrefFor}
|
|
229
|
-
viewAllHref={`/${slug}?lens=mature`}
|
|
195
|
+
description={`Long-running curated ${itemPlural} with strong history, active maintainers, and real-world usage.`}
|
|
230
196
|
eyebrow="Mature"
|
|
231
|
-
|
|
197
|
+
items={matureList}
|
|
198
|
+
hrefFor={(r) => `/${slug}/${r.slug}`}
|
|
199
|
+
viewAllHref={`/${slug}?label=mature`}
|
|
200
|
+
viewAllLabel={`View all established`}
|
|
232
201
|
/>
|
|
233
202
|
|
|
234
|
-
{stacks
|
|
235
|
-
|
|
236
|
-
stacks={stacks}
|
|
237
|
-
pathPrefix={`/${slug}`}
|
|
238
|
-
/>
|
|
239
|
-
)}
|
|
240
|
-
|
|
241
|
-
{categories.length > 0 && (
|
|
242
|
-
<CategoryGrid
|
|
243
|
-
categories={categories}
|
|
244
|
-
pathPrefix={`/${slug}`}
|
|
245
|
-
/>
|
|
246
|
-
)}
|
|
203
|
+
<StackGrid stacks={stacks} pathPrefix={`/${slug}`} itemLabelPlural={itemPlural} />
|
|
204
|
+
<CategoryGrid categories={categories} pathPrefix={`/${slug}`} itemLabelPlural={itemPlural} />
|
|
247
205
|
|
|
248
|
-
{
|
|
206
|
+
{contributors.length > 0 && (
|
|
249
207
|
<ContributorsGrid
|
|
250
|
-
contributors={contributors
|
|
251
|
-
total={contributors
|
|
252
|
-
viewAllHref={
|
|
253
|
-
viewAllLabel="View
|
|
208
|
+
contributors={contributors}
|
|
209
|
+
total={contributors.length}
|
|
210
|
+
viewAllHref={`/contributors`}
|
|
211
|
+
viewAllLabel="View all contributors"
|
|
254
212
|
/>
|
|
255
213
|
)}
|
|
256
214
|
|
|
257
|
-
{
|
|
215
|
+
{siteConfig.stats?.originalRepo && (
|
|
258
216
|
<OriginalCollection
|
|
259
|
-
repoUrl={
|
|
217
|
+
repoUrl={siteConfig.stats.originalRepo}
|
|
260
218
|
stats={{
|
|
261
|
-
stars:
|
|
262
|
-
forks:
|
|
263
|
-
contributors:
|
|
264
|
-
description: repoStats.description,
|
|
219
|
+
stars: siteConfig.stats.originalStars,
|
|
220
|
+
forks: siteConfig.stats.originalForks,
|
|
221
|
+
contributors: siteConfig.stats.originalContributors,
|
|
265
222
|
}}
|
|
266
223
|
/>
|
|
267
224
|
)}
|