@plebeiantech/lat.md 0.12.3-fork.15 → 0.12.3-fork.17

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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli/checklist-menu.d.ts +2 -1
  3. package/dist/src/cli/checklist-menu.js +3 -2
  4. package/dist/src/cli/index.js +59 -16
  5. package/dist/src/cli/init-preferences.d.ts +4 -0
  6. package/dist/src/cli/init-preferences.js +35 -0
  7. package/dist/src/cli/init.js +10 -5
  8. package/dist/src/cli/reindex.js +4 -8
  9. package/dist/src/cli/search.d.ts +3 -2
  10. package/dist/src/cli/search.js +71 -66
  11. package/dist/src/cli/ui-build-server.d.ts +12 -0
  12. package/dist/src/cli/ui-build-server.js +26 -0
  13. package/dist/src/cli/ui-build.js +1 -1
  14. package/dist/src/cli/ui.js +1 -1
  15. package/dist/src/format.d.ts +5 -0
  16. package/dist/src/format.js +32 -4
  17. package/dist/src/lattice-model.d.ts +8 -1
  18. package/dist/src/lattice-model.js +4 -1
  19. package/dist/src/lattice.d.ts +2 -2
  20. package/dist/src/lattice.js +17 -34
  21. package/dist/src/markdown-analysis.d.ts +11 -0
  22. package/dist/src/markdown-analysis.js +31 -1
  23. package/dist/src/mcp/server.js +6 -6
  24. package/dist/src/parser-cache.d.ts +1 -1
  25. package/dist/src/parser-cache.js +1 -1
  26. package/dist/src/search/cache.d.ts +3 -0
  27. package/dist/src/search/cache.js +71 -0
  28. package/dist/src/search/chunks.d.ts +23 -0
  29. package/dist/src/search/chunks.js +197 -0
  30. package/dist/src/search/db.d.ts +27 -16
  31. package/dist/src/search/db.js +108 -38
  32. package/dist/src/search/index.d.ts +5 -2
  33. package/dist/src/search/index.js +173 -69
  34. package/dist/src/search/lexical.d.ts +6 -0
  35. package/dist/src/search/lexical.js +36 -0
  36. package/dist/src/search/lock.d.ts +2 -0
  37. package/dist/src/search/lock.js +23 -0
  38. package/dist/src/search/query.d.ts +6 -3
  39. package/dist/src/search/query.js +45 -26
  40. package/dist/src/search/search.d.ts +13 -8
  41. package/dist/src/search/search.js +155 -23
  42. package/dist/src/search/types.d.ts +31 -0
  43. package/dist/src/search/types.js +1 -0
  44. package/dist/src/source-formats.d.ts +1 -1
  45. package/dist/src/source-formats.js +4 -0
  46. package/dist/src/source-parser.js +397 -1
  47. package/dist/src/view/client/assets/GraphView-BWlRdQM4.js +456 -0
  48. package/dist/src/view/client/assets/{MarkdownEditor-B7quqPb8.js → MarkdownEditor-MW-Ck1O0.js} +8 -8
  49. package/dist/src/view/client/assets/_commonjsHelpers-CqkleIqs.js +1 -0
  50. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-Bn3RshMV.js +1 -0
  51. package/dist/src/view/client/assets/{arc-BnwR8Bu7.js → arc-En9tdFQF.js} +1 -1
  52. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-B-9J1VAM.js +36 -0
  53. package/dist/src/view/client/assets/{blockDiagram-I7D4REHJ-DETl8e_H.js → blockDiagram-I7D4REHJ-QlznwalP.js} +2 -2
  54. package/dist/src/view/client/assets/{c4Diagram-7LVT6UL2-DJRAT1Ve.js → c4Diagram-7LVT6UL2-Bp2y8B4_.js} +2 -2
  55. package/dist/src/view/client/assets/channel-uy0fFvcW.js +1 -0
  56. package/dist/src/view/client/assets/{chunk-2Q5K7J3B-C0TmOC1Q.js → chunk-2Q5K7J3B-CnBKL-lm.js} +1 -1
  57. package/dist/src/view/client/assets/{chunk-5VM5RSS4-dfBOrYE6.js → chunk-5VM5RSS4-LLYMi_n6.js} +1 -1
  58. package/dist/src/view/client/assets/{chunk-F27PBJKO-DAnks99K.js → chunk-F27PBJKO-VIeO7VF0.js} +1 -1
  59. package/dist/src/view/client/assets/{chunk-IMKFNOWR-CPjB07if.js → chunk-IMKFNOWR-D5i67DVD.js} +1 -1
  60. package/dist/src/view/client/assets/{chunk-JWPE2WC7-VvU_gZI6.js → chunk-JWPE2WC7-D4qAtoL3.js} +1 -1
  61. package/dist/src/view/client/assets/{chunk-POPQ4Y6H-DDUbVF55.js → chunk-POPQ4Y6H-BMzOyiW_.js} +1 -1
  62. package/dist/src/view/client/assets/{chunk-SVP7TREG-BHROkr6D.js → chunk-SVP7TREG-DWy5nbfp.js} +1 -1
  63. package/dist/src/view/client/assets/{chunk-TICWLB2K-95PzGIOg.js → chunk-TICWLB2K-CVqCANFB.js} +1 -1
  64. package/dist/src/view/client/assets/{chunk-XXDRQBXY-MdmuLhIs.js → chunk-XXDRQBXY-Bp23h6Tf.js} +1 -1
  65. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-DbSEqjA5.js +1 -0
  66. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-DbSEqjA5.js +1 -0
  67. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-BCw7fCC2.js +1 -0
  68. package/dist/src/view/client/assets/{cynefin-OW5HDTMX-CeA8IKWo.js → cynefin-OW5HDTMX--lYN4N9m.js} +1 -1
  69. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-9sK5EV7v.js +62 -0
  70. package/dist/src/view/client/assets/{dagre-GXQ25YYZ-DLQwTa3H.js → dagre-GXQ25YYZ-CoMdEFCq.js} +2 -2
  71. package/dist/src/view/client/assets/{diagram-S7CK7UJ4-kbKdIyXG.js → diagram-S7CK7UJ4-D13dyWdd.js} +2 -2
  72. package/dist/src/view/client/assets/diagram-UQ7AKVKN-PdwLPPPA.js +41 -0
  73. package/dist/src/view/client/assets/{diagram-VSXAHHWV-EXJTU8qq.js → diagram-VSXAHHWV-CtWWa_A7.js} +2 -2
  74. package/dist/src/view/client/assets/diagram-VX7I27RA-BeCYcSU-.js +24 -0
  75. package/dist/src/view/client/assets/diagram-Z3DM3KII-DiWabOBM.js +24 -0
  76. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-CZQ3MD_K.js +1 -0
  77. package/dist/src/view/client/assets/{erDiagram-RLTQ6QDP-C925J5A3.js → erDiagram-RLTQ6QDP-B1AhXIrP.js} +2 -2
  78. package/dist/src/view/client/assets/{flowDiagram-HODETNUW-DzoCa-Nq.js → flowDiagram-HODETNUW-Bhr0JM6P.js} +1 -1
  79. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-CbP8iAkg.js +292 -0
  80. package/dist/src/view/client/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  81. package/dist/src/view/client/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  82. package/dist/src/view/client/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  83. package/dist/src/view/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  84. package/dist/src/view/client/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  85. package/dist/src/view/client/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  86. package/dist/src/view/client/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  87. package/dist/src/view/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  88. package/dist/src/view/client/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  89. package/dist/src/view/client/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  90. package/dist/src/view/client/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  91. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B9uuGdLF.js +106 -0
  92. package/dist/src/view/client/assets/index-DG0Ez15D.css +1 -0
  93. package/dist/src/view/client/assets/index-nBmbYfun.js +13 -0
  94. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-BrfwQ_Cq.js +2 -0
  95. package/dist/src/view/client/assets/{ishikawaDiagram-5VMMS53U-CW4mto2d.js → ishikawaDiagram-5VMMS53U-CHtJ36re.js} +2 -2
  96. package/dist/src/view/client/assets/{journeyDiagram-3NMN7TZE-8R4eacFW.js → journeyDiagram-3NMN7TZE-BChcgX1s.js} +2 -2
  97. package/dist/src/view/client/assets/{kanban-definition-UXKFOSKX-Cy9ioXEJ.js → kanban-definition-UXKFOSKX-BnwFsKiB.js} +6 -6
  98. package/dist/src/view/client/assets/{layout-C0MFuhth.js → layout-Dnd0pLdb.js} +1 -1
  99. package/dist/src/view/client/assets/{linear-Dv3N4dQR.js → linear-CFhW2ozn.js} +1 -1
  100. package/dist/src/view/client/assets/{maplibre-gl-DnqZgfUS.js → maplibre-gl-CDX6Zuow.js} +1 -1
  101. package/dist/src/view/client/assets/{mermaid.core-bds9A7e2.js → mermaid.core--OP6lXtW.js} +11 -11
  102. package/dist/src/view/client/assets/{mindmap-definition-YA3MSWOX-DjmGW355.js → mindmap-definition-YA3MSWOX-D51y2zbm.js} +2 -2
  103. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB--zyQJDvI.js +1 -0
  104. package/dist/src/view/client/assets/{pieDiagram-E7YTZNPT-CCH2gGR-.js → pieDiagram-E7YTZNPT-Cj8mvpJy.js} +2 -2
  105. package/dist/src/view/client/assets/{quadrantDiagram-AXDQQJYC-CjocPJFz.js → quadrantDiagram-AXDQQJYC-qvhJrnBf.js} +4 -4
  106. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-CtWvxpSo.js +1 -0
  107. package/dist/src/view/client/assets/{requirementDiagram-BXWQKSXE-Ct6AoXRr.js → requirementDiagram-BXWQKSXE-CBxen_KG.js} +2 -2
  108. package/dist/src/view/client/assets/{sankeyDiagram-P5KCCOFB-CIpgGN_w.js → sankeyDiagram-P5KCCOFB-B_fUkmfc.js} +2 -2
  109. package/dist/src/view/client/assets/{sequenceDiagram-WJ2MYXX4-DrmLFj6w.js → sequenceDiagram-WJ2MYXX4-Dclv4p68.js} +2 -2
  110. package/dist/src/view/client/assets/sizeCapture-INFHLROL-GfG0sMTZ.js +1 -0
  111. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-YQWPnzxU.js +1 -0
  112. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-gUj6Hzgl.js +1 -0
  113. package/dist/src/view/client/assets/swimlanes-42K2YHIH-5yiCveVS.js +1 -0
  114. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-DR7JhrxL.js +8 -0
  115. package/dist/src/view/client/assets/{timeline-definition-24CTP7MA-BExPu6X5.js → timeline-definition-24CTP7MA-BvAbB5Zs.js} +2 -2
  116. package/dist/src/view/client/assets/{vennDiagram-4TSXK5OY-PC1MU3si.js → vennDiagram-4TSXK5OY-guC-r60n.js} +2 -2
  117. package/dist/src/view/client/assets/{wardleyDiagram-VM6X3IG4-DNWqU3fa.js → wardleyDiagram-VM6X3IG4-C0yXcU2x.js} +2 -2
  118. package/dist/src/view/client/assets/{xychartDiagram-S5SC5T6Z-E1uA28_D.js → xychartDiagram-S5SC5T6Z-RPmkPNfK.js} +2 -2
  119. package/dist/src/view/client/index.html +2 -2
  120. package/dist/src/view/document-route.d.ts +2 -0
  121. package/dist/src/view/document-route.js +40 -7
  122. package/dist/src/view/graph.d.ts +2 -2
  123. package/dist/src/view/graph.js +7 -3
  124. package/dist/src/view/highlight.js +207 -203
  125. package/dist/src/view/markdown.js +19 -0
  126. package/dist/src/view/preindexed-search.d.ts +15 -0
  127. package/dist/src/view/preindexed-search.js +25 -0
  128. package/dist/src/view/protocol.d.ts +10 -1
  129. package/dist/src/view/references.js +6 -7
  130. package/dist/src/view/repository.js +10 -2
  131. package/dist/src/view/search-result.d.ts +4 -0
  132. package/dist/src/view/search-result.js +25 -0
  133. package/dist/src/view/search.d.ts +7 -2
  134. package/dist/src/view/search.js +23 -30
  135. package/dist/src/view/server-build.d.ts +21 -0
  136. package/dist/src/view/server-build.js +145 -0
  137. package/dist/src/view/server-deployment.d.ts +32 -0
  138. package/dist/src/view/server-deployment.js +134 -0
  139. package/dist/src/view/server-index-worker.d.ts +6 -0
  140. package/dist/src/view/server-index-worker.js +14 -0
  141. package/dist/src/view/server.js +25 -7
  142. package/dist/src/view/source-target.d.ts +2 -0
  143. package/dist/src/view/source-target.js +24 -0
  144. package/dist/src/view/static-build.d.ts +16 -1
  145. package/dist/src/view/static-build.js +187 -89
  146. package/dist/src/view/static-protocol.d.ts +7 -0
  147. package/dist/src/view/static-protocol.js +5 -0
  148. package/dist/src/view/store.js +17 -7
  149. package/dist/src/view/vercel-build.d.ts +24 -0
  150. package/dist/src/view/vercel-build.js +140 -0
  151. package/dist/src/view/vercel-server-build.d.ts +10 -0
  152. package/dist/src/view/vercel-server-build.js +61 -0
  153. package/dist/vendor/lat.md-server/index.d.ts +1 -1
  154. package/dist/vendor/lat.md-server/index.js +1 -1
  155. package/package.json +20 -5
  156. package/templates/AGENTS.md +1 -1
  157. package/templates/cursor-rules.md +1 -1
  158. package/templates/skill/SKILL.md +1 -1
  159. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-V7-T6qsl.js +0 -1
  160. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +0 -36
  161. package/dist/src/view/client/assets/channel-BwQbnWCN.js +0 -1
  162. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +0 -1
  163. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +0 -1
  164. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +0 -1
  165. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +0 -62
  166. package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +0 -41
  167. package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +0 -24
  168. package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +0 -24
  169. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +0 -1
  170. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +0 -292
  171. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +0 -106
  172. package/dist/src/view/client/assets/index-D0ajr_Ja.css +0 -1
  173. package/dist/src/view/client/assets/index-KZcnpXJH.js +0 -321
  174. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +0 -2
  175. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +0 -1
  176. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +0 -1
  177. package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +0 -1
  178. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +0 -1
  179. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +0 -1
  180. package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +0 -1
  181. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +0 -8
  182. /package/dist/src/view/client/assets/{logo-small-RR_KBOAT.svg → logo-RR_KBOAT.svg} +0 -0
