@grove-dev/core 0.1.4 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-data.d.ts +22 -34
- package/dist/build-data.d.ts.map +1 -1
- package/dist/build-data.js +55 -74
- package/dist/build-data.js.map +1 -1
- package/dist/config.d.ts +15 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -5
- package/dist/config.js.map +1 -1
- package/dist/decisions.d.ts +15 -12
- package/dist/decisions.d.ts.map +1 -1
- package/dist/decisions.js +52 -32
- package/dist/decisions.js.map +1 -1
- package/dist/enrich.d.ts +2 -2
- package/dist/enrich.js +2 -2
- package/dist/importer.d.ts +3 -2
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +2 -23
- package/dist/importer.js.map +1 -1
- package/dist/llms.d.ts +6 -10
- package/dist/llms.d.ts.map +1 -1
- package/dist/llms.js +55 -44
- package/dist/llms.js.map +1 -1
- package/dist/markdown.d.ts +32 -10
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +95 -55
- package/dist/markdown.js.map +1 -1
- package/dist/schema.d.ts +1459 -555
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +398 -474
- package/dist/schema.js.map +1 -1
- package/dist/sitemap.d.ts +4 -13
- package/dist/sitemap.d.ts.map +1 -1
- package/dist/sitemap.js +10 -8
- package/dist/sitemap.js.map +1 -1
- package/dist/validate.d.ts +21 -2
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +127 -28
- package/dist/validate.js.map +1 -1
- package/package.json +1 -1
package/dist/schema.js
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { parse, stringify } from "yaml";
|
|
3
3
|
// ──────────────────────────────────────────────────────────────────────
|
|
4
|
+
// Blueprints and kinds
|
|
5
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* A blueprint defines the shape and behavior of a Grove site.
|
|
8
|
+
*
|
|
9
|
+
* V1 ships three fixed blueprints. They are not extensible — no custom
|
|
10
|
+
* blueprint API in V1.
|
|
11
|
+
*/
|
|
12
|
+
export const blueprintSchema = z.enum([
|
|
13
|
+
"project-directory",
|
|
14
|
+
"resource-hub",
|
|
15
|
+
"ecosystem-map",
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Each blueprint is bound to a single resource kind. Records are
|
|
19
|
+
* discriminated by `kind`, which must match the blueprint of the site
|
|
20
|
+
* that owns them.
|
|
21
|
+
*/
|
|
22
|
+
export const resourceKindSchema = z.enum(["project", "resource", "entity"]);
|
|
23
|
+
export const blueprintKind = {
|
|
24
|
+
"project-directory": "project",
|
|
25
|
+
"resource-hub": "resource",
|
|
26
|
+
"ecosystem-map": "entity",
|
|
27
|
+
};
|
|
28
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
4
29
|
// Health & decision labels
|
|
5
30
|
// ──────────────────────────────────────────────────────────────────────
|
|
6
31
|
export const healthStatusSchema = z.enum([
|
|
@@ -30,7 +55,7 @@ export const healthTierSchema = z.enum([
|
|
|
30
55
|
"hidden",
|
|
31
56
|
]);
|
|
32
57
|
// ──────────────────────────────────────────────────────────────────────
|
|
33
|
-
// Curation
|
|
58
|
+
// Curation enums (shared)
|
|
34
59
|
// ──────────────────────────────────────────────────────────────────────
|
|
35
60
|
export const projectTypeSchema = z.enum([
|
|
36
61
|
"real-app",
|
|
@@ -42,8 +67,32 @@ export const projectTypeSchema = z.enum([
|
|
|
42
67
|
"template",
|
|
43
68
|
"historical",
|
|
44
69
|
]);
|
|
45
|
-
export const
|
|
46
|
-
|
|
70
|
+
export const resourceTypeSchema = z.enum([
|
|
71
|
+
"guide",
|
|
72
|
+
"comparison",
|
|
73
|
+
"link",
|
|
74
|
+
"explainer",
|
|
75
|
+
"tool",
|
|
76
|
+
"video",
|
|
77
|
+
"article",
|
|
78
|
+
"course",
|
|
79
|
+
"book",
|
|
80
|
+
"podcast",
|
|
81
|
+
"other",
|
|
82
|
+
]);
|
|
83
|
+
export const entityTypeSchema = z.enum([
|
|
84
|
+
"company",
|
|
85
|
+
"organization",
|
|
86
|
+
"community",
|
|
87
|
+
"school",
|
|
88
|
+
"university",
|
|
89
|
+
"research-lab",
|
|
90
|
+
"agency",
|
|
91
|
+
"service",
|
|
92
|
+
"product",
|
|
93
|
+
"person",
|
|
94
|
+
"other",
|
|
95
|
+
]);
|
|
47
96
|
export const appLabelSchema = z.enum(["new", "hot", "mature", "featured"]);
|
|
48
97
|
export const scoreSchema = z.object({
|
|
49
98
|
activity: z.number().min(0).max(100).optional(),
|
|
@@ -54,34 +103,19 @@ export const scoreSchema = z.object({
|
|
|
54
103
|
overall: z.number().min(0).max(100).optional(),
|
|
55
104
|
});
|
|
56
105
|
// ──────────────────────────────────────────────────────────────────────
|
|
57
|
-
//
|
|
58
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
59
|
-
export const distributionChannelTypeSchema = z.enum([
|
|
60
|
-
"app-store",
|
|
61
|
-
"play-store",
|
|
62
|
-
"fdroid",
|
|
63
|
-
"github-releases",
|
|
64
|
-
"apk",
|
|
65
|
-
"testflight",
|
|
66
|
-
"website",
|
|
67
|
-
"snapcraft",
|
|
68
|
-
"flathub",
|
|
69
|
-
"microsoft-store",
|
|
70
|
-
"package-registry",
|
|
71
|
-
"docs",
|
|
72
|
-
"demo",
|
|
73
|
-
"other",
|
|
74
|
-
]);
|
|
75
|
-
export const distributionChannelSchema = z.object({
|
|
76
|
-
type: distributionChannelTypeSchema,
|
|
77
|
-
platform: z.string().optional(),
|
|
78
|
-
label: z.string().optional(),
|
|
79
|
-
url: z.string().url(),
|
|
80
|
-
verified: z.boolean().optional(),
|
|
81
|
-
notes: z.string().optional(),
|
|
82
|
-
});
|
|
106
|
+
// Links
|
|
83
107
|
// ──────────────────────────────────────────────────────────────────────
|
|
84
|
-
|
|
108
|
+
export const linksSchema = z
|
|
109
|
+
.object({
|
|
110
|
+
github: z.string().url().optional(),
|
|
111
|
+
website: z.string().url().optional(),
|
|
112
|
+
docs: z.string().url().optional(),
|
|
113
|
+
source: z.string().url().optional(),
|
|
114
|
+
})
|
|
115
|
+
.catchall(z.string().url())
|
|
116
|
+
.default({});
|
|
117
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
118
|
+
// Health metadata
|
|
85
119
|
// ──────────────────────────────────────────────────────────────────────
|
|
86
120
|
export const githubLicenseSchema = z
|
|
87
121
|
.object({
|
|
@@ -125,205 +159,6 @@ export const githubRepositorySchema = z
|
|
|
125
159
|
pushed_at: z.string().nullable().optional(),
|
|
126
160
|
})
|
|
127
161
|
.passthrough();
|
|
128
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
129
|
-
// Stack (registry-backed taxonomy)
|
|
130
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
131
|
-
export const stackTechnologySchema = z
|
|
132
|
-
.object({
|
|
133
|
-
id: z.string().min(1),
|
|
134
|
-
role: z.string().optional(),
|
|
135
|
-
})
|
|
136
|
-
.passthrough();
|
|
137
|
-
export const stackSchema = z
|
|
138
|
-
.object({
|
|
139
|
-
primary: z.string().min(1),
|
|
140
|
-
families: z.array(z.string()).optional(),
|
|
141
|
-
technologies: z.array(stackTechnologySchema).optional(),
|
|
142
|
-
})
|
|
143
|
-
.passthrough();
|
|
144
|
-
export const stackRefSchema = z.object({
|
|
145
|
-
id: z.string().min(1),
|
|
146
|
-
name: z.string().min(1),
|
|
147
|
-
family: z.string().optional(),
|
|
148
|
-
languages: z.array(z.string()).optional(),
|
|
149
|
-
platforms: z.array(z.string()).optional(),
|
|
150
|
-
icon: z.string().optional(),
|
|
151
|
-
status: z.enum(["live", "expanding", "planned"]).optional(),
|
|
152
|
-
blurb: z.string().optional(),
|
|
153
|
-
});
|
|
154
|
-
export const platformRefSchema = z.object({
|
|
155
|
-
id: z.string().min(1),
|
|
156
|
-
name: z.string().min(1),
|
|
157
|
-
icon: z.string().optional(),
|
|
158
|
-
});
|
|
159
|
-
export const categoryRefSchema = z.object({
|
|
160
|
-
id: z.string().min(1),
|
|
161
|
-
name: z.string().min(1),
|
|
162
|
-
blurb: z.string().optional(),
|
|
163
|
-
});
|
|
164
|
-
export const distributionChannelRefSchema = z.object({
|
|
165
|
-
id: distributionChannelTypeSchema,
|
|
166
|
-
name: z.string().min(1),
|
|
167
|
-
platforms: z.array(z.string()).optional(),
|
|
168
|
-
});
|
|
169
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
170
|
-
// Item — generic open-source project record
|
|
171
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
172
|
-
export const itemSchema = z.object({
|
|
173
|
-
id: z.string().min(1),
|
|
174
|
-
name: z.string().min(1),
|
|
175
|
-
description: z.string().default(""),
|
|
176
|
-
links: z
|
|
177
|
-
.object({
|
|
178
|
-
github: z.string().url().optional(),
|
|
179
|
-
website: z.string().url().optional(),
|
|
180
|
-
docs: z.string().url().optional(),
|
|
181
|
-
source: z.string().url().optional(),
|
|
182
|
-
})
|
|
183
|
-
.catchall(z.string().url())
|
|
184
|
-
.default({}),
|
|
185
|
-
source: z
|
|
186
|
-
.object({
|
|
187
|
-
type: z.enum(["markdown", "manual", "github-topic", "awesome-list", "submit"]).default("manual"),
|
|
188
|
-
file: z.string().optional(),
|
|
189
|
-
line: z.number().int().positive().optional(),
|
|
190
|
-
url: z.string().optional(),
|
|
191
|
-
provider: z.string().optional(),
|
|
192
|
-
owner: z.string().optional(),
|
|
193
|
-
repo: z.string().optional(),
|
|
194
|
-
})
|
|
195
|
-
.default({ type: "manual" }),
|
|
196
|
-
taxonomy: z
|
|
197
|
-
.object({
|
|
198
|
-
category: z.string().min(1).default("uncategorized"),
|
|
199
|
-
tags: z.array(z.string()).default([]),
|
|
200
|
-
language: z.string().optional(),
|
|
201
|
-
stack: z.string().optional(),
|
|
202
|
-
stacks: z.array(z.string()).default([]),
|
|
203
|
-
platforms: z.array(z.string()).default([]),
|
|
204
|
-
})
|
|
205
|
-
.default({
|
|
206
|
-
category: "uncategorized",
|
|
207
|
-
tags: [],
|
|
208
|
-
stacks: [],
|
|
209
|
-
platforms: [],
|
|
210
|
-
}),
|
|
211
|
-
labels: z.array(appLabelSchema).default([]),
|
|
212
|
-
lenses: z.array(z.string()).default([]),
|
|
213
|
-
distribution: z
|
|
214
|
-
.object({
|
|
215
|
-
channels: z.array(distributionChannelSchema).default([]),
|
|
216
|
-
})
|
|
217
|
-
.default({ channels: [] }),
|
|
218
|
-
curation: z
|
|
219
|
-
.object({
|
|
220
|
-
projectType: projectTypeSchema.optional(),
|
|
221
|
-
difficulty: difficultySchema.optional(),
|
|
222
|
-
codebaseSize: codebaseSizeSchema.optional(),
|
|
223
|
-
stateManagement: z.string().optional(),
|
|
224
|
-
backend: z.string().optional(),
|
|
225
|
-
architecture: z.string().optional(),
|
|
226
|
-
bestFor: z.array(z.string()).default([]),
|
|
227
|
-
whyListed: z.array(z.string()).default([]),
|
|
228
|
-
caveats: z.array(z.string()).default([]),
|
|
229
|
-
goodFirstIssues: z.union([z.boolean(), z.string().url()]).optional(),
|
|
230
|
-
contributionGuide: z.union([z.boolean(), z.string().url()]).optional(),
|
|
231
|
-
launchedBy: z.string().optional(),
|
|
232
|
-
launchAsk: z.array(z.string()).default([]),
|
|
233
|
-
reviewed: z.boolean().optional(),
|
|
234
|
-
reviewedBy: z.string().optional(),
|
|
235
|
-
reviewedAt: z.string().optional(),
|
|
236
|
-
notes: z.string().optional(),
|
|
237
|
-
scores: scoreSchema.default({}),
|
|
238
|
-
})
|
|
239
|
-
.default({
|
|
240
|
-
bestFor: [],
|
|
241
|
-
whyListed: [],
|
|
242
|
-
caveats: [],
|
|
243
|
-
launchAsk: [],
|
|
244
|
-
scores: {},
|
|
245
|
-
}),
|
|
246
|
-
});
|
|
247
|
-
export const itemsFileSchema = z.union([
|
|
248
|
-
z.array(itemSchema),
|
|
249
|
-
z.object({ items: z.array(itemSchema) }),
|
|
250
|
-
]);
|
|
251
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
252
|
-
// GitHub-shaped app record (schema v1)
|
|
253
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
254
|
-
const isoLike = z.string().min(4).nullable().optional();
|
|
255
|
-
const finalAppSchema = z
|
|
256
|
-
.object({
|
|
257
|
-
schemaVersion: z.number().optional(),
|
|
258
|
-
id: z.string().optional(),
|
|
259
|
-
slug: z.string().min(1),
|
|
260
|
-
source: z
|
|
261
|
-
.object({
|
|
262
|
-
provider: z.literal("github"),
|
|
263
|
-
owner: z.string().min(1),
|
|
264
|
-
repo: z.string().min(1),
|
|
265
|
-
url: z.string().url().optional(),
|
|
266
|
-
})
|
|
267
|
-
.passthrough(),
|
|
268
|
-
app: z
|
|
269
|
-
.object({
|
|
270
|
-
name: z.string().min(1),
|
|
271
|
-
description: z.string().min(1),
|
|
272
|
-
category: z.string().min(1),
|
|
273
|
-
projectType: z.string().optional(),
|
|
274
|
-
platforms: z.array(z.string()).min(1),
|
|
275
|
-
tags: z.array(z.string()).optional(),
|
|
276
|
-
distribution: z
|
|
277
|
-
.object({
|
|
278
|
-
channels: z
|
|
279
|
-
.array(z
|
|
280
|
-
.object({
|
|
281
|
-
type: z.string().min(1),
|
|
282
|
-
platform: z.string().optional(),
|
|
283
|
-
label: z.string().optional(),
|
|
284
|
-
url: z.string().url(),
|
|
285
|
-
verified: z.boolean().optional(),
|
|
286
|
-
notes: z.string().optional(),
|
|
287
|
-
})
|
|
288
|
-
.passthrough())
|
|
289
|
-
.optional(),
|
|
290
|
-
})
|
|
291
|
-
.passthrough()
|
|
292
|
-
.optional(),
|
|
293
|
-
})
|
|
294
|
-
.passthrough(),
|
|
295
|
-
stack: stackSchema,
|
|
296
|
-
github: z
|
|
297
|
-
.object({
|
|
298
|
-
repository: githubRepositorySchema.optional(),
|
|
299
|
-
languages: z.record(z.string(), z.number()).optional(),
|
|
300
|
-
latestRelease: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
301
|
-
activity: z.record(z.string(), z.unknown()).optional(),
|
|
302
|
-
files: z.record(z.string(), z.boolean()).optional(),
|
|
303
|
-
labels: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
304
|
-
sync: z.record(z.string(), z.unknown()).optional(),
|
|
305
|
-
})
|
|
306
|
-
.passthrough()
|
|
307
|
-
.optional(),
|
|
308
|
-
health: z.record(z.string(), z.unknown()).optional(),
|
|
309
|
-
curation: z.record(z.string(), z.unknown()).optional(),
|
|
310
|
-
})
|
|
311
|
-
.passthrough();
|
|
312
|
-
const legacyAppSchema = z
|
|
313
|
-
.object({
|
|
314
|
-
slug: z.string().min(1),
|
|
315
|
-
name: z.string().min(1),
|
|
316
|
-
repoUrl: z.string().url(),
|
|
317
|
-
description: z.string().min(1),
|
|
318
|
-
stack: z.union([z.string(), z.record(z.string(), z.unknown())]),
|
|
319
|
-
stacks: z.array(z.string()).optional(),
|
|
320
|
-
platforms: z.array(z.string()).default([]),
|
|
321
|
-
category: z.string().min(1),
|
|
322
|
-
})
|
|
323
|
-
.passthrough();
|
|
324
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
325
|
-
// Health metadata
|
|
326
|
-
// ──────────────────────────────────────────────────────────────────────
|
|
327
162
|
export const githubMetadataSchema = z.object({
|
|
328
163
|
fullName: z.string().optional(),
|
|
329
164
|
stars: z.number().int().nonnegative().default(0),
|
|
@@ -356,19 +191,20 @@ export const githubMetadataSchema = z.object({
|
|
|
356
191
|
]))
|
|
357
192
|
.optional(),
|
|
358
193
|
});
|
|
194
|
+
export const healthBlockSchema = z.object({
|
|
195
|
+
status: healthStatusSchema.default("unknown"),
|
|
196
|
+
maturity: z.enum(["experimental", "useful", "mature", "unknown"]).default("unknown"),
|
|
197
|
+
tier: healthTierSchema.default("experimental"),
|
|
198
|
+
visibility: decisionVisibilitySchema.default("keep"),
|
|
199
|
+
cleanupCandidate: z.boolean().default(false),
|
|
200
|
+
staleReason: z.string().nullable().optional(),
|
|
201
|
+
confidence: z.enum(["low", "medium", "high"]).default("medium"),
|
|
202
|
+
reasons: z.array(z.string()).default([]),
|
|
203
|
+
});
|
|
359
204
|
export const healthEntrySchema = z.object({
|
|
360
205
|
id: z.string().min(1),
|
|
361
206
|
github: githubMetadataSchema.optional(),
|
|
362
|
-
health:
|
|
363
|
-
status: healthStatusSchema,
|
|
364
|
-
maturity: z.enum(["experimental", "useful", "mature", "unknown"]).default("unknown"),
|
|
365
|
-
tier: healthTierSchema.default("experimental"),
|
|
366
|
-
visibility: decisionVisibilitySchema.default("keep"),
|
|
367
|
-
cleanupCandidate: z.boolean().default(false),
|
|
368
|
-
staleReason: z.string().nullable().optional(),
|
|
369
|
-
confidence: z.enum(["low", "medium", "high"]).default("medium"),
|
|
370
|
-
reasons: z.array(z.string()).default([]),
|
|
371
|
-
}),
|
|
207
|
+
health: healthBlockSchema,
|
|
372
208
|
});
|
|
373
209
|
export const healthFileSchema = z.union([
|
|
374
210
|
z.array(healthEntrySchema),
|
|
@@ -391,7 +227,7 @@ export const decisionsFileSchema = z.union([
|
|
|
391
227
|
z.object({ decisions: z.array(decisionSchema) }),
|
|
392
228
|
]);
|
|
393
229
|
// ──────────────────────────────────────────────────────────────────────
|
|
394
|
-
// Overrides (manual corrections to imported
|
|
230
|
+
// Overrides (manual corrections to imported records)
|
|
395
231
|
// ──────────────────────────────────────────────────────────────────────
|
|
396
232
|
export const overrideSchema = z.object({
|
|
397
233
|
id: z.string().min(1),
|
|
@@ -402,36 +238,208 @@ export const overridesFileSchema = z.union([
|
|
|
402
238
|
z.object({ overrides: z.array(overrideSchema) }),
|
|
403
239
|
]);
|
|
404
240
|
// ──────────────────────────────────────────────────────────────────────
|
|
405
|
-
//
|
|
241
|
+
// Curation: shared block
|
|
242
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
243
|
+
const curationBlockSchema = z
|
|
244
|
+
.object({
|
|
245
|
+
reviewed: z.boolean().default(false),
|
|
246
|
+
reviewedBy: z.string().optional(),
|
|
247
|
+
reviewedAt: z.string().optional(),
|
|
248
|
+
notes: z.string().optional(),
|
|
249
|
+
labels: z.array(appLabelSchema).default([]),
|
|
250
|
+
lenses: z.array(z.string()).default([]),
|
|
251
|
+
})
|
|
252
|
+
.default({ reviewed: false, labels: [], lenses: [] });
|
|
253
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
254
|
+
// Resource base (shared by all blueprints)
|
|
255
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
256
|
+
const resourceBaseSchema = z.object({
|
|
257
|
+
slug: z.string().min(1),
|
|
258
|
+
description: z.string().default(""),
|
|
259
|
+
category: z.string().min(1).default("uncategorized"),
|
|
260
|
+
tags: z.array(z.string()).default([]),
|
|
261
|
+
links: linksSchema,
|
|
262
|
+
content: z.string().optional(), // path to markdown body under content/records/
|
|
263
|
+
source: z
|
|
264
|
+
.object({
|
|
265
|
+
type: z.enum(["manual", "github-topic", "awesome-list", "submit", "import"])
|
|
266
|
+
.default("manual"),
|
|
267
|
+
file: z.string().optional(),
|
|
268
|
+
url: z.string().optional(),
|
|
269
|
+
provider: z.string().optional(),
|
|
270
|
+
owner: z.string().optional(),
|
|
271
|
+
repo: z.string().optional(),
|
|
272
|
+
})
|
|
273
|
+
.default({ type: "manual" }),
|
|
274
|
+
curation: curationBlockSchema,
|
|
275
|
+
scores: scoreSchema.default({}),
|
|
276
|
+
});
|
|
406
277
|
// ──────────────────────────────────────────────────────────────────────
|
|
407
|
-
|
|
278
|
+
// Blueprint: project-directory — kind: project
|
|
279
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
280
|
+
export const projectRecordSchema = resourceBaseSchema.extend({
|
|
281
|
+
kind: z.literal("project"),
|
|
408
282
|
name: z.string().min(1),
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
283
|
+
projectType: projectTypeSchema.optional(),
|
|
284
|
+
stack: z.string().optional(),
|
|
285
|
+
stacks: z.array(z.string()).default([]),
|
|
286
|
+
platforms: z.array(z.string()).default([]),
|
|
287
|
+
difficulty: z.enum(["beginner", "intermediate", "advanced"]).optional(),
|
|
288
|
+
codebaseSize: z.enum(["small", "medium", "large", "huge"]).optional(),
|
|
289
|
+
// Canonical repo URL. Distinct from `links.github`, which is the
|
|
290
|
+
// human-facing link on the page; `repoUrl` is the single source
|
|
291
|
+
// for owner/repo extraction, avatar fallback, and "view repo" CTAs.
|
|
412
292
|
repoUrl: z.string().url().optional(),
|
|
413
|
-
|
|
293
|
+
// Optional project logo/avatar URL. Falls back to the GitHub
|
|
294
|
+
// owner avatar (when `repoUrl` is a github.com URL) or to the
|
|
295
|
+
// first-2-letters initials placeholder.
|
|
296
|
+
logoUrl: z.string().url().optional(),
|
|
297
|
+
bestFor: z.array(z.string()).default([]),
|
|
298
|
+
whyListed: z.array(z.string()).default([]),
|
|
299
|
+
caveats: z.array(z.string()).default([]),
|
|
300
|
+
distribution: z
|
|
301
|
+
.object({
|
|
302
|
+
channels: z
|
|
303
|
+
.array(z
|
|
304
|
+
.object({
|
|
305
|
+
type: z.string().min(1),
|
|
306
|
+
platform: z.string().optional(),
|
|
307
|
+
label: z.string().optional(),
|
|
308
|
+
url: z.string().url(),
|
|
309
|
+
verified: z.boolean().optional(),
|
|
310
|
+
notes: z.string().optional(),
|
|
311
|
+
})
|
|
312
|
+
.passthrough())
|
|
313
|
+
.default([]),
|
|
314
|
+
})
|
|
315
|
+
.default({ channels: [] }),
|
|
316
|
+
github: z
|
|
317
|
+
.object({
|
|
318
|
+
repository: githubRepositorySchema.optional(),
|
|
319
|
+
languages: z.record(z.string(), z.number()).optional(),
|
|
320
|
+
latestRelease: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
321
|
+
activity: z.record(z.string(), z.unknown()).optional(),
|
|
322
|
+
files: z.record(z.string(), z.boolean()).optional(),
|
|
323
|
+
labels: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
324
|
+
sync: z.record(z.string(), z.unknown()).optional(),
|
|
325
|
+
})
|
|
326
|
+
.passthrough()
|
|
327
|
+
.optional(),
|
|
328
|
+
health: healthBlockSchema.optional(),
|
|
329
|
+
});
|
|
330
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
331
|
+
// Blueprint: resource-hub — kind: resource
|
|
332
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
333
|
+
export const resourceRecordSchema = resourceBaseSchema.extend({
|
|
334
|
+
kind: z.literal("resource"),
|
|
335
|
+
title: z.string().min(1),
|
|
336
|
+
type: resourceTypeSchema,
|
|
337
|
+
topic: z.string().min(1),
|
|
338
|
+
related: z.array(z.string()).default([]), // slugs of related resources
|
|
339
|
+
publishedAt: z.string().optional(),
|
|
340
|
+
author: z.string().optional(),
|
|
341
|
+
});
|
|
342
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
343
|
+
// Blueprint: ecosystem-map — kind: entity
|
|
344
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
345
|
+
export const entityRecordSchema = resourceBaseSchema.extend({
|
|
346
|
+
kind: z.literal("entity"),
|
|
347
|
+
name: z.string().min(1),
|
|
348
|
+
type: entityTypeSchema,
|
|
349
|
+
founded: z.string().optional(),
|
|
350
|
+
location: z.string().optional(),
|
|
351
|
+
members: z.number().int().nonnegative().optional(),
|
|
352
|
+
parent: z.string().optional(), // slug of parent entity
|
|
353
|
+
});
|
|
354
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
355
|
+
// Discriminated union: Resource
|
|
356
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
357
|
+
export const resourceSchema = z.discriminatedUnion("kind", [
|
|
358
|
+
projectRecordSchema,
|
|
359
|
+
resourceRecordSchema,
|
|
360
|
+
entityRecordSchema,
|
|
361
|
+
]);
|
|
362
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
363
|
+
// Records file (one or many resources per file is also supported)
|
|
364
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
365
|
+
export const recordsFileSchema = z.union([
|
|
366
|
+
z.array(resourceSchema),
|
|
367
|
+
z.object({ records: z.array(resourceSchema) }),
|
|
368
|
+
]);
|
|
369
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
370
|
+
// Grove config
|
|
371
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
372
|
+
export const navItemSchema = z.object({
|
|
373
|
+
label: z.string().min(1),
|
|
374
|
+
href: z.string().min(1),
|
|
375
|
+
});
|
|
376
|
+
export const githubIntegrationSchema = z.union([
|
|
377
|
+
z.boolean(),
|
|
378
|
+
z.object({
|
|
379
|
+
metadata: z.boolean().default(false),
|
|
380
|
+
contributors: z.boolean().default(false),
|
|
381
|
+
health: z.boolean().default(false),
|
|
382
|
+
}),
|
|
383
|
+
]);
|
|
384
|
+
export const themeSchema = z.object({
|
|
385
|
+
primaryColor: z.string().default("#16a34a"),
|
|
386
|
+
radius: z.enum(["none", "soft", "round"]).default("soft"),
|
|
387
|
+
density: z.enum(["compact", "comfortable", "spacious"]).default("comfortable"),
|
|
388
|
+
containerWidth: z.string().default("72rem"),
|
|
389
|
+
});
|
|
390
|
+
export const componentOverrideSchema = z.object({
|
|
391
|
+
Header: z.string().optional(),
|
|
392
|
+
Footer: z.string().optional(),
|
|
393
|
+
Hero: z.string().optional(),
|
|
394
|
+
ItemCard: z.string().optional(),
|
|
395
|
+
DetailHeader: z.string().optional(),
|
|
396
|
+
});
|
|
397
|
+
export const groveConfigSchema = z.object({
|
|
398
|
+
blueprint: blueprintSchema.default("project-directory"),
|
|
399
|
+
site: z.object({
|
|
400
|
+
name: z.string().min(1),
|
|
401
|
+
tagline: z.string().default("A growing community knowledge site."),
|
|
402
|
+
description: z.string().optional(),
|
|
403
|
+
url: z.string().url().optional(),
|
|
404
|
+
repoUrl: z.string().url().optional(),
|
|
405
|
+
}),
|
|
406
|
+
nav: z.array(navItemSchema).default([]),
|
|
407
|
+
facets: z.array(z.string()).default(["category", "tags"]),
|
|
408
|
+
integrations: z
|
|
409
|
+
.object({
|
|
410
|
+
github: githubIntegrationSchema.default(false),
|
|
411
|
+
})
|
|
412
|
+
.default({ github: false }),
|
|
413
|
+
theme: themeSchema.default({
|
|
414
|
+
primaryColor: "#16a34a",
|
|
415
|
+
radius: "soft",
|
|
416
|
+
density: "comfortable",
|
|
417
|
+
containerWidth: "72rem",
|
|
418
|
+
}),
|
|
419
|
+
components: componentOverrideSchema.default({}),
|
|
414
420
|
paths: z
|
|
415
421
|
.object({
|
|
416
|
-
sourcesDir: z.string().default("sources"),
|
|
417
422
|
dataDir: z.string().default("data"),
|
|
418
423
|
contentDir: z.string().default("content"),
|
|
419
|
-
|
|
424
|
+
recordsDir: z.string().default("data/records"),
|
|
425
|
+
pagesDir: z.string().default("content/pages"),
|
|
426
|
+
bodiesDir: z.string().default("content/records"),
|
|
427
|
+
publicDir: z.string().default("public"),
|
|
420
428
|
taxonomyDir: z.string().default("data/taxonomy"),
|
|
421
429
|
generatedDir: z.string().default("data/generated"),
|
|
422
|
-
items: z.string().default("data/items.yml"),
|
|
423
430
|
health: z.string().default("data/health.yml"),
|
|
424
431
|
decisions: z.string().default("data/decisions.yml"),
|
|
425
432
|
overrides: z.string().default("data/overrides.yml"),
|
|
426
433
|
})
|
|
427
434
|
.default({
|
|
428
|
-
sourcesDir: "sources",
|
|
429
435
|
dataDir: "data",
|
|
430
436
|
contentDir: "content",
|
|
431
|
-
|
|
437
|
+
recordsDir: "data/records",
|
|
438
|
+
pagesDir: "content/pages",
|
|
439
|
+
bodiesDir: "content/records",
|
|
440
|
+
publicDir: "public",
|
|
432
441
|
taxonomyDir: "data/taxonomy",
|
|
433
442
|
generatedDir: "data/generated",
|
|
434
|
-
items: "data/items.yml",
|
|
435
443
|
health: "data/health.yml",
|
|
436
444
|
decisions: "data/decisions.yml",
|
|
437
445
|
overrides: "data/overrides.yml",
|
|
@@ -440,8 +448,8 @@ export const curatedConfigSchema = z.object({
|
|
|
440
448
|
// ──────────────────────────────────────────────────────────────────────
|
|
441
449
|
// Helpers
|
|
442
450
|
// ──────────────────────────────────────────────────────────────────────
|
|
443
|
-
export function
|
|
444
|
-
return Array.isArray(value) ? value : value.
|
|
451
|
+
export function unwrapRecords(value) {
|
|
452
|
+
return Array.isArray(value) ? value : value.records;
|
|
445
453
|
}
|
|
446
454
|
export function unwrapHealth(value) {
|
|
447
455
|
return Array.isArray(value) ? value : value.health;
|
|
@@ -452,17 +460,138 @@ export function unwrapDecisions(value) {
|
|
|
452
460
|
export function unwrapOverrides(value) {
|
|
453
461
|
return Array.isArray(value) ? value : value.overrides;
|
|
454
462
|
}
|
|
455
|
-
|
|
456
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Parse a single record YAML mapping into a typed Resource. The
|
|
465
|
+
* `kind` field on the YAML selects which blueprint schema to apply.
|
|
466
|
+
*/
|
|
467
|
+
export function parseRecordYaml(text, fileSlug) {
|
|
468
|
+
const raw = parse(text) ?? {};
|
|
469
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
470
|
+
throw new Error(`${fileSlug}: record file must contain a YAML mapping`);
|
|
471
|
+
}
|
|
472
|
+
return raw;
|
|
473
|
+
}
|
|
474
|
+
export function stringifyRecordYaml(record) {
|
|
475
|
+
return stringify(record, {
|
|
476
|
+
lineWidth: 100,
|
|
477
|
+
singleQuote: false,
|
|
478
|
+
defaultStringType: "PLAIN",
|
|
479
|
+
});
|
|
457
480
|
}
|
|
458
|
-
function
|
|
459
|
-
if (!
|
|
460
|
-
return
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
463
|
-
return
|
|
464
|
-
return
|
|
481
|
+
export function getOwnerRepoFromUrl(repoUrl) {
|
|
482
|
+
if (!repoUrl)
|
|
483
|
+
return null;
|
|
484
|
+
const m = String(repoUrl).match(/github\.com\/([^/]+)\/([^/?#]+)/);
|
|
485
|
+
if (!m)
|
|
486
|
+
return null;
|
|
487
|
+
return { owner: m[1], repo: m[2].replace(/\.git$/, "") };
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Normalize a raw record mapping into a typed Resource. The `kind`
|
|
491
|
+
* field must match the blueprint of the site that owns the record;
|
|
492
|
+
* mismatch produces a clear ZodError.
|
|
493
|
+
*/
|
|
494
|
+
export function normalizeRecord(raw, fileSlug, blueprint = "project-directory") {
|
|
495
|
+
const expectedKind = blueprintKind[blueprint];
|
|
496
|
+
if (!raw.kind) {
|
|
497
|
+
raw = { ...raw, kind: expectedKind };
|
|
498
|
+
}
|
|
499
|
+
else if (raw.kind !== expectedKind) {
|
|
500
|
+
throw new Error(`${fileSlug}: kind "${raw.kind}" does not match blueprint "${blueprint}" (expected "${expectedKind}")`);
|
|
501
|
+
}
|
|
502
|
+
return resourceSchema.parse(raw);
|
|
503
|
+
}
|
|
504
|
+
export function validateRecord(raw, fileSlug, blueprint) {
|
|
505
|
+
try {
|
|
506
|
+
normalizeRecord(raw, fileSlug, blueprint);
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
if (err instanceof z.ZodError) {
|
|
511
|
+
return err.issues.map((issue) => `${fileSlug}: ${issue.path.join(".") || "(root)"} ${issue.message}`);
|
|
512
|
+
}
|
|
513
|
+
return [`${fileSlug}: ${err.message}`];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Project a Resource to a stable, search-index-friendly summary
|
|
518
|
+
* object. Strips out blueprint-specific heavy fields and leaves
|
|
519
|
+
* only what the public list/detail UIs need.
|
|
520
|
+
*/
|
|
521
|
+
export function toIndexRecord(record) {
|
|
522
|
+
const base = {
|
|
523
|
+
slug: record.slug,
|
|
524
|
+
kind: record.kind,
|
|
525
|
+
category: record.category,
|
|
526
|
+
tags: record.tags ?? [],
|
|
527
|
+
links: record.links,
|
|
528
|
+
description: record.description,
|
|
529
|
+
content: record.content,
|
|
530
|
+
curation: record.curation,
|
|
531
|
+
};
|
|
532
|
+
if (record.kind === "project") {
|
|
533
|
+
return {
|
|
534
|
+
...base,
|
|
535
|
+
name: record.name,
|
|
536
|
+
stack: record.stack,
|
|
537
|
+
stacks: record.stacks ?? [],
|
|
538
|
+
platforms: record.platforms ?? [],
|
|
539
|
+
projectType: record.projectType,
|
|
540
|
+
repoUrl: record.repoUrl,
|
|
541
|
+
logoUrl: record.logoUrl,
|
|
542
|
+
difficulty: record.difficulty,
|
|
543
|
+
codebaseSize: record.codebaseSize,
|
|
544
|
+
bestFor: record.bestFor,
|
|
545
|
+
whyListed: record.whyListed,
|
|
546
|
+
caveats: record.caveats,
|
|
547
|
+
// Health surfaced alongside the record so list/detail UIs can
|
|
548
|
+
// show staleness/curation tier without a second lookup.
|
|
549
|
+
health: record.health,
|
|
550
|
+
// Visibility comes from either the health block (auto-derived
|
|
551
|
+
// from signals) or the curation override (`decisions.yml`).
|
|
552
|
+
// The generate step folds the latter in before serialising,
|
|
553
|
+
// so this is a best-effort projection of the current state.
|
|
554
|
+
visibility: record.health?.visibility ?? "keep",
|
|
555
|
+
github: record.github?.repository
|
|
556
|
+
? {
|
|
557
|
+
fullName: record.github.repository.full_name,
|
|
558
|
+
stars: record.github.repository.stargazers_count ?? 0,
|
|
559
|
+
forks: record.github.repository.forks_count ?? 0,
|
|
560
|
+
openIssues: record.github.repository.open_issues_count ?? 0,
|
|
561
|
+
language: record.github.repository.language ?? null,
|
|
562
|
+
pushedAt: record.github.repository.pushed_at ?? null,
|
|
563
|
+
archived: Boolean(record.github.repository.archived),
|
|
564
|
+
license: record.github.repository.license?.spdx_id ?? null,
|
|
565
|
+
topics: record.github.repository.topics ?? [],
|
|
566
|
+
}
|
|
567
|
+
: undefined,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (record.kind === "resource") {
|
|
571
|
+
return {
|
|
572
|
+
...base,
|
|
573
|
+
title: record.title,
|
|
574
|
+
type: record.type,
|
|
575
|
+
topic: record.topic,
|
|
576
|
+
related: record.related,
|
|
577
|
+
publishedAt: record.publishedAt,
|
|
578
|
+
author: record.author,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
// entity
|
|
582
|
+
return {
|
|
583
|
+
...base,
|
|
584
|
+
name: record.name,
|
|
585
|
+
type: record.type,
|
|
586
|
+
founded: record.founded,
|
|
587
|
+
location: record.location,
|
|
588
|
+
members: record.members,
|
|
589
|
+
parent: record.parent,
|
|
590
|
+
};
|
|
465
591
|
}
|
|
592
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
593
|
+
// Health signal derivation
|
|
594
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
466
595
|
function daysSince(value) {
|
|
467
596
|
if (!value)
|
|
468
597
|
return Infinity;
|
|
@@ -472,7 +601,7 @@ function daysSince(value) {
|
|
|
472
601
|
return (Date.now() - d.valueOf()) / 86_400_000;
|
|
473
602
|
}
|
|
474
603
|
export function healthFromSignals(params) {
|
|
475
|
-
const { repo, monthlyCommits
|
|
604
|
+
const { repo, monthlyCommits } = params;
|
|
476
605
|
const stars = repo?.stargazers_count ?? 0;
|
|
477
606
|
const lastCommitAt = repo?.pushed_at;
|
|
478
607
|
const archived = Boolean(repo?.archived);
|
|
@@ -503,10 +632,13 @@ export function healthFromSignals(params) {
|
|
|
503
632
|
? "listed"
|
|
504
633
|
: "experimental";
|
|
505
634
|
return {
|
|
506
|
-
status
|
|
635
|
+
status,
|
|
507
636
|
tier,
|
|
508
637
|
visibility: tier === "hidden" ? "hide" : "keep",
|
|
509
|
-
cleanupCandidate: status === "stale" ||
|
|
638
|
+
cleanupCandidate: status === "stale" ||
|
|
639
|
+
status === "archived" ||
|
|
640
|
+
status === "inactive" ||
|
|
641
|
+
status === "unavailable",
|
|
510
642
|
staleReason: status === "inactive"
|
|
511
643
|
? "no_commits_24_months"
|
|
512
644
|
: status === "stale"
|
|
@@ -518,212 +650,4 @@ export function healthFromSignals(params) {
|
|
|
518
650
|
: null,
|
|
519
651
|
};
|
|
520
652
|
}
|
|
521
|
-
export function parseAppYaml(text, fileSlug) {
|
|
522
|
-
const raw = parse(text) ?? {};
|
|
523
|
-
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
524
|
-
throw new Error(`${fileSlug}: app file must contain a YAML mapping`);
|
|
525
|
-
}
|
|
526
|
-
return raw;
|
|
527
|
-
}
|
|
528
|
-
export function stringifyAppYaml(app) {
|
|
529
|
-
return stringify(app, {
|
|
530
|
-
lineWidth: 100,
|
|
531
|
-
singleQuote: false,
|
|
532
|
-
defaultStringType: "PLAIN",
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
export function getOwnerRepoFromUrl(repoUrl) {
|
|
536
|
-
if (!repoUrl)
|
|
537
|
-
return null;
|
|
538
|
-
const m = String(repoUrl).match(/github\.com\/([^/]+)\/([^/?#]+)/);
|
|
539
|
-
if (!m)
|
|
540
|
-
return null;
|
|
541
|
-
return { owner: m[1], repo: m[2].replace(/\.git$/, "") };
|
|
542
|
-
}
|
|
543
|
-
export function normalizeAppRecord(raw, fileSlug) {
|
|
544
|
-
const hasFinalShape = typeof raw.app === "object" &&
|
|
545
|
-
raw.app !== null &&
|
|
546
|
-
typeof raw.source === "object" &&
|
|
547
|
-
raw.source !== null &&
|
|
548
|
-
raw.github !== undefined;
|
|
549
|
-
if (hasFinalShape) {
|
|
550
|
-
return normalizeFinal(raw, fileSlug);
|
|
551
|
-
}
|
|
552
|
-
return normalizeLegacy(raw, fileSlug);
|
|
553
|
-
}
|
|
554
|
-
function normalizeFinal(raw, fileSlug) {
|
|
555
|
-
const parsed = finalAppSchema.parse(raw);
|
|
556
|
-
const repo = parsed.github?.repository;
|
|
557
|
-
const repoUrl = parsed.source.url ?? repo?.html_url ?? `https://github.com/${parsed.source.owner}/${parsed.source.repo}`;
|
|
558
|
-
const activity = parsed.github?.activity ?? {};
|
|
559
|
-
const monthlyCommits = activity.monthlyCommits;
|
|
560
|
-
const health = {
|
|
561
|
-
...healthFromSignals({ repo, monthlyCommits }),
|
|
562
|
-
...parsed.health,
|
|
563
|
-
};
|
|
564
|
-
const technologies = compactArray((parsed.stack.technologies ?? []).map((t) => t.id));
|
|
565
|
-
const secondaryStacks = technologies.filter((id) => id !== parsed.stack.primary);
|
|
566
|
-
const curation = parsed.curation ?? {};
|
|
567
|
-
return {
|
|
568
|
-
...parsed,
|
|
569
|
-
schemaVersion: 1,
|
|
570
|
-
slug: parsed.slug || fileSlug,
|
|
571
|
-
name: parsed.app.name,
|
|
572
|
-
description: parsed.app.description,
|
|
573
|
-
repoUrl,
|
|
574
|
-
homepageUrl: repo?.homepage || raw.homepageUrl || undefined,
|
|
575
|
-
stack: parsed.stack.primary,
|
|
576
|
-
stacks: secondaryStacks,
|
|
577
|
-
platforms: parsed.app.platforms,
|
|
578
|
-
distribution: parsed.app.distribution ?? { channels: [] },
|
|
579
|
-
category: parsed.app.category,
|
|
580
|
-
tags: compactArray([...(parsed.app.tags ?? []), ...(repo?.topics ?? [])]),
|
|
581
|
-
license: repo?.license?.spdx_id || undefined,
|
|
582
|
-
status: health.status,
|
|
583
|
-
stars: repo?.stargazers_count,
|
|
584
|
-
forks: repo?.forks_count,
|
|
585
|
-
openIssues: repo?.open_issues_count,
|
|
586
|
-
lastCommitAt: formatDateOnly(repo?.pushed_at) ?? null,
|
|
587
|
-
labels: curation.labels ?? [],
|
|
588
|
-
lenses: curation.lenses ?? [],
|
|
589
|
-
projectType: parsed.app.projectType,
|
|
590
|
-
difficulty: curation.difficulty,
|
|
591
|
-
codebaseSize: curation.codebaseSize,
|
|
592
|
-
bestFor: curation.bestFor ?? [],
|
|
593
|
-
whyListed: curation.whyListed ?? [],
|
|
594
|
-
caveats: curation.caveats ?? [],
|
|
595
|
-
scores: curation.scores ?? {},
|
|
596
|
-
curation: {
|
|
597
|
-
reviewed: Boolean(curation.reviewed),
|
|
598
|
-
reviewedBy: curation.reviewedBy,
|
|
599
|
-
reviewedAt: curation.reviewedAt,
|
|
600
|
-
notes: curation.notes,
|
|
601
|
-
},
|
|
602
|
-
github: parsed.github,
|
|
603
|
-
health,
|
|
604
|
-
tier: health.tier,
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
function normalizeLegacy(raw, fileSlug) {
|
|
608
|
-
const parsed = legacyAppSchema.parse({ ...raw, slug: raw.slug ?? fileSlug });
|
|
609
|
-
const ownerRepo = getOwnerRepoFromUrl(parsed.repoUrl);
|
|
610
|
-
const activity = parsed.activity ?? {};
|
|
611
|
-
const repo = {
|
|
612
|
-
full_name: ownerRepo ? `${ownerRepo.owner}/${ownerRepo.repo}` : undefined,
|
|
613
|
-
name: ownerRepo?.repo,
|
|
614
|
-
html_url: parsed.repoUrl,
|
|
615
|
-
homepage: parsed.homepageUrl,
|
|
616
|
-
description: parsed.description,
|
|
617
|
-
fork: false,
|
|
618
|
-
archived: parsed.status === "archived",
|
|
619
|
-
disabled: false,
|
|
620
|
-
private: false,
|
|
621
|
-
visibility: "public",
|
|
622
|
-
language: typeof parsed.stack === "string" ? parsed.stack : undefined,
|
|
623
|
-
license: parsed.license ? { spdx_id: parsed.license } : undefined,
|
|
624
|
-
stargazers_count: activity.stars ?? parsed.stars,
|
|
625
|
-
watchers_count: activity.watchers,
|
|
626
|
-
forks_count: activity.forks ?? parsed.forks,
|
|
627
|
-
open_issues_count: activity.openIssues,
|
|
628
|
-
subscribers_count: activity.contributors,
|
|
629
|
-
pushed_at: activity.lastCommitAt,
|
|
630
|
-
updated_at: activity.updatedAt,
|
|
631
|
-
};
|
|
632
|
-
const monthlyCommits = activity.monthlyCommits ?? [];
|
|
633
|
-
const primaryStack = typeof parsed.stack === "string" ? parsed.stack : parsed.stack.primary;
|
|
634
|
-
const health = healthFromSignals({ repo, monthlyCommits, legacyStatus: parsed.status });
|
|
635
|
-
return {
|
|
636
|
-
schemaVersion: 0,
|
|
637
|
-
slug: parsed.slug,
|
|
638
|
-
name: parsed.name,
|
|
639
|
-
description: parsed.description,
|
|
640
|
-
repoUrl: parsed.repoUrl,
|
|
641
|
-
visibility: health.visibility,
|
|
642
|
-
cleanupCandidate: health.cleanupCandidate,
|
|
643
|
-
staleReason: health.staleReason,
|
|
644
|
-
homepageUrl: parsed.homepageUrl,
|
|
645
|
-
stack: primaryStack,
|
|
646
|
-
stacks: compactArray([...(parsed.stacks ?? [])]),
|
|
647
|
-
platforms: parsed.platforms,
|
|
648
|
-
distribution: parsed.distribution ?? { channels: [] },
|
|
649
|
-
category: parsed.category,
|
|
650
|
-
tags: compactArray((parsed.tags ?? [])),
|
|
651
|
-
license: parsed.license,
|
|
652
|
-
status: health.status,
|
|
653
|
-
stars: repo.stargazers_count,
|
|
654
|
-
forks: repo.forks_count,
|
|
655
|
-
openIssues: repo.open_issues_count,
|
|
656
|
-
lastCommitAt: formatDateOnly(repo.pushed_at) ?? null,
|
|
657
|
-
labels: parsed.labels ?? [],
|
|
658
|
-
lenses: parsed.lenses ?? [],
|
|
659
|
-
projectType: parsed.projectType,
|
|
660
|
-
difficulty: parsed.difficulty,
|
|
661
|
-
codebaseSize: parsed.codebaseSize,
|
|
662
|
-
bestFor: parsed.bestFor ?? [],
|
|
663
|
-
whyListed: parsed.whyListed ?? [],
|
|
664
|
-
caveats: parsed.caveats ?? [],
|
|
665
|
-
scores: parsed.scores ?? {},
|
|
666
|
-
curation: {
|
|
667
|
-
reviewed: false,
|
|
668
|
-
},
|
|
669
|
-
github: {
|
|
670
|
-
repository: repo,
|
|
671
|
-
activity: {
|
|
672
|
-
monthlyCommits,
|
|
673
|
-
totalCommitsKnown: activity.totalCommitsKnown,
|
|
674
|
-
contributorsKnown: activity.contributors,
|
|
675
|
-
openPullRequests: activity.openPullRequests,
|
|
676
|
-
},
|
|
677
|
-
sync: {
|
|
678
|
-
syncedAt: activity.updatedAt,
|
|
679
|
-
source: "legacy-activity-block",
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
health: {
|
|
683
|
-
status: health.status,
|
|
684
|
-
tier: health.tier,
|
|
685
|
-
visibility: health.visibility,
|
|
686
|
-
cleanupCandidate: health.cleanupCandidate,
|
|
687
|
-
staleReason: health.staleReason,
|
|
688
|
-
},
|
|
689
|
-
tier: health.tier,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
export function validateAppRecord(raw, fileSlug) {
|
|
693
|
-
try {
|
|
694
|
-
normalizeAppRecord(raw, fileSlug);
|
|
695
|
-
return [];
|
|
696
|
-
}
|
|
697
|
-
catch (err) {
|
|
698
|
-
if (err instanceof z.ZodError) {
|
|
699
|
-
return err.issues.map((issue) => `${fileSlug}: ${issue.path.join(".") || "(root)"} ${issue.message}`);
|
|
700
|
-
}
|
|
701
|
-
return [`${fileSlug}: ${err.message}`];
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
export function toIndexApp(app) {
|
|
705
|
-
return {
|
|
706
|
-
slug: app.slug,
|
|
707
|
-
name: app.name,
|
|
708
|
-
description: app.description,
|
|
709
|
-
repoUrl: app.repoUrl,
|
|
710
|
-
homepageUrl: app.homepageUrl,
|
|
711
|
-
category: app.category,
|
|
712
|
-
platforms: app.platforms ?? [],
|
|
713
|
-
distribution: app.distribution,
|
|
714
|
-
primaryStack: app.stack,
|
|
715
|
-
stack: app.stack,
|
|
716
|
-
stacks: app.stacks ?? [],
|
|
717
|
-
technologies: compactArray([app.stack, ...(app.stacks ?? [])]),
|
|
718
|
-
tier: app.health?.tier ?? app.tier,
|
|
719
|
-
status: app.health?.status ?? app.status,
|
|
720
|
-
visibility: app.health?.visibility ?? "keep",
|
|
721
|
-
stars: app.github?.repository?.stargazers_count ?? app.stars,
|
|
722
|
-
forks: app.github?.repository?.forks_count,
|
|
723
|
-
openIssues: app.github?.repository?.open_issues_count,
|
|
724
|
-
license: app.github?.repository?.license?.spdx_id ?? app.license,
|
|
725
|
-
lastCommitAt: formatDateOnly(app.github?.repository?.pushed_at ?? app.lastCommitAt),
|
|
726
|
-
tags: app.tags ?? [],
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
653
|
//# sourceMappingURL=schema.js.map
|