@grove-dev/core 0.2.21 → 0.3.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.
Files changed (102) hide show
  1. package/README.md +16 -142
  2. package/dist/build-data.d.ts.map +1 -1
  3. package/dist/build-data.js +40 -4
  4. package/dist/build-data.js.map +1 -1
  5. package/dist/config.d.ts +3 -3
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +1 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/contributors.d.ts +24 -0
  10. package/dist/contributors.d.ts.map +1 -0
  11. package/dist/contributors.js +90 -0
  12. package/dist/contributors.js.map +1 -0
  13. package/dist/directory-client.d.ts +11 -0
  14. package/dist/directory-client.d.ts.map +1 -0
  15. package/dist/directory-client.js +11 -0
  16. package/dist/directory-client.js.map +1 -0
  17. package/dist/directory-display.d.ts +27 -0
  18. package/dist/directory-display.d.ts.map +1 -0
  19. package/dist/directory-display.js +104 -0
  20. package/dist/directory-display.js.map +1 -0
  21. package/dist/directory-format.d.ts +29 -0
  22. package/dist/directory-format.d.ts.map +1 -0
  23. package/dist/directory-format.js +72 -0
  24. package/dist/directory-format.js.map +1 -0
  25. package/dist/directory-lenses.d.ts +59 -0
  26. package/dist/directory-lenses.d.ts.map +1 -0
  27. package/dist/directory-lenses.js +199 -0
  28. package/dist/directory-lenses.js.map +1 -0
  29. package/dist/directory-repo.d.ts +20 -0
  30. package/dist/directory-repo.d.ts.map +1 -0
  31. package/dist/directory-repo.js +31 -0
  32. package/dist/directory-repo.js.map +1 -0
  33. package/dist/directory-scores.d.ts +37 -0
  34. package/dist/directory-scores.d.ts.map +1 -0
  35. package/dist/directory-scores.js +76 -0
  36. package/dist/directory-scores.js.map +1 -0
  37. package/dist/directory-search.d.ts +119 -0
  38. package/dist/directory-search.d.ts.map +1 -0
  39. package/dist/directory-search.js +398 -0
  40. package/dist/directory-search.js.map +1 -0
  41. package/dist/directory-taxonomy.d.ts +11 -0
  42. package/dist/directory-taxonomy.d.ts.map +1 -0
  43. package/dist/directory-taxonomy.js +22 -0
  44. package/dist/directory-taxonomy.js.map +1 -0
  45. package/dist/index.d.ts +13 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +20 -7
  48. package/dist/index.js.map +1 -1
  49. package/dist/llms.d.ts.map +1 -1
  50. package/dist/llms.js +11 -4
  51. package/dist/llms.js.map +1 -1
  52. package/dist/prepare.d.ts +18 -0
  53. package/dist/prepare.d.ts.map +1 -0
  54. package/dist/prepare.js +72 -0
  55. package/dist/prepare.js.map +1 -0
  56. package/dist/schema.d.ts +54 -0
  57. package/dist/schema.d.ts.map +1 -1
  58. package/dist/schema.js +30 -0
  59. package/dist/schema.js.map +1 -1
  60. package/dist/sitemap.d.ts.map +1 -1
  61. package/dist/sitemap.js +4 -1
  62. package/dist/sitemap.js.map +1 -1
  63. package/dist/validate.d.ts.map +1 -1
  64. package/dist/validate.js +41 -0
  65. package/dist/validate.js.map +1 -1
  66. package/package.json +9 -2
  67. package/dist/build-data.test.d.ts +0 -2
  68. package/dist/build-data.test.d.ts.map +0 -1
  69. package/dist/build-data.test.js +0 -171
  70. package/dist/build-data.test.js.map +0 -1
  71. package/dist/decisions.test.d.ts +0 -2
  72. package/dist/decisions.test.d.ts.map +0 -1
  73. package/dist/decisions.test.js +0 -205
  74. package/dist/decisions.test.js.map +0 -1
  75. package/dist/enrich.test.d.ts +0 -2
  76. package/dist/enrich.test.d.ts.map +0 -1
  77. package/dist/enrich.test.js +0 -105
  78. package/dist/enrich.test.js.map +0 -1
  79. package/dist/github-client.test.d.ts +0 -2
  80. package/dist/github-client.test.d.ts.map +0 -1
  81. package/dist/github-client.test.js +0 -176
  82. package/dist/github-client.test.js.map +0 -1
  83. package/dist/github.test.d.ts +0 -2
  84. package/dist/github.test.d.ts.map +0 -1
  85. package/dist/github.test.js +0 -73
  86. package/dist/github.test.js.map +0 -1
  87. package/dist/health.test.d.ts +0 -2
  88. package/dist/health.test.d.ts.map +0 -1
  89. package/dist/health.test.js +0 -183
  90. package/dist/health.test.js.map +0 -1
  91. package/dist/importer.test.d.ts +0 -2
  92. package/dist/importer.test.d.ts.map +0 -1
  93. package/dist/importer.test.js +0 -173
  94. package/dist/importer.test.js.map +0 -1
  95. package/dist/slug.test.d.ts +0 -2
  96. package/dist/slug.test.d.ts.map +0 -1
  97. package/dist/slug.test.js +0 -101
  98. package/dist/slug.test.js.map +0 -1
  99. package/dist/validate.test.d.ts +0 -2
  100. package/dist/validate.test.d.ts.map +0 -1
  101. package/dist/validate.test.js +0 -249
  102. package/dist/validate.test.js.map +0 -1
