@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/directory-scores.js
CHANGED
|
@@ -25,40 +25,40 @@ export function scoreTier(n) {
|
|
|
25
25
|
export function scoreTierLabel(n) {
|
|
26
26
|
switch (scoreTier(n)) {
|
|
27
27
|
case 0:
|
|
28
|
-
return
|
|
28
|
+
return 'Very low';
|
|
29
29
|
case 1:
|
|
30
|
-
return
|
|
30
|
+
return 'Low';
|
|
31
31
|
case 2:
|
|
32
|
-
return
|
|
32
|
+
return 'Medium';
|
|
33
33
|
case 3:
|
|
34
|
-
return
|
|
34
|
+
return 'High';
|
|
35
35
|
case 4:
|
|
36
|
-
return
|
|
36
|
+
return 'Very high';
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
/** Short numeric label used inline next to a score bar, e.g. "82". */
|
|
40
40
|
export function scoreLabel(n) {
|
|
41
|
-
if (typeof n !==
|
|
42
|
-
return
|
|
41
|
+
if (typeof n !== 'number')
|
|
42
|
+
return '—';
|
|
43
43
|
return Math.round(n).toString();
|
|
44
44
|
}
|
|
45
45
|
/** Order of score dimensions, used by both list and detail. */
|
|
46
46
|
export const SCORE_DIMENSIONS = [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
'activity',
|
|
48
|
+
'maturity',
|
|
49
|
+
'learning',
|
|
50
|
+
'contribution',
|
|
51
|
+
'docs',
|
|
52
|
+
'overall',
|
|
53
53
|
];
|
|
54
54
|
/** Human-readable label for a score dimension. */
|
|
55
55
|
export const SCORE_LABELS = {
|
|
56
|
-
activity:
|
|
57
|
-
maturity:
|
|
58
|
-
learning:
|
|
59
|
-
contribution:
|
|
60
|
-
docs:
|
|
61
|
-
overall:
|
|
56
|
+
activity: 'Activity',
|
|
57
|
+
maturity: 'Maturity',
|
|
58
|
+
learning: 'Learning',
|
|
59
|
+
contribution: 'Contribution',
|
|
60
|
+
docs: 'Docs',
|
|
61
|
+
overall: 'Overall',
|
|
62
62
|
};
|
|
63
63
|
/**
|
|
64
64
|
* Reasoning copy per dimension, surfaced in the detail page so the
|
|
@@ -66,11 +66,11 @@ export const SCORE_LABELS = {
|
|
|
66
66
|
* curation.notes on each item carries the longer story.
|
|
67
67
|
*/
|
|
68
68
|
export const SCORE_REASONING = {
|
|
69
|
-
activity:
|
|
70
|
-
maturity:
|
|
71
|
-
learning:
|
|
72
|
-
contribution:
|
|
73
|
-
docs:
|
|
74
|
-
overall:
|
|
69
|
+
activity: 'Recent commits, open and merged PRs, issue response time, release cadence.',
|
|
70
|
+
maturity: 'Repo age, stable structure, real-world production usage, contributor stability.',
|
|
71
|
+
learning: 'Codebase readability, useful architecture patterns, comment quality, examples.',
|
|
72
|
+
contribution: 'Open issues, recent merged PRs from new contributors, maintainer responsiveness, contribution guide.',
|
|
73
|
+
docs: 'README quality, architecture docs, contributing guide, examples, code comments.',
|
|
74
|
+
overall: 'Composite judgment — how likely this item is to satisfy a curious developer.',
|
|
75
75
|
};
|
|
76
76
|
//# sourceMappingURL=directory-scores.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-scores.js","sourceRoot":"","sources":["../src/directory-scores.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,QAAQ,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC;YACJ,OAAO,UAAU,CAAC;QACpB,KAAK,CAAC;YACJ,OAAO,KAAK,CAAC;QACf,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,WAAW,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,CAAqB;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoC;IAC9D,QAAQ,
|
|
1
|
+
{"version":3,"file":"directory-scores.js","sourceRoot":"","sources":["../src/directory-scores.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,QAAQ,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC;YACJ,OAAO,UAAU,CAAC;QACpB,KAAK,CAAC;YACJ,OAAO,KAAK,CAAC;QACf,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,WAAW,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,CAAqB;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAwB;IACnD,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoC;IAC9D,QAAQ,EAAE,4EAA4E;IACtF,QAAQ,EAAE,iFAAiF;IAC3F,QAAQ,EAAE,gFAAgF;IAC1F,YAAY,EACV,sGAAsG;IACxG,IAAI,EAAE,iFAAiF;IACvF,OAAO,EAAE,8EAA8E;CACxF,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IndexRecord } from
|
|
1
|
+
import type { IndexRecord } from './schema.js';
|
|
2
2
|
/**
|
|
3
3
|
* URL-driven filter state for the /items discovery page.
|
|
4
4
|
*
|
|
@@ -38,7 +38,7 @@ export declare const SORT_OPTIONS: readonly [{
|
|
|
38
38
|
readonly value: "alphabetical";
|
|
39
39
|
readonly label: "Alphabetical";
|
|
40
40
|
}];
|
|
41
|
-
export type IndexSort = (typeof SORT_OPTIONS)[number][
|
|
41
|
+
export type IndexSort = (typeof SORT_OPTIONS)[number]['value'];
|
|
42
42
|
/**
|
|
43
43
|
* Read filters from a URLSearchParams (or anything URLSearchParams-shaped,
|
|
44
44
|
* e.g. the result of `new URL(req.url).searchParams`).
|
|
@@ -66,7 +66,7 @@ export declare function totalPages(itemCount: number, pageSize?: number): number
|
|
|
66
66
|
* component and the client-side rebuild in `DirectoryIndexClient`
|
|
67
67
|
* so the two never drift.
|
|
68
68
|
*/
|
|
69
|
-
export declare function paginationPageList(active: number, count: number): Array<number |
|
|
69
|
+
export declare function paginationPageList(active: number, count: number): Array<number | 'ellipsis'>;
|
|
70
70
|
/** Resolve the effective sort from a filters object, applying default. */
|
|
71
71
|
export declare function effectiveSort(f: IndexFilters): IndexSort;
|
|
72
72
|
/** Resolve the effective page from a filters object, applying default. */
|
|
@@ -160,7 +160,7 @@ export declare function buildFacets(items: IndexRecord[], options?: {
|
|
|
160
160
|
* in the default count-desc-then-alpha order. Curated tag ids
|
|
161
161
|
* (topics.yml) flow through the `tags` key the same way.
|
|
162
162
|
*/
|
|
163
|
-
order?: Partial<Record<
|
|
163
|
+
order?: Partial<Record<'stacks' | 'platforms' | 'categories' | 'tags' | 'licenses', string[]>>;
|
|
164
164
|
}): {
|
|
165
165
|
stacks: {
|
|
166
166
|
value: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-search.d.ts","sourceRoot":"","sources":["../src/directory-search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"directory-search.d.ts","sourceRoot":"","sources":["../src/directory-search.ts"],"names":[],"mappings":"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"}
|
package/dist/directory-search.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { labelDisplay, lensDisplay, statusDisplay } from
|
|
1
|
+
import { labelDisplay, lensDisplay, statusDisplay } from './directory-display.js';
|
|
2
2
|
/** Available sort orders, in UI order. */
|
|
3
3
|
export const SORT_OPTIONS = [
|
|
4
|
-
{ value:
|
|
5
|
-
{ value:
|
|
6
|
-
{ value:
|
|
7
|
-
{ value:
|
|
8
|
-
{ value:
|
|
4
|
+
{ value: 'recently-updated', label: 'Recently updated' },
|
|
5
|
+
{ value: 'most-starred', label: 'Most starred' },
|
|
6
|
+
{ value: 'recently-added', label: 'Recently added' },
|
|
7
|
+
{ value: 'best-overall', label: 'Best overall' },
|
|
8
|
+
{ value: 'alphabetical', label: 'Alphabetical' },
|
|
9
9
|
];
|
|
10
|
-
const DEFAULT_SORT =
|
|
10
|
+
const DEFAULT_SORT = 'recently-updated';
|
|
11
11
|
const DEFAULT_PAGE = 1;
|
|
12
12
|
const DEFAULT_PAGE_SIZE = 20;
|
|
13
13
|
const KEYS = {
|
|
14
|
-
q:
|
|
15
|
-
stacks:
|
|
16
|
-
platforms:
|
|
17
|
-
categories:
|
|
18
|
-
tags:
|
|
19
|
-
labels:
|
|
20
|
-
licenses:
|
|
21
|
-
statuses:
|
|
22
|
-
lens:
|
|
23
|
-
sort:
|
|
24
|
-
page:
|
|
14
|
+
q: 'q',
|
|
15
|
+
stacks: 'stack',
|
|
16
|
+
platforms: 'platform',
|
|
17
|
+
categories: 'category',
|
|
18
|
+
tags: 'tag',
|
|
19
|
+
labels: 'label',
|
|
20
|
+
licenses: 'license',
|
|
21
|
+
statuses: 'status',
|
|
22
|
+
lens: 'lens',
|
|
23
|
+
sort: 'sort',
|
|
24
|
+
page: 'page',
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Read filters from a URLSearchParams (or anything URLSearchParams-shaped,
|
|
@@ -30,8 +30,9 @@ const KEYS = {
|
|
|
30
30
|
export function filtersFromSearchParams(sp) {
|
|
31
31
|
const rawSort = sp.get(KEYS.sort);
|
|
32
32
|
// Backwards-compat: old "most-mature" sort now maps to "best-overall".
|
|
33
|
-
const normalizedSort = rawSort ===
|
|
34
|
-
const sort = normalizedSort &&
|
|
33
|
+
const normalizedSort = rawSort === 'most-mature' ? 'best-overall' : rawSort;
|
|
34
|
+
const sort = normalizedSort &&
|
|
35
|
+
SORT_OPTIONS.some((o) => o.value === normalizedSort)
|
|
35
36
|
? normalizedSort
|
|
36
37
|
: undefined;
|
|
37
38
|
const rawPage = Number(sp.get(KEYS.page));
|
|
@@ -46,7 +47,10 @@ export function filtersFromSearchParams(sp) {
|
|
|
46
47
|
tags: sp.getAll(KEYS.tags).filter(Boolean),
|
|
47
48
|
labels: sp.getAll(KEYS.labels).filter(Boolean),
|
|
48
49
|
licenses: sp.getAll(KEYS.licenses).filter(Boolean),
|
|
49
|
-
statuses: sp
|
|
50
|
+
statuses: sp
|
|
51
|
+
.getAll(KEYS.statuses)
|
|
52
|
+
.flatMap((s) => s.split(','))
|
|
53
|
+
.filter(Boolean),
|
|
50
54
|
...(lens !== null ? { lens } : {}),
|
|
51
55
|
...(sort !== undefined ? { sort } : {}),
|
|
52
56
|
...(page !== undefined ? { page } : {}),
|
|
@@ -74,7 +78,7 @@ export function searchParamsFromFilters(f) {
|
|
|
74
78
|
for (const v of f.licenses ?? [])
|
|
75
79
|
sp.append(KEYS.licenses, v);
|
|
76
80
|
if (f.statuses?.length)
|
|
77
|
-
sp.set(KEYS.statuses, f.statuses.join(
|
|
81
|
+
sp.set(KEYS.statuses, f.statuses.join(','));
|
|
78
82
|
if (f.lens)
|
|
79
83
|
sp.set(KEYS.lens, f.lens);
|
|
80
84
|
if (f.sort && f.sort !== DEFAULT_SORT)
|
|
@@ -105,37 +109,64 @@ export const PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
|
|
105
109
|
* than vanishing.
|
|
106
110
|
*/
|
|
107
111
|
function recordName(record) {
|
|
108
|
-
return record.kind ===
|
|
112
|
+
return record.kind === 'resource' ? record.title : record.name;
|
|
109
113
|
}
|
|
110
114
|
function projectStars(record) {
|
|
111
|
-
return record.kind ===
|
|
115
|
+
return record.kind === 'project' ? (record.github?.stars ?? 0) : 0;
|
|
112
116
|
}
|
|
113
117
|
function recordUpdatedAt(record) {
|
|
114
|
-
if (record.kind ===
|
|
118
|
+
if (record.kind === 'project')
|
|
115
119
|
return record.github?.pushedAt ?? null;
|
|
116
|
-
if (record.kind ===
|
|
120
|
+
if (record.kind === 'resource')
|
|
117
121
|
return record.publishedAt ?? null;
|
|
118
122
|
return null;
|
|
119
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* When the record joined the directory.
|
|
126
|
+
*
|
|
127
|
+
* `addedAt` is the honest answer and the only one a record can state
|
|
128
|
+
* outright. The two fallbacks exist for records written before the
|
|
129
|
+
* field did:
|
|
130
|
+
*
|
|
131
|
+
* - `curation.reviewedAt` — what this function used to return on its
|
|
132
|
+
* own. It answers "when did a human last review this", not "when
|
|
133
|
+
* was this added", so a brand-new record (reviewed: false, no date)
|
|
134
|
+
* scored 0 and sank to the bottom of the one sort whose entire job
|
|
135
|
+
* is to surface it. Kept only as a second-best signal.
|
|
136
|
+
* - `github.createdAt` — the repository's own creation date. Wrong in
|
|
137
|
+
* absolute terms (the repo predates the directory entry) but it
|
|
138
|
+
* keeps unstamped records in a defensible order instead of a heap.
|
|
139
|
+
*/
|
|
120
140
|
function recordAddedAt(record) {
|
|
121
|
-
|
|
141
|
+
if (record.addedAt)
|
|
142
|
+
return record.addedAt;
|
|
143
|
+
if (record.curation?.reviewedAt)
|
|
144
|
+
return record.curation.reviewedAt;
|
|
145
|
+
if (record.kind === 'project')
|
|
146
|
+
return record.github?.createdAt ?? null;
|
|
147
|
+
return null;
|
|
122
148
|
}
|
|
123
149
|
export function applySort(items, sort) {
|
|
124
150
|
const arr = items.slice();
|
|
125
151
|
const ts = (s) => (s ? new Date(s).valueOf() : 0);
|
|
126
152
|
switch (sort) {
|
|
127
|
-
case
|
|
153
|
+
case 'most-starred':
|
|
128
154
|
arr.sort((a, b) => projectStars(b) - projectStars(a));
|
|
129
155
|
break;
|
|
130
|
-
|
|
131
|
-
|
|
156
|
+
// Date sorts break ties by name. Without it every record missing
|
|
157
|
+
// the key shares score 0 and the tail comes out in whatever order
|
|
158
|
+
// the index happened to be built in — stable, but arbitrary, and
|
|
159
|
+
// it reads as a bug to anyone paging that far.
|
|
160
|
+
case 'recently-updated':
|
|
161
|
+
arr.sort((a, b) => ts(recordUpdatedAt(b)) - ts(recordUpdatedAt(a)) ||
|
|
162
|
+
recordName(a).localeCompare(recordName(b)));
|
|
132
163
|
break;
|
|
133
|
-
case
|
|
134
|
-
arr.sort((a, b) => ts(recordAddedAt(b)) - ts(recordAddedAt(a)));
|
|
164
|
+
case 'recently-added':
|
|
165
|
+
arr.sort((a, b) => ts(recordAddedAt(b)) - ts(recordAddedAt(a)) || recordName(a).localeCompare(recordName(b)));
|
|
135
166
|
break;
|
|
136
|
-
case
|
|
167
|
+
case 'best-overall': {
|
|
137
168
|
const reviewed = (a) => (a.curation?.reviewed ? 1 : 0);
|
|
138
|
-
const visible = (a) => (a.visibility ===
|
|
169
|
+
const visible = (a) => (a.visibility === 'keep' ? 1 : 0);
|
|
139
170
|
arr.sort((a, b) => {
|
|
140
171
|
const reviewedDelta = reviewed(b) - reviewed(a);
|
|
141
172
|
if (reviewedDelta !== 0)
|
|
@@ -147,7 +178,7 @@ export function applySort(items, sort) {
|
|
|
147
178
|
});
|
|
148
179
|
break;
|
|
149
180
|
}
|
|
150
|
-
case
|
|
181
|
+
case 'alphabetical':
|
|
151
182
|
arr.sort((a, b) => recordName(a).localeCompare(recordName(b)));
|
|
152
183
|
break;
|
|
153
184
|
}
|
|
@@ -177,8 +208,8 @@ export function paginationPageList(active, count) {
|
|
|
177
208
|
const result = [];
|
|
178
209
|
for (const page of sorted) {
|
|
179
210
|
const previous = result.at(-1);
|
|
180
|
-
if (typeof previous ===
|
|
181
|
-
result.push(
|
|
211
|
+
if (typeof previous === 'number' && page - previous > 1)
|
|
212
|
+
result.push('ellipsis');
|
|
182
213
|
result.push(page);
|
|
183
214
|
}
|
|
184
215
|
return result;
|
|
@@ -208,37 +239,31 @@ export function filterRecords(items, f) {
|
|
|
208
239
|
// License ids are case-insensitive (GitHub emits `MIT`, curated ids
|
|
209
240
|
// are lowercase). Normalize the filter side too so `?license=MIT`
|
|
210
241
|
// and `?license=mit` are equivalent.
|
|
211
|
-
const licenses = f.licenses?.length
|
|
212
|
-
? new Set(f.licenses.map((l) => l.toLowerCase()))
|
|
213
|
-
: null;
|
|
242
|
+
const licenses = f.licenses?.length ? new Set(f.licenses.map((l) => l.toLowerCase())) : null;
|
|
214
243
|
const statuses = f.statuses?.length ? new Set(f.statuses) : null;
|
|
215
244
|
return items.filter((a) => {
|
|
216
245
|
// Text search — match name, owner, description, category, stack,
|
|
217
246
|
// stacks, platforms, tags, backend, architecture, stateManagement,
|
|
218
247
|
// bestFor, whyListed, license, status.
|
|
219
248
|
if (q) {
|
|
220
|
-
const repoUrl = a.kind ===
|
|
221
|
-
const ownerMatch = /github\.com\/([^/]+)\//.exec(repoUrl ??
|
|
222
|
-
const projectFields = a.kind ===
|
|
249
|
+
const repoUrl = a.kind === 'project' ? a.repoUrl : (a.links.github ?? '');
|
|
250
|
+
const ownerMatch = /github\.com\/([^/]+)\//.exec(repoUrl ?? '')?.[1]?.toLowerCase() ?? '';
|
|
251
|
+
const projectFields = a.kind === 'project'
|
|
223
252
|
? [
|
|
224
253
|
a.stack,
|
|
225
254
|
...a.stacks,
|
|
226
255
|
...a.platforms,
|
|
227
256
|
a.projectType,
|
|
228
|
-
a.difficulty ??
|
|
229
|
-
a.codebaseSize ??
|
|
257
|
+
a.difficulty ?? '',
|
|
258
|
+
a.codebaseSize ?? '',
|
|
230
259
|
...a.bestFor,
|
|
231
260
|
...a.whyListed,
|
|
232
|
-
a.github?.license ??
|
|
233
|
-
a.health?.status ? statusDisplay(a.health.status) :
|
|
261
|
+
a.github?.license ?? '',
|
|
262
|
+
a.health?.status ? statusDisplay(a.health.status) : '',
|
|
234
263
|
]
|
|
235
264
|
: [];
|
|
236
|
-
const resourceFields = a.kind ===
|
|
237
|
-
|
|
238
|
-
: [];
|
|
239
|
-
const entityFields = a.kind === "entity"
|
|
240
|
-
? [a.type, a.location ?? "", a.parent ?? ""]
|
|
241
|
-
: [];
|
|
265
|
+
const resourceFields = a.kind === 'resource' ? [a.type, a.topic, a.author ?? ''] : [];
|
|
266
|
+
const entityFields = a.kind === 'entity' ? [a.type, a.location ?? '', a.parent ?? ''] : [];
|
|
242
267
|
const haystack = [
|
|
243
268
|
recordName(a),
|
|
244
269
|
ownerMatch,
|
|
@@ -249,20 +274,20 @@ export function filterRecords(items, f) {
|
|
|
249
274
|
...resourceFields,
|
|
250
275
|
...entityFields,
|
|
251
276
|
]
|
|
252
|
-
.join(
|
|
277
|
+
.join(' ')
|
|
253
278
|
.toLowerCase();
|
|
254
279
|
if (!haystack.includes(q))
|
|
255
280
|
return false;
|
|
256
281
|
}
|
|
257
282
|
if (stacks) {
|
|
258
|
-
if (a.kind !==
|
|
283
|
+
if (a.kind !== 'project')
|
|
259
284
|
return false;
|
|
260
285
|
const allStacks = [a.stack, ...a.stacks].filter((stack) => Boolean(stack));
|
|
261
286
|
if (!allStacks.some((s) => stacks.has(s)))
|
|
262
287
|
return false;
|
|
263
288
|
}
|
|
264
289
|
if (platforms) {
|
|
265
|
-
if (a.kind !==
|
|
290
|
+
if (a.kind !== 'project')
|
|
266
291
|
return false;
|
|
267
292
|
if (!a.platforms.some((p) => platforms.has(p)))
|
|
268
293
|
return false;
|
|
@@ -278,7 +303,7 @@ export function filterRecords(items, f) {
|
|
|
278
303
|
return false;
|
|
279
304
|
}
|
|
280
305
|
if (licenses) {
|
|
281
|
-
if (a.kind !==
|
|
306
|
+
if (a.kind !== 'project')
|
|
282
307
|
return false;
|
|
283
308
|
// Curated `licenses` array takes precedence; fall back to the
|
|
284
309
|
// GitHub sync's `github.license.spdx_id` (recorded as the
|
|
@@ -294,7 +319,7 @@ export function filterRecords(items, f) {
|
|
|
294
319
|
// `[]`-suppresses-fallback semantic tested below.
|
|
295
320
|
const curated = (a.licenses ?? []);
|
|
296
321
|
const hasCurated = Array.isArray(a.licenses);
|
|
297
|
-
const synced = (a.github?.license ??
|
|
322
|
+
const synced = (a.github?.license ?? '').toLowerCase();
|
|
298
323
|
const candidates = new Set();
|
|
299
324
|
if (hasCurated) {
|
|
300
325
|
for (const l of curated)
|
|
@@ -309,7 +334,7 @@ export function filterRecords(items, f) {
|
|
|
309
334
|
return false;
|
|
310
335
|
}
|
|
311
336
|
if (statuses) {
|
|
312
|
-
if (a.kind !==
|
|
337
|
+
if (a.kind !== 'project')
|
|
313
338
|
return false;
|
|
314
339
|
const status = a.health?.status;
|
|
315
340
|
if (!status || !statuses.has(status))
|
|
@@ -323,20 +348,20 @@ export function filterRecords(items, f) {
|
|
|
323
348
|
});
|
|
324
349
|
}
|
|
325
350
|
/** Serialize filters back to a browsable URL under `pathPrefix`. */
|
|
326
|
-
export function hrefForFilters(f, pathPrefix =
|
|
351
|
+
export function hrefForFilters(f, pathPrefix = '') {
|
|
327
352
|
const params = searchParamsFromFilters(f);
|
|
328
|
-
return `${pathPrefix}${params.size ? `?${params}` :
|
|
353
|
+
return `${pathPrefix}${params.size ? `?${params}` : ''}`;
|
|
329
354
|
}
|
|
330
355
|
/**
|
|
331
356
|
* URL for a given result page, keeping every other filter. One
|
|
332
357
|
* definition for the server render and the client rebuild — they had
|
|
333
358
|
* drifted as two hand-rolled copies.
|
|
334
359
|
*/
|
|
335
|
-
export function hrefForPage(f, page, pathPrefix =
|
|
360
|
+
export function hrefForPage(f, page, pathPrefix = '') {
|
|
336
361
|
return hrefForFilters({ ...f, page }, pathPrefix);
|
|
337
362
|
}
|
|
338
363
|
/** URL that drops every filter but keeps the chosen sort. */
|
|
339
|
-
export function hrefForClearedFilters(f, pathPrefix =
|
|
364
|
+
export function hrefForClearedFilters(f, pathPrefix = '') {
|
|
340
365
|
return hrefForFilters({ sort: f.sort }, pathPrefix);
|
|
341
366
|
}
|
|
342
367
|
/**
|
|
@@ -357,18 +382,18 @@ export function pagePathHref(pathPrefix, page) {
|
|
|
357
382
|
}
|
|
358
383
|
/** Which taxonomy file backs each filter key. `tags` live in topics.yml. */
|
|
359
384
|
const CHIP_TAXONOMY_KIND = {
|
|
360
|
-
stacks:
|
|
361
|
-
platforms:
|
|
362
|
-
categories:
|
|
363
|
-
tags:
|
|
364
|
-
licenses:
|
|
385
|
+
stacks: 'stacks',
|
|
386
|
+
platforms: 'platforms',
|
|
387
|
+
categories: 'categories',
|
|
388
|
+
tags: 'topics',
|
|
389
|
+
licenses: 'licenses',
|
|
365
390
|
};
|
|
366
391
|
const CHIP_PREFIX = {
|
|
367
|
-
stacks:
|
|
368
|
-
platforms:
|
|
369
|
-
categories:
|
|
370
|
-
tags:
|
|
371
|
-
licenses:
|
|
392
|
+
stacks: 'Stack',
|
|
393
|
+
platforms: 'Platform',
|
|
394
|
+
categories: 'Category',
|
|
395
|
+
tags: 'Tag',
|
|
396
|
+
licenses: 'License',
|
|
372
397
|
};
|
|
373
398
|
/**
|
|
374
399
|
* Build a flat list of "active filter" chips for display + removal.
|
|
@@ -379,48 +404,46 @@ const CHIP_PREFIX = {
|
|
|
379
404
|
* `Stack: react-native`, the client `Stack: React Native`.
|
|
380
405
|
*/
|
|
381
406
|
export function activeFilterChips(f, context = {}) {
|
|
382
|
-
const { taxonomy, pathPrefix =
|
|
407
|
+
const { taxonomy, pathPrefix = '' } = context;
|
|
383
408
|
const out = [];
|
|
384
409
|
const display = (key, value) => {
|
|
385
410
|
const kind = CHIP_TAXONOMY_KIND[key];
|
|
386
|
-
const name = kind
|
|
387
|
-
? taxonomy?.[kind]?.find((entry) => entry.id === value)?.name
|
|
388
|
-
: undefined;
|
|
411
|
+
const name = kind ? taxonomy?.[kind]?.find((entry) => entry.id === value)?.name : undefined;
|
|
389
412
|
return `${CHIP_PREFIX[key]}: ${name ?? value}`;
|
|
390
413
|
};
|
|
391
414
|
if (f.q && f.q.trim()) {
|
|
392
|
-
out.push({ key:
|
|
415
|
+
out.push({ key: 'q', value: '', label: `“${f.q}”` });
|
|
393
416
|
}
|
|
394
417
|
for (const v of f.stacks ?? []) {
|
|
395
|
-
out.push({ key:
|
|
418
|
+
out.push({ key: 'stacks', value: v, label: display('stacks', v) });
|
|
396
419
|
}
|
|
397
420
|
for (const v of f.platforms ?? []) {
|
|
398
|
-
out.push({ key:
|
|
421
|
+
out.push({ key: 'platforms', value: v, label: display('platforms', v) });
|
|
399
422
|
}
|
|
400
423
|
for (const v of f.categories ?? []) {
|
|
401
|
-
out.push({ key:
|
|
424
|
+
out.push({ key: 'categories', value: v, label: display('categories', v) });
|
|
402
425
|
}
|
|
403
426
|
for (const v of f.tags ?? []) {
|
|
404
|
-
out.push({ key:
|
|
427
|
+
out.push({ key: 'tags', value: v, label: display('tags', v) });
|
|
405
428
|
}
|
|
406
429
|
for (const v of f.labels ?? []) {
|
|
407
|
-
out.push({ key:
|
|
430
|
+
out.push({ key: 'labels', value: v, label: labelDisplay(v) ?? v });
|
|
408
431
|
}
|
|
409
432
|
for (const v of f.licenses ?? []) {
|
|
410
|
-
out.push({ key:
|
|
433
|
+
out.push({ key: 'licenses', value: v, label: display('licenses', v) });
|
|
411
434
|
}
|
|
412
435
|
if (f.lens) {
|
|
413
|
-
out.push({ key:
|
|
436
|
+
out.push({ key: 'lens', value: f.lens, label: lensDisplay(f.lens) });
|
|
414
437
|
}
|
|
415
438
|
// Status: collapse the "stale,quiet" composite into a single chip.
|
|
416
439
|
if (f.statuses && f.statuses.length) {
|
|
417
|
-
const joined = f.statuses.join(
|
|
418
|
-
if (joined ===
|
|
419
|
-
out.push({ key:
|
|
440
|
+
const joined = f.statuses.join(',');
|
|
441
|
+
if (joined === 'stale,quiet') {
|
|
442
|
+
out.push({ key: 'statuses', value: 'stale,quiet', label: statusDisplay('needs-maintainer') });
|
|
420
443
|
}
|
|
421
444
|
else {
|
|
422
445
|
for (const v of f.statuses) {
|
|
423
|
-
out.push({ key:
|
|
446
|
+
out.push({ key: 'statuses', value: v, label: statusDisplay(v) });
|
|
424
447
|
}
|
|
425
448
|
}
|
|
426
449
|
}
|
|
@@ -439,7 +462,7 @@ export function activeFilterChips(f, context = {}) {
|
|
|
439
462
|
*/
|
|
440
463
|
export function removeFilter(f, key, value) {
|
|
441
464
|
let next;
|
|
442
|
-
if (key ===
|
|
465
|
+
if (key === 'q' || key === 'lens') {
|
|
443
466
|
// Scalar filters are removed entirely rather than treated like arrays.
|
|
444
467
|
const { [key]: _drop, ...rest } = f;
|
|
445
468
|
next = rest;
|
|
@@ -450,9 +473,7 @@ export function removeFilter(f, key, value) {
|
|
|
450
473
|
return f;
|
|
451
474
|
// `needs-maintainer` is represented internally by the composite
|
|
452
475
|
// statuses `stale,quiet`, but is displayed as one removable chip.
|
|
453
|
-
const filtered = key ===
|
|
454
|
-
? []
|
|
455
|
-
: current.filter((v) => v !== value);
|
|
476
|
+
const filtered = key === 'statuses' && value === 'stale,quiet' ? [] : current.filter((v) => v !== value);
|
|
456
477
|
if (filtered.length === 0) {
|
|
457
478
|
const { [key]: _drop, ...rest } = f;
|
|
458
479
|
next = rest;
|
|
@@ -481,15 +502,15 @@ export function buildFacets(items, options) {
|
|
|
481
502
|
if (!filters)
|
|
482
503
|
return null;
|
|
483
504
|
const next = { ...filters };
|
|
484
|
-
if (facet ===
|
|
505
|
+
if (facet === 'stacks')
|
|
485
506
|
delete next.stacks;
|
|
486
|
-
if (facet ===
|
|
507
|
+
if (facet === 'platforms')
|
|
487
508
|
delete next.platforms;
|
|
488
|
-
if (facet ===
|
|
509
|
+
if (facet === 'categories')
|
|
489
510
|
delete next.categories;
|
|
490
|
-
if (facet ===
|
|
511
|
+
if (facet === 'tags')
|
|
491
512
|
delete next.tags;
|
|
492
|
-
if (facet ===
|
|
513
|
+
if (facet === 'licenses')
|
|
493
514
|
delete next.licenses;
|
|
494
515
|
return next;
|
|
495
516
|
};
|
|
@@ -505,20 +526,20 @@ export function buildFacets(items, options) {
|
|
|
505
526
|
label: new Map(),
|
|
506
527
|
license: new Map(),
|
|
507
528
|
};
|
|
508
|
-
const stackScoped = itemsForFacet(
|
|
509
|
-
const platformScoped = itemsForFacet(
|
|
510
|
-
const categoryScoped = itemsForFacet(
|
|
511
|
-
const tagScoped = itemsForFacet(
|
|
512
|
-
const licenseScoped = itemsForFacet(
|
|
529
|
+
const stackScoped = itemsForFacet('stacks');
|
|
530
|
+
const platformScoped = itemsForFacet('platforms');
|
|
531
|
+
const categoryScoped = itemsForFacet('categories');
|
|
532
|
+
const tagScoped = itemsForFacet('tags');
|
|
533
|
+
const licenseScoped = itemsForFacet('licenses');
|
|
513
534
|
for (const a of stackScoped) {
|
|
514
|
-
if (a.kind ===
|
|
535
|
+
if (a.kind === 'project') {
|
|
515
536
|
const allStacks = new Set([a.stack, ...a.stacks].filter((stack) => Boolean(stack)));
|
|
516
537
|
for (const s of allStacks)
|
|
517
538
|
counts.stack.set(s, (counts.stack.get(s) ?? 0) + 1);
|
|
518
539
|
}
|
|
519
540
|
}
|
|
520
541
|
for (const a of platformScoped) {
|
|
521
|
-
if (a.kind ===
|
|
542
|
+
if (a.kind === 'project') {
|
|
522
543
|
for (const p of a.platforms)
|
|
523
544
|
counts.platform.set(p, (counts.platform.get(p) ?? 0) + 1);
|
|
524
545
|
}
|
|
@@ -533,15 +554,13 @@ export function buildFacets(items, options) {
|
|
|
533
554
|
}
|
|
534
555
|
}
|
|
535
556
|
for (const a of licenseScoped) {
|
|
536
|
-
if (a.kind ===
|
|
557
|
+
if (a.kind === 'project') {
|
|
537
558
|
// Consult the curated `licenses` array first (lowercased for
|
|
538
559
|
// facet-key consistency), then fall back to the GitHub-synced
|
|
539
560
|
// SPDX id. Mirrors the filterRecords branch so facet counts
|
|
540
561
|
// and filter results can never disagree.
|
|
541
|
-
const curated = (a.licenses ?? [])
|
|
542
|
-
|
|
543
|
-
.filter(Boolean);
|
|
544
|
-
const synced = (a.github?.license ?? "").toLowerCase();
|
|
562
|
+
const curated = (a.licenses ?? []).map((l) => l.toLowerCase()).filter(Boolean);
|
|
563
|
+
const synced = (a.github?.license ?? '').toLowerCase();
|
|
545
564
|
const ids = curated.length ? curated : synced ? [synced] : [];
|
|
546
565
|
for (const id of ids) {
|
|
547
566
|
counts.license.set(id, (counts.license.get(id) ?? 0) + 1);
|