@grove-dev/core 0.7.0 → 0.9.0
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/audit.d.ts +3 -3
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +25 -6
- package/dist/audit.js.map +1 -1
- package/dist/awesome-readme.d.ts.map +1 -1
- package/dist/awesome-readme.js +31 -31
- package/dist/awesome-readme.js.map +1 -1
- package/dist/build-data.d.ts +1 -1
- package/dist/build-data.d.ts.map +1 -1
- package/dist/build-data.js +110 -80
- package/dist/build-data.js.map +1 -1
- package/dist/candidate.d.ts +34 -0
- package/dist/candidate.d.ts.map +1 -0
- package/dist/candidate.js +128 -0
- package/dist/candidate.js.map +1 -0
- package/dist/collections-io.d.ts +1 -1
- package/dist/collections-io.js +8 -8
- package/dist/collections-io.js.map +1 -1
- package/dist/collections.d.ts +2 -2
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +8 -9
- package/dist/collections.js.map +1 -1
- package/dist/collector.d.ts +1 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +2 -2
- package/dist/collector.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -4
- package/dist/config.js.map +1 -1
- package/dist/content-body.d.ts.map +1 -1
- package/dist/content-body.js +22 -22
- package/dist/content-body.js.map +1 -1
- package/dist/contributors.d.ts.map +1 -1
- package/dist/contributors.js +22 -23
- package/dist/contributors.js.map +1 -1
- package/dist/decisions.d.ts +1 -1
- package/dist/decisions.d.ts.map +1 -1
- package/dist/decisions.js +18 -20
- package/dist/decisions.js.map +1 -1
- package/dist/directory-client.d.ts +6 -4
- package/dist/directory-client.d.ts.map +1 -1
- package/dist/directory-client.js +9 -4
- package/dist/directory-client.js.map +1 -1
- package/dist/directory-facets.d.ts +1 -1
- package/dist/directory-facets.d.ts.map +1 -1
- package/dist/directory-facets.js +35 -17
- package/dist/directory-facets.js.map +1 -1
- package/dist/directory-filter-keys.d.ts +53 -0
- package/dist/directory-filter-keys.d.ts.map +1 -0
- package/dist/directory-filter-keys.js +66 -0
- package/dist/directory-filter-keys.js.map +1 -0
- package/dist/directory-format.d.ts.map +1 -1
- package/dist/directory-format.js +15 -15
- package/dist/directory-format.js.map +1 -1
- package/dist/directory-lenses.d.ts +1 -1
- package/dist/directory-lenses.d.ts.map +1 -1
- package/dist/directory-lenses.js +66 -61
- package/dist/directory-lenses.js.map +1 -1
- package/dist/directory-repo.js +2 -2
- package/dist/directory-scores.d.ts.map +1 -1
- package/dist/directory-scores.js +25 -25
- package/dist/directory-scores.js.map +1 -1
- package/dist/directory-search.d.ts +4 -4
- package/dist/directory-search.d.ts.map +1 -1
- package/dist/directory-search.js +129 -110
- package/dist/directory-search.js.map +1 -1
- package/dist/directory-taxonomy.d.ts +1 -1
- package/dist/directory-taxonomy.d.ts.map +1 -1
- package/dist/directory-taxonomy.js +2 -2
- package/dist/directory-taxonomy.js.map +1 -1
- package/dist/enrich.d.ts.map +1 -1
- package/dist/enrich.js +15 -12
- package/dist/enrich.js.map +1 -1
- package/dist/evidence.d.ts +33 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +181 -0
- package/dist/evidence.js.map +1 -0
- package/dist/github-client.js +6 -6
- package/dist/github.d.ts +9 -1
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +40 -13
- package/dist/github.js.map +1 -1
- package/dist/health.d.ts +1 -1
- package/dist/health.js +44 -44
- package/dist/host.js +2 -2
- package/dist/import-types.d.ts +24 -0
- package/dist/import-types.d.ts.map +1 -0
- package/dist/import-types.js +2 -0
- package/dist/import-types.js.map +1 -0
- package/dist/import-write.d.ts +12 -0
- package/dist/import-write.d.ts.map +1 -0
- package/dist/import-write.js +39 -0
- package/dist/import-write.js.map +1 -0
- package/dist/importer.d.ts +5 -3
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +4 -2
- package/dist/importer.js.map +1 -1
- package/dist/index.d.ts +74 -63
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -75
- package/dist/index.js.map +1 -1
- package/dist/io.js +8 -8
- package/dist/llms.d.ts +1 -1
- package/dist/llms.d.ts.map +1 -1
- package/dist/llms.js +65 -64
- package/dist/llms.js.map +1 -1
- package/dist/markdown.d.ts +3 -33
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +2 -39
- package/dist/markdown.js.map +1 -1
- package/dist/og-image.d.ts +8 -8
- package/dist/og-image.d.ts.map +1 -1
- package/dist/og-image.js +91 -84
- package/dist/og-image.js.map +1 -1
- package/dist/page-document.d.ts +8 -8
- package/dist/page-document.d.ts.map +1 -1
- package/dist/page-document.js +48 -54
- package/dist/page-document.js.map +1 -1
- package/dist/parseReadme.d.ts.map +1 -1
- package/dist/parseReadme.js +9 -11
- package/dist/parseReadme.js.map +1 -1
- package/dist/prepare.d.ts +5 -5
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +32 -38
- package/dist/prepare.js.map +1 -1
- package/dist/readme-health.d.ts +33 -0
- package/dist/readme-health.d.ts.map +1 -0
- package/dist/readme-health.js +88 -0
- package/dist/readme-health.js.map +1 -0
- package/dist/related.d.ts +1 -1
- package/dist/repository-health.d.ts +11 -0
- package/dist/repository-health.d.ts.map +1 -0
- package/dist/repository-health.js +94 -0
- package/dist/repository-health.js.map +1 -0
- package/dist/schema.d.ts +72 -30
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +156 -144
- package/dist/schema.js.map +1 -1
- package/dist/site-artifacts.js +1 -1
- package/dist/sitemap.d.ts +2 -2
- package/dist/sitemap.d.ts.map +1 -1
- package/dist/sitemap.js +39 -39
- package/dist/sitemap.js.map +1 -1
- package/dist/slug.js +4 -4
- package/dist/sync-icons.js +15 -15
- package/dist/taxonomy-inference.d.ts +27 -0
- package/dist/taxonomy-inference.d.ts.map +1 -0
- package/dist/taxonomy-inference.js +38 -0
- package/dist/taxonomy-inference.js.map +1 -0
- package/dist/validate.d.ts +3 -3
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +73 -62
- package/dist/validate.js.map +1 -1
- package/dist/yaml.d.ts +47 -0
- package/dist/yaml.d.ts.map +1 -0
- package/dist/yaml.js +56 -0
- package/dist/yaml.js.map +1 -0
- package/package.json +6 -1
package/dist/schema.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { DEFAULT_FACETS, FACET_IDS } from
|
|
1
|
+
import { parse, stringify } from 'yaml';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { DEFAULT_FACETS, FACET_IDS } from './directory-facets.js';
|
|
4
4
|
// ──────────────────────────────────────────────────────────────────────
|
|
5
5
|
// Blueprints and kinds
|
|
6
6
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -10,91 +10,82 @@ import { DEFAULT_FACETS, FACET_IDS } from "./directory-facets.js";
|
|
|
10
10
|
* V1 ships three fixed blueprints. They are not extensible — no custom
|
|
11
11
|
* blueprint API in V1.
|
|
12
12
|
*/
|
|
13
|
-
export const blueprintSchema = z.enum([
|
|
14
|
-
"project-directory",
|
|
15
|
-
"resource-hub",
|
|
16
|
-
"ecosystem-map",
|
|
17
|
-
]);
|
|
13
|
+
export const blueprintSchema = z.enum(['project-directory', 'resource-hub', 'ecosystem-map']);
|
|
18
14
|
/**
|
|
19
15
|
* Each blueprint is bound to a single resource kind. Records are
|
|
20
16
|
* discriminated by `kind`, which must match the blueprint of the site
|
|
21
17
|
* that owns them.
|
|
22
18
|
*/
|
|
23
|
-
export const resourceKindSchema = z.enum([
|
|
19
|
+
export const resourceKindSchema = z.enum(['project', 'resource', 'entity']);
|
|
24
20
|
export const blueprintKind = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
'project-directory': 'project',
|
|
22
|
+
'resource-hub': 'resource',
|
|
23
|
+
'ecosystem-map': 'entity',
|
|
28
24
|
};
|
|
29
25
|
// ──────────────────────────────────────────────────────────────────────
|
|
30
26
|
// Health & decision labels
|
|
31
27
|
// ──────────────────────────────────────────────────────────────────────
|
|
32
28
|
export const healthStatusSchema = z.enum([
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
'active',
|
|
30
|
+
'mature',
|
|
31
|
+
'stale',
|
|
32
|
+
'inactive',
|
|
33
|
+
'archived',
|
|
34
|
+
'unknown',
|
|
35
|
+
'historical',
|
|
36
|
+
'needs_review',
|
|
37
|
+
'quiet',
|
|
38
|
+
'unavailable',
|
|
43
39
|
]);
|
|
44
40
|
export const decisionVisibilitySchema = z.enum([
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]);
|
|
52
|
-
export const healthTierSchema = z.enum([
|
|
53
|
-
"curated",
|
|
54
|
-
"listed",
|
|
55
|
-
"experimental",
|
|
56
|
-
"hidden",
|
|
41
|
+
'highlight',
|
|
42
|
+
'keep',
|
|
43
|
+
'needs_review',
|
|
44
|
+
'hide',
|
|
45
|
+
'remove',
|
|
46
|
+
'historical',
|
|
57
47
|
]);
|
|
48
|
+
export const healthTierSchema = z.enum(['curated', 'listed', 'experimental', 'hidden']);
|
|
58
49
|
// ──────────────────────────────────────────────────────────────────────
|
|
59
50
|
// Curation enums (shared)
|
|
60
51
|
// ──────────────────────────────────────────────────────────────────────
|
|
61
52
|
export const projectTypeSchema = z.enum([
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
'real-app',
|
|
54
|
+
'production',
|
|
55
|
+
'reference',
|
|
56
|
+
'library',
|
|
57
|
+
'tool',
|
|
58
|
+
'demo',
|
|
59
|
+
'template',
|
|
60
|
+
'historical',
|
|
70
61
|
]);
|
|
71
62
|
export const resourceTypeSchema = z.enum([
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
63
|
+
'guide',
|
|
64
|
+
'comparison',
|
|
65
|
+
'link',
|
|
66
|
+
'explainer',
|
|
67
|
+
'tool',
|
|
68
|
+
'video',
|
|
69
|
+
'article',
|
|
70
|
+
'course',
|
|
71
|
+
'book',
|
|
72
|
+
'podcast',
|
|
73
|
+
'other',
|
|
83
74
|
]);
|
|
84
75
|
export const entityTypeSchema = z.enum([
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
'company',
|
|
77
|
+
'organization',
|
|
78
|
+
'community',
|
|
79
|
+
'school',
|
|
80
|
+
'university',
|
|
81
|
+
'research-lab',
|
|
82
|
+
'agency',
|
|
83
|
+
'service',
|
|
84
|
+
'product',
|
|
85
|
+
'person',
|
|
86
|
+
'other',
|
|
96
87
|
]);
|
|
97
|
-
export const appLabelSchema = z.enum([
|
|
88
|
+
export const appLabelSchema = z.enum(['new', 'hot', 'mature', 'featured']);
|
|
98
89
|
export const scoreSchema = z.object({
|
|
99
90
|
activity: z.number().min(0).max(100).optional(),
|
|
100
91
|
maturity: z.number().min(0).max(100).optional(),
|
|
@@ -189,20 +180,17 @@ export const githubMetadataSchema = z.object({
|
|
|
189
180
|
size: z.number().int().nonnegative().optional(),
|
|
190
181
|
files: z.record(z.string(), z.boolean()).optional(),
|
|
191
182
|
monthlyCommits: z
|
|
192
|
-
.array(z.union([
|
|
193
|
-
z.number(),
|
|
194
|
-
z.object({ month: z.string(), commits: z.number() }),
|
|
195
|
-
]))
|
|
183
|
+
.array(z.union([z.number(), z.object({ month: z.string(), commits: z.number() })]))
|
|
196
184
|
.optional(),
|
|
197
185
|
});
|
|
198
186
|
export const healthBlockSchema = z.object({
|
|
199
|
-
status: healthStatusSchema.default(
|
|
200
|
-
maturity: z.enum([
|
|
201
|
-
tier: healthTierSchema.default(
|
|
202
|
-
visibility: decisionVisibilitySchema.default(
|
|
187
|
+
status: healthStatusSchema.default('unknown'),
|
|
188
|
+
maturity: z.enum(['experimental', 'useful', 'mature', 'unknown']).default('unknown'),
|
|
189
|
+
tier: healthTierSchema.default('experimental'),
|
|
190
|
+
visibility: decisionVisibilitySchema.default('keep'),
|
|
203
191
|
cleanupCandidate: z.boolean().default(false),
|
|
204
192
|
staleReason: z.string().nullable().optional(),
|
|
205
|
-
confidence: z.enum([
|
|
193
|
+
confidence: z.enum(['low', 'medium', 'high']).default('medium'),
|
|
206
194
|
reasons: z.array(z.string()).default([]),
|
|
207
195
|
});
|
|
208
196
|
export const healthEntrySchema = z.object({
|
|
@@ -259,7 +247,18 @@ const curationBlockSchema = z
|
|
|
259
247
|
// ──────────────────────────────────────────────────────────────────────
|
|
260
248
|
const resourceBaseSchema = z.object({
|
|
261
249
|
slug: z.string().min(1),
|
|
262
|
-
|
|
250
|
+
/**
|
|
251
|
+
* When this record joined the directory — an ISO date or date-time.
|
|
252
|
+
*
|
|
253
|
+
* Distinct from `curation.reviewedAt`, which records when a human
|
|
254
|
+
* last reviewed the record. A record can be added today and reviewed
|
|
255
|
+
* never, or added last year and reviewed this morning; the
|
|
256
|
+
* `recently-added` sort and llms.txt's `added:` line want the former.
|
|
257
|
+
* Optional so records written before this field existed still
|
|
258
|
+
* validate — `recordAddedAt` falls back for them.
|
|
259
|
+
*/
|
|
260
|
+
addedAt: z.string().optional(),
|
|
261
|
+
description: z.string().default(''),
|
|
263
262
|
/**
|
|
264
263
|
* Open Apps-written summary, surfaced as the lead paragraph on the
|
|
265
264
|
* detail page above the original (GitHub-sourced) description.
|
|
@@ -275,21 +274,22 @@ const resourceBaseSchema = z.object({
|
|
|
275
274
|
* overwriting what GitHub says the project is.
|
|
276
275
|
*/
|
|
277
276
|
sourceDescription: z.string().optional(),
|
|
278
|
-
category: z.string().min(1).default(
|
|
277
|
+
category: z.string().min(1).default('uncategorized'),
|
|
279
278
|
tags: z.array(z.string()).default([]),
|
|
280
279
|
links: linksSchema,
|
|
281
280
|
content: z.string().optional(), // path to markdown body under content/records/
|
|
282
281
|
source: z
|
|
283
282
|
.object({
|
|
284
|
-
type: z
|
|
285
|
-
.
|
|
283
|
+
type: z
|
|
284
|
+
.enum(['manual', 'github-topic', 'awesome-list', 'submit', 'import'])
|
|
285
|
+
.default('manual'),
|
|
286
286
|
file: z.string().optional(),
|
|
287
287
|
url: z.string().optional(),
|
|
288
288
|
provider: z.string().optional(),
|
|
289
289
|
owner: z.string().optional(),
|
|
290
290
|
repo: z.string().optional(),
|
|
291
291
|
})
|
|
292
|
-
.default({ type:
|
|
292
|
+
.default({ type: 'manual' }),
|
|
293
293
|
curation: curationBlockSchema,
|
|
294
294
|
scores: scoreSchema.default({}),
|
|
295
295
|
/**
|
|
@@ -300,13 +300,26 @@ const resourceBaseSchema = z.object({
|
|
|
300
300
|
* Defaults to "keep" so existing YAML files without the field
|
|
301
301
|
* continue to validate.
|
|
302
302
|
*/
|
|
303
|
-
visibility: decisionVisibilitySchema.default(
|
|
303
|
+
visibility: decisionVisibilitySchema.default('keep'),
|
|
304
|
+
/**
|
|
305
|
+
* Curator override for the detail page's `<title>`/meta description.
|
|
306
|
+
* Mirrors `CollectionSeo` (`collections.ts`) — when set, it wins
|
|
307
|
+
* verbatim over the computed title/descriptor so a curator can hand-
|
|
308
|
+
* write a sharper title than the generic "Open-source {category}
|
|
309
|
+
* {noun}" fallback for a specific record.
|
|
310
|
+
*/
|
|
311
|
+
seo: z
|
|
312
|
+
.object({
|
|
313
|
+
title: z.string().optional(),
|
|
314
|
+
description: z.string().optional(),
|
|
315
|
+
})
|
|
316
|
+
.optional(),
|
|
304
317
|
});
|
|
305
318
|
// ──────────────────────────────────────────────────────────────────────
|
|
306
319
|
// Blueprint: project-directory — kind: project
|
|
307
320
|
// ──────────────────────────────────────────────────────────────────────
|
|
308
321
|
export const projectRecordSchema = resourceBaseSchema.extend({
|
|
309
|
-
kind: z.literal(
|
|
322
|
+
kind: z.literal('project'),
|
|
310
323
|
name: z.string().min(1),
|
|
311
324
|
projectType: projectTypeSchema.optional(),
|
|
312
325
|
stack: z.string().optional(),
|
|
@@ -316,8 +329,8 @@ export const projectRecordSchema = resourceBaseSchema.extend({
|
|
|
316
329
|
// authored when a record is curated; otherwise derived from the
|
|
317
330
|
// GitHub sync's `license.spdx_id` field at build time.
|
|
318
331
|
licenses: z.array(z.string()).default([]),
|
|
319
|
-
difficulty: z.enum([
|
|
320
|
-
codebaseSize: z.enum([
|
|
332
|
+
difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
|
|
333
|
+
codebaseSize: z.enum(['small', 'medium', 'large', 'huge']).optional(),
|
|
321
334
|
// Canonical repo URL. Distinct from `links.github`, which is the
|
|
322
335
|
// human-facing link on the page; `repoUrl` is the single source
|
|
323
336
|
// for owner/repo extraction, avatar fallback, and "view repo" CTAs.
|
|
@@ -365,10 +378,7 @@ export const projectRecordSchema = resourceBaseSchema.extend({
|
|
|
365
378
|
.object({
|
|
366
379
|
repository: githubRepositorySchema.optional(),
|
|
367
380
|
languages: z.record(z.string(), z.number()).optional(),
|
|
368
|
-
latestRelease: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
369
381
|
activity: z.record(z.string(), z.unknown()).optional(),
|
|
370
|
-
files: z.record(z.string(), z.boolean()).optional(),
|
|
371
|
-
labels: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
372
382
|
sync: z.record(z.string(), z.unknown()).optional(),
|
|
373
383
|
})
|
|
374
384
|
.passthrough()
|
|
@@ -379,7 +389,7 @@ export const projectRecordSchema = resourceBaseSchema.extend({
|
|
|
379
389
|
// Blueprint: resource-hub — kind: resource
|
|
380
390
|
// ──────────────────────────────────────────────────────────────────────
|
|
381
391
|
export const resourceRecordSchema = resourceBaseSchema.extend({
|
|
382
|
-
kind: z.literal(
|
|
392
|
+
kind: z.literal('resource'),
|
|
383
393
|
title: z.string().min(1),
|
|
384
394
|
type: resourceTypeSchema,
|
|
385
395
|
topic: z.string().min(1),
|
|
@@ -391,7 +401,7 @@ export const resourceRecordSchema = resourceBaseSchema.extend({
|
|
|
391
401
|
// Blueprint: ecosystem-map — kind: entity
|
|
392
402
|
// ──────────────────────────────────────────────────────────────────────
|
|
393
403
|
export const entityRecordSchema = resourceBaseSchema.extend({
|
|
394
|
-
kind: z.literal(
|
|
404
|
+
kind: z.literal('entity'),
|
|
395
405
|
name: z.string().min(1),
|
|
396
406
|
type: entityTypeSchema,
|
|
397
407
|
founded: z.string().optional(),
|
|
@@ -402,7 +412,7 @@ export const entityRecordSchema = resourceBaseSchema.extend({
|
|
|
402
412
|
// ──────────────────────────────────────────────────────────────────────
|
|
403
413
|
// Discriminated union: Resource
|
|
404
414
|
// ──────────────────────────────────────────────────────────────────────
|
|
405
|
-
export const resourceSchema = z.discriminatedUnion(
|
|
415
|
+
export const resourceSchema = z.discriminatedUnion('kind', [
|
|
406
416
|
projectRecordSchema,
|
|
407
417
|
resourceRecordSchema,
|
|
408
418
|
entityRecordSchema,
|
|
@@ -472,7 +482,7 @@ export const githubIntegrationSchema = z.union([
|
|
|
472
482
|
* callers (`grove sync`, CI workflows) can gate each feature.
|
|
473
483
|
*/
|
|
474
484
|
export function normalizeGithubIntegration(value) {
|
|
475
|
-
if (typeof value ===
|
|
485
|
+
if (typeof value === 'boolean') {
|
|
476
486
|
return { metadata: value, contributors: value, health: value };
|
|
477
487
|
}
|
|
478
488
|
return {
|
|
@@ -489,23 +499,23 @@ export const themeSchema = z.object({
|
|
|
489
499
|
*/
|
|
490
500
|
primaryColor: z
|
|
491
501
|
.string()
|
|
492
|
-
.regex(/^#[0-9a-fA-F]{3,8}$/,
|
|
502
|
+
.regex(/^#[0-9a-fA-F]{3,8}$/, 'theme.primaryColor must be a hex color such as #4f46e5')
|
|
493
503
|
.optional(),
|
|
494
|
-
radius: z.enum([
|
|
495
|
-
density: z.enum([
|
|
496
|
-
containerWidth: z.string().default(
|
|
504
|
+
radius: z.enum(['none', 'soft', 'round']).default('soft'),
|
|
505
|
+
density: z.enum(['compact', 'comfortable', 'spacious']).default('comfortable'),
|
|
506
|
+
containerWidth: z.string().default('72rem'),
|
|
497
507
|
});
|
|
498
508
|
// ──────────────────────────────────────────────────────────────────────
|
|
499
509
|
// Audit manifest (consumed by `grove audit`)
|
|
500
510
|
// ──────────────────────────────────────────────────────────────────────
|
|
501
511
|
const auditPageTypeSchema = z.enum([
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
512
|
+
'home',
|
|
513
|
+
'directory',
|
|
514
|
+
'collection',
|
|
515
|
+
'record',
|
|
516
|
+
'content',
|
|
517
|
+
'empty',
|
|
518
|
+
'404',
|
|
509
519
|
]);
|
|
510
520
|
const auditPageManifestEntrySchema = z.object({
|
|
511
521
|
path: z.string().min(1),
|
|
@@ -542,10 +552,10 @@ export const readmeConfigSchema = z.object({
|
|
|
542
552
|
showBrowseLink: z.boolean().default(true),
|
|
543
553
|
});
|
|
544
554
|
export const groveConfigSchema = z.object({
|
|
545
|
-
blueprint: blueprintSchema.default(
|
|
555
|
+
blueprint: blueprintSchema.default('project-directory'),
|
|
546
556
|
site: z.object({
|
|
547
557
|
name: z.string().min(1),
|
|
548
|
-
tagline: z.string().default(
|
|
558
|
+
tagline: z.string().default('A growing community knowledge site.'),
|
|
549
559
|
description: z.string().optional(),
|
|
550
560
|
url: z.string().url().optional(),
|
|
551
561
|
repoUrl: z.string().url().optional(),
|
|
@@ -565,7 +575,7 @@ export const groveConfigSchema = z.object({
|
|
|
565
575
|
* BCP-47 language tag for the published site. Drives `<html lang>`,
|
|
566
576
|
* `og:locale`, and JSON-LD `inLanguage`.
|
|
567
577
|
*/
|
|
568
|
-
locale: z.string().default(
|
|
578
|
+
locale: z.string().default('en'),
|
|
569
579
|
/**
|
|
570
580
|
* Twitter/X handle for the site (e.g. `@grove`). Emitted as
|
|
571
581
|
* `twitter:site` on every page when set.
|
|
@@ -576,7 +586,7 @@ export const groveConfigSchema = z.object({
|
|
|
576
586
|
.object({
|
|
577
587
|
googleAnalyticsId: z
|
|
578
588
|
.string()
|
|
579
|
-
.regex(/^G-[A-Z0-9]+$/,
|
|
589
|
+
.regex(/^G-[A-Z0-9]+$/, 'Expected a GA4 measurement ID such as G-XXXXXX')
|
|
580
590
|
.optional(),
|
|
581
591
|
})
|
|
582
592
|
.default({}),
|
|
@@ -622,7 +632,7 @@ export const groveConfigSchema = z.object({
|
|
|
622
632
|
facets: z
|
|
623
633
|
.array(z.enum(FACET_IDS))
|
|
624
634
|
.refine((facets) => new Set(facets).size === facets.length, {
|
|
625
|
-
message:
|
|
635
|
+
message: 'browse.facets contains duplicate entries',
|
|
626
636
|
})
|
|
627
637
|
.default([...DEFAULT_FACETS]),
|
|
628
638
|
})
|
|
@@ -638,8 +648,8 @@ export const groveConfigSchema = z.object({
|
|
|
638
648
|
.unknown()
|
|
639
649
|
.optional()
|
|
640
650
|
.refine((value) => value === undefined, {
|
|
641
|
-
message:
|
|
642
|
-
`Supported ids: ${FACET_IDS.join(
|
|
651
|
+
message: '`facets` moved to `browse.facets` in grove.config.ts. ' +
|
|
652
|
+
`Supported ids: ${FACET_IDS.join(', ')} (canonical spellings only).`,
|
|
643
653
|
}),
|
|
644
654
|
integrations: z
|
|
645
655
|
.object({
|
|
@@ -678,30 +688,30 @@ export const groveConfigSchema = z.object({
|
|
|
678
688
|
readme: readmeConfigSchema.optional(),
|
|
679
689
|
paths: z
|
|
680
690
|
.object({
|
|
681
|
-
dataDir: z.string().default(
|
|
682
|
-
contentDir: z.string().default(
|
|
683
|
-
recordsDir: z.string().default(
|
|
684
|
-
pagesDir: z.string().default(
|
|
685
|
-
bodiesDir: z.string().default(
|
|
686
|
-
publicDir: z.string().default(
|
|
687
|
-
taxonomyDir: z.string().default(
|
|
688
|
-
generatedDir: z.string().default(
|
|
689
|
-
health: z.string().default(
|
|
690
|
-
decisions: z.string().default(
|
|
691
|
-
overrides: z.string().default(
|
|
691
|
+
dataDir: z.string().default('data'),
|
|
692
|
+
contentDir: z.string().default('content'),
|
|
693
|
+
recordsDir: z.string().default('data/records'),
|
|
694
|
+
pagesDir: z.string().default('content/pages'),
|
|
695
|
+
bodiesDir: z.string().default('content/records'),
|
|
696
|
+
publicDir: z.string().default('public'),
|
|
697
|
+
taxonomyDir: z.string().default('data/taxonomy'),
|
|
698
|
+
generatedDir: z.string().default('data/generated'),
|
|
699
|
+
health: z.string().default('data/health.yml'),
|
|
700
|
+
decisions: z.string().default('data/decisions.yml'),
|
|
701
|
+
overrides: z.string().default('data/overrides.yml'),
|
|
692
702
|
})
|
|
693
703
|
.default({
|
|
694
|
-
dataDir:
|
|
695
|
-
contentDir:
|
|
696
|
-
recordsDir:
|
|
697
|
-
pagesDir:
|
|
698
|
-
bodiesDir:
|
|
699
|
-
publicDir:
|
|
700
|
-
taxonomyDir:
|
|
701
|
-
generatedDir:
|
|
702
|
-
health:
|
|
703
|
-
decisions:
|
|
704
|
-
overrides:
|
|
704
|
+
dataDir: 'data',
|
|
705
|
+
contentDir: 'content',
|
|
706
|
+
recordsDir: 'data/records',
|
|
707
|
+
pagesDir: 'content/pages',
|
|
708
|
+
bodiesDir: 'content/records',
|
|
709
|
+
publicDir: 'public',
|
|
710
|
+
taxonomyDir: 'data/taxonomy',
|
|
711
|
+
generatedDir: 'data/generated',
|
|
712
|
+
health: 'data/health.yml',
|
|
713
|
+
decisions: 'data/decisions.yml',
|
|
714
|
+
overrides: 'data/overrides.yml',
|
|
705
715
|
}),
|
|
706
716
|
});
|
|
707
717
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -725,8 +735,8 @@ export function parseRecordYaml(text, fileSlug) {
|
|
|
725
735
|
// record file cannot smuggle `!!binary` / `!!js/function` / etc.
|
|
726
736
|
// into host objects. The default schema would interpret them.
|
|
727
737
|
// (Implementation-checklist.md #27.)
|
|
728
|
-
const raw = parse(text, { schema:
|
|
729
|
-
if (typeof raw !==
|
|
738
|
+
const raw = parse(text, { schema: 'core' }) ?? {};
|
|
739
|
+
if (typeof raw !== 'object' || Array.isArray(raw)) {
|
|
730
740
|
throw new Error(`${fileSlug}: record file must contain a YAML mapping`);
|
|
731
741
|
}
|
|
732
742
|
return raw;
|
|
@@ -735,7 +745,7 @@ export function stringifyRecordYaml(record) {
|
|
|
735
745
|
return stringify(record, {
|
|
736
746
|
lineWidth: 100,
|
|
737
747
|
singleQuote: false,
|
|
738
|
-
defaultStringType:
|
|
748
|
+
defaultStringType: 'PLAIN',
|
|
739
749
|
});
|
|
740
750
|
}
|
|
741
751
|
export function getOwnerRepoFromUrl(repoUrl) {
|
|
@@ -748,16 +758,16 @@ export function getOwnerRepoFromUrl(repoUrl) {
|
|
|
748
758
|
const repo = m[2];
|
|
749
759
|
if (!owner || !repo)
|
|
750
760
|
return null;
|
|
751
|
-
return { owner, repo: repo.replace(/\.git$/,
|
|
761
|
+
return { owner, repo: repo.replace(/\.git$/, '') };
|
|
752
762
|
}
|
|
753
763
|
export function isIndexProject(r) {
|
|
754
|
-
return r.kind ===
|
|
764
|
+
return r.kind === 'project';
|
|
755
765
|
}
|
|
756
766
|
export function isIndexResource(r) {
|
|
757
|
-
return r.kind ===
|
|
767
|
+
return r.kind === 'resource';
|
|
758
768
|
}
|
|
759
769
|
export function isIndexEntity(r) {
|
|
760
|
-
return r.kind ===
|
|
770
|
+
return r.kind === 'entity';
|
|
761
771
|
}
|
|
762
772
|
/**
|
|
763
773
|
* Project a Resource to a stable, search-index-friendly summary
|
|
@@ -775,21 +785,22 @@ export function toIndexRecord(record) {
|
|
|
775
785
|
const base = {
|
|
776
786
|
slug: record.slug,
|
|
777
787
|
kind: record.kind,
|
|
788
|
+
addedAt: record.addedAt,
|
|
778
789
|
category: record.category,
|
|
779
790
|
tags: record.tags ?? [],
|
|
780
791
|
links: record.links,
|
|
781
792
|
description: record.description,
|
|
782
793
|
content: record.content,
|
|
783
794
|
curation: record.curation,
|
|
784
|
-
licenses: record.kind ===
|
|
795
|
+
licenses: record.kind === 'project' ? (record.licenses ?? []) : [],
|
|
785
796
|
summary: record.summary,
|
|
786
797
|
sourceDescription: record.sourceDescription,
|
|
787
798
|
};
|
|
788
|
-
if (record.kind ===
|
|
799
|
+
if (record.kind === 'project') {
|
|
789
800
|
const r = record;
|
|
790
801
|
return {
|
|
791
802
|
...base,
|
|
792
|
-
kind:
|
|
803
|
+
kind: 'project',
|
|
793
804
|
name: r.name,
|
|
794
805
|
stack: r.stack,
|
|
795
806
|
stacks: r.stacks ?? [],
|
|
@@ -810,10 +821,11 @@ export function toIndexRecord(record) {
|
|
|
810
821
|
// from signals) or the curation override (`decisions.yml`).
|
|
811
822
|
// The generate step folds the latter in before serialising,
|
|
812
823
|
// so this is a best-effort projection of the current state.
|
|
813
|
-
visibility: r.health?.visibility ??
|
|
824
|
+
visibility: r.health?.visibility ?? 'keep',
|
|
814
825
|
github: r.github?.repository
|
|
815
826
|
? {
|
|
816
827
|
fullName: r.github.repository.full_name,
|
|
828
|
+
createdAt: r.github.repository.created_at ?? null,
|
|
817
829
|
stars: r.github.repository.stargazers_count ?? 0,
|
|
818
830
|
forks: r.github.repository.forks_count ?? 0,
|
|
819
831
|
openIssues: r.github.repository.open_issues_count ?? 0,
|
|
@@ -826,11 +838,11 @@ export function toIndexRecord(record) {
|
|
|
826
838
|
: undefined,
|
|
827
839
|
};
|
|
828
840
|
}
|
|
829
|
-
if (record.kind ===
|
|
841
|
+
if (record.kind === 'resource') {
|
|
830
842
|
const r = record;
|
|
831
843
|
return {
|
|
832
844
|
...base,
|
|
833
|
-
kind:
|
|
845
|
+
kind: 'resource',
|
|
834
846
|
title: r.title,
|
|
835
847
|
type: r.type,
|
|
836
848
|
topic: r.topic,
|
|
@@ -848,7 +860,7 @@ export function toIndexRecord(record) {
|
|
|
848
860
|
const e = record;
|
|
849
861
|
return {
|
|
850
862
|
...base,
|
|
851
|
-
kind:
|
|
863
|
+
kind: 'entity',
|
|
852
864
|
name: e.name,
|
|
853
865
|
type: e.type,
|
|
854
866
|
founded: e.founded,
|