@eqtylab/docs 0.3.5 → 0.4.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 (50) hide show
  1. package/README.md +45 -12
  2. package/dist/{chunk-VJN3HXKM.js → chunk-LNQV7TVH.js} +18 -6
  3. package/dist/chunk-LNQV7TVH.js.map +1 -0
  4. package/dist/chunk-OM7745ZQ.js +77 -0
  5. package/dist/chunk-OM7745ZQ.js.map +1 -0
  6. package/dist/{chunk-IWKZQ4CW.js → chunk-VNQZJMDO.js} +4 -2
  7. package/dist/chunk-VNQZJMDO.js.map +1 -0
  8. package/dist/{chunk-K7PJRTK3.js → chunk-X4LT7ASQ.js} +31 -4
  9. package/dist/chunk-X4LT7ASQ.js.map +1 -0
  10. package/dist/config.d.ts +10 -1
  11. package/dist/config.js +1 -1
  12. package/dist/index.js +295 -28
  13. package/dist/index.js.map +1 -1
  14. package/dist/internal/extract-versions.d.ts +33 -0
  15. package/dist/internal/rehype-base-url.d.ts +9 -2
  16. package/dist/internal/rehype-base-url.js +1 -1
  17. package/dist/internal/remark-archive-document.d.ts +6 -0
  18. package/dist/loaders.d.ts +5 -5
  19. package/dist/loaders.js +18 -7
  20. package/dist/loaders.js.map +1 -1
  21. package/dist/nav.d.ts +4 -0
  22. package/dist/nav.js +21 -7
  23. package/dist/nav.js.map +1 -1
  24. package/dist/paths.d.ts +25 -3
  25. package/dist/paths.js +1 -1
  26. package/dist/runtime/chrome/GlobalSearch.tsx +7 -2
  27. package/dist/runtime/chrome/Header.astro +8 -3
  28. package/dist/runtime/chrome/NavDrawer.astro +44 -1
  29. package/dist/runtime/chrome/NavTree.astro +1 -11
  30. package/dist/runtime/chrome/Prose.astro +17 -3
  31. package/dist/runtime/chrome/VersionBanner.astro +19 -0
  32. package/dist/runtime/chrome/VersionSwitcher.tsx +105 -0
  33. package/dist/runtime/layouts/DocsPage.astro +36 -4
  34. package/dist/runtime/layouts/DocsShell.astro +4 -2
  35. package/dist/runtime/layouts/RedirectPage.astro +19 -0
  36. package/dist/runtime/lib/nav-data.ts +44 -15
  37. package/dist/runtime/lib/versions-ui.ts +12 -0
  38. package/dist/runtime/routes/docs-md.ts +50 -11
  39. package/dist/runtime/routes/docs.astro +182 -73
  40. package/dist/runtime/routes/llms-txt.ts +2 -1
  41. package/dist/runtime/routes/not-found.astro +27 -0
  42. package/dist/schema.d.ts +1 -0
  43. package/dist/schema.js +1 -1
  44. package/dist/versions.d.ts +37 -0
  45. package/package.json +2 -2
  46. package/dist/chunk-GSSSQTEV.js +0 -51
  47. package/dist/chunk-GSSSQTEV.js.map +0 -1
  48. package/dist/chunk-IWKZQ4CW.js.map +0 -1
  49. package/dist/chunk-K7PJRTK3.js.map +0 -1
  50. package/dist/chunk-VJN3HXKM.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import './chunk-55J6XMHW.js';
2
- import { rehypeBaseUrl } from './chunk-GSSSQTEV.js';
3
- import { resolveConfig } from './chunk-K7PJRTK3.js';
4
- export { resolveConfig } from './chunk-K7PJRTK3.js';
2
+ import { rehypeBaseUrl, versionFromPath } from './chunk-OM7745ZQ.js';
3
+ import { resolveConfig } from './chunk-X4LT7ASQ.js';
4
+ export { resolveConfig } from './chunk-X4LT7ASQ.js';
5
5
  import { resolveDocsEnv } from './chunk-ZZ45WAVR.js';
