@lepid-labs/weft-core 0.1.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.
Files changed (266) hide show
  1. package/LICENSE +21 -0
  2. package/dist/anchors/__tests__/markdown.test.d.ts +2 -0
  3. package/dist/anchors/__tests__/markdown.test.d.ts.map +1 -0
  4. package/dist/anchors/__tests__/markdown.test.js +130 -0
  5. package/dist/anchors/__tests__/markdown.test.js.map +1 -0
  6. package/dist/anchors/__tests__/openapi.test.d.ts +2 -0
  7. package/dist/anchors/__tests__/openapi.test.d.ts.map +1 -0
  8. package/dist/anchors/__tests__/openapi.test.js +115 -0
  9. package/dist/anchors/__tests__/openapi.test.js.map +1 -0
  10. package/dist/anchors/index.d.ts +35 -0
  11. package/dist/anchors/index.d.ts.map +1 -0
  12. package/dist/anchors/index.js +68 -0
  13. package/dist/anchors/index.js.map +1 -0
  14. package/dist/anchors/markdown.d.ts +15 -0
  15. package/dist/anchors/markdown.d.ts.map +1 -0
  16. package/dist/anchors/markdown.js +71 -0
  17. package/dist/anchors/markdown.js.map +1 -0
  18. package/dist/anchors/openapi.d.ts +24 -0
  19. package/dist/anchors/openapi.d.ts.map +1 -0
  20. package/dist/anchors/openapi.js +75 -0
  21. package/dist/anchors/openapi.js.map +1 -0
  22. package/dist/browser.d.ts +10 -0
  23. package/dist/browser.d.ts.map +1 -0
  24. package/dist/browser.js +8 -0
  25. package/dist/browser.js.map +1 -0
  26. package/dist/config.d.ts +47 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +393 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/config.test.d.ts +2 -0
  31. package/dist/config.test.d.ts.map +1 -0
  32. package/dist/config.test.js +454 -0
  33. package/dist/config.test.js.map +1 -0
  34. package/dist/content.d.ts +37 -0
  35. package/dist/content.d.ts.map +1 -0
  36. package/dist/content.js +57 -0
  37. package/dist/content.js.map +1 -0
  38. package/dist/content.test.d.ts +2 -0
  39. package/dist/content.test.d.ts.map +1 -0
  40. package/dist/content.test.js +100 -0
  41. package/dist/content.test.js.map +1 -0
  42. package/dist/contributions.d.ts +64 -0
  43. package/dist/contributions.d.ts.map +1 -0
  44. package/dist/contributions.js +184 -0
  45. package/dist/contributions.js.map +1 -0
  46. package/dist/contributions.test.d.ts +2 -0
  47. package/dist/contributions.test.d.ts.map +1 -0
  48. package/dist/contributions.test.js +202 -0
  49. package/dist/contributions.test.js.map +1 -0
  50. package/dist/fetch/auth.d.ts +15 -0
  51. package/dist/fetch/auth.d.ts.map +1 -0
  52. package/dist/fetch/auth.js +38 -0
  53. package/dist/fetch/auth.js.map +1 -0
  54. package/dist/fetch/cache.d.ts +25 -0
  55. package/dist/fetch/cache.d.ts.map +1 -0
  56. package/dist/fetch/cache.js +68 -0
  57. package/dist/fetch/cache.js.map +1 -0
  58. package/dist/fetch/cache.test.d.ts +2 -0
  59. package/dist/fetch/cache.test.d.ts.map +1 -0
  60. package/dist/fetch/cache.test.js +73 -0
  61. package/dist/fetch/cache.test.js.map +1 -0
  62. package/dist/fetch/clone.d.ts +26 -0
  63. package/dist/fetch/clone.d.ts.map +1 -0
  64. package/dist/fetch/clone.js +105 -0
  65. package/dist/fetch/clone.js.map +1 -0
  66. package/dist/fetch/fetch.test.d.ts +2 -0
  67. package/dist/fetch/fetch.test.d.ts.map +1 -0
  68. package/dist/fetch/fetch.test.js +167 -0
  69. package/dist/fetch/fetch.test.js.map +1 -0
  70. package/dist/fetch/resolve.d.ts +34 -0
  71. package/dist/fetch/resolve.d.ts.map +1 -0
  72. package/dist/fetch/resolve.js +59 -0
  73. package/dist/fetch/resolve.js.map +1 -0
  74. package/dist/freshness.d.ts +29 -0
  75. package/dist/freshness.d.ts.map +1 -0
  76. package/dist/freshness.js +170 -0
  77. package/dist/freshness.js.map +1 -0
  78. package/dist/freshness.test.d.ts +2 -0
  79. package/dist/freshness.test.d.ts.map +1 -0
  80. package/dist/freshness.test.js +209 -0
  81. package/dist/freshness.test.js.map +1 -0
  82. package/dist/frontmatter.d.ts +15 -0
  83. package/dist/frontmatter.d.ts.map +1 -0
  84. package/dist/frontmatter.js +24 -0
  85. package/dist/frontmatter.js.map +1 -0
  86. package/dist/frontmatter.test.d.ts +2 -0
  87. package/dist/frontmatter.test.d.ts.map +1 -0
  88. package/dist/frontmatter.test.js +54 -0
  89. package/dist/frontmatter.test.js.map +1 -0
  90. package/dist/git.d.ts +55 -0
  91. package/dist/git.d.ts.map +1 -0
  92. package/dist/git.js +127 -0
  93. package/dist/git.js.map +1 -0
  94. package/dist/git.test.d.ts +2 -0
  95. package/dist/git.test.d.ts.map +1 -0
  96. package/dist/git.test.js +198 -0
  97. package/dist/git.test.js.map +1 -0
  98. package/dist/includes.d.ts +53 -0
  99. package/dist/includes.d.ts.map +1 -0
  100. package/dist/includes.js +73 -0
  101. package/dist/includes.js.map +1 -0
  102. package/dist/includes.test.d.ts +2 -0
  103. package/dist/includes.test.d.ts.map +1 -0
  104. package/dist/includes.test.js +106 -0
  105. package/dist/includes.test.js.map +1 -0
  106. package/dist/index.d.ts +33 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +23 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/links/__tests__/markdown.test.d.ts +2 -0
  111. package/dist/links/__tests__/markdown.test.d.ts.map +1 -0
  112. package/dist/links/__tests__/markdown.test.js +173 -0
  113. package/dist/links/__tests__/markdown.test.js.map +1 -0
  114. package/dist/links/__tests__/sidecar.test.d.ts +2 -0
  115. package/dist/links/__tests__/sidecar.test.d.ts.map +1 -0
  116. package/dist/links/__tests__/sidecar.test.js +238 -0
  117. package/dist/links/__tests__/sidecar.test.js.map +1 -0
  118. package/dist/links/index.d.ts +3 -0
  119. package/dist/links/index.d.ts.map +1 -0
  120. package/dist/links/index.js +3 -0
  121. package/dist/links/index.js.map +1 -0
  122. package/dist/links/markdown.d.ts +13 -0
  123. package/dist/links/markdown.d.ts.map +1 -0
  124. package/dist/links/markdown.js +93 -0
  125. package/dist/links/markdown.js.map +1 -0
  126. package/dist/links/sidecar.d.ts +12 -0
  127. package/dist/links/sidecar.d.ts.map +1 -0
  128. package/dist/links/sidecar.js +88 -0
  129. package/dist/links/sidecar.js.map +1 -0
  130. package/dist/manifest.d.ts +45 -0
  131. package/dist/manifest.d.ts.map +1 -0
  132. package/dist/manifest.js +269 -0
  133. package/dist/manifest.js.map +1 -0
  134. package/dist/manifest.test.d.ts +2 -0
  135. package/dist/manifest.test.d.ts.map +1 -0
  136. package/dist/manifest.test.js +625 -0
  137. package/dist/manifest.test.js.map +1 -0
  138. package/dist/multi-repo.test.d.ts +2 -0
  139. package/dist/multi-repo.test.d.ts.map +1 -0
  140. package/dist/multi-repo.test.js +179 -0
  141. package/dist/multi-repo.test.js.map +1 -0
  142. package/dist/node-path.d.ts +13 -0
  143. package/dist/node-path.d.ts.map +1 -0
  144. package/dist/node-path.js +21 -0
  145. package/dist/node-path.js.map +1 -0
  146. package/dist/node-path.test.d.ts +2 -0
  147. package/dist/node-path.test.d.ts.map +1 -0
  148. package/dist/node-path.test.js +29 -0
  149. package/dist/node-path.test.js.map +1 -0
  150. package/dist/published-links.d.ts +16 -0
  151. package/dist/published-links.d.ts.map +1 -0
  152. package/dist/published-links.js +59 -0
  153. package/dist/published-links.js.map +1 -0
  154. package/dist/published-links.test.d.ts +2 -0
  155. package/dist/published-links.test.d.ts.map +1 -0
  156. package/dist/published-links.test.js +82 -0
  157. package/dist/published-links.test.js.map +1 -0
  158. package/dist/repos.d.ts +32 -0
  159. package/dist/repos.d.ts.map +1 -0
  160. package/dist/repos.js +46 -0
  161. package/dist/repos.js.map +1 -0
  162. package/dist/repos.test.d.ts +2 -0
  163. package/dist/repos.test.d.ts.map +1 -0
  164. package/dist/repos.test.js +61 -0
  165. package/dist/repos.test.js.map +1 -0
  166. package/dist/scalar-source.d.ts +15 -0
  167. package/dist/scalar-source.d.ts.map +1 -0
  168. package/dist/scalar-source.js +21 -0
  169. package/dist/scalar-source.js.map +1 -0
  170. package/dist/search.d.ts +14 -0
  171. package/dist/search.d.ts.map +1 -0
  172. package/dist/search.js +59 -0
  173. package/dist/search.js.map +1 -0
  174. package/dist/service-concurrency.test.d.ts +2 -0
  175. package/dist/service-concurrency.test.d.ts.map +1 -0
  176. package/dist/service-concurrency.test.js +89 -0
  177. package/dist/service-concurrency.test.js.map +1 -0
  178. package/dist/service-history.test.d.ts +2 -0
  179. package/dist/service-history.test.d.ts.map +1 -0
  180. package/dist/service-history.test.js +76 -0
  181. package/dist/service-history.test.js.map +1 -0
  182. package/dist/service.d.ts +118 -0
  183. package/dist/service.d.ts.map +1 -0
  184. package/dist/service.js +347 -0
  185. package/dist/service.js.map +1 -0
  186. package/dist/service.test.d.ts +2 -0
  187. package/dist/service.test.d.ts.map +1 -0
  188. package/dist/service.test.js +313 -0
  189. package/dist/service.test.js.map +1 -0
  190. package/dist/types.d.ts +403 -0
  191. package/dist/types.d.ts.map +1 -0
  192. package/dist/types.js +2 -0
  193. package/dist/types.js.map +1 -0
  194. package/dist/validate/__tests__/artifacts.test.d.ts +2 -0
  195. package/dist/validate/__tests__/artifacts.test.d.ts.map +1 -0
  196. package/dist/validate/__tests__/artifacts.test.js +167 -0
  197. package/dist/validate/__tests__/artifacts.test.js.map +1 -0
  198. package/dist/validate/__tests__/assertions.test.d.ts +2 -0
  199. package/dist/validate/__tests__/assertions.test.d.ts.map +1 -0
  200. package/dist/validate/__tests__/assertions.test.js +254 -0
  201. package/dist/validate/__tests__/assertions.test.js.map +1 -0
  202. package/dist/validate/__tests__/duplicates.test.d.ts +2 -0
  203. package/dist/validate/__tests__/duplicates.test.d.ts.map +1 -0
  204. package/dist/validate/__tests__/duplicates.test.js +130 -0
  205. package/dist/validate/__tests__/duplicates.test.js.map +1 -0
  206. package/dist/validate/__tests__/edge-resolution.test.d.ts +2 -0
  207. package/dist/validate/__tests__/edge-resolution.test.d.ts.map +1 -0
  208. package/dist/validate/__tests__/edge-resolution.test.js +279 -0
  209. package/dist/validate/__tests__/edge-resolution.test.js.map +1 -0
  210. package/dist/validate/__tests__/history.test.d.ts +2 -0
  211. package/dist/validate/__tests__/history.test.d.ts.map +1 -0
  212. package/dist/validate/__tests__/history.test.js +93 -0
  213. package/dist/validate/__tests__/history.test.js.map +1 -0
  214. package/dist/validate/__tests__/includes.test.d.ts +2 -0
  215. package/dist/validate/__tests__/includes.test.d.ts.map +1 -0
  216. package/dist/validate/__tests__/includes.test.js +93 -0
  217. package/dist/validate/__tests__/includes.test.js.map +1 -0
  218. package/dist/validate/__tests__/registry.test.d.ts +2 -0
  219. package/dist/validate/__tests__/registry.test.d.ts.map +1 -0
  220. package/dist/validate/__tests__/registry.test.js +78 -0
  221. package/dist/validate/__tests__/registry.test.js.map +1 -0
  222. package/dist/validate/__tests__/run.test.d.ts +2 -0
  223. package/dist/validate/__tests__/run.test.d.ts.map +1 -0
  224. package/dist/validate/__tests__/run.test.js +215 -0
  225. package/dist/validate/__tests__/run.test.js.map +1 -0
  226. package/dist/validate/history.d.ts +23 -0
  227. package/dist/validate/history.d.ts.map +1 -0
  228. package/dist/validate/history.js +45 -0
  229. package/dist/validate/history.js.map +1 -0
  230. package/dist/validate/index.d.ts +10 -0
  231. package/dist/validate/index.d.ts.map +1 -0
  232. package/dist/validate/index.js +9 -0
  233. package/dist/validate/index.js.map +1 -0
  234. package/dist/validate/registry.d.ts +27 -0
  235. package/dist/validate/registry.d.ts.map +1 -0
  236. package/dist/validate/registry.js +67 -0
  237. package/dist/validate/registry.js.map +1 -0
  238. package/dist/validate/rules/artifacts.d.ts +25 -0
  239. package/dist/validate/rules/artifacts.d.ts.map +1 -0
  240. package/dist/validate/rules/artifacts.js +87 -0
  241. package/dist/validate/rules/artifacts.js.map +1 -0
  242. package/dist/validate/rules/assertions.d.ts +19 -0
  243. package/dist/validate/rules/assertions.d.ts.map +1 -0
  244. package/dist/validate/rules/assertions.js +149 -0
  245. package/dist/validate/rules/assertions.js.map +1 -0
  246. package/dist/validate/rules/duplicates.d.ts +20 -0
  247. package/dist/validate/rules/duplicates.d.ts.map +1 -0
  248. package/dist/validate/rules/duplicates.js +0 -0
  249. package/dist/validate/rules/duplicates.js.map +1 -0
  250. package/dist/validate/rules/edge-resolution.d.ts +15 -0
  251. package/dist/validate/rules/edge-resolution.d.ts.map +1 -0
  252. package/dist/validate/rules/edge-resolution.js +185 -0
  253. package/dist/validate/rules/edge-resolution.js.map +1 -0
  254. package/dist/validate/rules/includes.d.ts +21 -0
  255. package/dist/validate/rules/includes.d.ts.map +1 -0
  256. package/dist/validate/rules/includes.js +112 -0
  257. package/dist/validate/rules/includes.js.map +1 -0
  258. package/dist/validate/run.d.ts +20 -0
  259. package/dist/validate/run.d.ts.map +1 -0
  260. package/dist/validate/run.js +90 -0
  261. package/dist/validate/run.js.map +1 -0
  262. package/dist/validate/types.d.ts +101 -0
  263. package/dist/validate/types.d.ts.map +1 -0
  264. package/dist/validate/types.js +2 -0
  265. package/dist/validate/types.js.map +1 -0
  266. package/package.json +54 -0
