@plebeiantech/lat.md 0.12.3-fork.13 → 0.12.3-fork.15
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.
- package/README.md +24 -0
- package/dist/src/cli/check-context.d.ts +56 -0
- package/dist/src/cli/check-context.js +215 -0
- package/dist/src/cli/check-coverage.js +9 -5
- package/dist/src/cli/check-frontmatter.js +1 -1
- package/dist/src/cli/check-mode.js +1 -1
- package/dist/src/cli/check-status.js +1 -1
- package/dist/src/cli/check.d.ts +19 -12
- package/dist/src/cli/check.js +259 -206
- package/dist/src/cli/context.js +1 -1
- package/dist/src/cli/expand.d.ts +1 -2
- package/dist/src/cli/expand.js +52 -5
- package/dist/src/cli/external.d.ts +11 -0
- package/dist/src/cli/external.js +185 -0
- package/dist/src/cli/fork-instructions.d.ts +1 -0
- package/dist/src/cli/fork-instructions.js +3 -2
- package/dist/src/cli/hook.d.ts +26 -0
- package/dist/src/cli/hook.js +112 -40
- package/dist/src/cli/index.js +55 -3
- package/dist/src/cli/init.d.ts +1 -0
- package/dist/src/cli/init.js +40 -2
- package/dist/src/cli/locate.js +4 -3
- package/dist/src/cli/refs.d.ts +2 -1
- package/dist/src/cli/refs.js +88 -43
- package/dist/src/cli/reindex.js +2 -1
- package/dist/src/cli/search.d.ts +12 -3
- package/dist/src/cli/search.js +45 -49
- package/dist/src/cli/section.d.ts +3 -1
- package/dist/src/cli/section.js +114 -38
- package/dist/src/code-refs.d.ts +17 -5
- package/dist/src/code-refs.js +246 -65
- package/dist/src/context.d.ts +3 -0
- package/dist/src/document-formats.d.ts +7 -0
- package/dist/src/document-formats.js +39 -0
- package/dist/src/extensions/alert-marker.d.ts +25 -0
- package/dist/src/extensions/alert-marker.js +55 -0
- package/dist/src/external-documents.d.ts +56 -0
- package/dist/src/external-documents.js +404 -0
- package/dist/src/external-sources.d.ts +136 -0
- package/dist/src/external-sources.js +1098 -0
- package/dist/src/fork/antigravity-hook.d.ts +47 -0
- package/dist/src/fork/antigravity-hook.js +424 -0
- package/dist/src/fork/antigravity-init.d.ts +17 -0
- package/dist/src/fork/antigravity-init.js +124 -0
- package/dist/src/fork/frontmatter-fields.d.ts +36 -0
- package/dist/src/fork/frontmatter-fields.js +23 -0
- package/dist/src/format.d.ts +5 -2
- package/dist/src/format.js +8 -3
- package/dist/src/graph-export.js +1 -1
- package/dist/src/lattice-model.d.ts +77 -0
- package/dist/src/lattice-model.js +464 -0
- package/dist/src/lattice.d.ts +9 -104
- package/dist/src/lattice.js +69 -516
- package/dist/src/markdown-analysis-cache.d.ts +20 -0
- package/dist/src/markdown-analysis-cache.js +139 -0
- package/dist/src/markdown-analysis-worker.d.ts +16 -0
- package/dist/src/markdown-analysis-worker.js +33 -0
- package/dist/src/markdown-analysis.d.ts +50 -0
- package/dist/src/markdown-analysis.js +139 -0
- package/dist/src/markdown-analyzer-loader.d.ts +4 -0
- package/dist/src/markdown-analyzer-loader.js +13 -0
- package/dist/src/markdown-validation.d.ts +25 -0
- package/dist/src/markdown-validation.js +104 -0
- package/dist/src/mcp/server.js +27 -11
- package/dist/src/parser-cache.d.ts +17 -0
- package/dist/src/parser-cache.js +65 -0
- package/dist/src/parser-import.d.ts +8 -0
- package/dist/src/parser-import.js +1 -0
- package/dist/src/parser.js +80 -5
- package/dist/src/path.d.ts +2 -0
- package/dist/src/path.js +4 -0
- package/dist/src/profiler.d.ts +16 -0
- package/dist/src/profiler.js +81 -0
- package/dist/src/project-analysis.d.ts +59 -0
- package/dist/src/project-analysis.js +233 -0
- package/dist/src/project-discovery.d.ts +6 -0
- package/dist/src/project-discovery.js +30 -0
- package/dist/src/repository-path.d.ts +11 -0
- package/dist/src/repository-path.js +52 -0
- package/dist/src/search/db.d.ts +1 -1
- package/dist/src/search/db.js +2 -2
- package/dist/src/search/embedder.d.ts +2 -1
- package/dist/src/search/embedder.js +6 -5
- package/dist/src/search/index.d.ts +2 -1
- package/dist/src/search/index.js +10 -8
- package/dist/src/search/query.d.ts +20 -0
- package/dist/src/search/query.js +67 -0
- package/dist/src/search/search.d.ts +3 -1
- package/dist/src/search/search.js +5 -2
- package/dist/src/source-formats.d.ts +7 -0
- package/dist/src/source-formats.js +26 -0
- package/dist/src/source-parser.d.ts +44 -4
- package/dist/src/source-parser.js +494 -57
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/src/view/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/src/view/client/assets/MarkdownEditor-B7quqPb8.js +28 -0
- package/dist/src/view/client/assets/OrbitControls-DwcpQNja.js +1 -0
- package/dist/src/view/client/assets/STLLoader-CLWTKt5i.js +1 -0
- package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-V7-T6qsl.js +1 -0
- package/dist/src/view/client/assets/arc-BnwR8Bu7.js +1 -0
- package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +36 -0
- package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-DETl8e_H.js +129 -0
- package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-DJRAT1Ve.js +38 -0
- package/dist/src/view/client/assets/channel-BwQbnWCN.js +1 -0
- package/dist/src/view/client/assets/chunk-2Q5K7J3B-C0TmOC1Q.js +1 -0
- package/dist/src/view/client/assets/chunk-5VM5RSS4-dfBOrYE6.js +15 -0
- package/dist/src/view/client/assets/chunk-F27PBJKO-DAnks99K.js +1 -0
- package/dist/src/view/client/assets/chunk-IMKFNOWR-CPjB07if.js +231 -0
- package/dist/src/view/client/assets/chunk-JWPE2WC7-VvU_gZI6.js +1 -0
- package/dist/src/view/client/assets/chunk-POPQ4Y6H-DDUbVF55.js +1 -0
- package/dist/src/view/client/assets/chunk-SVP7TREG-BHROkr6D.js +88 -0
- package/dist/src/view/client/assets/chunk-TICWLB2K-95PzGIOg.js +206 -0
- package/dist/src/view/client/assets/chunk-XXDRQBXY-MdmuLhIs.js +1 -0
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +1 -0
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +1 -0
- package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +1 -0
- package/dist/src/view/client/assets/cynefin-OW5HDTMX-CeA8IKWo.js +166 -0
- package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +62 -0
- package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
- package/dist/src/view/client/assets/dagre-GXQ25YYZ-DLQwTa3H.js +4 -0
- package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/src/view/client/assets/diagram-S7CK7UJ4-kbKdIyXG.js +30 -0
- package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +41 -0
- package/dist/src/view/client/assets/diagram-VSXAHHWV-EXJTU8qq.js +3 -0
- package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +24 -0
- package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +24 -0
- package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +1 -0
- package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-C925J5A3.js +99 -0
- package/dist/src/view/client/assets/flowDiagram-HODETNUW-DzoCa-Nq.js +168 -0
- package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +292 -0
- package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +106 -0
- package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
- package/dist/src/view/client/assets/index-D0ajr_Ja.css +1 -0
- package/dist/src/view/client/assets/index-KZcnpXJH.js +321 -0
- package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +2 -0
- package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
- package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-CW4mto2d.js +70 -0
- package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-8R4eacFW.js +139 -0
- package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-Cy9ioXEJ.js +89 -0
- package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
- package/dist/src/view/client/assets/layout-C0MFuhth.js +1 -0
- package/dist/src/view/client/assets/linear-Dv3N4dQR.js +1 -0
- package/dist/src/view/client/assets/logo-small-RR_KBOAT.svg +149 -0
- package/dist/src/view/client/assets/maplibre-gl-DnqZgfUS.js +800 -0
- package/dist/src/view/client/assets/mermaid.core-bds9A7e2.js +310 -0
- package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-DjmGW355.js +96 -0
- package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +1 -0
- package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-CCH2gGR-.js +39 -0
- package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC-CjocPJFz.js +7 -0
- package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +1 -0
- package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-Ct6AoXRr.js +84 -0
- package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-CIpgGN_w.js +40 -0
- package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-DrmLFj6w.js +162 -0
- package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +1 -0
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +1 -0
- package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +1 -0
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +8 -0
- package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
- package/dist/src/view/client/assets/timeline-definition-24CTP7MA-BExPu6X5.js +120 -0
- package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-PC1MU3si.js +34 -0
- package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-DNWqU3fa.js +78 -0
- package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-E1uA28_D.js +7 -0
- package/dist/src/view/client/index.html +2 -2
- package/dist/src/view/client-shell.d.ts +2 -0
- package/dist/src/view/client-shell.js +5 -0
- package/dist/src/view/diagnostics.d.ts +3 -2
- package/dist/src/view/diagnostics.js +49 -58
- package/dist/src/view/document-edit.d.ts +8 -0
- package/dist/src/view/document-edit.js +25 -0
- package/dist/src/view/document-route.d.ts +12 -0
- package/dist/src/view/document-route.js +93 -0
- package/dist/src/view/document-tree.d.ts +18 -0
- package/dist/src/view/document-tree.js +139 -0
- package/dist/src/view/external-document-tree.d.ts +3 -0
- package/dist/src/view/external-document-tree.js +674 -0
- package/dist/src/view/git-diff.js +187 -0
- package/dist/src/view/git.js +1 -1
- package/dist/src/view/graph.d.ts +3 -2
- package/dist/src/view/graph.js +94 -7
- package/dist/src/view/highlight.d.ts +10 -2
- package/dist/src/view/highlight.js +4540 -1360
- package/dist/src/view/markdown.d.ts +3 -2
- package/dist/src/view/markdown.js +214 -53
- package/dist/src/view/protocol.d.ts +59 -6
- package/dist/src/view/references.d.ts +10 -25
- package/dist/src/view/references.js +117 -107
- package/dist/src/view/repository.d.ts +13 -3
- package/dist/src/view/repository.js +234 -15
- package/dist/src/view/search.d.ts +1 -1
- package/dist/src/view/search.js +3 -7
- package/dist/src/view/server.d.ts +11 -2
- package/dist/src/view/server.js +212 -70
- package/dist/src/view/source-target.js +5 -8
- package/dist/src/view/static-build.d.ts +1 -0
- package/dist/src/view/static-build.js +245 -42
- package/dist/src/view/static-protocol.d.ts +19 -2
- package/dist/src/view/store.d.ts +16 -3
- package/dist/src/view/store.js +228 -36
- package/dist/src/view/table-of-contents.d.ts +2 -2
- package/dist/src/view/table-of-contents.js +2 -22
- package/dist/src/walk.d.ts +5 -3
- package/dist/src/walk.js +102 -8
- package/dist/vendor/lat.md-server/cli.d.ts +2 -0
- package/dist/vendor/lat.md-server/cli.js +12 -0
- package/dist/vendor/lat.md-server/index.d.ts +38 -0
- package/dist/vendor/lat.md-server/index.js +128 -0
- package/package.json +52 -12
- package/plugins/lat-md/README.md +61 -0
- package/plugins/lat-md/hooks.json +38 -0
- package/plugins/lat-md/plugin.json +9 -0
- package/plugins/lat-md/rules/AGENTS.md +118 -0
- package/plugins/lat-md/skills/lat-md/SKILL.md +178 -0
- package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +182 -0
- package/templates/AGENTS.md +15 -3
- package/templates/cursor-rules.md +12 -3
- package/templates/skill/SKILL.md +22 -1
- package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
- package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
- package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
package/dist/src/view/store.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { watch as watchFiles } from 'node:fs';
|
|
2
|
-
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { readFile, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
3
4
|
import { basename, extname, isAbsolute, relative, resolve, sep, } from 'node:path';
|
|
4
|
-
import { LAT_REF_RE,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { createCodeReferenceDiscovery, LAT_REF_RE, } from '../code-refs.js';
|
|
6
|
+
import { createExternalResolver, } from '../external-sources.js';
|
|
7
|
+
import { listLatticeFiles } from '../project-discovery.js';
|
|
8
|
+
import { analyzeMarkdownPath } from '../markdown-analysis-cache.js';
|
|
9
|
+
import { isSourceFileExtension } from '../source-formats.js';
|
|
10
|
+
import { toPosix } from '../path.js';
|
|
8
11
|
import { renderMarkdown } from './markdown.js';
|
|
9
12
|
import { buildViewDiagnostics } from './diagnostics.js';
|
|
13
|
+
import { applyDocumentEdit, ViewDocumentConflictError, } from './document-edit.js';
|
|
10
14
|
import { buildGitDiffTree } from './git-diff.js';
|
|
11
15
|
import { buildViewGraph } from './graph.js';
|
|
12
16
|
import { buildViewTableOfContents } from './table-of-contents.js';
|
|
13
17
|
import { emptyViewGitSnapshot, findViewGitRepository, readViewGitSnapshot, sameViewGitSnapshot, } from './git.js';
|
|
14
18
|
import { DEFAULT_VIEW_LOGO_TEXT } from './protocol.js';
|
|
15
|
-
import { createMarkdownWikiLinkResolver, getViewSource, ViewDocumentNotFoundError, } from './repository.js';
|
|
16
|
-
import { buildViewReferenceIndex,
|
|
19
|
+
import { createMarkdownWikiLinkResolver, getViewExternal, getViewSource, ViewDocumentNotFoundError, } from './repository.js';
|
|
20
|
+
import { buildViewReferenceIndex, renderSectionBackReferences, } from './references.js';
|
|
21
|
+
import { rewriteDocumentLink } from './document-route.js';
|
|
17
22
|
const DEFAULT_DEBOUNCE_MS = 75;
|
|
18
23
|
const DEFAULT_GIT_POLL_MS = 2_000;
|
|
24
|
+
const EXTERNAL_REFRESH_PATH = '@lat-external-refresh';
|
|
25
|
+
const DOCUMENT_EDIT_WRITE_ATTEMPTS = 3;
|
|
26
|
+
const DOCUMENT_EDIT_TEMP_PREFIX = '.lat-edit-';
|
|
19
27
|
function isInside(root, candidate) {
|
|
20
28
|
const rel = relative(root, candidate);
|
|
21
29
|
return (rel === '' ||
|
|
@@ -25,12 +33,16 @@ function projectPath(projectRoot, path) {
|
|
|
25
33
|
const normalized = isAbsolute(path) ? relative(projectRoot, path) : path;
|
|
26
34
|
return toPosix(normalized).replace(/^\.\//, '');
|
|
27
35
|
}
|
|
36
|
+
function isInternalLatCachePath(path, latPath) {
|
|
37
|
+
const cachePath = latPath ? `${latPath}/.cache` : '.cache';
|
|
38
|
+
return path === cachePath || path.startsWith(`${cachePath}/`);
|
|
39
|
+
}
|
|
28
40
|
function excludedCodePath(projectRoot, path, excludedPaths) {
|
|
29
41
|
const absolutePath = resolve(projectRoot, path);
|
|
30
42
|
return excludedPaths.some((root) => isInside(resolve(root), absolutePath));
|
|
31
43
|
}
|
|
32
44
|
function sourcePath(path) {
|
|
33
|
-
return
|
|
45
|
+
return isSourceFileExtension(extname(path).toLowerCase());
|
|
34
46
|
}
|
|
35
47
|
function obviouslyIgnoredCodePath(path, latPath) {
|
|
36
48
|
const parts = path.split('/');
|
|
@@ -73,9 +85,13 @@ async function loadCodeReferenceFiles(projectRoot, refs) {
|
|
|
73
85
|
return files;
|
|
74
86
|
}
|
|
75
87
|
async function scanCodeState(projectRoot, excludedPaths = []) {
|
|
76
|
-
const
|
|
88
|
+
const discovery = createCodeReferenceDiscovery(projectRoot);
|
|
89
|
+
const [scan, sourceFiles] = await Promise.all([
|
|
90
|
+
discovery.scan(),
|
|
91
|
+
discovery.listSourceFiles(),
|
|
92
|
+
]);
|
|
77
93
|
const allowed = (path) => !excludedCodePath(projectRoot, path, excludedPaths);
|
|
78
|
-
const files =
|
|
94
|
+
const files = sourceFiles.filter(allowed);
|
|
79
95
|
const refs = scan.refs.filter((ref) => allowed(ref.file));
|
|
80
96
|
const scope = new Set(files.map((path) => projectPath(projectRoot, path)));
|
|
81
97
|
return {
|
|
@@ -83,14 +99,34 @@ async function scanCodeState(projectRoot, excludedPaths = []) {
|
|
|
83
99
|
scope,
|
|
84
100
|
};
|
|
85
101
|
}
|
|
86
|
-
function viewIndex(latDir, paths, diagnostics, git) {
|
|
102
|
+
function viewIndex(latDir, paths, diagnostics, git, references, external) {
|
|
87
103
|
const files = [...paths].sort();
|
|
104
|
+
const externalFiles = new Map();
|
|
105
|
+
for (const target of references.externalByTarget.keys()) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = external.parse(target);
|
|
108
|
+
if (!parsed)
|
|
109
|
+
continue;
|
|
110
|
+
const hash = parsed.identity.indexOf('#');
|
|
111
|
+
const baseTarget = hash === -1 ? parsed.identity : parsed.identity.slice(0, hash);
|
|
112
|
+
externalFiles.set(baseTarget, {
|
|
113
|
+
handle: parsed.handle,
|
|
114
|
+
path: parsed.resolvedPath,
|
|
115
|
+
target: baseTarget,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Invalid external targets remain diagnostics, not sidebar entries.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
88
122
|
const directoryName = basename(latDir);
|
|
89
123
|
const indexName = directoryName.endsWith('.md')
|
|
90
124
|
? directoryName
|
|
91
125
|
: `${directoryName}.md`;
|
|
92
126
|
return {
|
|
93
127
|
files,
|
|
128
|
+
externalFiles: [...externalFiles.values()].sort((left, right) => left.handle.localeCompare(right.handle) ||
|
|
129
|
+
left.path.localeCompare(right.path)),
|
|
94
130
|
entry: files.includes(indexName) ? indexName : (files[0] ?? ''),
|
|
95
131
|
errorCounts: Object.fromEntries([...diagnostics]
|
|
96
132
|
.filter(([, errors]) => errors.length > 0)
|
|
@@ -103,21 +139,34 @@ function viewIndex(latDir, paths, diagnostics, git) {
|
|
|
103
139
|
logoText: DEFAULT_VIEW_LOGO_TEXT,
|
|
104
140
|
};
|
|
105
141
|
}
|
|
106
|
-
async function buildSnapshot(latDir, projectRoot, markdownFiles, codeFiles, git, generation, markdownGeneration) {
|
|
142
|
+
async function buildSnapshot(latDir, projectRoot, markdownFiles, codeFiles, git, generation, markdownGeneration, options) {
|
|
107
143
|
const files = new Map([...markdownFiles].sort(([left], [right]) => left.localeCompare(right)));
|
|
108
144
|
const allSections = [...files.values()].flatMap((file) => file.sections);
|
|
109
|
-
const
|
|
110
|
-
|
|
145
|
+
const external = await createExternalResolver(latDir, projectRoot, {
|
|
146
|
+
ignoreLocal: options.externalIgnoreLocal,
|
|
147
|
+
ca: options.externalCa,
|
|
148
|
+
});
|
|
149
|
+
await external.reconcile();
|
|
150
|
+
const diagnostics = await buildViewDiagnostics(files.values(), codeFiles.values(), allSections, projectRoot, external, latDir);
|
|
151
|
+
const references = buildViewReferenceIndex(files.values(), codeFiles.values(), allSections, (target) => {
|
|
152
|
+
try {
|
|
153
|
+
return external.parse(target)?.identity ?? null;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
111
159
|
return {
|
|
112
160
|
generation,
|
|
113
161
|
markdownGeneration,
|
|
114
162
|
files,
|
|
115
163
|
allSections,
|
|
116
164
|
references,
|
|
117
|
-
graph: buildViewGraph(files.values(), codeFiles.values(), allSections, diagnostics, git, generation),
|
|
165
|
+
graph: buildViewGraph(files.values(), codeFiles.values(), allSections, diagnostics, git, generation, external),
|
|
118
166
|
diagnostics,
|
|
119
167
|
git,
|
|
120
|
-
index: viewIndex(latDir, [...files.keys()], diagnostics, git),
|
|
168
|
+
index: viewIndex(latDir, [...files.keys()], diagnostics, git, references, external),
|
|
169
|
+
external,
|
|
121
170
|
};
|
|
122
171
|
}
|
|
123
172
|
/** Own the immutable, incrementally refreshed project snapshot for `lat ui`. */
|
|
@@ -133,11 +182,13 @@ export class ViewStore {
|
|
|
133
182
|
ignoredCodePaths = new Set();
|
|
134
183
|
listeners = new Set();
|
|
135
184
|
watcher = null;
|
|
185
|
+
externalWatchers = [];
|
|
136
186
|
pendingPaths = new Set();
|
|
137
187
|
debounceTimer = null;
|
|
138
188
|
gitPollTimer = null;
|
|
139
189
|
gitPollQueued = false;
|
|
140
190
|
refreshTail = Promise.resolve();
|
|
191
|
+
editTail = Promise.resolve();
|
|
141
192
|
closed = false;
|
|
142
193
|
constructor(latDir, projectRoot, realLatDir, snapshot, codeFiles, codeScope, gitRepository, options) {
|
|
143
194
|
this.latDir = latDir;
|
|
@@ -168,6 +219,29 @@ export class ViewStore {
|
|
|
168
219
|
catch (error) {
|
|
169
220
|
process.stderr.write(`lat ui watcher: ${error.message}\n`);
|
|
170
221
|
}
|
|
222
|
+
this.refreshExternalWatchers();
|
|
223
|
+
}
|
|
224
|
+
refreshExternalWatchers() {
|
|
225
|
+
for (const watcher of this.externalWatchers)
|
|
226
|
+
watcher.close();
|
|
227
|
+
this.externalWatchers = [];
|
|
228
|
+
if (this.options.watch === false)
|
|
229
|
+
return;
|
|
230
|
+
const paths = new Set([...this.snapshotValue.external.snapshot.sources.values()]
|
|
231
|
+
.map((source) => source.localPath)
|
|
232
|
+
.filter((path) => Boolean(path)));
|
|
233
|
+
for (const path of paths) {
|
|
234
|
+
try {
|
|
235
|
+
const watcher = watchFiles(path, { recursive: true }, () => this.scheduleRefresh(EXTERNAL_REFRESH_PATH));
|
|
236
|
+
watcher.on('error', (error) => {
|
|
237
|
+
process.stderr.write(`lat ui external watcher: ${error.message}\n`);
|
|
238
|
+
});
|
|
239
|
+
this.externalWatchers.push(watcher);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
process.stderr.write(`lat ui external watcher: ${error.message}\n`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
171
245
|
}
|
|
172
246
|
subscribe(listener) {
|
|
173
247
|
this.listeners.add(listener);
|
|
@@ -179,6 +253,15 @@ export class ViewStore {
|
|
|
179
253
|
getGraph() {
|
|
180
254
|
return this.snapshotValue.graph;
|
|
181
255
|
}
|
|
256
|
+
async renderSectionOutput(markdown, sectionId) {
|
|
257
|
+
const snapshot = this.snapshotValue;
|
|
258
|
+
const section = snapshot.allSections.find((candidate) => candidate.id.toLowerCase() === sectionId.toLowerCase());
|
|
259
|
+
const requestedPath = section
|
|
260
|
+
? toPosix(relative(this.latDir, resolve(this.projectRoot, section.filePath)))
|
|
261
|
+
: 'section-output.md';
|
|
262
|
+
const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references, snapshot.external);
|
|
263
|
+
return (await renderMarkdown(markdown, requestedPath, resolver)).tree;
|
|
264
|
+
}
|
|
182
265
|
async getDocument(requestedPath) {
|
|
183
266
|
if (!requestedPath ||
|
|
184
267
|
requestedPath.includes('\\') ||
|
|
@@ -191,24 +274,27 @@ export class ViewStore {
|
|
|
191
274
|
if (!file) {
|
|
192
275
|
throw new ViewDocumentNotFoundError('Markdown document not found');
|
|
193
276
|
}
|
|
194
|
-
const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references);
|
|
195
|
-
const rendered = await renderMarkdown(file.content, requestedPath, resolver, {
|
|
277
|
+
const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references, snapshot.external);
|
|
278
|
+
const rendered = await renderMarkdown(file.content, requestedPath, resolver, {
|
|
279
|
+
errors: [...(snapshot.diagnostics.get(requestedPath) ?? [])],
|
|
280
|
+
rewriteMarkdownLink: (url) => rewriteDocumentLink(url, requestedPath),
|
|
281
|
+
});
|
|
196
282
|
const errors = [...(snapshot.diagnostics.get(requestedPath) ?? [])];
|
|
197
283
|
const gitFile = snapshot.git.files.get(requestedPath);
|
|
198
284
|
const gitTree = gitFile
|
|
199
|
-
? buildGitDiffTree(gitFile.baseContent, file.content
|
|
285
|
+
? buildGitDiffTree(gitFile.baseContent, file.content)
|
|
200
286
|
: null;
|
|
201
287
|
const gitRendered = gitTree
|
|
202
|
-
? await renderMarkdown(file.content, requestedPath, resolver, {
|
|
288
|
+
? await renderMarkdown(file.content, requestedPath, resolver, {
|
|
289
|
+
errors,
|
|
290
|
+
rewriteMarkdownLink: (url) => rewriteDocumentLink(url, requestedPath),
|
|
291
|
+
}, gitTree)
|
|
203
292
|
: null;
|
|
204
293
|
return {
|
|
205
294
|
path: requestedPath,
|
|
206
295
|
...rendered,
|
|
207
|
-
|
|
208
|
-
tableOfContents: buildViewTableOfContents(file.sections, file.
|
|
209
|
-
errors,
|
|
210
|
-
gitTree,
|
|
211
|
-
}),
|
|
296
|
+
gitTree: gitRendered?.tree ?? null,
|
|
297
|
+
tableOfContents: buildViewTableOfContents(file.sections, file.headingTitles, { errors, gitTree }),
|
|
212
298
|
graphNodeIds: Object.fromEntries(snapshot.graph.nodes
|
|
213
299
|
.filter((node) => node.documentPath === requestedPath && node.kind === 'document')
|
|
214
300
|
.map((node) => {
|
|
@@ -223,20 +309,107 @@ export class ViewStore {
|
|
|
223
309
|
return [headingId, node.id];
|
|
224
310
|
})),
|
|
225
311
|
errors,
|
|
226
|
-
backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections, snapshot.references)),
|
|
312
|
+
backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections, snapshot.references, snapshot.external)),
|
|
227
313
|
frontmatter: {
|
|
228
|
-
requireCodeMention:
|
|
314
|
+
requireCodeMention: file.frontmatter.requireCodeMention === true,
|
|
229
315
|
},
|
|
230
316
|
};
|
|
231
317
|
}
|
|
318
|
+
async editableDocumentPath(requestedPath) {
|
|
319
|
+
if (!requestedPath ||
|
|
320
|
+
requestedPath.includes('\\') ||
|
|
321
|
+
isAbsolute(requestedPath) ||
|
|
322
|
+
!requestedPath.toLowerCase().endsWith('.md') ||
|
|
323
|
+
!this.snapshotValue.files.has(requestedPath)) {
|
|
324
|
+
throw new ViewDocumentNotFoundError('Markdown document not found');
|
|
325
|
+
}
|
|
326
|
+
let realFile;
|
|
327
|
+
try {
|
|
328
|
+
realFile = await realpath(resolve(this.latDir, requestedPath));
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
throw new ViewDocumentNotFoundError('Markdown document not found');
|
|
332
|
+
}
|
|
333
|
+
if (!isInside(this.realLatDir, realFile)) {
|
|
334
|
+
throw new ViewDocumentNotFoundError('Markdown document not found');
|
|
335
|
+
}
|
|
336
|
+
return realFile;
|
|
337
|
+
}
|
|
338
|
+
async getDocumentSource(requestedPath) {
|
|
339
|
+
const path = await this.editableDocumentPath(requestedPath);
|
|
340
|
+
return { path: requestedPath, content: await readFile(path, 'utf8') };
|
|
341
|
+
}
|
|
342
|
+
async getDocumentResource(requestedPath) {
|
|
343
|
+
if (!requestedPath ||
|
|
344
|
+
requestedPath.includes('\\') ||
|
|
345
|
+
isAbsolute(requestedPath) ||
|
|
346
|
+
requestedPath.toLowerCase().endsWith('.md')) {
|
|
347
|
+
throw new ViewDocumentNotFoundError('Document resource not found');
|
|
348
|
+
}
|
|
349
|
+
let path;
|
|
350
|
+
try {
|
|
351
|
+
path = await realpath(resolve(this.latDir, requestedPath));
|
|
352
|
+
if (!isInside(this.realLatDir, path) || !(await stat(path)).isFile()) {
|
|
353
|
+
throw new ViewDocumentNotFoundError('Document resource not found');
|
|
354
|
+
}
|
|
355
|
+
return await readFile(path);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (error instanceof ViewDocumentNotFoundError)
|
|
359
|
+
throw error;
|
|
360
|
+
throw new ViewDocumentNotFoundError('Document resource not found');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
editDocument(requestedPath, baseContent, editedContent) {
|
|
364
|
+
const operation = this.editTail.then(async () => {
|
|
365
|
+
const path = await this.editableDocumentPath(requestedPath);
|
|
366
|
+
for (let attempt = 0; attempt < DOCUMENT_EDIT_WRITE_ATTEMPTS; attempt++) {
|
|
367
|
+
const currentContent = await readFile(path, 'utf8');
|
|
368
|
+
const edit = applyDocumentEdit(baseContent, editedContent, currentContent);
|
|
369
|
+
if (edit.content === currentContent) {
|
|
370
|
+
return { path: requestedPath, ...edit };
|
|
371
|
+
}
|
|
372
|
+
const currentStat = await stat(path);
|
|
373
|
+
const temporaryPath = resolve(this.latDir, `${DOCUMENT_EDIT_TEMP_PREFIX}${process.pid}-${randomUUID()}.tmp`);
|
|
374
|
+
try {
|
|
375
|
+
await writeFile(temporaryPath, edit.content, {
|
|
376
|
+
encoding: 'utf8',
|
|
377
|
+
flag: 'wx',
|
|
378
|
+
mode: currentStat.mode,
|
|
379
|
+
});
|
|
380
|
+
if ((await readFile(path, 'utf8')) !== currentContent)
|
|
381
|
+
continue;
|
|
382
|
+
await rename(temporaryPath, path);
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
await rm(temporaryPath, { force: true });
|
|
386
|
+
}
|
|
387
|
+
await this.refresh([resolve(this.latDir, requestedPath)]);
|
|
388
|
+
return { path: requestedPath, ...edit };
|
|
389
|
+
}
|
|
390
|
+
throw new ViewDocumentConflictError('Could not save because this file kept changing. Your edits are still in the editor.');
|
|
391
|
+
});
|
|
392
|
+
this.editTail = operation.then(() => undefined, () => undefined);
|
|
393
|
+
return operation;
|
|
394
|
+
}
|
|
232
395
|
getSource(requestedPath, requestedSymbol = '', origin, requestedLine = 0) {
|
|
233
396
|
const snapshot = this.snapshotValue;
|
|
234
397
|
return getViewSource(this.latDir, this.projectRoot, requestedPath, requestedSymbol, origin, requestedLine, snapshot.allSections, snapshot.references);
|
|
235
398
|
}
|
|
399
|
+
getExternal(target) {
|
|
400
|
+
const snapshot = this.snapshotValue;
|
|
401
|
+
return getViewExternal(this.latDir, this.projectRoot, target, snapshot.external, snapshot.allSections, snapshot.references);
|
|
402
|
+
}
|
|
236
403
|
refresh(paths) {
|
|
237
404
|
if (this.closed)
|
|
238
405
|
return Promise.resolve();
|
|
239
|
-
const
|
|
406
|
+
const latPath = projectPath(this.projectRoot, this.latDir);
|
|
407
|
+
const normalized = paths
|
|
408
|
+
.map((path) => (path ? projectPath(this.projectRoot, path) : ''))
|
|
409
|
+
.filter((path) => !isInternalLatCachePath(path, latPath));
|
|
410
|
+
if (paths.length > 0 && normalized.length === 0) {
|
|
411
|
+
return Promise.resolve();
|
|
412
|
+
}
|
|
240
413
|
return this.queueRefresh(normalized, false);
|
|
241
414
|
}
|
|
242
415
|
queueRefresh(paths, pollGit) {
|
|
@@ -254,13 +427,30 @@ export class ViewStore {
|
|
|
254
427
|
this.gitPollTimer = null;
|
|
255
428
|
this.watcher?.close();
|
|
256
429
|
this.watcher = null;
|
|
430
|
+
for (const watcher of this.externalWatchers)
|
|
431
|
+
watcher.close();
|
|
432
|
+
this.externalWatchers = [];
|
|
433
|
+
await this.editTail;
|
|
257
434
|
await this.refreshTail;
|
|
258
435
|
this.listeners.clear();
|
|
259
436
|
}
|
|
260
437
|
scheduleRefresh(path) {
|
|
261
438
|
if (this.closed)
|
|
262
439
|
return;
|
|
263
|
-
|
|
440
|
+
if (path !== EXTERNAL_REFRESH_PATH &&
|
|
441
|
+
basename(path).startsWith(DOCUMENT_EDIT_TEMP_PREFIX) &&
|
|
442
|
+
path.endsWith('.tmp')) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const normalizedPath = path === EXTERNAL_REFRESH_PATH
|
|
446
|
+
? path
|
|
447
|
+
: path
|
|
448
|
+
? projectPath(this.projectRoot, path)
|
|
449
|
+
: '';
|
|
450
|
+
const latPath = projectPath(this.projectRoot, this.latDir);
|
|
451
|
+
if (isInternalLatCachePath(normalizedPath, latPath))
|
|
452
|
+
return;
|
|
453
|
+
this.pendingPaths.add(normalizedPath);
|
|
264
454
|
if (this.debounceTimer)
|
|
265
455
|
clearTimeout(this.debounceTimer);
|
|
266
456
|
this.debounceTimer = setTimeout(() => {
|
|
@@ -300,12 +490,13 @@ export class ViewStore {
|
|
|
300
490
|
}
|
|
301
491
|
if (!isInside(this.realLatDir, realFile))
|
|
302
492
|
return null;
|
|
303
|
-
|
|
304
|
-
return parseViewMarkdownFile(absolutePath, content, this.latDir, this.projectRoot);
|
|
493
|
+
return analyzeMarkdownPath(absolutePath, this.latDir, this.projectRoot);
|
|
305
494
|
}
|
|
306
495
|
async applyRefresh(paths, pollGit) {
|
|
307
496
|
const fullRefresh = paths.includes('');
|
|
308
497
|
const latPath = projectPath(this.projectRoot, this.latDir);
|
|
498
|
+
const externalChanged = paths.includes(EXTERNAL_REFRESH_PATH) ||
|
|
499
|
+
paths.includes(`${latPath}/config.local.yaml`);
|
|
309
500
|
const touchesMarkdown = fullRefresh ||
|
|
310
501
|
paths.some((path) => path === latPath || path.startsWith(`${latPath}/`));
|
|
311
502
|
let markdownFiles = new Map(this.snapshotValue.files);
|
|
@@ -419,10 +610,12 @@ export class ViewStore {
|
|
|
419
610
|
if (!markdownChanged &&
|
|
420
611
|
!codeChanged &&
|
|
421
612
|
!gitChanged &&
|
|
422
|
-
!linkedResourceChanged
|
|
613
|
+
!linkedResourceChanged &&
|
|
614
|
+
!externalChanged)
|
|
423
615
|
return;
|
|
424
616
|
this.codeFiles = codeFiles;
|
|
425
|
-
this.snapshotValue = await buildSnapshot(this.latDir, this.projectRoot, markdownFiles, codeFiles, git, this.snapshotValue.generation + 1, this.snapshotValue.markdownGeneration + (markdownChanged ? 1 : 0));
|
|
617
|
+
this.snapshotValue = await buildSnapshot(this.latDir, this.projectRoot, markdownFiles, codeFiles, git, this.snapshotValue.generation + 1, this.snapshotValue.markdownGeneration + (markdownChanged ? 1 : 0), this.options);
|
|
618
|
+
this.refreshExternalWatchers();
|
|
426
619
|
const change = {
|
|
427
620
|
generation: this.snapshotValue.generation,
|
|
428
621
|
markdownGeneration: this.snapshotValue.markdownGeneration,
|
|
@@ -449,8 +642,7 @@ export async function createViewStore(latDir, projectRoot, options = {}) {
|
|
|
449
642
|
const realFile = await realpath(absolutePath);
|
|
450
643
|
if (!isInside(realLatDir, realFile))
|
|
451
644
|
return;
|
|
452
|
-
const
|
|
453
|
-
const parsed = parseViewMarkdownFile(absolutePath, content, latDir, projectRoot);
|
|
645
|
+
const parsed = await analyzeMarkdownPath(absolutePath, latDir, projectRoot);
|
|
454
646
|
markdownFiles.set(parsed.path, parsed);
|
|
455
647
|
}));
|
|
456
648
|
if (markdownFiles.size === 0) {
|
|
@@ -465,7 +657,7 @@ export async function createViewStore(latDir, projectRoot, options = {}) {
|
|
|
465
657
|
process.stderr.write(`lat ui git: ${error.message}\n`);
|
|
466
658
|
}
|
|
467
659
|
}
|
|
468
|
-
const store = new ViewStore(latDir, projectRoot, realLatDir, await buildSnapshot(latDir, projectRoot, markdownFiles, codeState.files, git, 0, 0), codeState.files, codeState.scope, gitRepository, options);
|
|
660
|
+
const store = new ViewStore(latDir, projectRoot, realLatDir, await buildSnapshot(latDir, projectRoot, markdownFiles, codeState.files, git, 0, 0, options), codeState.files, codeState.scope, gitRepository, options);
|
|
469
661
|
store.startWatching();
|
|
470
662
|
return store;
|
|
471
663
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Root } from 'mdast';
|
|
2
|
-
import { type Section } from '../lattice.js';
|
|
2
|
+
import { type Section } from '../lattice-model.js';
|
|
3
3
|
import type { ViewDocumentError, ViewDocumentTocItem } from './protocol.js';
|
|
4
4
|
export type ViewTableOfContentsOptions = {
|
|
5
5
|
errors?: readonly ViewDocumentError[];
|
|
6
6
|
gitTree?: Root | null;
|
|
7
7
|
};
|
|
8
8
|
/** Project rendered headings into the document's local navigation. */
|
|
9
|
-
export declare function buildViewTableOfContents(sections: Section[],
|
|
9
|
+
export declare function buildViewTableOfContents(sections: Section[], titles: readonly string[], options?: ViewTableOfContentsOptions): ViewDocumentTocItem[];
|
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { flattenSections } from '../lattice.js';
|
|
3
|
-
function headingText(node) {
|
|
4
|
-
if (!node || typeof node !== 'object')
|
|
5
|
-
return '';
|
|
6
|
-
const value = node;
|
|
7
|
-
if (value.type === 'text' || value.type === 'inlineCode') {
|
|
8
|
-
return value.value ?? '';
|
|
9
|
-
}
|
|
10
|
-
if (value.type === 'image' || value.type === 'imageReference') {
|
|
11
|
-
return value.alt ?? '';
|
|
12
|
-
}
|
|
13
|
-
if (value.type === 'wikiLink') {
|
|
14
|
-
return value.data?.alias ?? value.value ?? '';
|
|
15
|
-
}
|
|
16
|
-
return value.children?.map(headingText).join('') ?? '';
|
|
17
|
-
}
|
|
1
|
+
import { flattenSections } from '../lattice-model.js';
|
|
18
2
|
function sectionAtLine(sections, line) {
|
|
19
3
|
let owner = null;
|
|
20
4
|
for (const section of sections) {
|
|
@@ -76,12 +60,8 @@ function gitChangedSections(sections, tree) {
|
|
|
76
60
|
return changed;
|
|
77
61
|
}
|
|
78
62
|
/** Project rendered headings into the document's local navigation. */
|
|
79
|
-
export function buildViewTableOfContents(sections,
|
|
63
|
+
export function buildViewTableOfContents(sections, titles, options = {}) {
|
|
80
64
|
const flatSections = flattenSections(sections);
|
|
81
|
-
const titles = [];
|
|
82
|
-
visit(tree, 'heading', (node) => {
|
|
83
|
-
titles.push(headingText(node));
|
|
84
|
-
});
|
|
85
65
|
const errorCounts = new Map();
|
|
86
66
|
for (const error of options.errors ?? []) {
|
|
87
67
|
const owner = sectionAtLine(flatSections, error.line);
|
package/dist/src/walk.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
/** Dependency trees are never project source, even without ignore metadata. */
|
|
2
|
+
export declare const ALWAYS_IGNORED_DIRECTORIES: readonly ["node_modules"];
|
|
1
3
|
/**
|
|
2
|
-
* Walk a directory tree respecting .gitignore rules.
|
|
3
|
-
*
|
|
4
|
+
* Walk a directory tree respecting nested .gitignore rules. Directories are
|
|
5
|
+
* consumed through a bounded queue, and each completed job submits its visible
|
|
6
|
+
* children back to the pool. Returns sorted project-relative POSIX paths.
|
|
4
7
|
*
|
|
5
8
|
* This is the single entry point for all directory walking in lat.md — both
|
|
6
9
|
* code-ref scanning and lat.md/ index validation use it so .gitignore rules
|
|
@@ -15,4 +18,3 @@ export declare function walkEntries(dir: string): Promise<string[]>;
|
|
|
15
18
|
* a single invariant — stored paths are always POSIX — so downstream lookups
|
|
16
19
|
* (e.g. `buildFileIndex`, ref resolution) work identically on every platform.
|
|
17
20
|
*/
|
|
18
|
-
export declare function toPosix(p: string): string;
|
package/dist/src/walk.js
CHANGED
|
@@ -1,18 +1,115 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
1
4
|
// @ts-expect-error -- no type declarations
|
|
2
5
|
import walk from 'ignore-walk';
|
|
6
|
+
const IgnoreWalker = walk
|
|
7
|
+
.Walker;
|
|
8
|
+
/** Dependency trees are never project source, even without ignore metadata. */
|
|
9
|
+
export const ALWAYS_IGNORED_DIRECTORIES = ['node_modules'];
|
|
10
|
+
class IgnoreContext extends IgnoreWalker {
|
|
11
|
+
filterEntry(entry, partial, entryBasename) {
|
|
12
|
+
const candidate = entryBasename ?? entry;
|
|
13
|
+
if (candidate
|
|
14
|
+
.split(/[\\/]/)
|
|
15
|
+
.some((part) => ALWAYS_IGNORED_DIRECTORIES.includes(part) ||
|
|
16
|
+
(part.startsWith('.') && part !== '.' && part !== '..'))) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return super.filterEntry(entry, partial, entryBasename);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function readDirectory(job) {
|
|
23
|
+
const entries = await readdir(job.path, { withFileTypes: true });
|
|
24
|
+
if (entries.some((entry) => entry.name === '.gitignore')) {
|
|
25
|
+
const rules = await readFile(join(job.path, '.gitignore'), 'utf8');
|
|
26
|
+
job.ignoreContext.onReadIgnoreFile('.gitignore', rules, () => { });
|
|
27
|
+
}
|
|
28
|
+
const directories = [];
|
|
29
|
+
const files = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isSymbolicLink())
|
|
32
|
+
continue;
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
const passDirectory = job.ignoreContext.filterEntry(entry.name, true);
|
|
35
|
+
if (!passDirectory)
|
|
36
|
+
continue;
|
|
37
|
+
const passFile = job.ignoreContext.filterEntry(entry.name);
|
|
38
|
+
const path = join(job.path, entry.name);
|
|
39
|
+
directories.push({
|
|
40
|
+
path,
|
|
41
|
+
relativePath: job.relativePath
|
|
42
|
+
? `${job.relativePath}/${entry.name}`
|
|
43
|
+
: entry.name,
|
|
44
|
+
ignoreContext: new IgnoreContext({
|
|
45
|
+
path,
|
|
46
|
+
parent: job.ignoreContext,
|
|
47
|
+
ignoreFiles: ['.gitignore'],
|
|
48
|
+
exact: passFile || job.ignoreContext.filterEntry(`${entry.name}/`),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else if (job.ignoreContext.filterEntry(entry.name)) {
|
|
53
|
+
files.push(job.relativePath ? `${job.relativePath}/${entry.name}` : entry.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { directories, files };
|
|
57
|
+
}
|
|
58
|
+
async function walkWithDirectoryPool(root) {
|
|
59
|
+
const queue = [root];
|
|
60
|
+
const files = [];
|
|
61
|
+
const workerLimit = availableParallelism();
|
|
62
|
+
let nextJob = 0;
|
|
63
|
+
let activeWorkers = 0;
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
let settled = false;
|
|
66
|
+
const schedule = () => {
|
|
67
|
+
if (settled)
|
|
68
|
+
return;
|
|
69
|
+
while (activeWorkers < workerLimit && nextJob < queue.length) {
|
|
70
|
+
const job = queue[nextJob++];
|
|
71
|
+
activeWorkers++;
|
|
72
|
+
void readDirectory(job).then((result) => {
|
|
73
|
+
files.push(...result.files);
|
|
74
|
+
queue.push(...result.directories);
|
|
75
|
+
activeWorkers--;
|
|
76
|
+
if (activeWorkers === 0 && nextJob === queue.length) {
|
|
77
|
+
settled = true;
|
|
78
|
+
files.sort((a, b) => a.localeCompare(b, 'en'));
|
|
79
|
+
resolve(files);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
schedule();
|
|
83
|
+
}
|
|
84
|
+
}, (error) => {
|
|
85
|
+
if (settled)
|
|
86
|
+
return;
|
|
87
|
+
settled = true;
|
|
88
|
+
reject(error);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
schedule();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
3
95
|
/**
|
|
4
|
-
* Walk a directory tree respecting .gitignore rules.
|
|
5
|
-
*
|
|
96
|
+
* Walk a directory tree respecting nested .gitignore rules. Directories are
|
|
97
|
+
* consumed through a bounded queue, and each completed job submits its visible
|
|
98
|
+
* children back to the pool. Returns sorted project-relative POSIX paths.
|
|
6
99
|
*
|
|
7
100
|
* This is the single entry point for all directory walking in lat.md — both
|
|
8
101
|
* code-ref scanning and lat.md/ index validation use it so .gitignore rules
|
|
9
102
|
* are consistently honored.
|
|
10
103
|
*/
|
|
11
104
|
export function walkEntries(dir) {
|
|
12
|
-
return
|
|
105
|
+
return walkWithDirectoryPool({
|
|
13
106
|
path: dir,
|
|
14
|
-
|
|
15
|
-
|
|
107
|
+
relativePath: '',
|
|
108
|
+
ignoreContext: new IgnoreContext({
|
|
109
|
+
path: dir,
|
|
110
|
+
ignoreFiles: ['.gitignore'],
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
16
113
|
}
|
|
17
114
|
/**
|
|
18
115
|
* Normalize a filesystem path to forward-slash (POSIX) form. Node's
|
|
@@ -22,6 +119,3 @@ export function walkEntries(dir) {
|
|
|
22
119
|
* a single invariant — stored paths are always POSIX — so downstream lookups
|
|
23
120
|
* (e.g. `buildFileIndex`, ref resolution) work identically on every platform.
|
|
24
121
|
*/
|
|
25
|
-
export function toPosix(p) {
|
|
26
|
-
return p.replace(/\\/g, '/');
|
|
27
|
-
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { runLatServer } from './index.js';
|
|
5
|
+
const entry = process.argv[2];
|
|
6
|
+
if (!entry)
|
|
7
|
+
throw new Error('Usage: lat-ui-server <app-module>');
|
|
8
|
+
const module = (await import(pathToFileURL(resolve(entry)).href));
|
|
9
|
+
if (typeof module.default !== 'function') {
|
|
10
|
+
throw new Error(`${entry} must default-export an Express app`);
|
|
11
|
+
}
|
|
12
|
+
await runLatServer(module.default, { close: module.close });
|