6
6
  export { resolveDocsEnv } from './chunk-ZZ45WAVR.js';
7
- import { docsHref, isExternalHref } from './chunk-VJN3HXKM.js';
8
- export { badgeSchema, docsSchema, groupSchema } from './chunk-IWKZQ4CW.js';
9
- import path4 from 'path';
7
+ import { docsHref, isExternalHref } from './chunk-LNQV7TVH.js';
8
+ export { badgeSchema, docsSchema, groupSchema } from './chunk-VNQZJMDO.js';
9
+ import path5 from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { globSync } from 'tinyglobby';
12
+ import { execFileSync } from 'child_process';
12
13
  import fs, { readdirSync, readFileSync } from 'fs';
13
14
  import { createRequire } from 'module';
14
15
  import { visit, SKIP } from 'unist-util-visit';
@@ -24,7 +25,7 @@ function assertMdxOnly(contentDir) {
24
25
  if (strays.length === 0) return;
25
26
  const list = strays.sort().map((file) => ` - ${file} -> ${file.replace(/\.md$/i, ".mdx")}`).join("\n");
26
27
  throw new Error(
27
- `[@eqtylab/docs] Found ${strays.length} Markdown file${strays.length > 1 ? "s" : ""} in ${path4.relative(process.cwd(), dir) || dir}:
28
+ `[@eqtylab/docs] Found ${strays.length} Markdown file${strays.length > 1 ? "s" : ""} in ${path5.relative(process.cwd(), dir) || dir}:
28
29
  ${list}
29
30
 
30
31
  Docs content must be .mdx. Only MDX supports the component overrides that render
@@ -33,12 +34,242 @@ different from every other page. Renaming is usually the only change needed --
33
34
  MDX is a superset of Markdown.`
34
35
  );
35
36
  }
37
+
38
+ // src/versions.ts
39
+ var SEMVER = /(?:^|[^0-9.])(\d+)\.(\d+)\.(\d+)$/;
40
+ var LEVELS = ["patch", "minor", "major"];
41
+ function parseTag(tag) {
42
+ const m = SEMVER.exec(tag.trim());
43
+ if (!m) return null;
44
+ const major = Number(m[1]);
45
+ const minor = Number(m[2]);
46
+ const patch = Number(m[3]);
47
+ return { tag, major, minor, patch, version: `${major}.${minor}.${patch}` };
48
+ }
49
+ function compareTags(a, b) {
50
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
51
+ }
52
+ function groupOf(v, granularity) {
53
+ if (granularity === "major") return `${v.major}`;
54
+ if (granularity === "minor") return `${v.major}.${v.minor}`;
55
+ return v.version;
56
+ }
57
+ function idOf(group) {
58
+ return `v${group}`;
59
+ }
60
+ function suffixOf(id) {
61
+ return id.replace(/\./g, "_");
62
+ }
63
+ function highestTag(tags) {
64
+ const parsed = tags.map(parseTag).filter((t) => t !== null);
65
+ if (!parsed.length) return null;
66
+ return parsed.sort(compareTags)[parsed.length - 1];
67
+ }
68
+ function selectVersions(tags, current, granularity, skippedGroups = []) {
69
+ const cur = parseTag(current);
70
+ if (!cur) {
71
+ throw new Error(`[@eqtylab/docs] versions.current "${current}" is not MAJOR.MINOR.PATCH`);
72
+ }
73
+ const skipped = [];
74
+ const above = [];
75
+ const parsed = [];
76
+ for (const tag of tags) {
77
+ const p = parseTag(tag);
78
+ if (p) parsed.push(p);
79
+ else skipped.push(tag);
80
+ }
81
+ parsed.sort(compareTags);
82
+ const currentGroup = groupOf(cur, granularity);
83
+ const skip = new Set(skippedGroups);
84
+ const byGroup = /* @__PURE__ */ new Map();
85
+ const inCurrent = [];
86
+ const covered = [];
87
+ for (const p of parsed) {
88
+ if (compareTags(p, cur) > 0) {
89
+ above.push(p.tag);
90
+ continue;
91
+ }
92
+ covered.push(p);
93
+ const g = groupOf(p, granularity);
94
+ if (g === currentGroup) {
95
+ inCurrent.push(p);
96
+ continue;
97
+ }
98
+ if (!byGroup.has(g)) byGroup.set(g, []);
99
+ byGroup.get(g).push(p);
100
+ }
101
+ const copies = [];
102
+ const picks = [];
103
+ const coverOf = /* @__PURE__ */ new Map();
104
+ let lastCovered = null;
105
+ for (const [g, members] of byGroup) {
106
+ if (skip.has(g)) {
107
+ coverOf.set(g, lastCovered);
108
+ continue;
109
+ }
110
+ const pick = members[members.length - 1];
111
+ const id = idOf(g);
112
+ copies.push({ id, suffix: suffixOf(id), group: g, tag: pick.tag });
113
+ picks.push(pick);
114
+ coverOf.set(g, id);
115
+ lastCovered = id;
116
+ }
117
+ const redirects = [];
118
+ for (const [g, members] of byGroup) {
119
+ const to = coverOf.get(g) ?? null;
120
+ for (const p of members) {
121
+ const id = idOf(p.version);
122
+ if (id === to) continue;
123
+ redirects.push({ id, to });
124
+ }
125
+ }
126
+ for (const p of inCurrent) redirects.push({ id: idOf(p.version), to: null });
127
+ copies.reverse();
128
+ picks.reverse();
129
+ const emittedIds = new Set(redirects.map((r) => r.id));
130
+ const copyIds = new Set(copies.map((c) => c.id));
131
+ const stubSources = copies.length ? covered.some((p) => p.version === cur.version) ? covered : [...covered, cur] : [];
132
+ for (const level of LEVELS.slice(LEVELS.indexOf(granularity))) {
133
+ const seenGroups = /* @__PURE__ */ new Set();
134
+ for (let i = stubSources.length - 1; i >= 0; i--) {
135
+ const group = groupOf(stubSources[i], level);
136
+ if (seenGroups.has(group)) continue;
137
+ seenGroups.add(group);
138
+ const id = idOf(group);
139
+ if (copyIds.has(id) || emittedIds.has(id)) continue;
140
+ const holdsCurrent = groupOf(cur, level) === group;
141
+ let cover = null;
142
+ for (let c = 0; !holdsCurrent && c < copies.length; c++) {
143
+ if (groupOf(picks[c], level) === group) {
144
+ cover = copies[c].id;
145
+ break;
146
+ }
147
+ }
148
+ if (!holdsCurrent && cover === null) continue;
149
+ redirects.push({ id, to: cover });
150
+ emittedIds.add(id);
151
+ }
152
+ }
153
+ return { copies, redirects, skipped, above };
154
+ }
155
+
156
+ // src/internal/extract-versions.ts
157
+ var EMPTY_VERSIONS = {
158
+ currentVersion: null,
159
+ versionManifest: [],
160
+ versionRedirects: []
161
+ };
162
+ var TAG = "[@eqtylab/docs] versions:";
163
+ function git(cwd, ...args) {
164
+ return execFileSync("git", args, {
165
+ cwd,
166
+ encoding: "utf8",
167
+ stdio: ["ignore", "pipe", "pipe"]
168
+ }).trim();
169
+ }
170
+ function realpath(p) {
171
+ try {
172
+ return fs.realpathSync(p);
173
+ } catch {
174
+ return p;
175
+ }
176
+ }
177
+ function hasPathAtTag(repoRoot, tag, rel) {
178
+ try {
179
+ execFileSync("git", ["cat-file", "-e", `${tag}:${rel}`], { cwd: repoRoot, stdio: "ignore" });
180
+ return true;
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+ function extractVersions(o) {
186
+ let repoRoot;
187
+ try {
188
+ repoRoot = git(o.root, "rev-parse", "--show-toplevel");
189
+ } catch {
190
+ o.logger.warn(
191
+ `${TAG} not a git repository, or git is missing. Versioning is off for this build.`
192
+ );
193
+ return EMPTY_VERSIONS;
194
+ }
195
+ if (git(o.root, "rev-parse", "--is-shallow-repository") === "true") {
196
+ o.logger.warn(`${TAG} shallow clone; tags may be incomplete. Check out with fetch-depth: 0.`);
197
+ }
198
+ const tags = git(o.root, "tag", "--list", o.tags).split("\n").filter(Boolean);
199
+ const currentVersion = o.current ?? highestTag(tags)?.version;
200
+ if (!currentVersion) {
201
+ o.logger.info(
202
+ `${TAG} on and dormant. No tag matches "${o.tags}"; versioning activates at the first release tag.`
203
+ );
204
+ return EMPTY_VERSIONS;
205
+ }
206
+ const contentRel = path5.relative(realpath(repoRoot), realpath(o.contentDirAbs)).split(path5.sep).join("/");
207
+ let selection = selectVersions(tags, currentVersion, o.granularity);
208
+ for (const tag of selection.skipped) {
209
+ o.logger.warn(`${TAG} tag "${tag}" is not MAJOR.MINOR.PATCH; skipped.`);
210
+ }
211
+ if (selection.above.length) {
212
+ o.logger.warn(
213
+ `${TAG} tags above current ${currentVersion} ignored: ${selection.above.join(", ")}.`
214
+ );
215
+ }
216
+ const missing = selection.copies.filter((c) => !hasPathAtTag(repoRoot, c.tag, contentRel));
217
+ if (missing.length) {
218
+ for (const c of missing) {
219
+ o.logger.warn(
220
+ `${TAG} ${c.tag} has no ${contentRel}; skipping ${c.id}. Its releases redirect to the copy below.`
221
+ );
222
+ }
223
+ selection = selectVersions(
224
+ tags,
225
+ currentVersion,
226
+ o.granularity,
227
+ missing.map((c) => c.group)
228
+ );
229
+ }
230
+ const versionsDir = path5.join(o.cacheDir, "versions");
231
+ fs.rmSync(versionsDir, { recursive: true, force: true });
232
+ const versionManifest = selection.copies.map((c) => {
233
+ const dir = path5.join(versionsDir, c.id);
234
+ fs.mkdirSync(dir, { recursive: true });
235
+ try {
236
+ const archive = execFileSync("git", ["archive", `${c.tag}:${contentRel}`], {
237
+ cwd: repoRoot,
238
+ maxBuffer: 512 * 1024 * 1024,
239
+ stdio: ["ignore", "pipe", "inherit"]
240
+ });
241
+ execFileSync("tar", ["-x", "-C", dir], {
242
+ input: archive,
243
+ stdio: ["pipe", "ignore", "inherit"]
244
+ });
245
+ } catch (cause) {
246
+ throw new Error(`${TAG} ${c.tag} failed to extract ${contentRel} into ${dir}`, { cause });
247
+ }
248
+ if (!fs.readdirSync(dir).length) {
249
+ throw new Error(`${TAG} ${c.tag} extracted no files into ${dir}`);
250
+ }
251
+ return { ...c, dir };
252
+ });
253
+ const cur = parseTag(currentVersion);
254
+ const group = groupOf(cur, o.granularity);
255
+ const result = {
256
+ currentVersion: { id: idOf(group), group, version: cur.version },
257
+ versionManifest,
258
+ versionRedirects: selection.redirects
259
+ };
260
+ fs.mkdirSync(o.cacheDir, { recursive: true });
261
+ fs.writeFileSync(path5.join(o.cacheDir, "versions.json"), JSON.stringify(result, null, 2));
262
+ o.logger.info(
263
+ `${TAG} current ${result.currentVersion.id}; ${versionManifest.length} older ${versionManifest.length === 1 ? "copy" : "copies"}, ${selection.redirects.length} redirects.`
264
+ );
265
+ return result;
266
+ }
36
267
  var GRAMMAR_IMPORT = "grammars/${";
37
268
  function findGrammarDir(projectRoot) {
38
269
  const requireFromProject = createRequire(new URL("package.json", projectRoot));
39
270
  const equalityEntry = requireFromProject.resolve("@eqtylab/equality");
40
271
  const requireFromEquality = createRequire(equalityEntry);
41
- return path4.resolve(path4.dirname(requireFromEquality.resolve("microlighter")), "grammars");
272
+ return path5.resolve(path5.dirname(requireFromEquality.resolve("microlighter")), "grammars");
42
273
  }
43
274
  function microlighterGrammarsPlugin(projectRoot, logger) {
44
275
  return {
@@ -46,7 +277,7 @@ function microlighterGrammarsPlugin(projectRoot, logger) {
46
277
  apply: "build",
47
278
  generateBundle(_options, bundle) {
48
279
  const directories = new Set(
49
- Object.values(bundle).filter((output) => output.type === "chunk" && output.code.includes(GRAMMAR_IMPORT)).map((output) => path4.posix.dirname(output.fileName))
280
+ Object.values(bundle).filter((output) => output.type === "chunk" && output.code.includes(GRAMMAR_IMPORT)).map((output) => path5.posix.dirname(output.fileName))
50
281
  );
51
282
  if (directories.size === 0) return;
52
283
  let grammarDir;
@@ -66,7 +297,7 @@ function microlighterGrammarsPlugin(projectRoot, logger) {
66
297
  this.emitFile({
67
298
  type: "asset",
68
299
  fileName: directory === "." ? `grammars/${grammar}` : `${directory}/grammars/${grammar}`,
69
- source: readFileSync(path4.resolve(grammarDir, grammar), "utf8")
300
+ source: readFileSync(path5.resolve(grammarDir, grammar), "utf8")
70
301
  });
71
302
  }
72
303
  }
@@ -97,7 +328,7 @@ function pagefindIntegration() {
97
328
  name: "@eqtylab/docs/pagefind",
98
329
  hooks: {
99
330
  "astro:config:setup"({ config, logger }) {
100
- indexDir = path4.join(fileURLToPath(config.outDir), INDEX_DIR);
331
+ indexDir = path5.join(fileURLToPath(config.outDir), INDEX_DIR);
101
332
  if (config.output === "server") {
102
333
  logger.warn(
103
334
  'Output "server" emits no static HTML, so there is nothing for Pagefind to index.'
@@ -113,9 +344,9 @@ function pagefindIntegration() {
113
344
  server.middlewares.use((req, res, next) => {
114
345
  const requested = indexRequest(req.url, base);
115
346
  if (requested === null) return next();
116
- const file = path4.join(indexDir, requested);
117
- if (!file.startsWith(indexDir + path4.sep) || !fs.existsSync(file)) return next();
118
- res.setHeader("Content-Type", MIME[path4.extname(file)] ?? "application/octet-stream");
347
+ const file = path5.join(indexDir, requested);
348
+ if (!file.startsWith(indexDir + path5.sep) || !fs.existsSync(file)) return next();
349
+ res.setHeader("Content-Type", MIME[path5.extname(file)] ?? "application/octet-stream");
119
350
  res.setHeader("Cache-Control", "no-store");
120
351
  fs.createReadStream(file).pipe(res);
121
352
  });
@@ -130,7 +361,7 @@ function pagefindIntegration() {
130
361
  const { page_count, errors: addErrors } = await index.addDirectory({ path: outDir });
131
362
  failOn(addErrors, logger, "index the built output");
132
363
  const { outputPath, errors: writeErrors } = await index.writeFiles({
133
- outputPath: path4.join(outDir, INDEX_DIR)
364
+ outputPath: path5.join(outDir, INDEX_DIR)
134
365
  });
135
366
  failOn(writeErrors, logger, "write the index");
136
367
  logger.info(`indexed ${page_count} pages to ${outputPath}`);
@@ -202,9 +433,9 @@ function rehypeProseScope() {
202
433
  }
203
434
  var CONTENT_EXTENSIONS = [".mdx", ".md"];
204
435
  function hasEntry(contentRoot, id) {
205
- const stem = path4.join(contentRoot, id);
436
+ const stem = path5.join(contentRoot, id);
206
437
  return CONTENT_EXTENSIONS.some(
207
- (ext) => fs.existsSync(stem + ext) || fs.existsSync(path4.join(stem, `index${ext}`))
438
+ (ext) => fs.existsSync(stem + ext) || fs.existsSync(path5.join(stem, `index${ext}`))
208
439
  );
209
440
  }
210
441
  function splitTarget(href) {
@@ -220,18 +451,18 @@ function rehypeRelativeLinks(options) {
220
451
  return function transformer(tree, file) {
221
452
  const filePath = file?.path;
222
453
  if (!filePath) return;
223
- const fromRoot = path4.relative(contentRoot, filePath);
224
- if (!fromRoot || fromRoot.startsWith("..") || path4.isAbsolute(fromRoot)) return;
454
+ const fromRoot = path5.relative(contentRoot, filePath);
455
+ if (!fromRoot || fromRoot.startsWith("..") || path5.isAbsolute(fromRoot)) return;
225
456
  visit(tree, "element", (node) => {
226
457
  const props = node.properties;
227
458
  const href = props?.href;
228
459
  if (!props || typeof href !== "string") return;
229
460
  const split = splitTarget(href);
230
461
  if (!split) return;
231
- const absolute = path4.resolve(path4.dirname(filePath), split.target);
232
- const relative = path4.relative(contentRoot, absolute);
233
- if (!relative || relative.startsWith("..") || path4.isAbsolute(relative)) return;
234
- const id = relative.split(path4.sep).join("/").replace(/\.mdx?$/i, "").replace(/\/index$/, "");
462
+ const absolute = path5.resolve(path5.dirname(filePath), split.target);
463
+ const relative = path5.relative(contentRoot, absolute);
464
+ if (!relative || relative.startsWith("..") || path5.isAbsolute(relative)) return;
465
+ const id = relative.split(path5.sep).join("/").replace(/\.mdx?$/i, "").replace(/\/index$/, "");
235
466
  if (!hasEntry(contentRoot, id)) onMissing?.(href, filePath);
236
467
  props.href = docsHref(id, ctx) + split.suffix;
237
468
  });
@@ -268,10 +499,31 @@ function rehypeTableColumns() {
268
499
  });
269
500
  };
270
501
  }
502
+
503
+ // src/internal/remark-archive-document.ts
504
+ var MDX_NODES = /* @__PURE__ */ new Set([
505
+ "mdxjsEsm",
506
+ "mdxJsxFlowElement",
507
+ "mdxJsxTextElement",
508
+ "mdxFlowExpression",
509
+ "mdxTextExpression"
510
+ ]);
511
+ function prune(node) {
512
+ if (!Array.isArray(node.children)) return;
513
+ node.children = node.children.filter((child) => !MDX_NODES.has(child.type));
514
+ for (const child of node.children) prune(child);
515
+ }
516
+ function remarkArchiveDocument(options) {
517
+ return function transformer(tree, file) {
518
+ const version = versionFromPath(file?.path ?? file?.history?.[0], options.versionsDir);
519
+ if (!version) return;
520
+ prune(tree);
521
+ };
522
+ }
271
523
  var PAGE_EXTENSIONS = ["astro", "md", "mdx", "html", "ts", "js"];
272
524
  function toRoutePath(relative) {
273
525
  const withoutExt = relative.replace(/\.[^./]+$/, "");
274
- const segments = withoutExt.split(path4.sep).join("/").split("/").filter(Boolean);
526
+ const segments = withoutExt.split(path5.sep).join("/").split("/").filter(Boolean);
275
527
  if (segments.at(-1) === "index") segments.pop();
276
528
  return "/" + segments.join("/");
277
529
  }
@@ -289,7 +541,7 @@ function scanConsumerPages(srcDir) {
289
541
  const ownedPaths = [];
290
542
  const claimedPatterns = /* @__PURE__ */ new Set();
291
543
  for (const file of files) {
292
- if (file.split(path4.sep).some((segment) => segment.startsWith("_"))) continue;
544
+ if (file.split(path5.sep).some((segment) => segment.startsWith("_"))) continue;
293
545
  const routePath = toRoutePath(file);
294
546
  claimedPatterns.add(routePath === "" ? "/" : routePath);
295
547
  if (!/[[\]]/.test(file)) {
@@ -353,6 +605,15 @@ function docs(options = {}) {
353
605
  async "astro:config:setup"(params) {
354
606
  const { config, injectRoute, injectScript, updateConfig, addWatchFile, logger, command } = params;
355
607
  assertMdxOnly(new URL(`./${cfg.contentDir}/`, config.srcDir));
608
+ const versionData = cfg.versions === false ? EMPTY_VERSIONS : extractVersions({
609
+ root: fileURLToPath(config.root),
610
+ contentDirAbs: fileURLToPath(new URL(`./${cfg.contentDir}/`, config.srcDir)),
611
+ cacheDir: fileURLToPath(new URL("./.astro/eqty-docs/", config.root)),
612
+ current: cfg.versions.current,
613
+ tags: cfg.versions.tags,
614
+ granularity: cfg.versions.granularity,
615
+ logger
616
+ });
356
617
  const consumer = scanConsumerPages(config.srcDir);
357
618
  for (const route of plannedRoutes(cfg)) {
358
619
  if (consumer.claimedPatterns.has(route.pattern)) {
@@ -367,7 +628,8 @@ function docs(options = {}) {
367
628
  ...cfg,
368
629
  env,
369
630
  // Read by the catch-all's getStaticPaths so no path is emitted twice.
370
- ownedByConsumer: consumer.ownedPaths
631
+ ownedByConsumer: consumer.ownedPaths,
632
+ ...versionData
371
633
  };
372
634
  if (command === "dev") {
373
635
  addWatchFile(new URL(`./${cfg.contentDir}/`, config.srcDir));
@@ -414,8 +676,10 @@ function docs(options = {}) {
414
676
  };
415
677
  markdown.gfm = true;
416
678
  markdown.smartypants = true;
679
+ const versionsDir = fileURLToPath(new URL("./.astro/eqty-docs/versions/", config.root));
680
+ markdown.remarkPlugins = [[remarkArchiveDocument, { versionsDir }]];
417
681
  markdown.rehypePlugins = [
418
- [rehypeBaseUrl, { base: config.base }],
682
+ [rehypeBaseUrl, { base: config.base, pathPrefix: cfg.pathPrefix, versionsDir }],
419
683
  // Must follow rehypeBaseUrl: the hrefs this emits already carry `base`, and
420
684
  // rehypeBaseUrl would prefix them a second time.
421
685
  [
@@ -425,7 +689,7 @@ function docs(options = {}) {
425
689
  base: config.base,
426
690
  pathPrefix: cfg.pathPrefix,
427
691
  onMissing: (href, filePath) => logger.warn(
428
- `${path4.relative(fileURLToPath(config.root), filePath)} links to ${href}, which is not a page in ${cfg.contentDir}`
692
+ `${path5.relative(fileURLToPath(config.root), filePath)} links to ${href}, which is not a page in ${cfg.contentDir}`
429
693
  )
430
694
  }
431
695
  ],
@@ -454,6 +718,9 @@ function docs(options = {}) {
454
718
  ` const config: import('@eqtylab/docs').DocsConfig & {`,
455
719
  ` env: import('@eqtylab/docs').DocsEnv;`,
456
720
  ` ownedByConsumer: string[];`,
721
+ ` currentVersion: { id: string; group: string; version: string } | null;`,
722
+ ` versionManifest: Array<{ id: string; suffix: string; group: string; tag: string; dir: string }>;`,
723
+ ` versionRedirects: Array<{ id: string; to: string | null }>;`,
457
724
  ` };`,
458
725
  ` export default config;`,
459
726
  `}`,