@@ -1,173 +0,0 @@
1
- /**
2
- * @grove-dev/core — markdown.ts importer unit tests.
3
- *
4
- * The `markdown.ts` module houses `parseAwesomeMarkdown`,
5
- * `detectGithubRepo`, `importAwesomeList`, and
6
- * `writeImportedRecords`. `importer.ts` re-exports the last two.
7
- *
8
- * Tests cover:
9
- * - detectGithubRepo: URL + slug derivation, edge cases
10
- * - parseAwesomeMarkdown: section grouping, link extraction,
11
- * dedup, empty input
12
- * - writeImportedRecords: real tmpdir round-trip
13
- */
14
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
15
- import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
16
- import { tmpdir } from "node:os";
17
- import { join } from "node:path";
18
- import { detectGithubRepo, parseAwesomeMarkdown, importAwesomeList, writeImportedRecords, } from "./markdown.js";
19
- describe("detectGithubRepo", () => {
20
- it("returns the canonical GitHub URL for a github.com URL", () => {
21
- expect(detectGithubRepo("https://github.com/owner/repo")).toBe("https://github.com/owner/repo");
22
- });
23
- it("strips a trailing .git from the repo name", () => {
24
- expect(detectGithubRepo("https://github.com/owner/repo.git")).toBe("https://github.com/owner/repo");
25
- });
26
- it("returns undefined for an empty / non-URL string", () => {
27
- expect(detectGithubRepo("")).toBeUndefined();
28
- expect(detectGithubRepo("No link here.")).toBeUndefined();
29
- });
30
- it("returns undefined for a non-github URL (e.g. gitlab)", () => {
31
- expect(detectGithubRepo("https://gitlab.com/owner/repo")).toBeUndefined();
32
- });
33
- it("returns undefined for reserved GitHub paths (issues, pulls, etc.)", () => {
34
- // `https://github.com/owner/issues` is a tab on the repo page,
35
- // not a repo. detectGithubRepo must return undefined for
36
- // these so they don't get imported as a record.
37
- expect(detectGithubRepo("https://github.com/owner/issues")).toBeUndefined();
38
- expect(detectGithubRepo("https://github.com/owner/pulls")).toBeUndefined();
39
- });
40
- });
41
- describe("parseAwesomeMarkdown", () => {
42
- it("returns an empty result for empty input", () => {
43
- const r = parseAwesomeMarkdown("");
44
- expect(r.records).toEqual([]);
45
- expect(r.report.imported).toBe(0);
46
- expect(r.report.skipped).toBe(0);
47
- });
48
- it("groups links by section heading and extracts name + url", () => {
49
- const md = [
50
- "# Awesome",
51
- "",
52
- "## Tools",
53
- "",
54
- "- [Repo One](https://github.com/owner/one) - A great tool",
55
- "- [Repo Two](https://github.com/owner/two)",
56
- "",
57
- "## Libraries",
58
- "",
59
- "- [Repo Three](https://github.com/owner/three) - A nice library",
60
- ].join("\n");
61
- const r = parseAwesomeMarkdown(md);
62
- expect(r.report.imported).toBe(3);
63
- expect(r.records.map((rec) => rec.name)).toEqual([
64
- "Repo One",
65
- "Repo Two",
66
- "Repo Three",
67
- ]);
68
- // Category comes from the section heading.
69
- expect(r.records[0]?.category).toBe("Tools");
70
- expect(r.records[2]?.category).toBe("Libraries");
71
- });
72
- it("dedupes repeated names (same label slug → -2, -3, ...)", () => {
73
- // The parser's collision counter is NAME-based (via
74
- // `uniqueSlug`), not URL-based. Two lines with different
75
- // names both pointing at the same URL produce two records;
76
- // two lines with the same name produce the second with a
77
- // -2 suffix. Pinning the actual behaviour so a future
78
- // switch to URL-based dedup is visible.
79
- const md = [
80
- "## Tools",
81
- "",
82
- "- [A](https://github.com/owner/repo) - first",
83
- "- [A](https://github.com/owner/other) - second",
84
- ].join("\n");
85
- const r = parseAwesomeMarkdown(md);
86
- expect(r.report.imported).toBe(2);
87
- expect(r.records.map((rec) => rec.slug)).toEqual(["a", "a-2"]);
88
- });
89
- it("skips lines that are not list items (text between sections)", () => {
90
- const md = [
91
- "## Tools",
92
- "",
93
- "Some prose describing the section.",
94
- "",
95
- "- [Real](https://github.com/owner/real) - A real repo",
96
- ].join("\n");
97
- const r = parseAwesomeMarkdown(md);
98
- expect(r.records).toHaveLength(1);
99
- expect(r.records[0]?.name).toBe("Real");
100
- });
101
- });
102
- describe("writeImportedRecords — filesystem round-trip", () => {
103
- let cwd;
104
- beforeEach(async () => {
105
- cwd = await mkdtemp(join(tmpdir(), "grove-importer-test-"));
106
- });
107
- afterEach(async () => {
108
- await rm(cwd, { recursive: true, force: true });
109
- });
110
- it("writes one YAML file per record to the target directory", async () => {
111
- const result = {
112
- records: [
113
- {
114
- name: "First",
115
- slug: "first",
116
- description: "first record",
117
- category: "tools",
118
- links: { github: "https://github.com/owner/first" },
119
- },
120
- {
121
- name: "Second",
122
- slug: "second",
123
- description: "second record",
124
- category: "tools",
125
- links: { github: "https://github.com/owner/second" },
126
- },
127
- ],
128
- report: { imported: 2, skipped: 0, categories: [], duplicateSlugs: 0, tocSkipped: 0, anchorLinksSkipped: 0 },
129
- };
130
- const { written, dir } = await writeImportedRecords(result, cwd, "project-directory");
131
- expect(written).toBe(2);
132
- expect(dir).toContain("data/records");
133
- const first = await readFile(join(dir, "first.yml"), "utf8");
134
- expect(first).toContain("name: First");
135
- expect(first).toContain("kind: project");
136
- expect(first).toContain("slug: first");
137
- });
138
- it("returns 0 and writes nothing for an empty input list", async () => {
139
- const result = {
140
- records: [],
141
- report: { imported: 0, skipped: 0, categories: [], duplicateSlugs: 0, tocSkipped: 0, anchorLinksSkipped: 0 },
142
- };
143
- const { written } = await writeImportedRecords(result, cwd, "project-directory");
144
- expect(written).toBe(0);
145
- });
146
- });
147
- describe("importAwesomeList — end-to-end on a local README.md", () => {
148
- let cwd;
149
- beforeEach(async () => {
150
- cwd = await mkdtemp(join(tmpdir(), "grove-import-test-"));
151
- });
152
- afterEach(async () => {
153
- await rm(cwd, { recursive: true, force: true });
154
- });
155
- it("parses a local markdown file and returns ImportedRecord[]", async () => {
156
- const md = [
157
- "# Awesome Stuff",
158
- "",
159
- "## Frameworks",
160
- "",
161
- "- [Framework A](https://github.com/owner/framework-a) - A nice framework",
162
- "- [Framework B](https://github.com/owner/framework-b)",
163
- ].join("\n");
164
- const readme = join(cwd, "README.md");
165
- await writeFile(readme, md);
166
- const result = await importAwesomeList(readme);
167
- expect(result.report.imported).toBe(2);
168
- expect(result.records).toHaveLength(2);
169
- expect(result.records[0]?.name).toBe("Framework A");
170
- expect(result.records[0]?.category).toBe("Frameworks");
171
- });
172
- });
173
- //# sourceMappingURL=importer.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"importer.test.js","sourceRoot":"","sources":["../src/importer.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=slug.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slug.test.d.ts","sourceRoot":"","sources":["../src/slug.test.ts"],"names":[],"mappings":""}
package/dist/slug.test.js DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * @grove-dev/core — slug helpers unit tests.
3
- *
4
- * Coverage:
5
- * - slugify: empty input, unicode, smart quotes, length cap
6
- * - uniqueSlug: collision counter, repeated calls with same input
7
- * (idempotency), unknown-fallback when slugify yields ""
8
- */
9
- import { describe, it, expect } from "vitest";
10
- import { slugify, uniqueSlug } from "./slug.js";
11
- describe("slugify", () => {
12
- it("lower-cases and replaces runs of non-alphanumerics with single hyphens", () => {
13
- expect(slugify("Hello World")).toBe("hello-world");
14
- expect(slugify("Foo Bar Baz")).toBe("foo-bar-baz");
15
- expect(slugify("a__b--c")).toBe("a-b-c");
16
- });
17
- it("strips straight ASCII single quotes (the only quote the regex matches)", () => {
18
- // The regex /['']/g contains two ASCII apostrophes (0x27) — not
19
- // the curly U+2018 / U+2019 characters a casual read of the
20
- // source might suggest. Pinning this behaviour here so a
21
- // well-meaning future refactor (e.g. "let's also strip smart
22
- // quotes") doesn't silently change every slug with a
23
- // possessive in the name. The actual product input rarely
24
- // has straight apostrophes because record names go through a
25
- // curator; the smart-quote path below is the one that matters
26
- // for copy-paste from docs and READMEs.
27
- expect(slugify("Don't Stop")).toBe("dont-stop");
28
- expect(slugify("It's a Test")).toBe("its-a-test");
29
- });
30
- it("does NOT strip smart quotes (U+2018 / U+2019) — they become hyphens", () => {
31
- // The opposite of the previous test. Smart quotes are NOT in
32
- // the regex's character class, so they fall through to the
33
- // [^a-z0-9]+ replace and become a single "-". A user pasting
34
- // a record name with a curly apostrophe from a Markdown
35
- // document gets a slug with a hyphen at that position, not a
36
- // silently-dropped character. Document the behaviour here so
37
- // it cannot drift.
38
- expect(slugify("Don\u2019t Stop")).toBe("don-t-stop");
39
- expect(slugify("It\u2019s a Test")).toBe("it-s-a-test");
40
- });
41
- it("trims leading and trailing hyphens", () => {
42
- expect(slugify("---hello---")).toBe("hello");
43
- expect(slugify("!@#hello!@#")).toBe("hello");
44
- });
45
- it("returns an empty string for non-ASCII-only input (e.g. emoji, accented)", () => {
46
- // Accented characters are dropped by the [^a-z0-9]+ replace and
47
- // then the leading/trailing hyphen trim removes the surrounding
48
- // hyphens. Emoji is the same. This test pins the exact empty
49
- // string — not "item" — because uniqueSlug is the function that
50
- // falls back to "item", not slugify itself.
51
- expect(slugify("café")).toBe("caf");
52
- expect(slugify("🚀 rocket")).toBe("rocket");
53
- expect(slugify("über")).toBe("ber");
54
- });
55
- it("truncates to 80 characters", () => {
56
- const long = "a".repeat(200);
57
- expect(slugify(long).length).toBe(80);
58
- });
59
- it("returns an empty string for empty / whitespace-only input", () => {
60
- expect(slugify("")).toBe("");
61
- expect(slugify(" ")).toBe("");
62
- expect(slugify("---")).toBe("");
63
- });
64
- });
65
- describe("uniqueSlug", () => {
66
- it("returns the slug unchanged on first occurrence", () => {
67
- const seen = new Map();
68
- expect(uniqueSlug("Hello World", seen)).toBe("hello-world");
69
- expect(seen.get("hello-world")).toBe(1);
70
- });
71
- it("appends -2, -3, ... on subsequent collisions", () => {
72
- const seen = new Map();
73
- expect(uniqueSlug("Hello", seen)).toBe("hello");
74
- expect(uniqueSlug("Hello", seen)).toBe("hello-2");
75
- expect(uniqueSlug("Hello", seen)).toBe("hello-3");
76
- // The seen map reflects the latest count, not just the touched
77
- // counter — second occurrence is "hello-2" so the next collision
78
- // should be "hello-3", not "hello-2" again.
79
- expect(seen.get("hello")).toBe(3);
80
- });
81
- it("repeated calls with the same input are independent (caller owns the seen map)", () => {
82
- // Two callers passing the same input string but two different
83
- // seen maps each get a fresh first-occurrence behaviour. This
84
- // pins the rule that the seen map is the source of truth — the
85
- // function does not maintain internal state.
86
- const a = new Map();
87
- const b = new Map();
88
- expect(uniqueSlug("Foo", a)).toBe("foo");
89
- expect(uniqueSlug("Foo", b)).toBe("foo");
90
- expect(uniqueSlug("Foo", a)).toBe("foo-2");
91
- expect(uniqueSlug("Foo", b)).toBe("foo-2");
92
- });
93
- it("falls back to 'item' when slugify returns empty (emoji / accents only)", () => {
94
- const seen = new Map();
95
- // slugify("🚀") → "" → uniqueSlug substitutes "item"
96
- expect(uniqueSlug("🚀", seen)).toBe("item");
97
- // second collision: "item-2"
98
- expect(uniqueSlug("🚀", seen)).toBe("item-2");
99
- });
100
- });
101
- //# sourceMappingURL=slug.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slug.test.js","sourceRoot":"","sources":["../src/slug.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=validate.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate.test.d.ts","sourceRoot":"","sources":["../src/validate.test.ts"],"names":[],"mappings":""}
@@ -1,249 +0,0 @@
1
- /**
2
- * @grove-dev/core — `validateProject` unit tests.
3
- *
4
- * The brief calls out two silent try/catch blocks in validate.ts:
5
- * - line 70: `readdir(recordsDir).catch(() => [])` — missing
6
- * records dir is silently treated as zero records.
7
- * Tests pin this behaviour so a future "throw on
8
- * missing dir" change is visible.
9
- * - line 54-69: the Zod parse step at line 109 is wrapped in a
10
- * try/catch that produces structured `zod_error`
11
- * issues. Tests cover BOTH the valid-input path
12
- * (no issues) and the malformed-input path
13
- * (one or more issues with the right codes).
14
- *
15
- * Tests use `tmpdir()` (per the brief — no fs mocks) and chdir
16
- * into the test dir for the duration of each test (validateProject
17
- * resolves paths against `process.cwd()`).
18
- */
19
- import { describe, it, expect } from "vitest";
20
- import { mkdir, writeFile, rm, mkdtemp } from "node:fs/promises";
21
- import { tmpdir } from "node:os";
22
- import { join } from "node:path";
23
- import { validateProject } from "./validate.js";
24
- function makeConfig(overrides = {}) {
25
- return {
26
- site: { name: "test", tagline: "test" },
27
- paths: {
28
- recordsDir: "data/records",
29
- generatedDir: "data/generated",
30
- health: "data/health.yml",
31
- decisions: "data/decisions.yml",
32
- },
33
- blueprint: "project-directory",
34
- nav: [],
35
- theme: {},
36
- integrations: {},
37
- ...overrides,
38
- };
39
- }
40
- /**
41
- * Run `fn` with `process.cwd()` set to a fresh tmpdir. Restores
42
- * the original CWD in a finally block so test failures (or thrown
43
- * asserts) cannot leave the runner in a deleted directory.
44
- *
45
- * `mkdtemp` is the right helper here: `os.tmpdir({prefix})` (the
46
- * Node 22 form) does NOT create a subdir, it just returns the
47
- * env TMPDIR. `mkdtemp(prefix + '-')` creates a unique directory
48
- * under the env TMPDIR.
49
- */
50
- async function withTmpCwd(prefix, fn) {
51
- const cwd = await mkdtemp(join(tmpdir(), prefix + "-"));
52
- const original = process.cwd();
53
- process.chdir(cwd);
54
- try {
55
- return await fn(cwd);
56
- }
57
- finally {
58
- process.chdir(original);
59
- await rm(cwd, { recursive: true, force: true });
60
- }
61
- }
62
- describe("validateProject — happy path", () => {
63
- it("returns ok=true with no issues when the records dir is empty", async () => {
64
- await withTmpCwd("grove-validate-empty-", async (cwd) => {
65
- await mkdir(join(cwd, "data", "records"), { recursive: true });
66
- const result = await validateProject(makeConfig());
67
- expect(result.ok).toBe(true);
68
- expect(result.errors).toEqual([]);
69
- expect(result.warnings).toEqual([]);
70
- });
71
- });
72
- it("validates a single well-formed record and returns ok=true", async () => {
73
- await withTmpCwd("grove-validate-ok-", async (cwd) => {
74
- await mkdir(join(cwd, "data", "records"), { recursive: true });
75
- await writeFile(join(cwd, "data", "records", "demo.yml"), [
76
- "kind: project",
77
- "slug: demo",
78
- "name: Demo",
79
- "description: a demo",
80
- "category: tools",
81
- "links: {}",
82
- "curation: { reviewed: false, labels: [], lenses: [] }",
83
- "scores: {}",
84
- ].join("\n"));
85
- const result = await validateProject(makeConfig());
86
- expect(result.ok).toBe(true);
87
- expect(result.errors).toEqual([]);
88
- expect(result.warnings).toEqual([]);
89
- });
90
- });
91
- it("emits a slug_mismatch warning when filename and record.slug differ", async () => {
92
- await withTmpCwd("grove-validate-mismatch-", async (cwd) => {
93
- await mkdir(join(cwd, "data", "records"), { recursive: true });
94
- // Filename is "demo", record.slug is "different-slug". This
95
- // is a warning, not an error — the record is still considered
96
- // valid; the warning helps curators notice the mismatch.
97
- await writeFile(join(cwd, "data", "records", "demo.yml"), [
98
- "kind: project",
99
- "slug: different-slug",
100
- "name: Demo",
101
- "description: a demo",
102
- "category: tools",
103
- "links: {}",
104
- "curation: { reviewed: false, labels: [], lenses: [] }",
105
- "scores: {}",
106
- ].join("\n"));
107
- const result = await validateProject(makeConfig());
108
- expect(result.ok).toBe(true); // warnings do not fail by default
109
- expect(result.warnings.some((w) => w.code === "slug_mismatch")).toBe(true);
110
- });
111
- });
112
- });
113
- describe("validateProject — silent try/catch at line 70 (missing records dir)", () => {
114
- it("returns a missing_records_dir error, NOT a throw, when recordsDir does not exist", async () => {
115
- await withTmpCwd("grove-validate-missing-", async () => {
116
- // validateProject at line 60 checks `exists(recordsDir)` first
117
- // and emits a `missing_records_dir` error. The brief flagged
118
- // the readdir at line 70 with a `.catch(() => [])` as audit
119
- // concern. Pin the *current* behaviour: a missing dir is a
120
- // top-level error with the code `missing_records_dir`, not a
121
- // throw and not a silent pass.
122
- const config = makeConfig({
123
- paths: {
124
- recordsDir: "data/does-not-exist",
125
- generatedDir: "data/generated",
126
- health: "data/health.yml",
127
- decisions: "data/decisions.yml",
128
- },
129
- });
130
- const result = await validateProject(config);
131
- expect(result.ok).toBe(false);
132
- expect(result.errors.some((e) => e.code === "missing_records_dir")).toBe(true);
133
- });
134
- });
135
- });
136
- describe("validateProject — silent try/catch at lines 54-69 (Zod parse)", () => {
137
- it("emits zod_error issues for a record missing required fields", async () => {
138
- await withTmpCwd("grove-validate-bad-fields-", async (cwd) => {
139
- await mkdir(join(cwd, "data", "records"), { recursive: true });
140
- // project requires `name` (min 1). The minimal stub here is
141
- // missing it, so Zod should reject with one or more
142
- // zod_error issues — the validation pipeline's structured
143
- // error path that CLI's `grove validate` renders.
144
- await writeFile(join(cwd, "data", "records", "broken.yml"), [
145
- "kind: project",
146
- "slug: broken",
147
- "description: missing the name field",
148
- "category: tools",
149
- "links: {}",
150
- "curation: { reviewed: false, labels: [], lenses: [] }",
151
- "scores: {}",
152
- ].join("\n"));
153
- const result = await validateProject(makeConfig());
154
- expect(result.ok).toBe(false);
155
- expect(result.errors.length).toBeGreaterThan(0);
156
- // The error code from the Zod try/catch is "zod_error", and
157
- // the path should mention the missing field ("name").
158
- const zodErr = result.errors.find((e) => e.code === "zod_error");
159
- expect(zodErr).toBeDefined();
160
- expect(zodErr?.message).toContain("name");
161
- });
162
- });
163
- it("emits a schema_error (not zod_error) when the YAML is an empty mapping", async () => {
164
- await withTmpCwd("grove-validate-empty-yml-", async (cwd) => {
165
- await mkdir(join(cwd, "data", "records"), { recursive: true });
166
- // Edge case the brief flagged: parseYaml returns `null` for
167
- // an empty document, and the code at line 79-88 treats that
168
- // as `schema_error` (not zod_error) because the value never
169
- // reaches the Zod parse step. Pin the specific code.
170
- await writeFile(join(cwd, "data", "records", "empty.yml"), "");
171
- const result = await validateProject(makeConfig());
172
- expect(result.ok).toBe(false);
173
- const e = result.errors.find((err) => err.code === "schema_error");
174
- expect(e).toBeDefined();
175
- expect(e?.message).toContain("empty");
176
- });
177
- });
178
- it("strict mode: a single warning causes ok=false", async () => {
179
- await withTmpCwd("grove-validate-strict-", async (cwd) => {
180
- await mkdir(join(cwd, "data", "records"), { recursive: true });
181
- // Pin the strict-mode contract: any warning becomes a
182
- // failure. The `slug_mismatch` test above returns ok=true
183
- // (warnings don't fail by default) — the same record with
184
- // `strict: true` returns ok=false.
185
- await writeFile(join(cwd, "data", "records", "demo.yml"), [
186
- "kind: project",
187
- "slug: different-slug",
188
- "name: Demo",
189
- "description: a demo",
190
- "category: tools",
191
- "links: {}",
192
- "curation: { reviewed: false, labels: [], lenses: [] }",
193
- "scores: {}",
194
- ].join("\n"));
195
- const lenient = await validateProject(makeConfig());
196
- expect(lenient.ok).toBe(true);
197
- const strict = await validateProject(makeConfig(), { strict: true });
198
- expect(strict.ok).toBe(false);
199
- });
200
- });
201
- });
202
- describe("validateProject — decision / health cross-references", () => {
203
- it("emits unknown_decision_record when a decisions.yml references a non-existent slug", async () => {
204
- await withTmpCwd("grove-validate-xref-decision-", async (cwd) => {
205
- await mkdir(join(cwd, "data", "records"), { recursive: true });
206
- await writeFile(join(cwd, "data", "records", "real.yml"), [
207
- "kind: project",
208
- "slug: real",
209
- "name: Real",
210
- "description: real record",
211
- "category: tools",
212
- "links: {}",
213
- "curation: { reviewed: false, labels: [], lenses: [] }",
214
- "scores: {}",
215
- ].join("\n"));
216
- // decisions.yml points at "ghost" which is not in records/
217
- await writeFile(join(cwd, "data", "decisions.yml"), [
218
- "decisions:",
219
- " - id: ghost",
220
- " decision:",
221
- " visibility: hide",
222
- " reason: tested as unknown reference",
223
- ].join("\n"));
224
- const result = await validateProject(makeConfig());
225
- const e = result.errors.find((err) => err.code === "unknown_decision_record");
226
- expect(e).toBeDefined();
227
- expect(e?.message).toContain("ghost");
228
- });
229
- });
230
- it("emits missing_health when a record points at GitHub but no health.yml exists", async () => {
231
- await withTmpCwd("grove-validate-xref-health-", async (cwd) => {
232
- await mkdir(join(cwd, "data", "records"), { recursive: true });
233
- await writeFile(join(cwd, "data", "records", "needs-health.yml"), [
234
- "kind: project",
235
- "slug: needs-health",
236
- "name: Needs Health",
237
- "description: this record points at GitHub",
238
- "category: tools",
239
- "links: { github: 'https://github.com/owner/repo' }",
240
- "curation: { reviewed: false, labels: [], lenses: [] }",
241
- "scores: {}",
242
- ].join("\n"));
243
- const result = await validateProject(makeConfig());
244
- const w = result.warnings.find((warn) => warn.code === "missing_health_file");
245
- expect(w).toBeDefined();
246
- });
247
- });
248
- });
249
- //# sourceMappingURL=validate.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate.test.js","sourceRoot":"","sources":["../src/validate.test.ts"],"names":[],"mappings":"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"}