@@ -0,0 +1,14 @@
1
+ import { runIndex } from '../cli/search.js';
2
+ process.once('message', async (request) => {
3
+ try {
4
+ await runIndex(request.latDir, undefined, request.project, {
5
+ cacheDir: request.cacheDir,
6
+ });
7
+ // Process exit releases native database handles before staging is renamed.
8
+ process.exit(0);
9
+ }
10
+ catch (error) {
11
+ console.error(error);
12
+ process.exit(1);
13
+ }
14
+ });
@@ -11,7 +11,7 @@ import { ViewExternalNotFoundError, ViewDocumentNotFoundError, ViewSourceNotFoun
11
11
  import { createViewSearch } from './search.js';
12
12
  import { createViewStore } from './store.js';
13
13
  import { rewriteClientAssetUrls } from './client-shell.js';
14
- import { documentResourcePath, documentUrl, rawDocumentPath, } from './document-route.js';
14
+ import { documentResourcePath, documentPath, rawDocumentPath, validateDocumentRoutes, } from './document-route.js';
15
15
  const DEFAULT_HOST = '127.0.0.1';
16
16
  export const DEFAULT_VIEW_PORT = 4242;
17
17
  const MAX_DOCUMENT_EDIT_BODY_BYTES = 16 * 1024 * 1024;
@@ -98,9 +98,13 @@ async function sendClientFile(res, path, headOnly, immutable = false) {
98
98
  send(res, 404, 'text/plain; charset=utf-8', 'Not found', headOnly);
99
99
  }
100
100
  }
