@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,59 @@
1
+ import { existsSync } from "node:fs";
2
+ import { REPO_IDENTITY, resolveRepos } from "../repos.js";
3
+ import { resolveToken } from "./auth.js";
4
+ import { readCachedSha, repoCacheDir, writeCachedSha } from "./cache.js";
5
+ import { cloneAtSha, githubUrl, resolveRemoteRef } from "./clone.js";
6
+ /**
7
+ * Fetch a repo into the cache and return the checkout's absolute path.
8
+ *
9
+ * Resolution is remembered per ref (with a TTL for branches) and the clone is
10
+ * keyed by commit sha, so repeated serves of an unmoved ref cost one
11
+ * `ls-remote` at most and no clone.
12
+ */
13
+ export async function fetchRepo(identity, opts = {}) {
14
+ if (!REPO_IDENTITY.test(identity)) {
15
+ throw new Error(`weft fetch: "${identity}" is not a repo identity of the form "org/repo"`);
16
+ }
17
+ const env = opts.env ?? process.env;
18
+ const token = opts.token ?? (await resolveToken(env));
19
+ const url = (opts.urlFor ?? githubUrl)(identity);
20
+ const ref = opts.ref ?? "HEAD";
21
+ let sha = opts.refresh ? undefined : readCachedSha(identity, ref, env);
22
+ if (!sha) {
23
+ const resolved = await resolveRemoteRef(url, opts.ref, token);
24
+ sha = resolved.sha;
25
+ writeCachedSha(identity, ref, sha, resolved.movable, env);
26
+ }
27
+ const dir = repoCacheDir(identity, sha, env);
28
+ await cloneAtSha(url, sha, dir, token);
29
+ return dir;
30
+ }
31
+ /**
32
+ * Fill the gaps in `config.repos` by fetching, and return the config with the
33
+ * fetched checkout paths in place.
34
+ *
35
+ * A repo mapped to a real local path keeps winning — fetching is the fallback,
36
+ * not the override — so someone with three of five repos checked out reads
37
+ * their local working trees for those three and fetched copies of the rest.
38
+ * Referenced repos are fetched at their remote HEAD; a ref in `opts` names a
39
+ * ref of the primary repo, not of everything it references.
40
+ */
41
+ export async function resolveFetchedRepos(config, opts = {}) {
42
+ const needed = new Set(Object.keys(config.repos ?? {}));
43
+ for (const project of config.projects ?? []) {
44
+ if (project.repo !== undefined)
45
+ needed.add(project.repo);
46
+ }
47
+ const local = resolveRepos(config.repos, config.rootDir);
48
+ const repos = { ...config.repos };
49
+ let changed = false;
50
+ for (const identity of needed) {
51
+ const checkout = local.get(identity);
52
+ if (checkout && existsSync(checkout))
53
+ continue;
54
+ repos[identity] = await fetchRepo(identity, { ...opts, ref: undefined });
55
+ changed = true;
56
+ }
57
+ return changed ? { ...config, repos } : config;
58
+ }
59
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/fetch/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Freshness, Manifest, WeftConfig } from "./types.js";
2
+ /**
3
+ * Baseline hash of everything a manifest's own nodes cannot already tell you
4
+ * changed: the sorted set of indexed file paths across every docs root, plus
5
+ * the artifact paths a root's `artifacts` globs match, plus the content of
6
+ * every input that is not itself a node — sidecar files, contribution files,
7
+ * and the config files, including the machine-local overlay.
8
+ *
9
+ * Every node already carries a `contentHash`, so an edited document's content
10
+ * is not covered here — hashing it again would be redundant with the
11
+ * manifest, and an artifact's bytes are deliberately not read here either.
12
+ * What no amount of comparing existing nodes can detect is a node added or
13
+ * deleted — document or artifact — or a change to one of the non-node
14
+ * inputs, which is exactly what this hash covers instead.
15
+ */
16
+ export declare function computeInputsHash(config: WeftConfig): Promise<string>;
17
+ /**
18
+ * Compare a manifest's recorded provenance against the docs tree it claims to
19
+ * describe.
20
+ *
21
+ * A document added, removed, or a changed non-node input is caught by
22
+ * {@link computeInputsHash}. An edited document is not — nothing about its
23
+ * path changes — so every node with a recorded hash is re-read and compared
24
+ * against it. A node that no longer resolves to a real file is skipped rather
25
+ * than treated as stale: that is a document Weft never indexed from disk in
26
+ * the first place, such as one declared entirely by a contribution.
27
+ */
28
+ export declare function checkFreshness(manifest: Manifest, config: WeftConfig): Promise<Freshness>;
29
+ //# sourceMappingURL=freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAuClE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoE3E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAsC/F"}
@@ -0,0 +1,170 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { basename, resolve } from "node:path";
4
+ import { glob } from "glob";
5
+ import { resolveIndexedExtensions } from "./anchors/index.js";
6
+ import { CONFIG_FILES, LOCAL_CONFIG_FILES, nodeIdFor, resolveDocsRoots, rootForNodeId, } from "./config.js";
7
+ import { hashBytes, hashContent } from "./content.js";
8
+ /**
9
+ * Matches the manifest output directory everywhere a docs root is globbed,
10
+ * the same way chokidar's watcher ignores it. Applied on top of the config's
11
+ * own `ignore` globs so a freshly written manifest never makes its own tree
12
+ * look changed (DD-15). Applied only here, not in the indexer's globs — both
13
+ * currently skip `.weft/` via glob's defaults, but they are not otherwise
14
+ * kept in scope-agreement by this constant.
15
+ *
16
+ * Currently redundant in practice: `glob` excludes dot-directories from `**`
17
+ * by default, so `.weft/` is already skipped before this pattern is ever
18
+ * consulted — verified by calling the same globs below with `ignore: []` and
19
+ * seeing no change. Kept anyway as the explicit statement of intent, and as
20
+ * the only thing standing between a freshly-built manifest and a
21
+ * self-invalidating loop the day one of *these* globs starts passing
22
+ * `dot: true`. Note the guard is one-sided: if the *indexer's* globs gain
23
+ * `dot: true` first, the indexer would see `.weft/` while this check still
24
+ * ignores it, and the manifest would report permanently stale — a loud
25
+ * failure rather than a silent one, but the fix then is to apply this
26
+ * pattern on both sides.
27
+ */
28
+ const IGNORE_MANIFEST_OUTPUT = "**/.weft/**";
29
+ /**
30
+ * The config files present at the root: the project's config file, plus the
31
+ * machine-local overlay if there is one. The local file only carries `repos`,
32
+ * but a changed `repos` re-points every GitHub blob URL edge, so it is as
33
+ * much an input to the graph as the committed config.
34
+ */
35
+ function findConfigFiles(rootDir) {
36
+ const found = [];
37
+ const first = CONFIG_FILES.find((file) => existsSync(resolve(rootDir, file)));
38
+ if (first)
39
+ found.push(resolve(rootDir, first));
40
+ const local = LOCAL_CONFIG_FILES.find((file) => existsSync(resolve(rootDir, file)));
41
+ if (local)
42
+ found.push(resolve(rootDir, local));
43
+ return found;
44
+ }
45
+ /**
46
+ * Baseline hash of everything a manifest's own nodes cannot already tell you
47
+ * changed: the sorted set of indexed file paths across every docs root, plus
48
+ * the artifact paths a root's `artifacts` globs match, plus the content of
49
+ * every input that is not itself a node — sidecar files, contribution files,
50
+ * and the config files, including the machine-local overlay.
51
+ *
52
+ * Every node already carries a `contentHash`, so an edited document's content
53
+ * is not covered here — hashing it again would be redundant with the
54
+ * manifest, and an artifact's bytes are deliberately not read here either.
55
+ * What no amount of comparing existing nodes can detect is a node added or
56
+ * deleted — document or artifact — or a change to one of the non-node
57
+ * inputs, which is exactly what this hash covers instead.
58
+ */
59
+ export async function computeInputsHash(config) {
60
+ const roots = resolveDocsRoots(config);
61
+ const ignore = [...config.ignore, IGNORE_MANIFEST_OUTPUT];
62
+ // Resolved the same way the indexer resolves it, so a project that opted
63
+ // extra extensions in via `extensions` config has those files in the
64
+ // baseline too — a static default list here would silently miss them.
65
+ const indexedExtensions = resolveIndexedExtensions(config);
66
+ const paths = [];
67
+ const inputs = [];
68
+ for (const root of roots) {
69
+ const docIds = new Set();
70
+ const docFiles = await glob(`**/*.{${indexedExtensions.join(",")}}`, {
71
+ cwd: root.absDir,
72
+ ignore,
73
+ nodir: true,
74
+ });
75
+ for (const file of docFiles) {
76
+ const id = nodeIdFor(root, file);
77
+ docIds.add(id);
78
+ paths.push(id);
79
+ }
80
+ // Mirrors buildRootGraph/findArtifacts's own resolution of the artifacts
81
+ // globs — a wide pattern that also catches an indexed document loses to
82
+ // the document there, so the same dedup applies here.
83
+ if (config.artifacts?.length) {
84
+ const artifactFiles = await glob(config.artifacts, {
85
+ cwd: root.absDir,
86
+ ignore,
87
+ nodir: true,
88
+ posix: true,
89
+ });
90
+ for (const file of artifactFiles) {
91
+ const id = nodeIdFor(root, file);
92
+ if (!docIds.has(id))
93
+ paths.push(id);
94
+ }
95
+ }
96
+ const sidecarFiles = await glob("**/*.weft", { cwd: root.absDir, ignore, nodir: true });
97
+ for (const file of sidecarFiles) {
98
+ const content = await readFile(resolve(root.absDir, file), "utf8");
99
+ inputs.push(`${nodeIdFor(root, file)}:${hashContent(content)}`);
100
+ }
101
+ }
102
+ if (config.contributions?.length) {
103
+ const files = await glob(config.contributions, {
104
+ cwd: config.rootDir,
105
+ nodir: true,
106
+ posix: true,
107
+ });
108
+ for (const file of files) {
109
+ const content = await readFile(resolve(config.rootDir, file), "utf8");
110
+ inputs.push(`${file}:${hashContent(content)}`);
111
+ }
112
+ }
113
+ for (const configFile of findConfigFiles(config.rootDir)) {
114
+ const content = await readFile(configFile, "utf8");
115
+ inputs.push(`${basename(configFile)}:${hashContent(content)}`);
116
+ }
117
+ paths.sort();
118
+ inputs.sort();
119
+ return hashContent(JSON.stringify({ paths, inputs }));
120
+ }
121
+ /**
122
+ * Compare a manifest's recorded provenance against the docs tree it claims to
123
+ * describe.
124
+ *
125
+ * A document added, removed, or a changed non-node input is caught by
126
+ * {@link computeInputsHash}. An edited document is not — nothing about its
127
+ * path changes — so every node with a recorded hash is re-read and compared
128
+ * against it. A node that no longer resolves to a real file is skipped rather
129
+ * than treated as stale: that is a document Weft never indexed from disk in
130
+ * the first place, such as one declared entirely by a contribution.
131
+ */
132
+ export async function checkFreshness(manifest, config) {
133
+ if (!manifest.build)
134
+ return { status: "unknown" };
135
+ const inputsHash = await computeInputsHash(config);
136
+ if (inputsHash !== manifest.build.inputsHash) {
137
+ return { status: "stale", builtAt: manifest.build.builtAt };
138
+ }
139
+ const roots = resolveDocsRoots(config);
140
+ for (const node of manifest.nodes) {
141
+ if (!node.contentHash)
142
+ continue;
143
+ const match = rootForNodeId(roots, node.id);
144
+ if (!match)
145
+ continue;
146
+ const filePath = resolve(match.root.absDir, match.relPath);
147
+ let current;
148
+ try {
149
+ current =
150
+ node.type === "artifact"
151
+ ? hashBytes(await readFile(filePath))
152
+ : hashContent(await readFile(filePath, "utf8"));
153
+ }
154
+ catch (error) {
155
+ // ENOENT is the expected case — a node with no file behind it, such
156
+ // as one declared entirely by a contribution. Anything else
157
+ // (permissions, transient IO) is a real failure: swallowing it here
158
+ // would fail toward `fresh`, the one direction this module must
159
+ // never fail toward, so let it propagate instead.
160
+ if (error.code === "ENOENT")
161
+ continue;
162
+ throw error;
163
+ }
164
+ if (current !== node.contentHash) {
165
+ return { status: "stale", builtAt: manifest.build.builtAt };
166
+ }
167
+ }
168
+ return { status: "fresh", builtAt: manifest.build.builtAt };
169
+ }
170
+ //# sourceMappingURL=freshness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.js","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=freshness.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.test.d.ts","sourceRoot":"","sources":["../src/freshness.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,209 @@
1
+ import { cpSync, mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import { checkFreshness, computeInputsHash } from "./freshness.js";
7
+ import { buildManifest } from "./manifest.js";
8
+ const __dirname = resolve(fileURLToPath(import.meta.url), "..");
9
+ const FIXTURES_DIR = resolve(__dirname, "__fixtures__");
10
+ function fixtureConfig(overrides = {}) {
11
+ return {
12
+ rootDir: FIXTURES_DIR,
13
+ docsDir: "docs",
14
+ entryPoint: "docs/README.md",
15
+ ignore: [],
16
+ ...overrides,
17
+ };
18
+ }
19
+ const tempDirs = [];
20
+ /** A writable copy of the docs fixture, so a test can mutate the tree. */
21
+ function copyDocsFixture() {
22
+ const dir = mkdtempSync(resolve(tmpdir(), "weft-freshness-"));
23
+ tempDirs.push(dir);
24
+ cpSync(resolve(FIXTURES_DIR, "docs"), resolve(dir, "docs"), { recursive: true });
25
+ return dir;
26
+ }
27
+ afterEach(() => {
28
+ for (const dir of tempDirs.splice(0))
29
+ rmSync(dir, { recursive: true, force: true });
30
+ });
31
+ describe("computeInputsHash", () => {
32
+ it("is stable across two computations of an unchanged tree", async () => {
33
+ const config = fixtureConfig();
34
+ expect(await computeInputsHash(config)).toBe(await computeInputsHash(config));
35
+ });
36
+ it("changes when a document is added", async () => {
37
+ const dir = copyDocsFixture();
38
+ const config = fixtureConfig({ rootDir: dir });
39
+ const before = await computeInputsHash(config);
40
+ writeFileSync(resolve(dir, "docs", "extra.md"), "# Extra\n");
41
+ expect(await computeInputsHash(config)).not.toBe(before);
42
+ });
43
+ it("changes when a document is deleted", async () => {
44
+ const dir = copyDocsFixture();
45
+ const config = fixtureConfig({ rootDir: dir });
46
+ const before = await computeInputsHash(config);
47
+ rmSync(resolve(dir, "docs", "architecture.md"));
48
+ expect(await computeInputsHash(config)).not.toBe(before);
49
+ });
50
+ it("changes when a .weft sidecar changes", async () => {
51
+ const dir = copyDocsFixture();
52
+ writeFileSync(resolve(dir, "docs", "architecture.md.weft"), "links: []\n");
53
+ const config = fixtureConfig({ rootDir: dir });
54
+ const before = await computeInputsHash(config);
55
+ writeFileSync(resolve(dir, "docs", "architecture.md.weft"), "links:\n - target: README.md\n");
56
+ expect(await computeInputsHash(config)).not.toBe(before);
57
+ });
58
+ it("changes when a contribution file changes", async () => {
59
+ const dir = copyDocsFixture();
60
+ writeFileSync(resolve(dir, "build.json"), JSON.stringify({ version: 1, nodes: [] }));
61
+ const config = fixtureConfig({ rootDir: dir, contributions: ["build.json"] });
62
+ const before = await computeInputsHash(config);
63
+ writeFileSync(resolve(dir, "build.json"), JSON.stringify({ version: 1, nodes: [{ id: "generated.md", type: "markdown" }] }));
64
+ expect(await computeInputsHash(config)).not.toBe(before);
65
+ });
66
+ it("changes when weft.config.yaml changes", async () => {
67
+ const dir = copyDocsFixture();
68
+ writeFileSync(resolve(dir, "weft.config.yaml"), "docsDir: docs\n");
69
+ const config = fixtureConfig({ rootDir: dir });
70
+ const before = await computeInputsHash(config);
71
+ writeFileSync(resolve(dir, "weft.config.yaml"), "docsDir: docs\nignore:\n - archive/**\n");
72
+ expect(await computeInputsHash(config)).not.toBe(before);
73
+ });
74
+ it("changes when weft.config.local.yaml changes", async () => {
75
+ const dir = copyDocsFixture();
76
+ const config = fixtureConfig({ rootDir: dir });
77
+ const before = await computeInputsHash(config);
78
+ // The local overlay only carries `repos`, but a changed `repos` re-points
79
+ // every GitHub blob URL edge — it is as much a graph input as the
80
+ // committed config.
81
+ writeFileSync(resolve(dir, "weft.config.local.yaml"), "repos:\n acme/docs: ../docs\n");
82
+ expect(await computeInputsHash(config)).not.toBe(before);
83
+ });
84
+ it("covers a file whose extension the project opted in via `extensions` config", async () => {
85
+ const dir = copyDocsFixture();
86
+ const config = fixtureConfig({ rootDir: dir, extensions: { ".adoc": "markdown" } });
87
+ const before = await computeInputsHash(config);
88
+ writeFileSync(resolve(dir, "docs", "extra.adoc"), "# Extra\n");
89
+ // The baseline globs what the indexer globs — including configured
90
+ // extensions. A static default list here would miss this file.
91
+ expect(await computeInputsHash(config)).not.toBe(before);
92
+ });
93
+ it("ignores a file whose extension is not indexed", async () => {
94
+ const dir = copyDocsFixture();
95
+ const config = fixtureConfig({ rootDir: dir });
96
+ const before = await computeInputsHash(config);
97
+ writeFileSync(resolve(dir, "docs", "extra.adoc"), "# Extra\n");
98
+ // Without the `extensions` opt-in the indexer never sees this file, so
99
+ // the baseline must not either.
100
+ expect(await computeInputsHash(config)).toBe(before);
101
+ });
102
+ it("changes when an artifact is added", async () => {
103
+ const dir = copyDocsFixture();
104
+ const config = fixtureConfig({ rootDir: dir, artifacts: ["**/*.pdf"] });
105
+ const before = await computeInputsHash(config);
106
+ writeFileSync(resolve(dir, "docs", "handbook.pdf"), "%PDF-1.4\n");
107
+ expect(await computeInputsHash(config)).not.toBe(before);
108
+ });
109
+ it("changes when an artifact is deleted", async () => {
110
+ const dir = copyDocsFixture();
111
+ writeFileSync(resolve(dir, "docs", "handbook.pdf"), "%PDF-1.4\n");
112
+ const config = fixtureConfig({ rootDir: dir, artifacts: ["**/*.pdf"] });
113
+ const before = await computeInputsHash(config);
114
+ rmSync(resolve(dir, "docs", "handbook.pdf"));
115
+ expect(await computeInputsHash(config)).not.toBe(before);
116
+ });
117
+ it("does not change when only a file's mtime changes", async () => {
118
+ // Regression guard against code deliberately not written (DD-4): this
119
+ // earns its place by stopping mtime from creeping back in later, not by
120
+ // exercising any behaviour the hash is supposed to have.
121
+ const dir = copyDocsFixture();
122
+ const config = fixtureConfig({ rootDir: dir });
123
+ const before = await computeInputsHash(config);
124
+ const future = new Date(Date.now() + 60_000);
125
+ utimesSync(resolve(dir, "docs", "architecture.md"), future, future);
126
+ expect(await computeInputsHash(config)).toBe(before);
127
+ });
128
+ it("does not change when a document-shaped file lands inside .weft/ (DD-15)", async () => {
129
+ // The self-invalidating-loop guard. manifest.json itself would not have
130
+ // exercised this: .json is not an indexed extension, so it never matched
131
+ // the doc glob regardless of the ignore. A .md file is the one thing the
132
+ // doc glob would pick up — except `glob` also excludes dot-directories
133
+ // from `**` by default, independent of the ignore list, so this passes
134
+ // even with IGNORE_MANIFEST_OUTPUT removed (verified by hand, not by this
135
+ // test). It stays as documentation of intent and as the guard that
136
+ // matters the day a `dot: true` option gets added to one of these globs.
137
+ const dir = copyDocsFixture();
138
+ const config = fixtureConfig({ rootDir: dir });
139
+ const before = await computeInputsHash(config);
140
+ mkdirSync(resolve(dir, "docs", ".weft"), { recursive: true });
141
+ writeFileSync(resolve(dir, "docs", ".weft", "extra.md"), "# Extra\n");
142
+ expect(await computeInputsHash(config)).toBe(before);
143
+ });
144
+ it("produces the same hash for a CRLF and an LF working tree", async () => {
145
+ const dir = copyDocsFixture();
146
+ writeFileSync(resolve(dir, "docs", "architecture.md.weft"), "links: []\r\n");
147
+ const crlf = await computeInputsHash(fixtureConfig({ rootDir: dir }));
148
+ writeFileSync(resolve(dir, "docs", "architecture.md.weft"), "links: []\n");
149
+ const lf = await computeInputsHash(fixtureConfig({ rootDir: dir }));
150
+ expect(crlf).toBe(lf);
151
+ });
152
+ });
153
+ describe("checkFreshness", () => {
154
+ it("reports unknown for a manifest with no build block", async () => {
155
+ const config = fixtureConfig();
156
+ const manifest = await buildManifest(config);
157
+ manifest.build = undefined;
158
+ expect(await checkFreshness(manifest, config)).toEqual({ status: "unknown" });
159
+ });
160
+ it("reports fresh for an untouched tree", async () => {
161
+ const config = fixtureConfig();
162
+ const manifest = await buildManifest(config);
163
+ expect((await checkFreshness(manifest, config)).status).toBe("fresh");
164
+ });
165
+ it("reports stale after a document is edited", async () => {
166
+ const dir = copyDocsFixture();
167
+ const config = fixtureConfig({ rootDir: dir });
168
+ const manifest = await buildManifest(config);
169
+ writeFileSync(resolve(dir, "docs", "architecture.md"), "# Changed\n");
170
+ expect((await checkFreshness(manifest, config)).status).toBe("stale");
171
+ });
172
+ it("reports stale after a document is added", async () => {
173
+ const dir = copyDocsFixture();
174
+ const config = fixtureConfig({ rootDir: dir });
175
+ const manifest = await buildManifest(config);
176
+ writeFileSync(resolve(dir, "docs", "extra.md"), "# Extra\n");
177
+ expect((await checkFreshness(manifest, config)).status).toBe("stale");
178
+ });
179
+ it("reports stale after a document is deleted", async () => {
180
+ const dir = copyDocsFixture();
181
+ const config = fixtureConfig({ rootDir: dir });
182
+ const manifest = await buildManifest(config);
183
+ rmSync(resolve(dir, "docs", "architecture.md"));
184
+ expect((await checkFreshness(manifest, config)).status).toBe("stale");
185
+ });
186
+ it("reports stale after an artifact is added", async () => {
187
+ const dir = copyDocsFixture();
188
+ const config = fixtureConfig({ rootDir: dir, artifacts: ["**/*.pdf"] });
189
+ const manifest = await buildManifest(config);
190
+ writeFileSync(resolve(dir, "docs", "handbook.pdf"), "%PDF-1.4\n");
191
+ expect((await checkFreshness(manifest, config)).status).toBe("stale");
192
+ });
193
+ it("reports stale after an artifact is deleted", async () => {
194
+ const dir = copyDocsFixture();
195
+ writeFileSync(resolve(dir, "docs", "handbook.pdf"), "%PDF-1.4\n");
196
+ const config = fixtureConfig({ rootDir: dir, artifacts: ["**/*.pdf"] });
197
+ const manifest = await buildManifest(config);
198
+ rmSync(resolve(dir, "docs", "handbook.pdf"));
199
+ expect((await checkFreshness(manifest, config)).status).toBe("stale");
200
+ });
201
+ it("carries the manifest's builtAt through a stale result", async () => {
202
+ const dir = copyDocsFixture();
203
+ const config = fixtureConfig({ rootDir: dir });
204
+ const manifest = await buildManifest(config);
205
+ writeFileSync(resolve(dir, "docs", "extra.md"), "# Extra\n");
206
+ expect((await checkFreshness(manifest, config)).builtAt).toBe(manifest.build?.builtAt);
207
+ });
208
+ });
209
+ //# sourceMappingURL=freshness.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.test.js","sourceRoot":"","sources":["../src/freshness.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface Frontmatter {
2
+ title?: string;
3
+ theme?: "light" | "dark";
4
+ description?: string;
5
+ ogImage?: string;
6
+ /** Document version, always a string — see `documentVersion`. */
7
+ version?: string;
8
+ [key: string]: unknown;
9
+ }
10
+ /** Strip and parse YAML frontmatter from markdown content. */
11
+ export declare function parseFrontmatter(content: string): {
12
+ data: Frontmatter;
13
+ body: string;
14
+ };
15
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAUD,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAarF"}
@@ -0,0 +1,24 @@
1
+ import { isMap, parseDocument } from "yaml";
2
+ import { scalarText } from "./scalar-source.js";
3
+ /** Read `version` exactly as it was written — see `scalarText`. */
4
+ function documentVersion(doc) {
5
+ const contents = doc.contents;
6
+ if (!isMap(contents))
7
+ return undefined;
8
+ return scalarText(contents.get("version", true));
9
+ }
10
+ /** Strip and parse YAML frontmatter from markdown content. */
11
+ export function parseFrontmatter(content) {
12
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
13
+ if (!match)
14
+ return { data: {}, body: content };
15
+ const doc = parseDocument(match[1]);
16
+ const parsed = (doc.toJS() ?? {});
17
+ // Overwritten rather than taken as parsed: `version:` with nothing after it
18
+ // yields null, and a consumer typed against `string | undefined` would carry
19
+ // that straight into the manifest.
20
+ const data = { ...parsed, version: documentVersion(doc) };
21
+ const body = content.slice(match[0].length);
22
+ return { data, body };
23
+ }
24
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAYhD,mEAAmE;AACnE,SAAS,eAAe,CAAC,GAAa;IACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAEvC,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAE/C,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAgB,CAAC;IACjD,4EAA4E;IAC5E,6EAA6E;IAC7E,mCAAmC;IACnC,MAAM,IAAI,GAAgB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;IAEvE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=frontmatter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.test.d.ts","sourceRoot":"","sources":["../src/frontmatter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseFrontmatter } from "./frontmatter.js";
3
+ function frontmatter(yaml) {
4
+ return parseFrontmatter(`---\n${yaml}\n---\n# Title\n`).data;
5
+ }
6
+ describe("parseFrontmatter", () => {
7
+ it("parses the fields the indexer consumes", () => {
8
+ const data = frontmatter("title: API\ntheme: dark\ndescription: Reference\nogImage: /og.png");
9
+ expect(data).toMatchObject({
10
+ title: "API",
11
+ theme: "dark",
12
+ description: "Reference",
13
+ ogImage: "/og.png",
14
+ });
15
+ });
16
+ it("returns no data and the whole document when there is no frontmatter", () => {
17
+ const { data, body } = parseFrontmatter("# Title\n");
18
+ expect(data).toEqual({});
19
+ expect(body).toBe("# Title\n");
20
+ });
21
+ it("strips the frontmatter block from the body", () => {
22
+ expect(parseFrontmatter("---\ntitle: A\n---\n# Title\n").body).toBe("# Title\n");
23
+ });
24
+ });
25
+ describe("parseFrontmatter (version)", () => {
26
+ it("reads a quoted version", () => {
27
+ expect(frontmatter('version: "2.41"').version).toBe("2.41");
28
+ });
29
+ it("reads an unquoted version as a string", () => {
30
+ expect(frontmatter("version: 2.41").version).toBe("2.41");
31
+ });
32
+ it("keeps a trailing zero YAML would otherwise drop", () => {
33
+ // Unquoted 2.10 parses as the number 2.1, which would report a mismatch
34
+ // against a document that plainly says 2.10.
35
+ expect(frontmatter("version: 2.10").version).toBe("2.10");
36
+ });
37
+ it("keeps a version that is only digits as written", () => {
38
+ expect(frontmatter("version: 7").version).toBe("7");
39
+ });
40
+ it("reads a version that is not a number at all", () => {
41
+ expect(frontmatter("version: 1.2.3-rc1").version).toBe("1.2.3-rc1");
42
+ });
43
+ it("leaves version absent when the document declares none", () => {
44
+ expect(frontmatter("title: Registry").version).toBeUndefined();
45
+ });
46
+ it("leaves version absent when the key is present but empty", () => {
47
+ // An append-only registry has no version, and absence must not be an error.
48
+ expect(frontmatter("version:").version).toBeUndefined();
49
+ });
50
+ it("does not invent a version from a document with no frontmatter", () => {
51
+ expect(parseFrontmatter("# Title\n").data.version).toBeUndefined();
52
+ });
53
+ });
54
+ //# sourceMappingURL=frontmatter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.test.js","sourceRoot":"","sources":["../src/frontmatter.test.ts"],"names":[],"mappings":"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"}