@grove-dev/astro 0.2.20 → 0.3.1
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/LICENSE +21 -0
- package/dist/template-routes.test.d.ts +2 -0
- package/dist/template-routes.test.d.ts.map +1 -0
- package/dist/template-routes.test.js +63 -0
- package/dist/template-routes.test.js.map +1 -0
- package/dist/theme.test.js +5 -0
- package/dist/theme.test.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Icon.astro +52 -22
- package/src/template-routes.test.ts +103 -0
- package/src/theme.test.ts +9 -0
- package/templates/default/.github/workflows/build.yml +1 -3
- package/templates/default/.github/workflows/cleanup-stale-records.yml +1 -3
- package/templates/default/.github/workflows/daily-refresh.yml +1 -3
- package/templates/default/.github/workflows/sync-contributors.yml +2 -4
- package/templates/default/.github/workflows/sync-github-metadata.yml +1 -3
- package/templates/default/.github/workflows/validate-data.yml +1 -3
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +9 -4
- package/templates/default/public/llms-full.txt +63 -186
- package/templates/default/public/llms.txt +6 -107
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/data/records.ts +80 -2
- package/templates/default/src/pages/[slug]/[recordSlug].astro +9 -7
- package/templates/default/src/pages/[slug]/index.astro +215 -11
- package/templates/default/src/pages/about.astro +8 -1
- package/templates/default/src/pages/apps/[recordSlug].astro +2 -1
- package/templates/default/src/pages/index.astro +11 -3
- package/templates/default/src/pages/sitemap.xml.ts +3 -3
- package/templates/default/src/pages/submit.astro +5 -5
- package/templates/default/tsconfig.json +9 -0
- package/templates/default/scripts/build-llms.mjs +0 -123
- package/templates/default/scripts/build-records-json.mjs +0 -27
- package/templates/default/scripts/build-sitemap.mjs +0 -20
- package/templates/default/scripts/cleanup-stale-records.mjs +0 -20
- package/templates/default/scripts/enrich-github-metadata.mjs +0 -99
- package/templates/default/scripts/fetch-icons.mjs +0 -137
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +0 -86
- package/templates/default/scripts/parse-legacy-readme.mjs +0 -59
- package/templates/default/scripts/refresh-records-activity.mjs +0 -24
- package/templates/default/scripts/repair-license-format.mjs +0 -72
- package/templates/default/scripts/report-cleanup-candidates.mjs +0 -21
- package/templates/default/scripts/seed-from-github.mjs +0 -62
- package/templates/default/scripts/sync-contributors.mjs +0 -145
- package/templates/default/scripts/sync-github-metadata.mjs +0 -28
- package/templates/default/scripts/validate-records.mjs +0 -27
|
@@ -1,206 +1,83 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Grove Directory — full directory
|
|
2
2
|
|
|
3
|
-
Generated
|
|
4
|
-
|
|
3
|
+
> Generated 2026-07-01T16:50:32.236Z from 8 records.
|
|
4
|
+
> Source: https://example.com/projects · Regenerate with `grove generate`.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Each section below mirrors one record detail page.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Index
|
|
9
9
|
|
|
10
|
-
Open-source, self-hostable Heroku / Netlify / Vercel alternative — deploy apps and databases from a single dashboard.
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **Stars**: 0
|
|
19
|
-
- **Last commit**: —
|
|
20
|
-
|
|
21
|
-
**Best for:**
|
|
22
|
-
- Self-hosted PaaS
|
|
23
|
-
- VPS deployments
|
|
24
|
-
- Docker-based stacks
|
|
10
|
+
- [Coolify](#coolify) — DevOps · SvelteKit · 0★ — Open-source, self-hostable Heroku / Netlify / Vercel alternative — deploy apps and databases from a single dashboard.
|
|
11
|
+
- [Gitea](#gitea) — DevOps · Go · 0★ — Lightweight, self-hosted Git service — painless, single-binary, low-resource alternative to GitHub.
|
|
12
|
+
- [Hoppscotch](#hoppscotch) — Web · Vue · 0★ — Open-source API development ecosystem — design, test, and debug APIs faster from a single interface.
|
|
13
|
+
- [InvenTree](#inventree) — Mobile · Flutter · 0★ — Open-source inventory management system with parts, stock, and purchase orders.
|
|
14
|
+
- [Label Studio](#label-studio) — AI/ML · Python · 0★ — Open-source data labeling platform — annotate images, text, audio, video, and time series for ML training.
|
|
15
|
+
- [Logseq](#logseq) — Web · ClojureScript · 0★ — Privacy-first, open-source knowledge base that works on top of plain text Markdown and Org-mode files.
|
|
16
|
+
- [PrivateGPT](#private-gpt) — AI/ML · Python · 0★ — Interact privately with your documents using LLMs — 100% private, no data leaves your execution environment.
|
|
17
|
+
- [Wakapi](#wakapi) — Mobile · Kotlin · 0★ — Open-source time tracker for developers — coding stats, language breakdowns, and project-level metrics.
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
- One-click app + DB deploys
|
|
28
|
-
- Active dev
|
|
29
|
-
- Clear upgrade path from manual Docker
|
|
19
|
+
## Projects
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
### Coolify
|
|
22
|
+
Open-source, self-hostable Heroku / Netlify / Vercel alternative — deploy apps and databases from a single dashboard.
|
|
23
|
+
- slug: coolify
|
|
24
|
+
- category: DevOps
|
|
25
|
+
- stack: SvelteKit
|
|
26
|
+
- stars: 0
|
|
27
|
+
- url: https://example.com/projects/coolify
|
|
34
28
|
|
|
29
|
+
### Gitea
|
|
35
30
|
Lightweight, self-hosted Git service — painless, single-binary, low-resource alternative to GitHub.
|
|
31
|
+
- slug: gitea
|
|
32
|
+
- category: DevOps
|
|
33
|
+
- stack: Go
|
|
34
|
+
- stars: 0
|
|
35
|
+
- url: https://example.com/projects/gitea
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
- **Repository**: https://github.com/go-gitea/gitea
|
|
39
|
-
- **Stack**: Go
|
|
40
|
-
- **Platforms**: linux, self-host, docker
|
|
41
|
-
- **Category**: DevOps
|
|
42
|
-
- **Tags**: git, forge, self-host, go
|
|
43
|
-
- **Stars**: 0
|
|
44
|
-
- **Last commit**: —
|
|
45
|
-
|
|
46
|
-
**Best for:**
|
|
47
|
-
- Self-hosted Git hosting
|
|
48
|
-
- Lightweight CI/CD integration
|
|
49
|
-
- Internal team forges
|
|
50
|
-
|
|
51
|
-
**Why listed:**
|
|
52
|
-
- Single binary, easy install
|
|
53
|
-
- Mature, stable releases
|
|
54
|
-
- Active community
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Hoppscotch
|
|
59
|
-
|
|
37
|
+
### Hoppscotch
|
|
60
38
|
Open-source API development ecosystem — design, test, and debug APIs faster from a single interface.
|
|
39
|
+
- slug: hoppscotch
|
|
40
|
+
- category: Web
|
|
41
|
+
- stack: Vue
|
|
42
|
+
- stars: 0
|
|
43
|
+
- url: https://example.com/projects/hoppscotch
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
- **Repository**: https://github.com/hoppscotch/hoppscotch
|
|
64
|
-
- **Stack**: Vue
|
|
65
|
-
- **Platforms**: web, desktop, self-host
|
|
66
|
-
- **Category**: Web
|
|
67
|
-
- **Tags**: api, testing, developer-tools, vue
|
|
68
|
-
- **Stars**: 0
|
|
69
|
-
- **Last commit**: —
|
|
70
|
-
|
|
71
|
-
**Best for:**
|
|
72
|
-
- API exploration
|
|
73
|
-
- REST/GraphQL/WebSocket testing
|
|
74
|
-
- Self-hosted team workflows
|
|
75
|
-
|
|
76
|
-
**Why listed:**
|
|
77
|
-
- Lightweight, fast UI
|
|
78
|
-
- PWA + desktop builds
|
|
79
|
-
- Open-source Postman/Insomnia alternative
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## InvenTree
|
|
84
|
-
|
|
45
|
+
### InvenTree
|
|
85
46
|
Open-source inventory management system with parts, stock, and purchase orders.
|
|
47
|
+
- slug: inventree
|
|
48
|
+
- category: Mobile
|
|
49
|
+
- stack: Flutter
|
|
50
|
+
- stars: 0
|
|
51
|
+
- url: https://example.com/projects/inventree
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
- **Repository**: https://github.com/inventree/InvenTree
|
|
89
|
-
- **Stack**: Flutter
|
|
90
|
-
- **Platforms**: ios, android, web
|
|
91
|
-
- **Category**: Mobile
|
|
92
|
-
- **Tags**: inventory, stock, mobile, dart, flutter
|
|
93
|
-
- **Stars**: 0
|
|
94
|
-
- **Last commit**: —
|
|
95
|
-
|
|
96
|
-
**Best for:**
|
|
97
|
-
- Inventory tracking
|
|
98
|
-
- Manufacturing
|
|
99
|
-
- Hardware projects
|
|
100
|
-
|
|
101
|
-
**Why listed:**
|
|
102
|
-
- Production-grade mobile + web stack
|
|
103
|
-
- Strong community
|
|
104
|
-
- Cleanly factored Dart codebase
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Label Studio
|
|
109
|
-
|
|
53
|
+
### Label Studio
|
|
110
54
|
Open-source data labeling platform — annotate images, text, audio, video, and time series for ML training.
|
|
55
|
+
- slug: label-studio
|
|
56
|
+
- category: AI/ML
|
|
57
|
+
- stack: Python
|
|
58
|
+
- stars: 0
|
|
59
|
+
- url: https://example.com/projects/label-studio
|
|
111
60
|
|
|
112
|
-
|
|
113
|
-
- **Repository**: https://github.com/HumanSignal/label-studio
|
|
114
|
-
- **Stack**: Python
|
|
115
|
-
- **Platforms**: web, self-host
|
|
116
|
-
- **Category**: AI/ML
|
|
117
|
-
- **Tags**: labeling, ml-ops, annotation, python
|
|
118
|
-
- **Stars**: 0
|
|
119
|
-
- **Last commit**: —
|
|
120
|
-
|
|
121
|
-
**Best for:**
|
|
122
|
-
- ML dataset labeling
|
|
123
|
-
- Multi-modal annotation
|
|
124
|
-
- Active learning pipelines
|
|
125
|
-
|
|
126
|
-
**Why listed:**
|
|
127
|
-
- Mature, production-grade
|
|
128
|
-
- Strong ML backend
|
|
129
|
-
- Active commercial + community backing
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Logseq
|
|
134
|
-
|
|
61
|
+
### Logseq
|
|
135
62
|
Privacy-first, open-source knowledge base that works on top of plain text Markdown and Org-mode files.
|
|
63
|
+
- slug: logseq
|
|
64
|
+
- category: Web
|
|
65
|
+
- stack: ClojureScript
|
|
66
|
+
- stars: 0
|
|
67
|
+
- url: https://example.com/projects/logseq
|
|
136
68
|
|
|
137
|
-
|
|
138
|
-
- **Repository**: https://github.com/logseq/logseq
|
|
139
|
-
- **Stack**: ClojureScript
|
|
140
|
-
- **Platforms**: web, desktop
|
|
141
|
-
- **Category**: Web
|
|
142
|
-
- **Tags**: notes, knowledge-base, productivity, clojure
|
|
143
|
-
- **Stars**: 0
|
|
144
|
-
- **Last commit**: —
|
|
145
|
-
|
|
146
|
-
**Best for:**
|
|
147
|
-
- PKM (personal knowledge management)
|
|
148
|
-
- Local-first note taking
|
|
149
|
-
- Graph-based outliner
|
|
150
|
-
|
|
151
|
-
**Why listed:**
|
|
152
|
-
- Local-first with sync
|
|
153
|
-
- Active community
|
|
154
|
-
- Strong plugin ecosystem
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## PrivateGPT
|
|
159
|
-
|
|
69
|
+
### PrivateGPT
|
|
160
70
|
Interact privately with your documents using LLMs — 100% private, no data leaves your execution environment.
|
|
71
|
+
- slug: private-gpt
|
|
72
|
+
- category: AI/ML
|
|
73
|
+
- stack: Python
|
|
74
|
+
- stars: 0
|
|
75
|
+
- url: https://example.com/projects/private-gpt
|
|
161
76
|
|
|
162
|
-
|
|
163
|
-
- **Repository**: https://github.com/zylon-ai/private-gpt
|
|
164
|
-
- **Stack**: Python
|
|
165
|
-
- **Platforms**: linux, macos, self-host
|
|
166
|
-
- **Category**: AI/ML
|
|
167
|
-
- **Tags**: llm, rag, privacy, python
|
|
168
|
-
- **Stars**: 0
|
|
169
|
-
- **Last commit**: —
|
|
170
|
-
|
|
171
|
-
**Best for:**
|
|
172
|
-
- Private document Q&A
|
|
173
|
-
- Local LLM experimentation
|
|
174
|
-
- Enterprise privacy use cases
|
|
175
|
-
|
|
176
|
-
**Why listed:**
|
|
177
|
-
- Strict no-network mode
|
|
178
|
-
- Pluggable LLM providers
|
|
179
|
-
- Clean ingestion pipeline
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## Wakapi
|
|
184
|
-
|
|
77
|
+
### Wakapi
|
|
185
78
|
Open-source time tracker for developers — coding stats, language breakdowns, and project-level metrics.
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
- **Category**: Mobile
|
|
192
|
-
- **Tags**: time-tracking, analytics, mobile, kotlin
|
|
193
|
-
- **Stars**: 0
|
|
194
|
-
- **Last commit**: —
|
|
195
|
-
|
|
196
|
-
**Best for:**
|
|
197
|
-
- Personal coding stats
|
|
198
|
-
- WakaTime alternative
|
|
199
|
-
- Privacy-first telemetry
|
|
200
|
-
|
|
201
|
-
**Why listed:**
|
|
202
|
-
- Self-host friendly
|
|
203
|
-
- Companion mobile app
|
|
204
|
-
- Clean MVVM architecture
|
|
205
|
-
|
|
206
|
-
---
|
|
79
|
+
- slug: wakapi
|
|
80
|
+
- category: Mobile
|
|
81
|
+
- stack: Kotlin
|
|
82
|
+
- stars: 0
|
|
83
|
+
- url: https://example.com/projects/wakapi
|
|
@@ -1,112 +1,11 @@
|
|
|
1
1
|
# Grove Directory
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
the Grove framework. Every entry is real, runnable, and actively
|
|
5
|
-
maintained — not an SDK, not a template, not a toy demo.
|
|
3
|
+
A curated, health-aware developer directory powered by Grove.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Directory: https://example.com/projects
|
|
6
|
+
Records indexed: 8
|
|
7
|
+
Categories: 4
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## Usage
|
|
12
10
|
|
|
13
|
-
-
|
|
14
|
-
- Browse: https://example.com/projects
|
|
15
|
-
- About: https://example.com/about
|
|
16
|
-
- Contributors: https://example.com/contributors
|
|
17
|
-
- Submit a record: https://example.com/submit
|
|
18
|
-
|
|
19
|
-
## Filter views (one URL = one curated lens)
|
|
20
|
-
|
|
21
|
-
- Trending: https://example.com/projects?label=hot
|
|
22
|
-
- Recently added: https://example.com/projects?label=new
|
|
23
|
-
- Established (mature): https://example.com/projects?label=mature
|
|
24
|
-
- Featured: https://example.com/projects?label=featured
|
|
25
|
-
- Good to learn: https://example.com/projects?lens=good-to-learn
|
|
26
|
-
- Production-like: https://example.com/projects?lens=production-like
|
|
27
|
-
- Beginner-friendly: https://example.com/projects?lens=beginner-friendly
|
|
28
|
-
- Contribution-ready: https://example.com/projects?lens=contribution-ready
|
|
29
|
-
|
|
30
|
-
## Browse axes
|
|
31
|
-
|
|
32
|
-
- By stack: https://example.com/projects (use the `stack` query param)
|
|
33
|
-
- By category: https://example.com/projects (use the `category` query param)
|
|
34
|
-
- By platform: https://example.com/projects (use the `platform` query param)
|
|
35
|
-
- By license: https://example.com/projects (use the `license` query param)
|
|
36
|
-
- By health: https://example.com/projects (use the `status` query param)
|
|
37
|
-
|
|
38
|
-
## What the directory contains
|
|
39
|
-
|
|
40
|
-
- Real, runnable open-source records (projects, resources, entities)
|
|
41
|
-
- Stack, language, and architecture at a glance
|
|
42
|
-
- Activity, health signals, and maturity tiers
|
|
43
|
-
- License, stars, and contribution readiness
|
|
44
|
-
- Curated notes: best for, why listed, caveats
|
|
45
|
-
- Filters by stack, platform, license, and status
|
|
46
|
-
|
|
47
|
-
## Data layout (for AI agents that want to ingest the catalog)
|
|
48
|
-
|
|
49
|
-
- `data/records/*.yml` — human-edited source of truth (one file per record)
|
|
50
|
-
- `data/taxonomy/categories.yml` — 16 categories (Productivity, Finance, …)
|
|
51
|
-
- `data/taxonomy/stacks.yml` — 6 stacks (Flutter, React Native, …)
|
|
52
|
-
- `data/taxonomy/platforms.yml` — 7 platforms (iOS, Android, …)
|
|
53
|
-
- `data/taxonomy/distribution-channels.yml` — 11 distribution channels
|
|
54
|
-
- `data/generated/records.full.json` — full record projection
|
|
55
|
-
- `data/generated/records.index.json` — slim list-page projection
|
|
56
|
-
- `data/generated/site-config.json` — blueprint + site metadata
|
|
57
|
-
|
|
58
|
-
For per-record markdown (one section per record with description, repo,
|
|
59
|
-
stack, platforms, stars, last-commit), see `/llms-full.txt`.
|
|
60
|
-
|
|
61
|
-
## Blueprint system
|
|
62
|
-
|
|
63
|
-
Grove supports three blueprints out of the box:
|
|
64
|
-
|
|
65
|
-
- `project-directory` (default) — `kind: project` records under
|
|
66
|
-
`/projects/<slug>`. Use this for code-reference showcases.
|
|
67
|
-
- `resource-hub` — `kind: resource` records (datasets, libraries,
|
|
68
|
-
tools) under `/resources/<slug>`.
|
|
69
|
-
- `ecosystem-map` — `kind: entity` records (companies, foundations,
|
|
70
|
-
people) under `/entities/<slug>`.
|
|
71
|
-
|
|
72
|
-
The blueprint is selected in `grove.config.ts` and propagates through
|
|
73
|
-
to every page, route, and component — no per-blueprint forks needed.
|
|
74
|
-
|
|
75
|
-
## How the build runs
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
pnpm run build:data # yml → JSON (grove generate)
|
|
79
|
-
pnpm run build:sitemap # emit public/sitemap.xml (grove sitemap)
|
|
80
|
-
pnpm run build:llms # emit public/llms-full.txt (committed llms.txt is preserved)
|
|
81
|
-
astro build # render the static site
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Contributing
|
|
85
|
-
|
|
86
|
-
Open a PR against `data/records/<slug>.yml`. The build pipeline will
|
|
87
|
-
fail if your record doesn't validate against the blueprint schema.
|
|
88
|
-
Read `grove.config.ts` and the `data/taxonomy/` files for the
|
|
89
|
-
available fields and enum values.
|
|
90
|
-
|
|
91
|
-
## License
|
|
92
|
-
|
|
93
|
-
MIT — the directory content, schema, and source code are all
|
|
94
|
-
permissively licensed. The data is yours to fork, mirror, or
|
|
95
|
-
republish; attribution to the original author is appreciated but not
|
|
96
|
-
required.
|
|
97
|
-
|
|
98
|
-
## Contact
|
|
99
|
-
|
|
100
|
-
Issues: open a ticket on the consumer repository's issue tracker.
|
|
101
|
-
Pull requests: see `CONTRIBUTING.md` for the workflow.
|
|
102
|
-
|
|
103
|
-
## LLM-specific guidance
|
|
104
|
-
|
|
105
|
-
- Per-record markdown: `/llms-full.txt` (generated, ~2,000 lines for
|
|
106
|
-
~150 records).
|
|
107
|
-
- Sitemap: `/sitemap.xml` (lists every record's detail URL).
|
|
108
|
-
- robots.txt: explicitly allows GPTBot, ChatGPT-User, ClaudeBot,
|
|
109
|
-
Claude-Web, anthropic-ai, PerplexityBot, Google-Extended,
|
|
110
|
-
Applebot-Extended, cohere-ai, CCBot, Amazonbot.
|
|
111
|
-
- Schema: each detail page emits a `SoftwareSourceCode` (or
|
|
112
|
-
`Dataset` / `Organization`) JSON-LD block for AI crawlers.
|
|
11
|
+
Use /llms-full.txt for record-level details. Prefer detail pages for citations.
|
|
@@ -2,61 +2,61 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://example.com/</loc>
|
|
5
|
-
<lastmod>2026-
|
|
5
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
6
6
|
<changefreq>daily</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://example.com/projects</loc>
|
|
11
|
-
<lastmod>2026-
|
|
11
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
12
12
|
<changefreq>daily</changefreq>
|
|
13
13
|
<priority>0.9</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://example.com/projects/coolify</loc>
|
|
17
|
-
<lastmod>2026-
|
|
17
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.7</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://example.com/projects/gitea</loc>
|
|
23
|
-
<lastmod>2026-
|
|
23
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
24
24
|
<changefreq>weekly</changefreq>
|
|
25
25
|
<priority>0.7</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://example.com/projects/hoppscotch</loc>
|
|
29
|
-
<lastmod>2026-
|
|
29
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.7</priority>
|
|
32
32
|
</url>
|
|
33
33
|
<url>
|
|
34
34
|
<loc>https://example.com/projects/inventree</loc>
|
|
35
|
-
<lastmod>2026-
|
|
35
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
36
36
|
<changefreq>weekly</changefreq>
|
|
37
37
|
<priority>0.7</priority>
|
|
38
38
|
</url>
|
|
39
39
|
<url>
|
|
40
40
|
<loc>https://example.com/projects/label-studio</loc>
|
|
41
|
-
<lastmod>2026-
|
|
41
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
42
42
|
<changefreq>weekly</changefreq>
|
|
43
43
|
<priority>0.7</priority>
|
|
44
44
|
</url>
|
|
45
45
|
<url>
|
|
46
46
|
<loc>https://example.com/projects/logseq</loc>
|
|
47
|
-
<lastmod>2026-
|
|
47
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
48
48
|
<changefreq>weekly</changefreq>
|
|
49
49
|
<priority>0.7</priority>
|
|
50
50
|
</url>
|
|
51
51
|
<url>
|
|
52
52
|
<loc>https://example.com/projects/private-gpt</loc>
|
|
53
|
-
<lastmod>2026-
|
|
53
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
54
54
|
<changefreq>weekly</changefreq>
|
|
55
55
|
<priority>0.7</priority>
|
|
56
56
|
</url>
|
|
57
57
|
<url>
|
|
58
58
|
<loc>https://example.com/projects/wakapi</loc>
|
|
59
|
-
<lastmod>2026-
|
|
59
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
60
60
|
<changefreq>weekly</changefreq>
|
|
61
61
|
<priority>0.7</priority>
|
|
62
62
|
</url>
|
|
@@ -38,6 +38,7 @@ import { fileURLToPath } from "node:url";
|
|
|
38
38
|
import { dirname, resolve } from "node:path";
|
|
39
39
|
import { marked } from "marked";
|
|
40
40
|
import sanitizeHtml from "sanitize-html";
|
|
41
|
+
import { prettySlug } from "@grove-dev/astro";
|
|
41
42
|
import type {
|
|
42
43
|
ProjectRecord,
|
|
43
44
|
ResourceRecord,
|
|
@@ -79,12 +80,39 @@ interface SiteConfigPayload {
|
|
|
79
80
|
labelSingular?: string;
|
|
80
81
|
labelPlural?: string;
|
|
81
82
|
};
|
|
83
|
+
taxonomy?: Partial<
|
|
84
|
+
Record<
|
|
85
|
+
"categories" | "stacks" | "platforms" | "distributionChannels",
|
|
86
|
+
Array<{ id: string; name: string }>
|
|
87
|
+
>
|
|
88
|
+
>;
|
|
82
89
|
name?: string;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
const fullRecordsRaw: Resource[] =
|
|
86
|
-
|
|
92
|
+
const fullRecordsRaw: Resource[] =
|
|
93
|
+
(fullPayload as unknown as FullPayload).records ?? [];
|
|
94
|
+
const indexRecordsRaw: IndexRecord[] =
|
|
95
|
+
(indexPayload as unknown as IndexPayload).records ?? [];
|
|
87
96
|
const siteConfigRaw: SiteConfigPayload = siteConfigPayload as SiteConfigPayload;
|
|
97
|
+
const taxonomyMaps = Object.fromEntries(
|
|
98
|
+
Object.entries(siteConfigRaw.taxonomy ?? {}).map(([kind, entries]) => [
|
|
99
|
+
kind,
|
|
100
|
+
new Map((entries ?? []).map((entry) => [entry.id, entry.name])),
|
|
101
|
+
]),
|
|
102
|
+
) as Partial<
|
|
103
|
+
Record<
|
|
104
|
+
"categories" | "stacks" | "platforms" | "distributionChannels",
|
|
105
|
+
Map<string, string>
|
|
106
|
+
>
|
|
107
|
+
>;
|
|
108
|
+
export const taxonomy = siteConfigRaw.taxonomy ?? {};
|
|
109
|
+
|
|
110
|
+
export function taxonomyLabel(
|
|
111
|
+
kind: "categories" | "stacks" | "platforms" | "distributionChannels",
|
|
112
|
+
id: string,
|
|
113
|
+
): string {
|
|
114
|
+
return taxonomyMaps[kind]?.get(id) ?? prettySlug(id);
|
|
115
|
+
}
|
|
88
116
|
|
|
89
117
|
/** Full records (every record, all visibility). Use this for the
|
|
90
118
|
* detail page (where you need `content`, `bestFor`, `whyListed`,
|
|
@@ -224,6 +252,10 @@ export const fullItems: Resource[] = (() => {
|
|
|
224
252
|
return fullRecords.filter((r) => r.kind === blueprintKind);
|
|
225
253
|
})();
|
|
226
254
|
|
|
255
|
+
export const fullProjects: ProjectRecord[] = fullRecords.filter(
|
|
256
|
+
(record): record is ProjectRecord => record.kind === "project",
|
|
257
|
+
);
|
|
258
|
+
|
|
227
259
|
// ──────────────────────────────────────────────────────────────────────
|
|
228
260
|
// Markdown content rendering (sanitized)
|
|
229
261
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -316,3 +348,49 @@ for (const r of fullRecords) {
|
|
|
316
348
|
export function getContentHtml(slug: string): string | null {
|
|
317
349
|
return contentHtmlBySlug.get(slug) ?? null;
|
|
318
350
|
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Sanitized Markdown for a consumer-authored page under
|
|
354
|
+
* `content/pages/<page>.md`. Default template pages use this to
|
|
355
|
+
* accept custom copy while keeping Grove's layout and components.
|
|
356
|
+
*/
|
|
357
|
+
export function getPageContentHtml(page: string): string | null {
|
|
358
|
+
const candidates = [
|
|
359
|
+
resolve(here, "..", "..", "content", "pages", `${page}.md`),
|
|
360
|
+
resolve(here, "..", "..", "..", "content", "pages", `${page}.md`),
|
|
361
|
+
resolve(process.cwd(), "content", "pages", `${page}.md`),
|
|
362
|
+
];
|
|
363
|
+
const path = candidates.find((candidate) => existsSync(candidate));
|
|
364
|
+
if (!path) return null;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const rawHtml = marked.parse(readFileSync(path, "utf8"), { async: false }) as string;
|
|
368
|
+
return sanitizeHtml(rawHtml, {
|
|
369
|
+
allowedTags: [
|
|
370
|
+
"h1", "h2", "h3", "h4",
|
|
371
|
+
"p", "br", "hr",
|
|
372
|
+
"ul", "ol", "li",
|
|
373
|
+
"strong", "em", "b", "i", "u", "s", "del",
|
|
374
|
+
"a", "code", "pre", "blockquote", "img",
|
|
375
|
+
],
|
|
376
|
+
allowedAttributes: {
|
|
377
|
+
a: ["href", "title", "rel", "target"],
|
|
378
|
+
img: ["src", "alt", "title", "width", "height", "loading"],
|
|
379
|
+
},
|
|
380
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
381
|
+
allowedSchemesByTag: {
|
|
382
|
+
a: ["http", "https", "mailto", "tel"],
|
|
383
|
+
img: ["http", "https"],
|
|
384
|
+
},
|
|
385
|
+
transformTags: {
|
|
386
|
+
a: sanitizeHtml.simpleTransform("a", {
|
|
387
|
+
rel: "noopener noreferrer",
|
|
388
|
+
target: "_blank",
|
|
389
|
+
}, true),
|
|
390
|
+
},
|
|
391
|
+
disallowedTagsMode: "discard",
|
|
392
|
+
});
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -64,7 +64,7 @@ if (!record) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const isProject = record.kind === "project";
|
|
67
|
-
const proj: ProjectRecord | null = project;
|
|
67
|
+
const proj: ProjectRecord | null = project ?? null;
|
|
68
68
|
|
|
69
69
|
const name =
|
|
70
70
|
record.kind === "resource" ? (record as { title: string }).title : (record as { name: string }).name;
|
|
@@ -220,18 +220,20 @@ if (isProject && proj) {
|
|
|
220
220
|
keywords: (record.tags ?? []).length > 0 ? (record.tags ?? []).join(", ") : undefined,
|
|
221
221
|
isAccessibleForFree: true,
|
|
222
222
|
};
|
|
223
|
-
} else {
|
|
223
|
+
} else if (entity) {
|
|
224
224
|
jsonLd = {
|
|
225
225
|
"@context": "https://schema.org",
|
|
226
|
-
"@type":
|
|
226
|
+
"@type": entity.type === "person" ? "Person" : "Organization",
|
|
227
227
|
name,
|
|
228
228
|
headline: name,
|
|
229
|
-
description:
|
|
229
|
+
description: entity.description || undefined,
|
|
230
230
|
url: homepageUrl || undefined,
|
|
231
|
-
foundingDate:
|
|
232
|
-
location:
|
|
233
|
-
keywords: (
|
|
231
|
+
foundingDate: entity.founded || undefined,
|
|
232
|
+
location: entity.location || undefined,
|
|
233
|
+
keywords: (entity.tags ?? []).length > 0 ? (entity.tags ?? []).join(", ") : undefined,
|
|
234
234
|
};
|
|
235
|
+
} else {
|
|
236
|
+
jsonLd = {};
|
|
235
237
|
}
|
|
236
238
|
---
|
|
237
239
|
|