@@ -0,0 +1,279 @@
1
+ import { resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, it } from "vitest";
4
+ import { buildManifest } from "../../manifest.js";
5
+ import { ValidatorRegistry } from "../registry.js";
6
+ import { edgeResolutionValidator } from "../rules/edge-resolution.js";
7
+ import { validateManifest } from "../run.js";
8
+ function anchor(slug, text = slug.slice(1)) {
9
+ return { slug, text, line: 1, level: 2 };
10
+ }
11
+ function node(id, anchors = []) {
12
+ return { id, type: "markdown", title: id, anchors };
13
+ }
14
+ function edge(from, to, extra = {}) {
15
+ const [fromNode, fromAnchor] = from.split(/(?=#)/);
16
+ const [toNode, toAnchor] = to.split(/(?=#)/);
17
+ return {
18
+ from: { node: fromNode, ...(fromAnchor ? { anchor: fromAnchor } : {}) },
19
+ to: { node: toNode, ...(toAnchor ? { anchor: toAnchor } : {}) },
20
+ type: "references",
21
+ ...extra,
22
+ };
23
+ }
24
+ const CONFIG = {
25
+ rootDir: "/project",
26
+ docsDir: "docs",
27
+ entryPoint: "docs/README.md",
28
+ ignore: [],
29
+ };
30
+ /** Run only this validator, so the assertions are about it alone. */
31
+ async function check(manifest, config = CONFIG) {
32
+ const registry = new ValidatorRegistry().register(edgeResolutionValidator);
33
+ return validateManifest(manifest, config, registry);
34
+ }
35
+ function graph(nodes, edges) {
36
+ return { version: 2, nodes, edges };
37
+ }
38
+ describe("edge resolution", () => {
39
+ it("reports nothing when every edge resolves", async () => {
40
+ const manifest = graph([node("README.md"), node("api.md", [anchor("#users")])], [edge("README.md", "api.md#users")]);
41
+ expect((await check(manifest)).diagnostics).toEqual([]);
42
+ });
43
+ it("reports an edge whose target document is not in the graph", async () => {
44
+ const manifest = graph([node("README.md")], [edge("README.md", "gone.md")]);
45
+ const [diagnostic] = (await check(manifest)).diagnostics;
46
+ expect(diagnostic.rule).toBe("edge-target-missing");
47
+ expect(diagnostic.severity).toBe("error");
48
+ // The location lives on the diagnostic's target, so the message does not
49
+ // repeat it — the reporter renders both.
50
+ expect(diagnostic.message).toBe("No document gone.md is in the graph");
51
+ expect(diagnostic.target).toEqual({ kind: "edge", edge: expect.objectContaining({}) });
52
+ expect(diagnostic.data?.target).toBe("gone.md");
53
+ });
54
+ it("distinguishes a missing anchor from a missing document", async () => {
55
+ const manifest = graph([node("README.md"), node("api.md", [anchor("#users")])], [edge("README.md", "api.md#accounts")]);
56
+ const [diagnostic] = (await check(manifest)).diagnostics;
57
+ // The two have different causes and different fixes, so they are
58
+ // different rules rather than one "broken link".
59
+ expect(diagnostic.rule).toBe("edge-anchor-missing");
60
+ expect(diagnostic.message).toContain("exists but defines no anchor #accounts");
61
+ });
62
+ it("reports a source anchor the linking document does not define", async () => {
63
+ const manifest = graph([node("README.md", [anchor("#intro")]), node("api.md")], [edge("README.md#nope", "api.md")]);
64
+ const [diagnostic] = (await check(manifest)).diagnostics;
65
+ expect(diagnostic.rule).toBe("edge-source-anchor-missing");
66
+ expect(diagnostic.message).toContain("README.md defines no anchor #nope");
67
+ });
68
+ it("carries the edge itself as the diagnostic target", async () => {
69
+ const broken = edge("README.md", "gone.md");
70
+ const manifest = graph([node("README.md")], [broken]);
71
+ const [diagnostic] = (await check(manifest)).diagnostics;
72
+ expect(diagnostic.target).toEqual({ kind: "edge", edge: broken });
73
+ });
74
+ it("reports every unresolved edge, not just the first", async () => {
75
+ const manifest = graph([node("README.md")], [edge("README.md", "a.md"), edge("README.md", "b.md")]);
76
+ expect((await check(manifest)).counts.error).toBe(2);
77
+ });
78
+ });
79
+ describe("edge resolution (link targets that were never nodes)", () => {
80
+ it("ignores a link to a file type the indexer does not index", async () => {
81
+ // extractMarkdownLinks makes an edge for any path inside a docs root, so
82
+ // an image link becomes an edge to a node that could never exist.
83
+ const manifest = graph([node("README.md")], [edge("README.md", "assets/diagram.png"), edge("README.md", "spec.pdf")]);
84
+ expect((await check(manifest)).diagnostics).toEqual([]);
85
+ });
86
+ it("still reports an indexable target that is missing", async () => {
87
+ const manifest = graph([node("README.md")], [edge("README.md", "guide.md")]);
88
+ expect((await check(manifest)).counts.error).toBe(1);
89
+ });
90
+ it("checks yaml targets alongside markdown ones", async () => {
91
+ const manifest = graph([node("README.md")], [edge("README.md", "api.yaml")]);
92
+ expect((await check(manifest)).diagnostics[0].rule).toBe("edge-target-missing");
93
+ });
94
+ it("ignores a link to an extension the project has not configured", async () => {
95
+ const manifest = graph([node("README.md")], [edge("README.md", "notes.qmd")]);
96
+ expect((await check(manifest)).diagnostics).toEqual([]);
97
+ });
98
+ it("resolves a link to an extension the project configured", async () => {
99
+ const config = { ...CONFIG, extensions: { ".qmd": "markdown" } };
100
+ const manifest = graph([node("README.md"), node("notes.qmd")], [edge("README.md", "notes.qmd")]);
101
+ expect((await check(manifest, config)).diagnostics).toEqual([]);
102
+ });
103
+ it("reports a missing target once its extension is configured", async () => {
104
+ const config = { ...CONFIG, extensions: { ".qmd": "markdown" } };
105
+ const manifest = graph([node("README.md")], [edge("README.md", "notes.qmd")]);
106
+ expect((await check(manifest, config)).counts.error).toBe(1);
107
+ });
108
+ });
109
+ describe("edge resolution (rename suggestions)", () => {
110
+ it("suggests the anchor a reworded heading most likely became", async () => {
111
+ const manifest = graph([
112
+ node("README.md"),
113
+ node("impl.md", [{ slug: "#layout--presenting-mode", text: "Layout — Presenting Mode" }]),
114
+ ], [edge("README.md", "impl.md#layout-presenting-mode")]);
115
+ const [diagnostic] = (await check(manifest)).diagnostics;
116
+ expect(diagnostic.hint).toContain("#layout--presenting-mode");
117
+ expect(diagnostic.hint).toContain("Layout — Presenting Mode");
118
+ expect(diagnostic.data?.suggestion).toBe("#layout--presenting-mode");
119
+ });
120
+ it("says the section looks removed when nothing is similar", async () => {
121
+ const manifest = graph([node("README.md"), node("api.md", [anchor("#authentication")])], [edge("README.md", "api.md#deployment-topology")]);
122
+ const [diagnostic] = (await check(manifest)).diagnostics;
123
+ expect(diagnostic.hint).toContain("removed");
124
+ expect(diagnostic.data?.suggestion).toBeUndefined();
125
+ });
126
+ it("picks the closest of several candidates", async () => {
127
+ const manifest = graph([
128
+ node("README.md"),
129
+ node("api.md", [anchor("#data-flows"), anchor("#deployment"), anchor("#security")]),
130
+ ], [edge("README.md", "api.md#data-flow")]);
131
+ expect((await check(manifest)).diagnostics[0].data?.suggestion).toBe("#data-flows");
132
+ });
133
+ it("suggests a replacement for a broken source anchor too", async () => {
134
+ const manifest = graph([node("README.md", [anchor("#getting-started")]), node("api.md")], [edge("README.md#getting-strated", "api.md")]);
135
+ expect((await check(manifest)).diagnostics[0].data?.suggestion).toBe("#getting-started");
136
+ });
137
+ });
138
+ describe("edge resolution (pending references)", () => {
139
+ const pending = { pending: true };
140
+ it("reports a pending edge separately rather than as breakage", async () => {
141
+ const manifest = graph([node("README.md")], [edge("README.md", "appendix.md", pending)]);
142
+ const { diagnostics, counts } = await check(manifest);
143
+ // Writing a pointer to something you are about to create is normal
144
+ // practice; a check that cannot express it gets switched off.
145
+ expect(diagnostics[0].rule).toBe("edge-pending");
146
+ expect(diagnostics[0].severity).toBe("info");
147
+ expect(counts.error).toBe(0);
148
+ });
149
+ it("keeps pending references visible and countable", async () => {
150
+ const manifest = graph([node("README.md")], [edge("README.md", "a.md", pending), edge("README.md", "b.md", pending)]);
151
+ const { counts } = await check(manifest);
152
+ expect(counts.info).toBe(2);
153
+ });
154
+ it("covers a pending anchor as well as a pending document", async () => {
155
+ const manifest = graph([node("README.md"), node("api.md", [anchor("#users")])], [edge("README.md", "api.md#accounts", pending)]);
156
+ const [diagnostic] = (await check(manifest)).diagnostics;
157
+ expect(diagnostic.rule).toBe("edge-pending");
158
+ expect(diagnostic.message).toContain("anchor does not exist yet");
159
+ });
160
+ it("reports a pending marker that is no longer needed", async () => {
161
+ const manifest = graph([node("README.md"), node("api.md", [anchor("#users")])], [edge("README.md", "api.md#users", pending)]);
162
+ const [diagnostic] = (await check(manifest)).diagnostics;
163
+ // Otherwise the suppression outlives the reason for it.
164
+ expect(diagnostic.rule).toBe("edge-pending-resolved");
165
+ expect(diagnostic.message).toContain("can be removed");
166
+ });
167
+ it("lets a project silence pending notices without losing the real errors", async () => {
168
+ const manifest = graph([node("README.md")], [edge("README.md", "later.md", pending), edge("README.md", "gone.md")]);
169
+ const result = await check(manifest, { ...CONFIG, rules: { "edge-pending": "off" } });
170
+ expect(result.diagnostics.map((d) => d.rule)).toEqual(["edge-target-missing"]);
171
+ });
172
+ it("can be promoted to an error for a project that wants no pending links", async () => {
173
+ const manifest = graph([node("README.md")], [edge("README.md", "later.md", pending)]);
174
+ const result = await check(manifest, { ...CONFIG, rules: { "edge-pending": "error" } });
175
+ expect(result.counts.error).toBe(1);
176
+ });
177
+ });
178
+ describe("edge resolution (targets that moved)", () => {
179
+ /** Run the validator against a fixed rename map rather than a real repository. */
180
+ async function checkWithRenames(manifest, renames) {
181
+ const registry = new ValidatorRegistry().register({
182
+ ...edgeResolutionValidator,
183
+ run: (context) => edgeResolutionValidator.run({
184
+ ...context,
185
+ history: { blobs: new Map(), renames: new Map(Object.entries(renames)) },
186
+ }),
187
+ });
188
+ return validateManifest(manifest, CONFIG, registry);
189
+ }
190
+ it("says a target moved rather than leaving the breakage unexplained", async () => {
191
+ const manifest = graph([node("README.md"), node("guides/setup.md")], [edge("README.md", "setup.md")]);
192
+ const [diagnostic] = (await checkWithRenames(manifest, { "setup.md": "guides/setup.md" }))
193
+ .diagnostics;
194
+ // Moving a document is ordinary maintenance, and it breaks every link to
195
+ // it. Git already knows what happened, so the report should too.
196
+ expect(diagnostic.message).toBe("setup.md moved to guides/setup.md");
197
+ expect(diagnostic.data?.renamedTo).toBe("guides/setup.md");
198
+ });
199
+ it("still reports it as broken, since the link does need fixing", async () => {
200
+ const manifest = graph([node("README.md"), node("guides/setup.md")], [edge("README.md", "setup.md")]);
201
+ const { diagnostics, counts } = await checkWithRenames(manifest, {
202
+ "setup.md": "guides/setup.md",
203
+ });
204
+ expect(diagnostics[0].rule).toBe("edge-target-missing");
205
+ expect(counts.error).toBe(1);
206
+ });
207
+ it("names the new id in the hint", async () => {
208
+ const manifest = graph([node("README.md"), node("guides/setup.md")], [edge("README.md", "setup.md")]);
209
+ const [diagnostic] = (await checkWithRenames(manifest, { "setup.md": "guides/setup.md" }))
210
+ .diagnostics;
211
+ expect(diagnostic.hint).toBe("Point the link at guides/setup.md.");
212
+ });
213
+ it("reports a genuinely missing target plainly", async () => {
214
+ const manifest = graph([node("README.md")], [edge("README.md", "gone.md")]);
215
+ const [diagnostic] = (await checkWithRenames(manifest, {})).diagnostics;
216
+ expect(diagnostic.message).toBe("No document gone.md is in the graph");
217
+ expect(diagnostic.data?.renamedTo).toBeUndefined();
218
+ });
219
+ it("does not suggest a destination that is no longer in the graph", async () => {
220
+ // A file moved and then deleted explains nothing, and pointing the link
221
+ // at it would just break it differently.
222
+ const manifest = graph([node("README.md")], [edge("README.md", "setup.md")]);
223
+ const [diagnostic] = (await checkWithRenames(manifest, { "setup.md": "guides/setup.md" }))
224
+ .diagnostics;
225
+ expect(diagnostic.message).toBe("No document setup.md is in the graph");
226
+ expect(diagnostic.data?.renamedTo).toBeUndefined();
227
+ });
228
+ it("leaves a pending link alone even when it names something that moved", async () => {
229
+ const manifest = graph([node("README.md"), node("guides/setup.md")], [edge("README.md", "setup.md", { pending: true })]);
230
+ const [diagnostic] = (await checkWithRenames(manifest, { "setup.md": "guides/setup.md" }))
231
+ .diagnostics;
232
+ expect(diagnostic.rule).toBe("edge-pending");
233
+ });
234
+ });
235
+ // Extraction and validation exercised together over real files, since the
236
+ // check is only as good as the edges the indexer hands it.
237
+ describe("edge resolution (over a real docs tree)", () => {
238
+ const BROKEN_DIR = resolve(fileURLToPath(import.meta.url), "../../../__fixtures__/broken");
239
+ const config = {
240
+ rootDir: BROKEN_DIR,
241
+ docsDir: "docs",
242
+ entryPoint: "docs/README.md",
243
+ ignore: [],
244
+ };
245
+ async function checkFixture() {
246
+ return check(await buildManifest(config), config);
247
+ }
248
+ it("reports each unresolved reference once, by rule", async () => {
249
+ const { diagnostics } = await checkFixture();
250
+ expect(diagnostics.map((d) => d.rule).sort()).toEqual([
251
+ "edge-anchor-missing",
252
+ "edge-pending",
253
+ "edge-source-anchor-missing",
254
+ "edge-target-missing",
255
+ ]);
256
+ });
257
+ it("fails a build on the real breakage but not on the pending reference", async () => {
258
+ const { counts } = await checkFixture();
259
+ expect(counts).toEqual({ error: 3, warn: 0, info: 1 });
260
+ });
261
+ it("does not report the image link, which was never going to be a node", async () => {
262
+ const { diagnostics } = await checkFixture();
263
+ const reported = diagnostics.map((d) => d.target.kind === "edge" ? d.target.edge.to.node : "");
264
+ expect(reported).not.toContain("assets/diagram.png");
265
+ });
266
+ it("says which document and which anchor, for the anchor case", async () => {
267
+ const { diagnostics } = await checkFixture();
268
+ const anchorMissing = diagnostics.find((d) => d.rule === "edge-anchor-missing");
269
+ expect(anchorMissing?.message).toContain("guide.md");
270
+ expect(anchorMissing?.message).toContain("#deployment-topology");
271
+ expect(anchorMissing?.data?.target).toBe("guide.md");
272
+ });
273
+ it("leaves the resolving link alone", async () => {
274
+ const { diagnostics } = await checkFixture();
275
+ const reported = diagnostics.map((d) => d.target.kind === "edge" ? `${d.target.edge.to.node}${d.target.edge.to.anchor ?? ""}` : "");
276
+ expect(reported).not.toContain("guide.md#getting-started");
277
+ });
278
+ });
279
+ //# sourceMappingURL=edge-resolution.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-resolution.test.js","sourceRoot":"","sources":["../../../src/validate/__tests__/edge-resolution.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=history.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.test.d.ts","sourceRoot":"","sources":["../../../src/validate/__tests__/history.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,93 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
6
+ import { NO_HISTORY, graphHistory } from "../history.js";
7
+ describe("graphHistory", () => {
8
+ let repo;
9
+ function config(overrides = {}) {
10
+ return {
11
+ rootDir: repo,
12
+ docsDir: "docs",
13
+ entryPoint: "docs/README.md",
14
+ ignore: [],
15
+ ...overrides,
16
+ };
17
+ }
18
+ beforeAll(() => {
19
+ repo = mkdtempSync(join(tmpdir(), "weft-history-"));
20
+ const git = (...args) => execFileSync("git", args, { cwd: repo, stdio: "ignore" });
21
+ git("init", "-q");
22
+ git("config", "user.email", "test@example.com");
23
+ git("config", "user.name", "Test");
24
+ git("config", "commit.gpgsign", "false");
25
+ for (const dir of ["docs", "products/alpha/docs", "products/beta/docs"]) {
26
+ mkdirSync(join(repo, dir), { recursive: true });
27
+ }
28
+ // The same content in both products, so a check comparing raw paths rather
29
+ // than node ids would have something to conflate.
30
+ writeFileSync(join(repo, "docs/guide.md"), "# Guide\n");
31
+ writeFileSync(join(repo, "products/alpha/docs/shared.md"), "# Shared\n");
32
+ writeFileSync(join(repo, "products/beta/docs/shared.md"), "# Shared\n");
33
+ git("add", "-A");
34
+ git("commit", "-q", "-m", "initial");
35
+ git("mv", "docs/guide.md", "docs/handbook.md");
36
+ git("commit", "-q", "-m", "rename guide");
37
+ });
38
+ afterAll(() => rmSync(repo, { recursive: true, force: true }));
39
+ it("keys a single-project history by bare node id", async () => {
40
+ const { blobs } = await graphHistory(config());
41
+ expect(blobs.has("handbook.md")).toBe(true);
42
+ });
43
+ it("keys a multi-project history by namespaced node id", async () => {
44
+ const { blobs } = await graphHistory(config({
45
+ projects: [
46
+ { name: "Alpha", docsDir: "products/alpha/docs" },
47
+ { name: "Beta", docsDir: "products/beta/docs" },
48
+ ],
49
+ }));
50
+ // Namespacing happens once, in the runner, so no check has to redo it —
51
+ // and two products' identically named documents stay distinct.
52
+ expect(blobs.has("alpha/shared.md")).toBe(true);
53
+ expect(blobs.has("beta/shared.md")).toBe(true);
54
+ expect(blobs.has("shared.md")).toBe(false);
55
+ });
56
+ it("still records that the two products hold identical content", async () => {
57
+ const { blobs } = await graphHistory(config({
58
+ projects: [
59
+ { name: "Alpha", docsDir: "products/alpha/docs" },
60
+ { name: "Beta", docsDir: "products/beta/docs" },
61
+ ],
62
+ }));
63
+ const alpha = blobs.get("alpha/shared.md") ?? new Set();
64
+ const beta = blobs.get("beta/shared.md") ?? new Set();
65
+ expect([...alpha].some((blob) => beta.has(blob))).toBe(true);
66
+ });
67
+ it("namespaces both sides of a rename", async () => {
68
+ const { renames } = await graphHistory(config({ projects: [{ name: "Docs", docsDir: "docs" }] }));
69
+ expect(renames.get("docs/guide.md")).toBe("docs/handbook.md");
70
+ });
71
+ it("reports a rename by bare id in single-project mode", async () => {
72
+ const { renames } = await graphHistory(config());
73
+ expect(renames.get("guide.md")).toBe("handbook.md");
74
+ });
75
+ it("is empty outside a repository rather than throwing", async () => {
76
+ const loose = mkdtempSync(join(tmpdir(), "weft-nogit-"));
77
+ try {
78
+ const history = await graphHistory({ ...config(), rootDir: loose });
79
+ expect(history.blobs.size).toBe(0);
80
+ expect(history.renames.size).toBe(0);
81
+ }
82
+ finally {
83
+ rmSync(loose, { recursive: true, force: true });
84
+ }
85
+ });
86
+ });
87
+ describe("NO_HISTORY", () => {
88
+ it("is readable without guarding, so a check needs no special case", () => {
89
+ expect(NO_HISTORY.blobs.get("anything.md")).toBeUndefined();
90
+ expect(NO_HISTORY.renames.size).toBe(0);
91
+ });
92
+ });
93
+ //# sourceMappingURL=history.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.test.js","sourceRoot":"","sources":["../../../src/validate/__tests__/history.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=includes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"includes.test.d.ts","sourceRoot":"","sources":["../../../src/validate/__tests__/includes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ValidatorRegistry } from "../registry.js";
3
+ import { includeValidator } from "../rules/includes.js";
4
+ import { validateManifest } from "../run.js";
5
+ const CONFIG = {
6
+ rootDir: "/project",
7
+ docsDir: "docs",
8
+ entryPoint: "docs/README.md",
9
+ ignore: [],
10
+ };
11
+ function doc(id) {
12
+ return { id, type: "markdown", title: id, anchors: [] };
13
+ }
14
+ function includes(from, to, extra = {}) {
15
+ return { from: { node: from }, to: { node: to }, type: "includes", ...extra };
16
+ }
17
+ function graph(edges) {
18
+ const ids = new Set(edges.flatMap((e) => [e.from.node, e.to.node]));
19
+ return { version: 2, nodes: [...ids].map(doc), edges };
20
+ }
21
+ /** Run only this validator, so the assertions are about it alone. */
22
+ async function check(manifest) {
23
+ const registry = new ValidatorRegistry().register(includeValidator);
24
+ return validateManifest(manifest, CONFIG, registry);
25
+ }
26
+ describe("include-cycle", () => {
27
+ it("reports nothing for an acyclic include chain", async () => {
28
+ const manifest = graph([
29
+ includes("faq.md", "runbook.md"),
30
+ includes("faq.md", "pricing.md"),
31
+ includes("overview.md", "faq.md"),
32
+ ]);
33
+ expect((await check(manifest)).diagnostics).toEqual([]);
34
+ });
35
+ it("reports a two-document cycle once", async () => {
36
+ const manifest = graph([includes("a.md", "b.md"), includes("b.md", "a.md")]);
37
+ const { diagnostics } = await check(manifest);
38
+ expect(diagnostics).toHaveLength(1);
39
+ expect(diagnostics[0].rule).toBe("include-cycle");
40
+ expect(diagnostics[0].severity).toBe("error");
41
+ expect(diagnostics[0].data?.nodes).toEqual(["a.md", "b.md"]);
42
+ });
43
+ it("reports a document that includes itself", async () => {
44
+ const { diagnostics } = await check(graph([includes("a.md", "a.md")]));
45
+ expect(diagnostics).toHaveLength(1);
46
+ expect(diagnostics[0].data?.nodes).toEqual(["a.md"]);
47
+ });
48
+ it("cycles at document granularity even when the edges select anchors", async () => {
49
+ // The two ranges may not actually overlap, but proving that would mean a
50
+ // second range-extraction implementation kept in agreement forever.
51
+ const manifest = graph([
52
+ includes("a.md", "b.md", { to: { node: "b.md", anchor: "#one" } }),
53
+ includes("b.md", "a.md", { to: { node: "a.md", anchor: "#two" } }),
54
+ ]);
55
+ expect((await check(manifest)).diagnostics).toHaveLength(1);
56
+ });
57
+ it("reports each disjoint cycle separately", async () => {
58
+ const manifest = graph([
59
+ includes("a.md", "b.md"),
60
+ includes("b.md", "a.md"),
61
+ includes("x.md", "y.md"),
62
+ includes("y.md", "z.md"),
63
+ includes("z.md", "x.md"),
64
+ ]);
65
+ const { diagnostics } = await check(manifest);
66
+ expect(diagnostics).toHaveLength(2);
67
+ const groups = diagnostics.map((d) => d.data?.nodes);
68
+ expect(groups).toContainEqual(["a.md", "b.md"]);
69
+ expect(groups).toContainEqual(["x.md", "y.md", "z.md"]);
70
+ });
71
+ it("ignores pending include edges", async () => {
72
+ const manifest = graph([includes("a.md", "b.md"), includes("b.md", "a.md", { pending: true })]);
73
+ expect((await check(manifest)).diagnostics).toEqual([]);
74
+ });
75
+ it("ignores cycles in other edge types", async () => {
76
+ const manifest = graph([
77
+ { from: { node: "a.md" }, to: { node: "b.md" }, type: "references" },
78
+ { from: { node: "b.md" }, to: { node: "a.md" }, type: "references" },
79
+ ]);
80
+ expect((await check(manifest)).diagnostics).toEqual([]);
81
+ });
82
+ it("finds a cycle reachable only through an acyclic prefix", async () => {
83
+ const manifest = graph([
84
+ includes("entry.md", "a.md"),
85
+ includes("a.md", "b.md"),
86
+ includes("b.md", "a.md"),
87
+ ]);
88
+ const { diagnostics } = await check(manifest);
89
+ expect(diagnostics).toHaveLength(1);
90
+ expect(diagnostics[0].data?.nodes).toEqual(["a.md", "b.md"]);
91
+ });
92
+ });
93
+ //# sourceMappingURL=includes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"includes.test.js","sourceRoot":"","sources":["../../../src/validate/__tests__/includes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../../src/validate/__tests__/registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { VALIDATOR_ERROR_RULE, ValidatorRegistry, defaultRegistry } from "../registry.js";
3
+ function stubValidator(rules) {
4
+ return { rules, run: () => [] };
5
+ }
6
+ const RULE_A = { id: "rule-a", description: "Checks a", defaultSeverity: "error" };
7
+ const RULE_B = { id: "rule-b", description: "Checks b", defaultSeverity: "warn" };
8
+ describe("ValidatorRegistry", () => {
9
+ it("exposes registered validators in registration order", () => {
10
+ const first = stubValidator([RULE_A]);
11
+ const second = stubValidator([RULE_B]);
12
+ const registry = new ValidatorRegistry().register(first).register(second);
13
+ expect(registry.validators).toEqual([first, second]);
14
+ });
15
+ it("exposes every declared rule alongside the reserved ones", () => {
16
+ const registry = new ValidatorRegistry().register(stubValidator([RULE_A, RULE_B]));
17
+ expect(registry.rules.map((rule) => rule.id)).toEqual([
18
+ VALIDATOR_ERROR_RULE.id,
19
+ "rule-a",
20
+ "rule-b",
21
+ ]);
22
+ });
23
+ it("looks up a rule by id", () => {
24
+ const registry = new ValidatorRegistry().register(stubValidator([RULE_A]));
25
+ expect(registry.rule("rule-a")).toEqual(RULE_A);
26
+ expect(registry.rule("nope")).toBeUndefined();
27
+ });
28
+ it("rejects a duplicate rule id", () => {
29
+ const registry = new ValidatorRegistry().register(stubValidator([RULE_A]));
30
+ expect(() => registry.register(stubValidator([RULE_A]))).toThrow(/duplicate rule id "rule-a"/);
31
+ });
32
+ it("rejects a rule id that collides with a reserved one", () => {
33
+ const registry = new ValidatorRegistry();
34
+ expect(() => registry.register(stubValidator([{ id: VALIDATOR_ERROR_RULE.id, description: "", defaultSeverity: "warn" }]))).toThrow(/duplicate rule id "validator-error"/);
35
+ });
36
+ it("rejects a validator that declares no rules", () => {
37
+ expect(() => new ValidatorRegistry().register(stubValidator([]))).toThrow(/at least one rule/);
38
+ });
39
+ it("does not leak a failed registration's rules", () => {
40
+ const registry = new ValidatorRegistry().register(stubValidator([RULE_A]));
41
+ expect(() => registry.register(stubValidator([RULE_B, RULE_A]))).toThrow();
42
+ expect(registry.validators).toHaveLength(1);
43
+ expect(registry.rule("rule-b")).toBeUndefined();
44
+ });
45
+ it("rejects a validator that declares the same rule id twice", () => {
46
+ expect(() => new ValidatorRegistry().register(stubValidator([RULE_A, RULE_A]))).toThrow(/duplicate rule id "rule-a"/);
47
+ });
48
+ });
49
+ describe("defaultRegistry", () => {
50
+ it("registers the built-in checks", () => {
51
+ expect(defaultRegistry().rules.map((rule) => rule.id)).toEqual([
52
+ VALIDATOR_ERROR_RULE.id,
53
+ "edge-target-missing",
54
+ "edge-anchor-missing",
55
+ "edge-source-anchor-missing",
56
+ "edge-pending",
57
+ "edge-pending-resolved",
58
+ "assert-version-mismatch",
59
+ "assert-line-count-mismatch",
60
+ "assert-modified-mismatch",
61
+ "assert-unverifiable",
62
+ "artifact-stale",
63
+ "artifact-source-unrecorded",
64
+ "node-duplicate",
65
+ "node-diverged",
66
+ "include-cycle",
67
+ ]);
68
+ });
69
+ it("still knows the reserved runner rule", () => {
70
+ expect(defaultRegistry().rule(VALIDATOR_ERROR_RULE.id)).toEqual(VALIDATOR_ERROR_RULE);
71
+ });
72
+ it("returns a fresh registry each call", () => {
73
+ const registry = defaultRegistry();
74
+ registry.register(stubValidator([RULE_A]));
75
+ expect(defaultRegistry().rule("rule-a")).toBeUndefined();
76
+ });
77
+ });
78
+ //# sourceMappingURL=registry.test.js.map