101
- async function sendClientShell(res, path, headOnly) {
101
+ async function sendClientShell(res, path, headOnly, entry) {
102
102
  try {
103
- const body = rewriteClientAssetUrls(await readFile(path, 'utf8'), '/');
103
+ const html = rewriteClientAssetUrls(await readFile(path, 'utf8'), '/');
104
+ const config = `<meta name="lat-live-entry" content="${encodeURIComponent(entry)}">`;
105
+ const body = html.includes('</head>')
106
+ ? html.replace('</head>', `${config}</head>`)
107
+ : `${config}${html}`;
104
108
  res.setHeader('Cache-Control', 'no-cache');
105
109
  send(res, 200, 'text/html; charset=utf-8', body, headOnly);
106
110
  }
@@ -121,6 +125,13 @@ export async function createViewApp(ctx, options = {}) {
121
125
  watch: options.watch,
122
126
  externalCa: options.externalCa,
123
127
  });
128
+ try {
129
+ validateDocumentRoutes(store.getIndex().files);
130
+ }
131
+ catch (error) {
132
+ await store.close();
133
+ throw error;
134
+ }
124
135
  const search = options.search ??
125
136
  createViewSearch(ctx.latDir, undefined, () => store.markdownGeneration);
126
137
  const eventClients = new Set();
@@ -153,8 +164,15 @@ export async function createViewApp(ctx, options = {}) {
153
164
  send(res, 404, 'text/plain; charset=utf-8', 'No Markdown files found');
154
165
  return;
155
166
  }
156
- res.statusCode = 302;
157
- res.setHeader('Location', documentUrl(entry));
167
+ await sendClientShell(res, join(clientDir, 'index.html'), headOnly, entry);
168
+ return;
169
+ }
170
+ const index = store.getIndex();
171
+ const routePath = url.pathname.replace(/\/$/, '');
172
+ const markdownPath = documentPath(routePath);
173
+ if (markdownPath === index.entry) {
174
+ res.statusCode = 308;
175
+ res.setHeader('Location', `/${url.search}`);
158
176
  res.end();
159
177
  return;
160
178
  }
@@ -351,10 +369,10 @@ export async function createViewApp(ctx, options = {}) {
351
369
  }
352
370
  if (url.pathname === '/search' ||
353
371
  url.pathname === '/graph' ||
354
- url.pathname.startsWith('/docs/') ||
372
+ (markdownPath !== null && index.files.includes(markdownPath)) ||
355
373
  url.pathname.startsWith('/code/') ||
356
374
  url.pathname.startsWith('/external/')) {
357
- await sendClientShell(res, join(clientDir, 'index.html'), headOnly);
375
+ await sendClientShell(res, join(clientDir, 'index.html'), headOnly, index.entry);
358
376
  return;
359
377
  }
360
378
  send(res, 404, 'text/plain; charset=utf-8', 'Not found', headOnly);
