@doxbrix/doxloop 0.1.5 → 0.2.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 (357) hide show
  1. package/CHANGELOG.md +1145 -0
  2. package/README.md +422 -227
  3. package/assets/doxbrix-preview.css +77 -15
  4. package/contracts/agent-events-v1.schema.json +1 -0
  5. package/contracts/coverage-v1.schema.json +59 -0
  6. package/contracts/drift-v1.schema.json +1 -0
  7. package/contracts/evaluation-v1.schema.json +62 -0
  8. package/contracts/quality-codes-v1.json +1 -0
  9. package/contracts/quality-report-v1.schema.json +1 -0
  10. package/contracts/validation-v1.schema.json +1 -0
  11. package/dist/agent-failure.d.ts +36 -0
  12. package/dist/agent-failure.js +98 -0
  13. package/dist/agent-isolation.d.ts +17 -0
  14. package/dist/agent-isolation.js +94 -0
  15. package/dist/agent-log.d.ts +197 -0
  16. package/dist/agent-log.js +1094 -0
  17. package/dist/agent-process.d.ts +63 -0
  18. package/dist/agent-process.js +128 -0
  19. package/dist/agent-reply.d.ts +47 -0
  20. package/dist/agent-reply.js +315 -0
  21. package/dist/agents.d.ts +24 -5
  22. package/dist/agents.js +60 -9
  23. package/dist/api-coverage.d.ts +11 -0
  24. package/dist/api-coverage.js +72 -0
  25. package/dist/application-probe.d.ts +28 -0
  26. package/dist/application-probe.js +93 -0
  27. package/dist/application-url.d.ts +14 -0
  28. package/dist/application-url.js +26 -0
  29. package/dist/args.js +15 -0
  30. package/dist/artifact-deploy.d.ts +9 -0
  31. package/dist/artifact-deploy.js +28 -8
  32. package/dist/assets.d.ts +72 -0
  33. package/dist/assets.js +382 -0
  34. package/dist/auth.d.ts +10 -0
  35. package/dist/auth.js +38 -9
  36. package/dist/author.d.ts +206 -4
  37. package/dist/author.js +1650 -52
  38. package/dist/authoring-batches.d.ts +190 -0
  39. package/dist/authoring-batches.js +489 -0
  40. package/dist/authoring-postpass.d.ts +79 -0
  41. package/dist/authoring-postpass.js +1289 -0
  42. package/dist/authoring-progress.d.ts +95 -0
  43. package/dist/authoring-progress.js +269 -0
  44. package/dist/autosync.d.ts +56 -0
  45. package/dist/autosync.js +442 -0
  46. package/dist/batch-artifacts.d.ts +81 -0
  47. package/dist/batch-artifacts.js +412 -0
  48. package/dist/batch-limits.d.ts +37 -0
  49. package/dist/batch-limits.js +80 -0
  50. package/dist/branding.d.ts +41 -0
  51. package/dist/branding.js +229 -0
  52. package/dist/bundle-upload.d.ts +26 -0
  53. package/dist/bundle-upload.js +99 -0
  54. package/dist/capture-auth.d.ts +95 -0
  55. package/dist/capture-auth.js +204 -0
  56. package/dist/capture.d.ts +48 -0
  57. package/dist/capture.js +20 -1
  58. package/dist/cli.js +839 -32
  59. package/dist/content-links.d.ts +10 -0
  60. package/dist/content-links.js +49 -0
  61. package/dist/contract-validation.d.ts +4 -0
  62. package/dist/contract-validation.js +25 -0
  63. package/dist/coverage-actions.d.ts +8 -0
  64. package/dist/coverage-actions.js +71 -0
  65. package/dist/coverage-resolutions.d.ts +16 -0
  66. package/dist/coverage-resolutions.js +58 -0
  67. package/dist/db.d.ts +37 -0
  68. package/dist/db.js +288 -0
  69. package/dist/demo.d.ts +12 -0
  70. package/dist/demo.js +122 -0
  71. package/dist/deploy-credentials.d.ts +7 -0
  72. package/dist/deploy-credentials.js +76 -0
  73. package/dist/deploy-targets/github-pages.d.ts +3 -0
  74. package/dist/deploy-targets/github-pages.js +100 -0
  75. package/dist/deploy-targets/index.d.ts +8 -0
  76. package/dist/deploy-targets/index.js +78 -0
  77. package/dist/deploy-targets/netlify.d.ts +3 -0
  78. package/dist/deploy-targets/netlify.js +49 -0
  79. package/dist/deploy-targets/types.d.ts +32 -0
  80. package/dist/deploy-targets/types.js +2 -0
  81. package/dist/deploy-targets/vercel.d.ts +3 -0
  82. package/dist/deploy-targets/vercel.js +95 -0
  83. package/dist/deploy.d.ts +9 -0
  84. package/dist/deploy.js +87 -13
  85. package/dist/deterministic-capture.d.ts +90 -0
  86. package/dist/deterministic-capture.js +435 -0
  87. package/dist/direct-edit.d.ts +40 -0
  88. package/dist/direct-edit.js +159 -0
  89. package/dist/docs-crawl.d.ts +86 -0
  90. package/dist/docs-crawl.js +536 -0
  91. package/dist/docs-site.d.ts +49 -0
  92. package/dist/docs-site.js +233 -0
  93. package/dist/doctor.js +8 -0
  94. package/dist/documentation-collections.d.ts +23 -0
  95. package/dist/documentation-collections.js +191 -0
  96. package/dist/documentation-plan.d.ts +253 -0
  97. package/dist/documentation-plan.js +2563 -0
  98. package/dist/doxbrix-build.d.ts +19 -0
  99. package/dist/doxbrix-build.js +167 -0
  100. package/dist/doxbrix-markdown.d.ts +9 -0
  101. package/dist/doxbrix-markdown.js +115 -14
  102. package/dist/drift.d.ts +10 -0
  103. package/dist/drift.js +164 -0
  104. package/dist/evaluation.d.ts +46 -0
  105. package/dist/evaluation.js +113 -0
  106. package/dist/evidence-pack.d.ts +47 -0
  107. package/dist/evidence-pack.js +358 -0
  108. package/dist/evidence.d.ts +25 -0
  109. package/dist/evidence.js +175 -0
  110. package/dist/fs.d.ts +8 -2
  111. package/dist/fs.js +41 -11
  112. package/dist/generator-api.d.ts +89 -0
  113. package/dist/generator-preflight.d.ts +27 -0
  114. package/dist/generator-preflight.js +105 -0
  115. package/dist/generator-runtime.d.ts +7 -0
  116. package/dist/generator-runtime.js +17 -1
  117. package/dist/generators.d.ts +24 -2
  118. package/dist/generators.js +54 -1
  119. package/dist/git-delivery.d.ts +17 -0
  120. package/dist/git-delivery.js +123 -0
  121. package/dist/globs.d.ts +16 -0
  122. package/dist/globs.js +65 -0
  123. package/dist/glossary.d.ts +26 -0
  124. package/dist/glossary.js +179 -0
  125. package/dist/history.d.ts +106 -0
  126. package/dist/history.js +600 -0
  127. package/dist/html-markdown.d.ts +46 -0
  128. package/dist/html-markdown.js +423 -0
  129. package/dist/interactive.js +3 -0
  130. package/dist/job-events.d.ts +74 -0
  131. package/dist/job-events.js +377 -0
  132. package/dist/keep-awake.d.ts +50 -0
  133. package/dist/keep-awake.js +123 -0
  134. package/dist/local-source-snapshot.d.ts +20 -0
  135. package/dist/local-source-snapshot.js +61 -0
  136. package/dist/mintlify-detect.d.ts +3 -0
  137. package/dist/mintlify-detect.js +18 -0
  138. package/dist/mintlify-import.d.ts +75 -0
  139. package/dist/mintlify-import.js +190 -0
  140. package/dist/navigation.d.ts +98 -0
  141. package/dist/navigation.js +310 -0
  142. package/dist/openapi.d.ts +60 -0
  143. package/dist/openapi.js +439 -0
  144. package/dist/page-editor-bridge.d.ts +3 -0
  145. package/dist/page-editor-bridge.js +109 -0
  146. package/dist/page-editor-preview.d.ts +10 -0
  147. package/dist/page-editor-preview.js +55 -0
  148. package/dist/page-extension.d.ts +9 -0
  149. package/dist/page-extension.js +15 -0
  150. package/dist/page-metadata.d.ts +28 -0
  151. package/dist/page-metadata.js +166 -0
  152. package/dist/page-operations.d.ts +34 -0
  153. package/dist/page-operations.js +215 -0
  154. package/dist/page-routes.d.ts +4 -0
  155. package/dist/page-routes.js +61 -0
  156. package/dist/pages.d.ts +20 -0
  157. package/dist/pages.js +184 -0
  158. package/dist/plan-generator.d.ts +3 -0
  159. package/dist/plan-generator.js +21 -0
  160. package/dist/plan-navigation.d.ts +11 -0
  161. package/dist/plan-navigation.js +30 -0
  162. package/dist/planning-captures.d.ts +20 -0
  163. package/dist/planning-captures.js +143 -0
  164. package/dist/planning-research.d.ts +135 -0
  165. package/dist/planning-research.js +472 -0
  166. package/dist/planning-triage.d.ts +23 -0
  167. package/dist/planning-triage.js +131 -0
  168. package/dist/preview.d.ts +24 -0
  169. package/dist/preview.js +280 -29
  170. package/dist/project-detect.d.ts +36 -0
  171. package/dist/project-detect.js +251 -0
  172. package/dist/project-import.d.ts +54 -0
  173. package/dist/project-import.js +157 -0
  174. package/dist/project-lock.d.ts +6 -0
  175. package/dist/project-lock.js +96 -0
  176. package/dist/project-registry.d.ts +25 -0
  177. package/dist/project-registry.js +79 -0
  178. package/dist/project.d.ts +35 -3
  179. package/dist/project.js +388 -38
  180. package/dist/prompts.d.ts +9 -0
  181. package/dist/prompts.js +25 -0
  182. package/dist/proposal-replay.d.ts +32 -0
  183. package/dist/proposal-replay.js +99 -0
  184. package/dist/quality-claims.d.ts +8 -0
  185. package/dist/quality-claims.js +168 -0
  186. package/dist/quality-config.d.ts +5 -0
  187. package/dist/quality-config.js +84 -0
  188. package/dist/quality-contract.d.ts +37 -0
  189. package/dist/quality-contract.js +46 -0
  190. package/dist/quality-examples.d.ts +4 -0
  191. package/dist/quality-examples.js +233 -0
  192. package/dist/quality-gates.d.ts +16 -0
  193. package/dist/quality-gates.js +192 -0
  194. package/dist/quality-links.d.ts +7 -0
  195. package/dist/quality-links.js +149 -0
  196. package/dist/quality-lint.d.ts +6 -0
  197. package/dist/quality-lint.js +124 -0
  198. package/dist/quality-rendered.d.ts +11 -0
  199. package/dist/quality-rendered.js +222 -0
  200. package/dist/quality-schema.d.ts +3 -0
  201. package/dist/quality-schema.js +71 -0
  202. package/dist/release-notes.d.ts +44 -0
  203. package/dist/release-notes.js +183 -0
  204. package/dist/remote-monitor.d.ts +16 -0
  205. package/dist/remote-monitor.js +74 -0
  206. package/dist/remote-source.d.ts +34 -0
  207. package/dist/remote-source.js +426 -0
  208. package/dist/review-diff.d.ts +82 -0
  209. package/dist/review-diff.js +400 -0
  210. package/dist/review-learning.d.ts +11 -0
  211. package/dist/review-learning.js +60 -0
  212. package/dist/review-render.d.ts +38 -0
  213. package/dist/review-render.js +224 -0
  214. package/dist/review-report.d.ts +9 -0
  215. package/dist/review-report.js +89 -0
  216. package/dist/review-ui.d.ts +14 -0
  217. package/dist/review-ui.js +1248 -0
  218. package/dist/schedule.d.ts +78 -0
  219. package/dist/schedule.js +480 -0
  220. package/dist/screen-capture-provider.d.ts +52 -0
  221. package/dist/screen-capture-provider.js +218 -0
  222. package/dist/screenshot-workflow.d.ts +167 -0
  223. package/dist/screenshot-workflow.js +1237 -0
  224. package/dist/settings.d.ts +1 -1
  225. package/dist/settings.js +95 -7
  226. package/dist/site-export.d.ts +18 -0
  227. package/dist/site-export.js +87 -0
  228. package/dist/source-connectors.d.ts +33 -0
  229. package/dist/source-connectors.js +268 -0
  230. package/dist/source-discovery.d.ts +132 -0
  231. package/dist/source-discovery.js +823 -0
  232. package/dist/source-intelligence.d.ts +9 -0
  233. package/dist/source-intelligence.js +306 -0
  234. package/dist/sync-review.d.ts +28 -0
  235. package/dist/sync-review.js +264 -0
  236. package/dist/sync-runs.d.ts +192 -0
  237. package/dist/sync-runs.js +2244 -0
  238. package/dist/sync.d.ts +35 -0
  239. package/dist/sync.js +298 -32
  240. package/dist/text-diff.d.ts +9 -0
  241. package/dist/text-diff.js +59 -0
  242. package/dist/types.d.ts +946 -1
  243. package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
  244. package/dist/ui/assets/index-BHBYU2aG.css +1 -0
  245. package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
  246. package/dist/ui/index.html +18 -0
  247. package/dist/ui-server.d.ts +83 -0
  248. package/dist/ui-server.js +3532 -0
  249. package/dist/usage-budget.d.ts +28 -0
  250. package/dist/usage-budget.js +90 -0
  251. package/dist/validation.d.ts +25 -1
  252. package/dist/validation.js +312 -26
  253. package/dist/workspace-tools.d.ts +54 -0
  254. package/dist/workspace-tools.js +123 -0
  255. package/docs/agent-compatibility.md +49 -32
  256. package/docs/ci-and-automation.md +105 -66
  257. package/docs/doxbrix-http-api.md +8 -1
  258. package/docs/existing-documentation.md +80 -0
  259. package/docs/generation-performance.md +108 -0
  260. package/docs/generator-authoring.md +68 -5
  261. package/docs/generator-selection.md +50 -13
  262. package/docs/mintlify-import.md +71 -0
  263. package/docs/openapi-security.md +25 -0
  264. package/docs/project-format.md +331 -36
  265. package/docs/release-quality.md +158 -0
  266. package/docs/releasing.md +72 -0
  267. package/docs/review-workflows.md +51 -0
  268. package/docs/security-model.md +102 -39
  269. package/docs/troubleshooting.md +226 -93
  270. package/package.json +46 -15
  271. package/scripts/test-auto-screenshot.mjs +172 -0
  272. package/skills/doxloop-authoring/SKILL.md +242 -361
  273. package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
  274. package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
  275. package/skills/doxloop-authoring/references/page-depth.md +169 -0
  276. package/skills/doxloop-authoring/references/project-format.md +106 -7
  277. package/skills/doxloop-authoring/references/quality.md +10 -0
  278. package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
  279. package/skills/doxloop-authoring/references/screenshots.md +149 -235
  280. package/skills/doxloop-authoring/references/workflows.md +76 -0
  281. package/skills/doxloop-doxbrix/SKILL.md +38 -22
  282. package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
  283. package/skills/doxloop-doxbrix/references/components.md +30 -3
  284. package/skills/doxloop-doxbrix/references/manifest.md +3 -2
  285. package/vendor/doxbrix-import/LICENSE +202 -0
  286. package/vendor/doxbrix-import/README.md +17 -0
  287. package/vendor/doxbrix-import/UPSTREAM.json +47 -0
  288. package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
  289. package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
  290. package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
  291. package/vendor/doxbrix-import/dist/docs/import.js +147 -0
  292. package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
  293. package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
  294. package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
  295. package/vendor/doxbrix-import/dist/docs/project.js +77 -0
  296. package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
  297. package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
  298. package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
  299. package/vendor/doxbrix-import/dist/importer.js +1567 -0
  300. package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
  301. package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
  302. package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
  303. package/vendor/doxbrix-import/dist/safe-path.js +47 -0
  304. package/dist/agents.d.ts.map +0 -1
  305. package/dist/agents.js.map +0 -1
  306. package/dist/args.d.ts.map +0 -1
  307. package/dist/args.js.map +0 -1
  308. package/dist/artifact-deploy.d.ts.map +0 -1
  309. package/dist/artifact-deploy.js.map +0 -1
  310. package/dist/auth.d.ts.map +0 -1
  311. package/dist/auth.js.map +0 -1
  312. package/dist/author.d.ts.map +0 -1
  313. package/dist/author.js.map +0 -1
  314. package/dist/capture.d.ts.map +0 -1
  315. package/dist/capture.js.map +0 -1
  316. package/dist/cli.d.ts.map +0 -1
  317. package/dist/cli.js.map +0 -1
  318. package/dist/deploy.d.ts.map +0 -1
  319. package/dist/deploy.js.map +0 -1
  320. package/dist/deployment-visibility.d.ts.map +0 -1
  321. package/dist/deployment-visibility.js.map +0 -1
  322. package/dist/doctor.d.ts.map +0 -1
  323. package/dist/doctor.js.map +0 -1
  324. package/dist/doxbrix-markdown.d.ts.map +0 -1
  325. package/dist/doxbrix-markdown.js.map +0 -1
  326. package/dist/errors.d.ts.map +0 -1
  327. package/dist/errors.js.map +0 -1
  328. package/dist/fs.d.ts.map +0 -1
  329. package/dist/fs.js.map +0 -1
  330. package/dist/generator-api.d.ts.map +0 -1
  331. package/dist/generator-api.js.map +0 -1
  332. package/dist/generator-manager.d.ts.map +0 -1
  333. package/dist/generator-manager.js.map +0 -1
  334. package/dist/generator-runtime.d.ts.map +0 -1
  335. package/dist/generator-runtime.js.map +0 -1
  336. package/dist/generators.d.ts.map +0 -1
  337. package/dist/generators.js.map +0 -1
  338. package/dist/interactive.d.ts.map +0 -1
  339. package/dist/interactive.js.map +0 -1
  340. package/dist/preview.d.ts.map +0 -1
  341. package/dist/preview.js.map +0 -1
  342. package/dist/progress.d.ts.map +0 -1
  343. package/dist/progress.js.map +0 -1
  344. package/dist/project.d.ts.map +0 -1
  345. package/dist/project.js.map +0 -1
  346. package/dist/prompts.d.ts.map +0 -1
  347. package/dist/prompts.js.map +0 -1
  348. package/dist/settings.d.ts.map +0 -1
  349. package/dist/settings.js.map +0 -1
  350. package/dist/sync.d.ts.map +0 -1
  351. package/dist/sync.js.map +0 -1
  352. package/dist/types.d.ts.map +0 -1
  353. package/dist/types.js.map +0 -1
  354. package/dist/validation.d.ts.map +0 -1
  355. package/dist/validation.js.map +0 -1
  356. package/dist/version.d.ts.map +0 -1
  357. package/dist/version.js.map +0 -1
@@ -0,0 +1,75 @@
1
+ export interface MintlifySource {
2
+ path?: string;
3
+ repository?: string;
4
+ branch?: string;
5
+ subdirectory?: string;
6
+ }
7
+ export interface MintlifyInspection {
8
+ id: string;
9
+ title: string;
10
+ source: {
11
+ path?: string;
12
+ repository?: string;
13
+ branch?: string;
14
+ head?: string;
15
+ subdirectory?: string;
16
+ };
17
+ pageCount: number;
18
+ assetCount: number;
19
+ pages: string[];
20
+ spaces: string[];
21
+ redirects: Array<{
22
+ from: string;
23
+ to: string;
24
+ }>;
25
+ unmapped: string[];
26
+ warnings: string[];
27
+ suggestedDestination: string;
28
+ }
29
+ /** A reviewed conversion is a frozen snapshot, including assets, not a second conversion on submit. */
30
+ export declare class MintlifyImports {
31
+ private prepared;
32
+ private preparing;
33
+ private converting;
34
+ inspect(input: MintlifySource, cwd: string): Promise<MintlifyInspection>;
35
+ convert(id: string, destination: string, allowWarnings?: boolean): Promise<{
36
+ conversion: {
37
+ importedAt: string;
38
+ destination: string;
39
+ id: string;
40
+ title: string;
41
+ source: {
42
+ path?: string;
43
+ repository?: string;
44
+ branch?: string;
45
+ head?: string;
46
+ subdirectory?: string;
47
+ };
48
+ pageCount: number;
49
+ assetCount: number;
50
+ pages: string[];
51
+ spaces: string[];
52
+ redirects: Array<{
53
+ from: string;
54
+ to: string;
55
+ }>;
56
+ unmapped: string[];
57
+ warnings: string[];
58
+ suggestedDestination: string;
59
+ };
60
+ root: string;
61
+ title: string;
62
+ generator: import("./types.js").GeneratorName;
63
+ contentDir: string;
64
+ pageCount: number;
65
+ skills: Array<{
66
+ path: string;
67
+ action: "installed" | "updated" | "unchanged";
68
+ }>;
69
+ warnings: string[];
70
+ }>;
71
+ private convertPrepared;
72
+ discard(id: string): Promise<void>;
73
+ close(): Promise<void>;
74
+ }
75
+ //# sourceMappingURL=mintlify-import.d.ts.map
@@ -0,0 +1,190 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { cp, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+ import { convertSourceTree, materializeMintlifyOpenApiNavigation } from '../vendor/doxbrix-import/dist/importer.js';
6
+ import { collectSourceAssets, collectSourceFiles, copySourceAssets, writeConvertResult } from '../vendor/doxbrix-import/dist/docs/import.js';
7
+ import { listFilesRecursive } from '../vendor/doxbrix-import/dist/docs/project.js';
8
+ import { withPageExtension } from '../vendor/doxbrix-import/dist/docs/manifest.js';
9
+ import { DoxloopError } from './errors.js';
10
+ import { mintlifyMarker } from './mintlify-detect.js';
11
+ import { pathExists, resolveContainedDirectory } from './fs.js';
12
+ import { fetchRemoteOpenApi } from './openapi.js';
13
+ import { REDIRECTS_FILE } from './page-operations.js';
14
+ import { importExistingDocumentation } from './project-import.js';
15
+ import { listRemoteBranches, materializeRemoteSource, parseGitHubRepository } from './remote-source.js';
16
+ const EXPIRY_MS = 30 * 60 * 1000;
17
+ /** A reviewed conversion is a frozen snapshot, including assets, not a second conversion on submit. */
18
+ export class MintlifyImports {
19
+ prepared = new Map();
20
+ preparing = false;
21
+ converting = new Set();
22
+ async inspect(input, cwd) {
23
+ if (this.preparing)
24
+ throw new DoxloopError('Another Mintlify inspection is running. Wait for it to finish.');
25
+ this.preparing = true;
26
+ let temporary;
27
+ try {
28
+ for (const [id, item] of this.prepared)
29
+ if (item.expires < Date.now())
30
+ await this.discard(id);
31
+ if (this.prepared.size >= 3)
32
+ throw new DoxloopError('Finish or cancel an existing Mintlify import before inspecting another project.');
33
+ temporary = await mkdtemp(join(tmpdir(), 'doxloop-mintlify-'));
34
+ let root;
35
+ let source;
36
+ if (input.repository) {
37
+ const repository = `https://github.com/${parseGitHubRepository(input.repository)}.git`;
38
+ const branch = input.branch || (await listRemoteBranches({ provider: 'git', repository, branch: 'HEAD' }))[0]?.name;
39
+ if (!branch)
40
+ throw new DoxloopError('The repository has no readable branches.');
41
+ const remote = { provider: 'git', repository, branch };
42
+ const snapshot = await materializeRemoteSource(join(temporary, 'project'), { name: 'mintlify', remote });
43
+ root = await realpath(snapshot.path);
44
+ source = { repository, branch, head: snapshot.head };
45
+ }
46
+ else {
47
+ if (!input.path?.trim())
48
+ throw new DoxloopError('Choose a local Mintlify folder or enter a GitHub repository.');
49
+ root = await realpath(resolve(cwd, input.path));
50
+ if (root === homedir() || dirname(root) === root)
51
+ throw new DoxloopError('Choose the documentation repository, not your home folder or filesystem root.');
52
+ source = { path: root };
53
+ }
54
+ const originalRoot = root;
55
+ if (input.subdirectory)
56
+ root = await resolveContainedDirectory(root, input.subdirectory, 'Mintlify documentation directory');
57
+ else if (!(await mintlifyMarker(root))) {
58
+ // Reuse the CLI's traversal rules (including its symlink and tooling exclusions).
59
+ const candidates = [];
60
+ for (const path of listFilesRecursive(root)) {
61
+ if (!/(^|\/)(docs|mint)\.json$/.test(path))
62
+ continue;
63
+ const candidate = dirname(join(root, path));
64
+ if (await mintlifyMarker(candidate) && !candidates.includes(candidate))
65
+ candidates.push(candidate);
66
+ }
67
+ if (candidates.length !== 1)
68
+ throw new DoxloopError(candidates.length
69
+ ? `Several Mintlify sites were found. Choose a documentation subfolder: ${candidates.map((path) => relative(root, path)).join(', ')}`
70
+ : 'No Mintlify docs.json or mint.json with navigation was found. Choose the documentation folder.');
71
+ root = candidates[0];
72
+ }
73
+ if (!(await mintlifyMarker(root)))
74
+ throw new DoxloopError('The selected folder is not a Mintlify documentation project.');
75
+ const subdirectory = relative(originalRoot, root).replace(/\\/g, '/');
76
+ if (subdirectory)
77
+ source.subdirectory = subdirectory;
78
+ const isProjectFile = (path) => !path.split(/[\\/]/).some((part) => ['.doxloop', '.codex'].includes(part));
79
+ const files = collectSourceFiles(root).filter((file) => isProjectFile(file.path));
80
+ const converted = convertSourceTree(files);
81
+ if (converted.dialect !== 'mintlify')
82
+ throw new DoxloopError('The Doxbrix converter did not recognize this project as Mintlify.');
83
+ const expanded = await materializeMintlifyOpenApiNavigation({ result: converted, files, loadRemote: async (url) => (await fetchRemoteOpenApi(url)).content });
84
+ const result = expanded.result;
85
+ if (!result.pages.length)
86
+ throw new DoxloopError('The selected Mintlify project contains no convertible pages.');
87
+ const paths = result.pages.map((page) => withPageExtension(page.path));
88
+ if (new Set(paths.map((path) => path.toLowerCase())).size !== paths.length)
89
+ throw new DoxloopError('Several converted pages have the same output path. Resolve those collisions before importing.');
90
+ const warnings = [...expanded.warnings];
91
+ const convertedIds = new Set(paths.map((path) => path.replace(/\.(md|mdx)$/i, '')));
92
+ const missingPages = new Set();
93
+ const inspectNavigation = (nodes) => {
94
+ for (const node of nodes) {
95
+ if (node.type === 'group')
96
+ inspectNavigation(node.items);
97
+ else if (node.type === 'page' && !convertedIds.has(node.file))
98
+ missingPages.add(node.file);
99
+ }
100
+ };
101
+ for (const space of result.manifest.spaces)
102
+ inspectNavigation(space.nav);
103
+ if (missingPages.size)
104
+ warnings.push(`Navigation references ${missingPages.size} page(s) absent from the converted output: ${[...missingPages].join(', ')}. The converter excludes repository instruction files such as AGENTS.md; these pages need review before deployment.`);
105
+ const collected = new Set(files.map((file) => file.path));
106
+ const omitted = listFilesRecursive(root).filter((path) => /\.(mdx?|markdown)$/i.test(path) && !collected.has(path));
107
+ if (omitted.length)
108
+ warnings.push(`${omitted.length} source page(s) exceeded the converter's file limits or could not be read: ${omitted.slice(0, 10).join(', ')}`);
109
+ const output = join(temporary, 'converted');
110
+ writeConvertResult(output, result);
111
+ const assets = collectSourceAssets(root).filter(isProjectFile);
112
+ const assetCount = copySourceAssets(root, output, assets);
113
+ if (assetCount !== assets.length)
114
+ throw new DoxloopError('Some documentation assets could not be copied. Check source file access and try again.');
115
+ // The CLI returns redirects separately. Preserve them in the native manifest as well as the report.
116
+ if (result.redirects.length)
117
+ await writeFile(join(output, 'docs.json'), JSON.stringify({ ...result.manifest, redirects: result.redirects }, null, 2) + '\n');
118
+ const id = randomUUID();
119
+ const title = result.manifest.name || basename(root);
120
+ const folder = `${(source.repository ? parseGitHubRepository(source.repository).split('/')[1] : basename(root)) || 'documentation'}-doxbrix`;
121
+ const report = { id, title, source, pageCount: paths.length, assetCount, pages: paths.slice(0, 25), spaces: result.manifest.spaces.map((space) => space.name), redirects: result.redirects, unmapped: result.unmapped, warnings, suggestedDestination: join(source.path ? dirname(originalRoot) : cwd, folder) };
122
+ this.prepared.set(id, { temporary, output, sourceRoot: originalRoot, expires: Date.now() + EXPIRY_MS, report });
123
+ return report;
124
+ }
125
+ catch (error) {
126
+ if (temporary)
127
+ await rm(temporary, { recursive: true, force: true });
128
+ throw error;
129
+ }
130
+ finally {
131
+ this.preparing = false;
132
+ }
133
+ }
134
+ async convert(id, destination, allowWarnings = false) {
135
+ if (this.converting.has(id))
136
+ throw new DoxloopError('This conversion is already running.');
137
+ this.converting.add(id);
138
+ try {
139
+ return await this.convertPrepared(id, destination, allowWarnings);
140
+ }
141
+ finally {
142
+ this.converting.delete(id);
143
+ }
144
+ }
145
+ async convertPrepared(id, destination, allowWarnings) {
146
+ const item = this.prepared.get(id);
147
+ if (!item || item.expires < Date.now())
148
+ throw new DoxloopError('This conversion preview expired. Inspect the Mintlify project again.');
149
+ if (!allowWarnings && (item.report.unmapped.length || item.report.warnings.length))
150
+ throw new DoxloopError('Review the conversion warnings and acknowledge them before importing.');
151
+ if (!destination.trim())
152
+ throw new DoxloopError('Choose a new folder for the Doxbrix project.');
153
+ const requested = resolve(destination);
154
+ // Resolve the parent to catch aliases through symlinks; never place output in the source tree.
155
+ const parent = await realpath(dirname(requested));
156
+ const root = join(parent, basename(requested));
157
+ const rel = relative(item.sourceRoot, root);
158
+ if (!rel || (rel !== '..' && !rel.startsWith('..' + sep) && !isAbsolute(rel)))
159
+ throw new DoxloopError('Choose a destination outside the original Mintlify project.');
160
+ if (await pathExists(root))
161
+ throw new DoxloopError('The destination already exists. Choose a new project folder.');
162
+ // Claim a new directory exclusively. A failure may clean up only the directory we created.
163
+ await mkdir(root);
164
+ this.prepared.delete(id);
165
+ try {
166
+ await cp(item.output, root, { recursive: true, force: false, errorOnExist: true });
167
+ const imported = await importExistingDocumentation({ directory: root, generator: 'doxbrix', contentDir: '', title: item.report.title });
168
+ if (item.report.redirects.length)
169
+ await writeFile(join(root, REDIRECTS_FILE), JSON.stringify(Object.fromEntries(item.report.redirects.map(({ from, to }) => [from, to])), null, 2) + '\n');
170
+ const report = { ...item.report, importedAt: new Date().toISOString(), destination: root };
171
+ await writeFile(join(root, '.doxloop', 'mintlify-import.json'), JSON.stringify(report, null, 2) + '\n');
172
+ return { ...imported, conversion: report };
173
+ }
174
+ catch (error) {
175
+ await rm(root, { recursive: true, force: true });
176
+ throw error;
177
+ }
178
+ finally {
179
+ await rm(item.temporary, { recursive: true, force: true });
180
+ }
181
+ }
182
+ async discard(id) {
183
+ const item = this.prepared.get(id);
184
+ this.prepared.delete(id);
185
+ if (item)
186
+ await rm(item.temporary, { recursive: true, force: true });
187
+ }
188
+ async close() { await Promise.all([...this.prepared.keys()].map((id) => this.discard(id))); }
189
+ }
190
+ //# sourceMappingURL=mintlify-import.js.map
@@ -0,0 +1,98 @@
1
+ import type { DoxloopProject } from './types.js';
2
+ /**
3
+ * The navigation editor's model. It is the Doxbrix node shape for every
4
+ * generator: page and group nodes are what MkDocs and the other tree-capable
5
+ * adapters map to, and label, divider, link, and API nodes only appear for
6
+ * Doxbrix. Page nodes carry the project-relative `path` so the editor can join
7
+ * them to the page list; the path is derived and never written back.
8
+ */
9
+ export type NavigationNode = {
10
+ type: 'page';
11
+ file: string;
12
+ path?: string;
13
+ title?: string;
14
+ pageTitle?: string;
15
+ icon?: string;
16
+ hidden?: boolean;
17
+ } | {
18
+ type: 'group';
19
+ label: string;
20
+ icon?: string;
21
+ hidden?: boolean;
22
+ items: NavigationNode[];
23
+ } | {
24
+ type: 'label';
25
+ text: string;
26
+ } | {
27
+ type: 'divider';
28
+ } | {
29
+ type: 'link';
30
+ title: string;
31
+ href: string;
32
+ icon?: string;
33
+ } | {
34
+ type: 'api';
35
+ title: string;
36
+ spec: string;
37
+ icon?: string;
38
+ };
39
+ export interface NavigationSpace {
40
+ name: string;
41
+ version?: string;
42
+ icon?: string;
43
+ nav: NavigationNode[];
44
+ }
45
+ export interface NavigationSupport {
46
+ icons: boolean;
47
+ hidden: boolean;
48
+ labels: boolean;
49
+ links: boolean;
50
+ dividers: boolean;
51
+ spaces: boolean;
52
+ }
53
+ export interface NavigationTree {
54
+ generator: string;
55
+ /** False when the generator keeps its navigation in code Doxloop cannot rewrite. */
56
+ editable: boolean;
57
+ reason?: string;
58
+ /** Project-relative file the navigation lives in. */
59
+ configFile: string;
60
+ fingerprint: string;
61
+ /** Whether every page must stay in navigation, which is the Doxbrix rule. */
62
+ requiresEveryPage: boolean;
63
+ /** Node types the generator can represent beyond pages and groups. */
64
+ supports: NavigationSupport;
65
+ spaces: NavigationSpace[];
66
+ /** Pages that exist on disk but are not referenced by the navigation. */
67
+ orphans: Array<{
68
+ file: string;
69
+ path: string;
70
+ title: string;
71
+ }>;
72
+ /** Icon names the Doxbrix reader can draw. */
73
+ icons: string[];
74
+ }
75
+ export declare const DOXBRIX_NAV_ICONS: string[];
76
+ export declare function readNavigation(root: string): Promise<NavigationTree>;
77
+ export interface NavigationWrite {
78
+ fingerprint: string;
79
+ spaces: Array<{
80
+ name: string;
81
+ version?: string;
82
+ nav: unknown;
83
+ }>;
84
+ }
85
+ export declare function writeNavigation(root: string, raw: unknown): Promise<NavigationTree>;
86
+ /**
87
+ * Add one page to the Doxbrix navigation, used by generated content such as
88
+ * the glossary. The page joins the named group when it exists, otherwise the
89
+ * end of the first space. Other generators go through the adapter's
90
+ * `writeNavigation` hook.
91
+ */
92
+ export declare function appendDoxbrixNavigationPage(root: string, project: DoxloopProject, page: {
93
+ file: string;
94
+ title: string;
95
+ icon?: string;
96
+ section?: string;
97
+ }): Promise<void>;
98
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1,310 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { applyDirectEdit } from './direct-edit.js';
5
+ import { DoxloopError } from './errors.js';
6
+ import { resolveContainedDirectory } from './fs.js';
7
+ import { loadGeneratorAdapter } from './generators.js';
8
+ import { loadPages, loadProject, loadSiteConfig, pageId, readPage, relativePath, siteConfigPath, } from './project.js';
9
+ export const DOXBRIX_NAV_ICONS = [
10
+ 'book', 'file', 'home', 'rocket', 'bolt', 'zap', 'terminal', 'code', 'braces', 'plug', 'settings',
11
+ 'wrench', 'key', 'database', 'server', 'cloud', 'package', 'workflow', 'users', 'compass', 'globe',
12
+ 'list', 'link', 'github', 'clock', 'history', 'search', 'sparkle',
13
+ ];
14
+ const MAX_LABEL = 120;
15
+ const NO_SUPPORT = { icons: false, hidden: false, labels: false, links: false, dividers: false, spaces: false };
16
+ export async function readNavigation(root) {
17
+ const project = await loadProject(root);
18
+ const contentRoot = await contentDirectory(root, project);
19
+ const files = await loadPages(root, project);
20
+ const known = new Map();
21
+ for (const absolute of files) {
22
+ const page = await readPage(absolute);
23
+ const path = relativePath(root, absolute);
24
+ const entry = { path, title: page.title || labelFromFile(path) };
25
+ known.set(pageId(contentRoot, absolute), entry);
26
+ known.set(relativePath(contentRoot, absolute), entry);
27
+ }
28
+ if (project.generator === 'doxbrix') {
29
+ const configPath = await siteConfigPath(root, project);
30
+ const site = await loadSiteConfig(root, project);
31
+ const raw = await readFile(configPath, 'utf8');
32
+ const referenced = new Set();
33
+ const defaultVersion = (site.versions?.find((entry) => entry.default || entry.isDefault) ?? site.versions?.[0])?.version;
34
+ const spaces = site.spaces.map((space) => ({
35
+ name: space.name,
36
+ ...((space.version ?? defaultVersion) ? { version: (space.version ?? defaultVersion) } : {}),
37
+ ...(space.icon ? { icon: space.icon } : {}),
38
+ nav: decorate(space.nav, known, referenced),
39
+ }));
40
+ return {
41
+ generator: 'doxbrix',
42
+ editable: true,
43
+ configFile: relativePath(root, configPath),
44
+ fingerprint: fingerprint(raw),
45
+ requiresEveryPage: true,
46
+ supports: { icons: true, hidden: true, labels: true, links: true, dividers: true, spaces: true },
47
+ spaces,
48
+ orphans: orphans(files, root, known, referenced, (absolute) => pageId(contentRoot, absolute)),
49
+ icons: DOXBRIX_NAV_ICONS,
50
+ };
51
+ }
52
+ const adapter = await loadGeneratorAdapter(root, project);
53
+ const configFile = adapter.planning?.navigationFiles[0] ?? '';
54
+ const base = { generator: project.generator, configFile, requiresEveryPage: false, supports: NO_SUPPORT, icons: [] };
55
+ if (!adapter.readNavigationTree || !adapter.writeNavigationTree) {
56
+ return {
57
+ ...base,
58
+ editable: false,
59
+ reason: configFile
60
+ ? `${adapter.displayName} keeps its navigation in ${configFile}. Doxloop cannot rewrite that file safely, so edit it directly or ask the agent to change the navigation.`
61
+ : `${adapter.displayName} derives its navigation from the file system, so there is nothing to reorder here.`,
62
+ fingerprint: '',
63
+ spaces: [],
64
+ orphans: [],
65
+ };
66
+ }
67
+ const tree = await adapter.readNavigationTree({ root, contentRoot, project });
68
+ const referenced = new Set();
69
+ const nav = decorate(fromGeneratorTree(tree.nodes), known, referenced);
70
+ const configText = configFile ? await readFile(join(root, configFile), 'utf8').catch(() => '') : '';
71
+ return {
72
+ ...base,
73
+ editable: true,
74
+ fingerprint: fingerprint(configText || JSON.stringify(tree)),
75
+ spaces: [{ name: project.title, nav }],
76
+ orphans: orphans(files, root, known, referenced, (absolute) => relativePath(contentRoot, absolute)),
77
+ };
78
+ }
79
+ export async function writeNavigation(root, raw) {
80
+ const input = parseWrite(raw);
81
+ const current = await readNavigation(root);
82
+ if (!current.editable)
83
+ throw new DoxloopError(current.reason ?? 'This navigation cannot be edited from Doxloop.', 2);
84
+ if (input.fingerprint !== current.fingerprint) {
85
+ throw new DoxloopError('The navigation changed on disk since it was loaded. Reload the navigation and apply your changes again.');
86
+ }
87
+ const project = await loadProject(root);
88
+ const contentRoot = await contentDirectory(root, project);
89
+ const files = await loadPages(root, project);
90
+ const knownFiles = new Set(files.map((absolute) => (project.generator === 'doxbrix' ? pageId(contentRoot, absolute) : relativePath(contentRoot, absolute))));
91
+ const spaces = input.spaces.map((space, index) => ({
92
+ name: requireLabel(space.name, `Space ${index + 1} name`),
93
+ ...(space.version ? { version: space.version } : {}),
94
+ nav: sanitizeNodes(space.nav, knownFiles, new Set(), current.supports, `space "${space.name}"`),
95
+ }));
96
+ if (spaces.length === 0)
97
+ throw new DoxloopError('Navigation needs at least one space.', 2);
98
+ if (!current.supports.spaces && spaces.length > 1)
99
+ throw new DoxloopError(`${project.generator} navigation is a single tree.`, 2);
100
+ await applyDirectEdit(root, {
101
+ kind: 'navigation',
102
+ requestText: 'Reorganized the navigation',
103
+ files: current.configFile ? [current.configFile] : [],
104
+ apply: async () => {
105
+ if ((await readNavigation(root)).fingerprint !== input.fingerprint)
106
+ throw new DoxloopError('Navigation changed before saving. Reload and try again.');
107
+ if (project.generator === 'doxbrix') {
108
+ await writeDoxbrixNavigation(root, project, spaces);
109
+ return;
110
+ }
111
+ const adapter = await loadGeneratorAdapter(root, project);
112
+ await adapter.writeNavigationTree({ root, contentRoot, project, tree: { nodes: toGeneratorTree(spaces[0].nav) } });
113
+ },
114
+ });
115
+ return readNavigation(root);
116
+ }
117
+ /**
118
+ * Add one page to the Doxbrix navigation, used by generated content such as
119
+ * the glossary. The page joins the named group when it exists, otherwise the
120
+ * end of the first space. Other generators go through the adapter's
121
+ * `writeNavigation` hook.
122
+ */
123
+ export async function appendDoxbrixNavigationPage(root, project, page) {
124
+ const configPath = await siteConfigPath(root, project);
125
+ const site = await loadSiteConfig(root, project);
126
+ const space = site.spaces[0];
127
+ if (!space)
128
+ throw new DoxloopError('docs.json has no space to add the page to.');
129
+ const existing = new Set();
130
+ const walk = (nodes) => {
131
+ for (const node of nodes) {
132
+ if (node.type === 'page')
133
+ existing.add(node.file);
134
+ else if (node.type === 'group')
135
+ walk(node.items);
136
+ }
137
+ };
138
+ for (const entry of site.spaces)
139
+ walk(entry.nav);
140
+ if (existing.has(page.file))
141
+ return;
142
+ const node = { type: 'page', file: page.file, title: page.title, ...(page.icon ? { icon: page.icon } : {}) };
143
+ const group = page.section
144
+ ? space.nav.find((entry) => entry.type === 'group' && entry.label === page.section)
145
+ : undefined;
146
+ if (group)
147
+ group.items.push(node);
148
+ else
149
+ space.nav.push(node);
150
+ await writeFile(configPath, `${JSON.stringify(site, null, 2)}\n`, 'utf8');
151
+ }
152
+ async function writeDoxbrixNavigation(root, project, spaces) {
153
+ const configPath = await siteConfigPath(root, project);
154
+ const site = await loadSiteConfig(root, project);
155
+ const defaultVersion = (site.versions?.find((entry) => entry.default || entry.isDefault) ?? site.versions?.[0])?.version;
156
+ const existing = new Map(site.spaces.map((space) => [JSON.stringify([space.version ?? defaultVersion, space.name]), space]));
157
+ const next = spaces.map((space, index) => {
158
+ if (space.version && site.versions?.length && !site.versions.some((entry) => entry.version === space.version))
159
+ throw new DoxloopError(`Unknown documentation version "${space.version}".`);
160
+ const previous = existing.get(JSON.stringify([space.version ?? defaultVersion, space.name])) ?? site.spaces[index];
161
+ return { ...(previous ?? {}), ...(space.version ? { version: space.version } : {}), name: space.name, nav: space.nav };
162
+ });
163
+ await writeFile(configPath, `${JSON.stringify({ ...site, spaces: next }, null, 2)}\n`, 'utf8');
164
+ }
165
+ function parseWrite(raw) {
166
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
167
+ throw new DoxloopError('A navigation write needs spaces and a fingerprint.', 2);
168
+ const body = raw;
169
+ if (typeof body.fingerprint !== 'string')
170
+ throw new DoxloopError('A navigation write needs the fingerprint it was loaded with.', 2);
171
+ if (!Array.isArray(body.spaces))
172
+ throw new DoxloopError('A navigation write needs a spaces array.', 2);
173
+ return {
174
+ fingerprint: body.fingerprint,
175
+ spaces: body.spaces.map((space) => {
176
+ const entry = (space && typeof space === 'object' ? space : {});
177
+ if (entry.version !== undefined && (typeof entry.version !== 'string' || !entry.version.trim()))
178
+ throw new DoxloopError('Space version must be a non-empty string.');
179
+ return { name: typeof entry.name === 'string' ? entry.name : '', ...(typeof entry.version === 'string' ? { version: entry.version } : {}), nav: entry.nav };
180
+ }),
181
+ };
182
+ }
183
+ function sanitizeNodes(raw, knownFiles, seen, supports, location) {
184
+ if (!Array.isArray(raw))
185
+ throw new DoxloopError(`${location} needs a list of navigation items.`, 2);
186
+ return raw.map((item, index) => {
187
+ const node = (item && typeof item === 'object' ? item : {});
188
+ const where = `${location} item ${index + 1}`;
189
+ const icon = supports.icons ? optionalIcon(node.icon, where) : undefined;
190
+ const hidden = supports.hidden && node.hidden === true;
191
+ switch (node.type) {
192
+ case 'page': {
193
+ const file = typeof node.file === 'string' ? node.file.trim().replace(/^\.?\//, '') : '';
194
+ if (!file || !knownFiles.has(file))
195
+ throw new DoxloopError(`${where} points at "${file || '?'}", which is not a page in this project.`, 2);
196
+ if (seen.has(file))
197
+ throw new DoxloopError(`"${file}" appears more than once in the navigation.`, 2);
198
+ seen.add(file);
199
+ const title = optionalLabel(node.title, `${where} title`);
200
+ return { type: 'page', file, ...(title ? { title } : {}), ...(icon ? { icon } : {}), ...(hidden ? { hidden } : {}) };
201
+ }
202
+ case 'group': {
203
+ const label = requireLabel(node.label, `${where} group label`);
204
+ return {
205
+ type: 'group',
206
+ label,
207
+ ...(icon ? { icon } : {}),
208
+ ...(hidden ? { hidden } : {}),
209
+ items: sanitizeNodes(node.items ?? [], knownFiles, seen, supports, `group "${label}"`),
210
+ };
211
+ }
212
+ case 'label':
213
+ if (!supports.labels)
214
+ throw new DoxloopError(`${where}: this generator has no section labels.`, 2);
215
+ return { type: 'label', text: requireLabel(node.text, `${where} label text`) };
216
+ case 'divider':
217
+ if (!supports.dividers)
218
+ throw new DoxloopError(`${where}: this generator has no dividers.`, 2);
219
+ return { type: 'divider' };
220
+ case 'link': {
221
+ if (!supports.links)
222
+ throw new DoxloopError(`${where}: this generator has no navigation links.`, 2);
223
+ const href = typeof node.href === 'string' ? node.href.trim() : '';
224
+ if (!/^(?:https:\/\/|\/(?!\/))/i.test(href))
225
+ throw new DoxloopError(`${where} link needs an HTTPS or root-relative address.`, 2);
226
+ return { type: 'link', title: requireLabel(node.title, `${where} link title`), href, ...(icon ? { icon } : {}) };
227
+ }
228
+ case 'api': {
229
+ if (!supports.links)
230
+ throw new DoxloopError(`${where}: this generator has no API entries.`, 2);
231
+ const spec = typeof node.spec === 'string' ? node.spec.trim() : '';
232
+ if (!spec)
233
+ throw new DoxloopError(`${where} API entry needs a specification path.`, 2);
234
+ return { type: 'api', title: requireLabel(node.title, `${where} API title`), spec, ...(icon ? { icon } : {}) };
235
+ }
236
+ default:
237
+ throw new DoxloopError(`${where} has an unsupported type.`, 2);
238
+ }
239
+ });
240
+ }
241
+ function requireLabel(value, label) {
242
+ const text = optionalLabel(value, label);
243
+ if (!text)
244
+ throw new DoxloopError(`${label} is required.`, 2);
245
+ return text;
246
+ }
247
+ function optionalLabel(value, label) {
248
+ if (value === undefined || value === null || value === '')
249
+ return undefined;
250
+ if (typeof value !== 'string')
251
+ throw new DoxloopError(`${label} must be text.`, 2);
252
+ const text = value.trim().replace(/\s+/g, ' ');
253
+ if (text.length > MAX_LABEL)
254
+ throw new DoxloopError(`${label} must be ${MAX_LABEL} characters or fewer.`, 2);
255
+ return text || undefined;
256
+ }
257
+ function optionalIcon(value, where) {
258
+ if (value === undefined || value === null || value === '')
259
+ return undefined;
260
+ if (typeof value !== 'string' || !/^[a-z][a-z0-9-]{0,39}$/.test(value))
261
+ throw new DoxloopError(`${where} icon must be an icon name.`, 2);
262
+ return value;
263
+ }
264
+ function decorate(nodes, known, referenced) {
265
+ return nodes.map((node) => {
266
+ if (node.type === 'page') {
267
+ const file = node.file.replace(/^\.?\//, '');
268
+ const page = known.get(file);
269
+ if (page)
270
+ referenced.add(page.path);
271
+ return { ...node, file, ...(page ? { path: page.path, pageTitle: page.title } : {}) };
272
+ }
273
+ if (node.type === 'group')
274
+ return { ...node, items: decorate(node.items ?? [], known, referenced) };
275
+ return node;
276
+ });
277
+ }
278
+ function orphans(files, root, known, referenced, fileOf) {
279
+ return files
280
+ .map((absolute) => ({ file: fileOf(absolute), path: relativePath(root, absolute) }))
281
+ .filter((entry) => !referenced.has(entry.path))
282
+ .map((entry) => ({ ...entry, title: known.get(entry.file)?.title ?? labelFromFile(entry.path) }));
283
+ }
284
+ function fromGeneratorTree(nodes) {
285
+ return nodes.map((node) => (node.type === 'group'
286
+ ? { type: 'group', label: node.label, items: fromGeneratorTree(node.items) }
287
+ : { type: 'page', file: node.file, ...(node.title ? { title: node.title } : {}) }));
288
+ }
289
+ function toGeneratorTree(nodes) {
290
+ const output = [];
291
+ for (const node of nodes) {
292
+ if (node.type === 'page')
293
+ output.push({ type: 'page', file: node.file, ...(node.title ? { title: node.title } : {}) });
294
+ else if (node.type === 'group')
295
+ output.push({ type: 'group', label: node.label, items: toGeneratorTree(node.items) });
296
+ }
297
+ return output;
298
+ }
299
+ function fingerprint(text) {
300
+ return createHash('sha256').update(text).digest('hex');
301
+ }
302
+ async function contentDirectory(root, project) {
303
+ return resolveContainedDirectory(root, project.contentDir, 'Documentation content directory', {
304
+ allowRoot: project.generator === 'doxbrix',
305
+ });
306
+ }
307
+ function labelFromFile(path) {
308
+ return path.split('/').at(-1).replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
309
+ }
310
+ //# sourceMappingURL=navigation.js.map