@@ -1,3 +1,5 @@
1
+ /** Resolve ordinary code links relative to their Markdown file, not the vault root. */
2
+ export declare function rewriteLocalFileLink(value: string, sourcePath: string): string;
1
3
  export type ViewSourceTarget = {
2
4
  path: string;
3
5
  symbol: string;
@@ -1,6 +1,30 @@
1
1
  import { extname } from 'node:path';
2
2
  import { normalizeRepositoryPath } from '../repository-path.js';
3
3
  import { isSourceFileExtension } from '../source-formats.js';
4
+ import { rewriteDocumentLink } from './document-route.js';
5
+ /** Resolve ordinary code links relative to their Markdown file, not the vault root. */
6
+ export function rewriteLocalFileLink(value, sourcePath) {
7
+ if (value && !/^(?:[#/]|[a-z][a-z\d+.-]*:)/i.test(value)) {
8
+ try {
9
+ // Keep a project prefix so URL normalization cannot silently clamp an
10
+ // escaping relative path back into the project.
11
+ const base = sourcePath.split('/').map(encodeURIComponent).join('/');
12
+ const url = new URL(value, `http://lat.local/project/lat.md/${base}`);
13
+ if (url.origin === 'http://lat.local' &&
14
+ url.pathname.startsWith('/project/')) {
15
+ const path = decodeURIComponent(url.pathname.slice('/project/'.length));
16
+ if (normalizeRepositoryPath(path) &&
17
+ (!path.startsWith('lat.md/') || isSourceFileExtension(extname(path)))) {
18
+ return `/code/${path.split('/').map(encodeURIComponent).join('/')}${url.search}${url.hash}`;
19
+ }
20
+ }
21
+ }
22
+ catch {
23
+ // Malformed URLs retain the ordinary document/resource fallback.
24
+ }
25
+ }
26
+ return rewriteDocumentLink(value, sourcePath);
27
+ }
4
28
  /** Normalize a supported source wiki-link target for view indexes and routes. */
5
29
  export function viewSourceTarget(target) {
6
30
  const hash = target.indexOf('#');
@@ -1,9 +1,13 @@
1
+ import { rename } from 'node:fs/promises';
1
2
  import type { CmdContext } from '../context.js';
2
3
  export type StaticViewBuildOptions = {
3
4
  basePath?: string;
4
5
  clientDir?: string;
6
+ codeExcludePaths?: string[];
7
+ force?: boolean;
5
8
  logoText?: string;
6
9
  externalCa?: string | Buffer;
10
+ searchApi?: string;
7
11
  };
8
12
  export type StaticViewBuildResult = {
9
13
  documents: number;
@@ -12,6 +16,17 @@ export type StaticViewBuildResult = {
12
16
  };
13
17
  export declare function normalizeStaticViewBasePath(value: string): string;
14
18
  /** Convert a live view route to its physical static-directory URL. */
15
- export declare function staticViewUrl(value: string, basePath: string): string;
19
+ export declare function staticViewUrl(value: string, basePath: string, entryPath?: string): string;
20
+ /** Refuse build targets that exist or could replace the project itself. */
21
+ export declare function validateViewBuildOutput(outputDir: string, projectRoot: string, kind?: string, force?: boolean): Promise<void>;
22
+ type ViewBuildMoveDependencies = {
23
+ move: typeof rename;
24
+ wait: (milliseconds: number) => Promise<void>;
25
+ };
26
+ /** Commit a staged build after transient filesystem locks have cleared. */
27
+ export declare function moveViewBuildOutput(stagingDir: string, outputDir: string, dependencies?: ViewBuildMoveDependencies): Promise<void>;
28
+ /** Remove abandoned staging after transient filesystem locks have cleared. */
29
+ export declare function removeViewBuildStaging(stagingDir: string): Promise<void>;
16
30
  /** Build a serverless snapshot of the current read-only Lat UI. */
17
31
  export declare function buildStaticView(ctx: CmdContext, requestedOutput: string, options?: StaticViewBuildOptions): Promise<StaticViewBuildResult>;
32
+ export {};
@@ -5,21 +5,24 @@ import { fileURLToPath } from 'node:url';
5
5
  import { isDocumentPath } from '../document-formats.js';
6
6
  import { DEFAULT_VIEW_LOGO_TEXT } from './protocol.js';
7
7
  import { documentTreeUrls, rewriteDocumentTreeUrls } from './document-tree.js';
8
- import { viewStaticSourceKey, } from './static-protocol.js';
8
+ import { VIEW_STATIC_BOOTSTRAP_ID, viewStaticDocumentRequest, viewStaticSourceKey, } from './static-protocol.js';
9
9
  import { createViewStore } from './store.js';
10
10
  import { rewriteClientAssetUrls } from './client-shell.js';
11
- import { documentResourcePath, documentPath, documentUrl, rawDocumentPath, } from './document-route.js';
11
+ import { documentResourcePath, documentPath, documentUrl, rawDocumentPath, validateDocumentRoutes, } from './document-route.js';
12
12
  const defaultClientDir = fileURLToPath(new URL('./client/', import.meta.url));
13
+ const VIEW_BUILD_MOVE_MAX_RETRIES = 10;
14
+ const VIEW_BUILD_MOVE_RETRY_DELAY_MS = 150;
13
15
  function isInside(parent, child) {
14
16
  const rel = relative(parent, child);
15
17
  return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`));
16
18
  }
17
19
  export function normalizeStaticViewBasePath(value) {
18
- const parsed = new URL(value || '/', 'http://lat.local');
20
+ const path = value.startsWith('/') ? value : `/${value}`;
21
+ const parsed = new URL(path, 'http://lat.local');
19
22
  if (parsed.origin !== 'http://lat.local' ||
20
23
  parsed.search ||
21
24
  parsed.hash ||
22
- !value.startsWith('/')) {
25
+ value.includes('://')) {
23
26
  throw new Error('Static UI base path must be an absolute URL path');
24
27
  }
25
28
  return parsed.pathname.endsWith('/')
@@ -47,8 +50,8 @@ function decodeHtmlUrlAttribute(value) {
47
50
  return value.replace(/&(amp|#38|#x26);/gi, '&');
48
51
  }
49
52
  /** Convert a live view route to its physical static-directory URL. */
50
- export function staticViewUrl(value, basePath) {
51
- if (!value || value.startsWith('#'))
53
+ export function staticViewUrl(value, basePath, entryPath) {
54
+ if (!value || !value.startsWith('/') || value.startsWith('//'))
52
55
  return value;
53
56
  let url;
54
57
  try {
@@ -59,9 +62,13 @@ export function staticViewUrl(value, basePath) {
59
62
  }
60
63
  if (url.origin !== 'http://lat.local')
61
64
  return value;
62
- if (url.pathname.startsWith('/docs/')) {
65
+ const markdownPath = documentPath(url.pathname.replace(/\/$/, ''));
66
+ if (markdownPath || rawDocumentPath(url.pathname)) {
63
67
  const route = url.pathname.slice(1).replace(/\/+$/, '');
64
68
  const suffix = `${url.search}${url.hash}`;
69
+ if (entryPath && markdownPath === entryPath) {
70
+ return `${basePath}${suffix}`;
71
+ }
65
72
  return `${basePath}${route}${suffix}`;
66
73
  }
67
74
  if (url.pathname.startsWith('/resources/')) {
@@ -77,13 +84,16 @@ export function staticViewUrl(value, basePath) {
77
84
  if (url.pathname === '/graph') {
78
85
  return `${basePath}graph/${url.search}${url.hash}`;
79
86
  }
87
+ if (url.pathname === '/' || url.pathname === '/search') {
88
+ return `${basePath}${url.pathname.slice(1)}${url.search}${url.hash}`;
89
+ }
80
90
  return value;
81
91
  }
82
92
  function documentPathFromUrl(value) {
83
93
  return documentPath(value.pathname) ?? rawDocumentPath(value.pathname);
84
94
  }
85
- function rewriteHtmlLink(value, basePath, sourcePath, documentPaths) {
86
- const direct = staticViewUrl(value, basePath);
95
+ function rewriteHtmlLink(value, basePath, entryPath, sourcePath, documentPaths) {
96
+ const direct = staticViewUrl(value, basePath, entryPath);
87
97
  if (direct !== value || !sourcePath || value.startsWith('#'))
88
98
  return direct;
89
99
  let resolved;
@@ -103,64 +113,67 @@ function rewriteHtmlLink(value, basePath, sourcePath, documentPaths) {
103
113
  }
104
114
  const documentPath = documentPathFromUrl(resolved);
105
115
  if (resolved.pathname.startsWith('/external/')) {
106
- return staticViewUrl(`${resolved.pathname}${resolved.search}${resolved.hash}`, basePath);
116
+ return staticViewUrl(`${resolved.pathname}${resolved.search}${resolved.hash}`, basePath, entryPath);
107
117
  }
108
118
  if (resolved.origin !== 'http://lat.local' ||
109
119
  !documentPath ||
110
120
  !documentPaths.has(documentPath)) {
111
121
  return value;
112
122
  }
113
- return staticViewUrl(`${documentUrl(documentPath)}${resolved.search}${resolved.hash}`, basePath);
123
+ return staticViewUrl(`${documentUrl(documentPath)}${resolved.search}${resolved.hash}`, basePath, entryPath);
114
124
  }
115
- function rewriteDocumentLinks(tree, basePath, sourcePath, documentPaths) {
116
- return rewriteDocumentTreeUrls(tree, (value) => rewriteHtmlLink(value, basePath, sourcePath, documentPaths));
125
+ function rewriteDocumentLinks(tree, basePath, entryPath, sourcePath, documentPaths) {
126
+ return rewriteDocumentTreeUrls(tree, (value) => rewriteHtmlLink(value, basePath, entryPath, sourcePath, documentPaths));
117
127
  }
118
- function rewriteMarkdownReference(reference, basePath, sourcePath, documentPaths) {
128
+ function rewriteMarkdownReference(reference, basePath, entryPath, sourcePath, documentPaths) {
119
129
  return {
120
130
  ...reference,
121
- paragraphTree: rewriteDocumentLinks(reference.paragraphTree, basePath, sourcePath, documentPaths),
122
- url: staticViewUrl(reference.url, basePath),
131
+ paragraphTree: rewriteDocumentLinks(reference.paragraphTree, basePath, entryPath, sourcePath, documentPaths),
132
+ url: staticViewUrl(reference.url, basePath, entryPath),
123
133
  };
124
134
  }
125
- function rewriteBackReference(reference, basePath, sectionPaths, documentPaths) {
135
+ function rewriteBackReference(reference, basePath, entryPath, sectionPaths, documentPaths) {
126
136
  return reference.kind === 'markdown'
127
- ? rewriteMarkdownReference(reference, basePath, sectionPaths.get(reference.sectionId.split('#', 1)[0]) ?? null, documentPaths)
128
- : { ...reference, url: staticViewUrl(reference.url, basePath) };
137
+ ? rewriteMarkdownReference(reference, basePath, entryPath, sectionPaths.get(reference.sectionId.split('#', 1)[0]) ?? null, documentPaths)
138
+ : {
139
+ ...reference,
140
+ url: staticViewUrl(reference.url, basePath, entryPath),
141
+ };
129
142
  }
130
- function rewriteDocument(document, basePath, sectionPaths, documentPaths) {
143
+ function rewriteDocument(document, basePath, entryPath, sectionPaths, documentPaths) {
131
144
  return {
132
145
  ...document,
133
- tree: rewriteDocumentLinks(document.tree, basePath, document.path, documentPaths),
146
+ tree: rewriteDocumentLinks(document.tree, basePath, entryPath, document.path, documentPaths),
134
147
  gitTree: null,
135
148
  backReferences: document.backReferences.map((section) => ({
136
149
  ...section,
137
- references: section.references.map((reference) => rewriteBackReference(reference, basePath, sectionPaths, documentPaths)),
150
+ references: section.references.map((reference) => rewriteBackReference(reference, basePath, entryPath, sectionPaths, documentPaths)),
138
151
  })),
139
152
  };
140
153
  }
141
- function rewriteSourceReference(reference, basePath, sectionPaths, documentPaths) {
154
+ function rewriteSourceReference(reference, basePath, entryPath, sectionPaths, documentPaths) {
142
155
  const sectionPath = reference.sectionId.split('#', 1)[0];
143
156
  return {
144
157
  ...reference,
145
- paragraphTree: rewriteDocumentLinks(reference.paragraphTree, basePath, sectionPaths.get(sectionPath) ?? null, documentPaths),
146
- url: staticViewUrl(reference.url, basePath),
158
+ paragraphTree: rewriteDocumentLinks(reference.paragraphTree, basePath, entryPath, sectionPaths.get(sectionPath) ?? null, documentPaths),
159
+ url: staticViewUrl(reference.url, basePath, entryPath),
147
160
  };
148
161
  }
149
- function rewriteSource(source, basePath, sectionPaths, documentPaths) {
162
+ function rewriteSource(source, basePath, entryPath, sectionPaths, documentPaths) {
150
163
  return {
151
164
  ...source,
152
165
  context: source.context
153
- ? rewriteSourceReference(source.context, basePath, sectionPaths, documentPaths)
166
+ ? rewriteSourceReference(source.context, basePath, entryPath, sectionPaths, documentPaths)
154
167
  : null,
155
- otherReferences: source.otherReferences.map((reference) => rewriteSourceReference(reference, basePath, sectionPaths, documentPaths)),
168
+ otherReferences: source.otherReferences.map((reference) => rewriteSourceReference(reference, basePath, entryPath, sectionPaths, documentPaths)),
156
169
  };
157
170
  }
158
- function rewriteGraph(graph, basePath) {
171
+ function rewriteGraph(graph, basePath, entryPath) {
159
172
  return {
160
173
  ...graph,
161
174
  nodes: graph.nodes.map(({ gitStatus: _gitStatus, ...node }) => ({
162
175
  ...node,
163
- url: staticViewUrl(node.url, basePath),
176
+ url: staticViewUrl(node.url, basePath, entryPath),
164
177
  })),
165
178
  };
166
179
  }
@@ -307,13 +320,26 @@ function sourceRequestsFromSource(source, requests) {
307
320
  }
308
321
  }
309
322
  }
310
- function dataFile(kind, key) {
311
- const digest = createHash('sha256').update(key).digest('hex').slice(0, 20);
323
+ function serializeJson(value) {
324
+ return `${JSON.stringify(value)}\n`;
325
+ }
326
+ function dataFile(kind, content) {
327
+ const digest = createHash('sha256')
328
+ .update(content)
329
+ .digest('hex')
330
+ .slice(0, 20);
312
331
  return `data/${kind}/${digest}.json`;
313
332
  }
314
333
  async function writeJson(path, value) {
315
334
  await mkdir(dirname(path), { recursive: true });
316
- await writeFile(path, `${JSON.stringify(value)}\n`);
335
+ await writeFile(path, serializeJson(value));
336
+ }
337
+ async function writeDataJson(outputDir, kind, value) {
338
+ const content = serializeJson(value);
339
+ const path = dataFile(kind, content);
340
+ await mkdir(dirname(join(outputDir, path)), { recursive: true });
341
+ await writeFile(join(outputDir, path), content);
342
+ return path;
317
343
  }
318
344
  async function outputExists(path) {
319
345
  try {
@@ -326,16 +352,47 @@ async function outputExists(path) {
326
352
  throw error;
327
353
  }
328
354
  }
329
- async function validateOutput(outputDir, projectRoot) {
355
+ /** Refuse build targets that exist or could replace the project itself. */
356
+ export async function validateViewBuildOutput(outputDir, projectRoot, kind = 'Static', force = false) {
330
357
  if (outputDir === parse(outputDir).root ||
331
358
  outputDir === projectRoot ||
332
359
  isInside(outputDir, projectRoot)) {
333
- throw new Error('Static UI output must not contain the project root');
360
+ throw new Error(`${kind} UI output must not contain the project root`);
334
361
  }
335
- if (await outputExists(outputDir)) {
336
- throw new Error(`Static UI output already exists: ${outputDir}`);
362
+ if (!force && (await outputExists(outputDir))) {
363
+ throw new Error(`${kind} UI output already exists: ${outputDir}. Use --force to replace it.`);
337
364
  }
338
365
  }
366
+ const defaultViewBuildMoveDependencies = {
367
+ move: rename,
368
+ wait: (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)),
369
+ };
370
+ /** Commit a staged build after transient filesystem locks have cleared. */
371
+ export async function moveViewBuildOutput(stagingDir, outputDir, dependencies = defaultViewBuildMoveDependencies) {
372
+ for (let retries = 0;; retries += 1) {
373
+ try {
374
+ await dependencies.move(stagingDir, outputDir);
375
+ return;
376
+ }
377
+ catch (error) {
378
+ const code = error.code;
379
+ if (retries >= VIEW_BUILD_MOVE_MAX_RETRIES ||
380
+ (code !== 'EBUSY' && code !== 'EPERM' && code !== 'ENOTEMPTY')) {
381
+ throw error;
382
+ }
383
+ await dependencies.wait(VIEW_BUILD_MOVE_RETRY_DELAY_MS);
384
+ }
385
+ }
386
+ }
387
+ /** Remove abandoned staging after transient filesystem locks have cleared. */
388
+ export async function removeViewBuildStaging(stagingDir) {
389
+ await rm(stagingDir, {
390
+ recursive: true,
391
+ force: true,
392
+ maxRetries: VIEW_BUILD_MOVE_MAX_RETRIES,
393
+ retryDelay: VIEW_BUILD_MOVE_RETRY_DELAY_MS,
394
+ });
395
+ }
339
396
  function sectionDocumentPaths(documents) {
340
397
  const result = new Map();
341
398
  for (const path of documents.keys()) {
@@ -343,25 +400,44 @@ function sectionDocumentPaths(documents) {
343
400
  }
344
401
  return result;
345
402
  }
346
- function clientShell(html, basePath) {
403
+ function clientShell(html, basePath, entry, searchApi) {
347
404
  const assets = rewriteClientAssetUrls(html, basePath);
348
- const configValue = JSON.stringify({ basePath }).replaceAll('<', '\\u003c');
349
- const config = `<script>globalThis.__LAT_STATIC_VIEW__=${configValue}</script>`;
405
+ const configValue = encodeURIComponent(JSON.stringify({ basePath, entry, searchApi }));
406
+ const config = `<meta name="lat-static-view" content="${configValue}">`;
350
407
  return assets.includes('</head>')
351
408
  ? assets.replace('</head>', ` ${config}\n </head>`)
352
409
  : `${config}\n${assets}`;
353
410
  }
354
- function redirectShell(target) {
411
+ function bootstrapShell(shell, manifest, responses = {}) {
412
+ const bootstrap = { manifest, responses };
413
+ const value = JSON.stringify(bootstrap).replaceAll('<', '\\u003c');
414
+ const data = `<script id="${VIEW_STATIC_BOOTSTRAP_ID}" type="application/json">${value}</script>`;
415
+ return shell.includes('</head>')
416
+ ? shell.replace('</head>', ` ${data}\n </head>`)
417
+ : `${data}\n${shell}`;
418
+ }
419
+ const REDIRECT_SCRIPT_PATH = 'data/redirect.js';
420
+ const REDIRECT_SCRIPT = `const meta = document.querySelector('meta[name="lat-redirect"]');
421
+ if (meta) {
422
+ try {
423
+ window.location.replace(decodeURIComponent(meta.content) + window.location.search + window.location.hash);
424
+ } catch {}
425
+ }
426
+ `;
427
+ function redirectShell(target, basePath) {
355
428
  const escaped = target.replaceAll('&', '&amp;').replaceAll('"', '&quot;');
356
- const scriptTarget = JSON.stringify(target).replaceAll('<', '\\u003c');
429
+ const scriptUrl = `${basePath}${REDIRECT_SCRIPT_PATH}`
430
+ .replaceAll('&', '&amp;')
431
+ .replaceAll('"', '&quot;');
357
432
  return `<!doctype html>
358
433
  <html lang="en">
359
434
  <head>
360
435
  <meta charset="UTF-8" />
361
- <meta http-equiv="refresh" content="0;url=${escaped}" />
436
+ <meta name="lat-redirect" content="${encodeURIComponent(target)}" />
362
437
  <title>lat.md</title>
438
+ <script src="${scriptUrl}" defer></script>
363
439
  </head>
364
- <body><script>window.location.replace(${scriptTarget} + window.location.hash)</script></body>
440
+ <body><a href="${escaped}">Open lat.md</a></body>
365
441
  </html>
366
442
  `;
367
443
  }
@@ -376,11 +452,11 @@ export async function buildStaticView(ctx, requestedOutput, options = {}) {
376
452
  const basePath = normalizeStaticViewBasePath(options.basePath ?? '/');
377
453
  const clientDir = options.clientDir ?? defaultClientDir;
378
454
  const logoText = options.logoText ?? DEFAULT_VIEW_LOGO_TEXT;
379
- await validateOutput(outputDir, ctx.projectRoot);
455
+ await validateViewBuildOutput(outputDir, ctx.projectRoot, 'Static', options.force);
380
456
  await mkdir(dirname(outputDir), { recursive: true });
381
- const stagingDir = await mkdtemp(join(dirname(outputDir), '.lat-ui-build-'));
457
+ const stagingDir = await mkdtemp(join(dirname(outputDir), '.lat-static-staging-'));
382
458
  const store = await createViewStore(ctx.latDir, ctx.projectRoot, {
383
- codeExcludePaths: [outputDir],
459
+ codeExcludePaths: [outputDir, ...(options.codeExcludePaths ?? [])],
384
460
  git: false,
385
461
  watch: false,
386
462
  externalIgnoreLocal: true,
@@ -391,8 +467,9 @@ export async function buildStaticView(ctx, requestedOutput, options = {}) {
391
467
  const payloadDir = staticViewPayloadDir(stagingDir, basePath);
392
468
  await mkdir(payloadDir, { recursive: true });
393
469
  await cp(clientDir, payloadDir, { recursive: true });
394
- const shell = clientShell(clientHtml, basePath);
395
470
  const index = { ...store.getIndex(), git: null, logoText };
471
+ validateDocumentRoutes(index.files);
472
+ const shell = clientShell(clientHtml, basePath, index.entry, options.searchApi);
396
473
  const documents = new Map();
397
474
  const documentResources = new Set();
398
475
  const sourceRequests = new Map();
@@ -489,25 +566,28 @@ export async function buildStaticView(ctx, requestedOutput, options = {}) {
489
566
  }
490
567
  }
491
568
  }
569
+ const rewrittenGraph = rewriteGraph(graph, basePath, index.entry);
492
570
  const manifest = {
493
571
  version: 1,
494
572
  index,
495
- graph: 'data/graph.json',
573
+ graph: await writeDataJson(payloadDir, 'graphs', rewrittenGraph),
496
574
  documents: {},
497
575
  sources: {},
498
576
  externals: {},
499
577
  };
500
- await writeJson(join(payloadDir, manifest.graph), rewriteGraph(graph, basePath));
578
+ const documentBootstraps = new Map();
501
579
  for (const [path, document] of documents) {
502
- const dataPath = dataFile('documents', path);
503
- manifest.documents[path] = dataPath;
504
- await writeJson(join(payloadDir, dataPath), rewriteDocument(document, basePath, sectionPaths, documentPaths));
580
+ const rewritten = rewriteDocument(document, basePath, index.entry, sectionPaths, documentPaths);
581
+ manifest.documents[path] = await writeDataJson(payloadDir, 'documents', rewritten);
505
582
  const source = await store.getDocumentSource(path);
506
- const rawPath = join(payloadDir, 'docs', ...path.split('/'));
583
+ const rawPath = join(payloadDir, ...path.split('/'));
507
584
  await mkdir(dirname(rawPath), { recursive: true });
508
585
  await writeFile(rawPath, source.content);
509
- const route = `docs/${path.slice(0, -'.md'.length)}`;
510
- await writeRouteShell(payloadDir, route, shell);
586
+ const route = path.slice(0, -'.md'.length);
587
+ documentBootstraps.set(route, {
588
+ request: viewStaticDocumentRequest(path),
589
+ document: rewritten,
590
+ });
511
591
  }
512
592
  for (const path of [...documentResources].sort()) {
513
593
  let content;
@@ -523,78 +603,96 @@ export async function buildStaticView(ctx, requestedOutput, options = {}) {
523
603
  }
524
604
  const sourceFiles = new Map();
525
605
  for (const [key, source] of sources) {
526
- const rewritten = rewriteSource(source, basePath, sectionPaths, documentPaths);
606
+ const rewritten = rewriteSource(source, basePath, index.entry, sectionPaths, documentPaths);
527
607
  const { path, content, highlightedLines, ...view } = rewritten;
528
608
  let fileDataPath = sourceFiles.get(path);
529
609
  if (!fileDataPath) {
530
- fileDataPath = dataFile('source-files', path);
531
- sourceFiles.set(path, fileDataPath);
532
- await writeJson(join(payloadDir, fileDataPath), {
610
+ fileDataPath = await writeDataJson(payloadDir, 'source-files', {
533
611
  path,
534
612
  content,
535
613
  highlightedLines,
536
614
  });
615
+ sourceFiles.set(path, fileDataPath);
537
616
  }
538
- const viewDataPath = dataFile('source-views', key);
617
+ const viewDataPath = await writeDataJson(payloadDir, 'source-views', view);
539
618
  manifest.sources[key] = { file: fileDataPath, view: viewDataPath };
540
- await writeJson(join(payloadDir, viewDataPath), view);
541
- }
542
- for (const path of sourceFiles.keys()) {
543
- const route = `code/${path}`;
544
- await writeRouteShell(payloadDir, route, shell);
545
619
  }
546
620
  const externalSourceFiles = new Map();
547
621
  const externalRoutes = new Set();
548
622
  for (const [target, external] of externals) {
549
623
  if (external.kind === 'markdown') {
550
- const dataPath = dataFile('external-documents', target);
551
- manifest.externals[target] = { kind: 'markdown', document: dataPath };
552
- await writeJson(join(payloadDir, dataPath), {
624
+ const dataPath = await writeDataJson(payloadDir, 'external-documents', {
553
625
  ...external,
554
- document: rewriteDocument(external.document, basePath, sectionPaths, documentPaths),
626
+ document: rewriteDocument(external.document, basePath, index.entry, sectionPaths, documentPaths),
555
627
  });
628
+ manifest.externals[target] = { kind: 'markdown', document: dataPath };
556
629
  const colon = external.document.path.indexOf(':');
557
630
  externalRoutes.add(`external/${external.document.path.slice(0, colon)}/${external.document.path.slice(colon + 1)}`);
558
631
  continue;
559
632
  }
560
- const rewritten = rewriteSource(external.source, basePath, sectionPaths, documentPaths);
633
+ const rewritten = rewriteSource(external.source, basePath, index.entry, sectionPaths, documentPaths);
561
634
  const { path, content, highlightedLines, ...sourceView } = rewritten;
562
635
  let fileDataPath = externalSourceFiles.get(path);
563
636
  if (!fileDataPath) {
564
- fileDataPath = dataFile('external-source-files', path);
565
- externalSourceFiles.set(path, fileDataPath);
566
- await writeJson(join(payloadDir, fileDataPath), {
637
+ fileDataPath = await writeDataJson(payloadDir, 'external-source-files', {
567
638
  path,
568
639
  content,
569
640
  highlightedLines,
570
641
  });
642
+ externalSourceFiles.set(path, fileDataPath);
571
643
  }
572
- const viewDataPath = dataFile('external-source-views', target);
573
- manifest.externals[target] = {
574
- kind: 'source',
575
- file: fileDataPath,
576
- view: viewDataPath,
577
- };
578
644
  const view = {
579
645
  kind: 'source',
580
646
  target: external.target,
581
647
  source: sourceView,
582
648
  };
583
- await writeJson(join(payloadDir, viewDataPath), view);
649
+ const viewDataPath = await writeDataJson(payloadDir, 'external-source-views', view);
650
+ manifest.externals[target] = {
651
+ kind: 'source',
652
+ file: fileDataPath,
653
+ view: viewDataPath,
654
+ };
584
655
  const colon = path.indexOf(':');
585
656
  externalRoutes.add(`external/${path.slice(0, colon)}/${path.slice(colon + 1)}`);
586
657
  }
658
+ await writeJson(join(payloadDir, 'data/manifest.json'), manifest);
659
+ const entryRoute = index.entry.slice(0, -'.md'.length);
660
+ const entryBootstrap = documentBootstraps.get(entryRoute);
661
+ if (!entryBootstrap) {
662
+ throw new Error(`Static UI entry document is missing: ${index.entry}`);
663
+ }
664
+ for (const [route, initial] of documentBootstraps) {
665
+ if (route === entryRoute)
666
+ continue;
667
+ await writeRouteShell(payloadDir, route, bootstrapShell(shell, manifest, {
668
+ [initial.request]: initial.document,
669
+ }));
670
+ }
671
+ const genericShell = bootstrapShell(shell, manifest);
672
+ for (const path of sourceFiles.keys()) {
673
+ await writeRouteShell(payloadDir, `code/${path}`, genericShell);
674
+ }
587
675
  for (const route of externalRoutes) {
588
- await writeRouteShell(payloadDir, route, shell);
676
+ await writeRouteShell(payloadDir, route, genericShell);
589
677
  }
590
- await writeRouteShell(payloadDir, 'graph', shell);
591
- await writeJson(join(payloadDir, 'data/manifest.json'), manifest);
592
- const entryRedirect = redirectShell(staticViewUrl(documentUrl(index.entry), basePath));
593
- await writeFile(join(payloadDir, 'index.html'), entryRedirect);
678
+ await writeRouteShell(payloadDir, 'graph', bootstrapShell(shell, manifest, { '/api/graph': rewrittenGraph }));
679
+ if (options.searchApi) {
680
+ await writeRouteShell(payloadDir, 'search', genericShell);
681
+ }
682
+ const redirectScriptPath = join(payloadDir, REDIRECT_SCRIPT_PATH);
683
+ await mkdir(dirname(redirectScriptPath), { recursive: true });
684
+ await writeFile(redirectScriptPath, REDIRECT_SCRIPT);
685
+ await writeFile(join(payloadDir, 'index.html'), bootstrapShell(shell, manifest, {
686
+ [entryBootstrap.request]: entryBootstrap.document,
687
+ }));
688
+ await writeRouteShell(payloadDir, entryRoute, redirectShell(basePath, basePath));
594
689
  if (payloadDir !== stagingDir) {
595
- await writeFile(join(stagingDir, 'index.html'), entryRedirect);
690
+ await writeFile(join(stagingDir, 'index.html'), redirectShell(basePath, basePath));
691
+ }
692
+ if (options.force) {
693
+ await rm(outputDir, { recursive: true, force: true });
596
694
  }
597
- await rename(stagingDir, outputDir);
695
+ await moveViewBuildOutput(stagingDir, outputDir);
598
696
  return {
599
697
  documents: documents.size,
600
698
  outputDir,
@@ -602,7 +700,7 @@ export async function buildStaticView(ctx, requestedOutput, options = {}) {
602
700
  };
603
701
  }
604
702
  catch (error) {
605
- await rm(stagingDir, { recursive: true, force: true });
703
+ await removeViewBuildStaging(stagingDir);
606
704
  throw error;
607
705
  }
608
706
  finally {