redraft-local 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +143 -0
  2. package/bin/redraft.js +5 -0
  3. package/bin/redraft.mjs +5 -0
  4. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/dist/assets/apl-B4CMkyY2.js +1 -0
  64. package/dist/assets/arc-BgLKgAzo.js +1 -0
  65. package/dist/assets/architectureDiagram-3BPJPVTR-DUjZFSch.js +36 -0
  66. package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  67. package/dist/assets/asn1-EdZsLKOL.js +1 -0
  68. package/dist/assets/asterisk-B-8jnY81.js +1 -0
  69. package/dist/assets/blockDiagram-GPEHLZMM-B5lXfnYt.js +132 -0
  70. package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  71. package/dist/assets/c4Diagram-AAUBKEIU-BvClm0qm.js +10 -0
  72. package/dist/assets/channel-7EQqZR3B.js +1 -0
  73. package/dist/assets/chunk-2J33WTMH-CEHXzvmq.js +1 -0
  74. package/dist/assets/chunk-4BX2VUAB-DSC0P7-r.js +1 -0
  75. package/dist/assets/chunk-55IACEB6--6omHD3y.js +1 -0
  76. package/dist/assets/chunk-727SXJPM-_oYajenN.js +206 -0
  77. package/dist/assets/chunk-AQP2D5EJ-CtBEk7Wt.js +231 -0
  78. package/dist/assets/chunk-FMBD7UC4-CUG06_Py.js +15 -0
  79. package/dist/assets/chunk-ND2GUHAM-ByZ-QCVp.js +1 -0
  80. package/dist/assets/chunk-QZHKN3VN-DyKKbGn5.js +1 -0
  81. package/dist/assets/classDiagram-4FO5ZUOK-E-KsPQ0n.js +1 -0
  82. package/dist/assets/classDiagram-v2-Q7XG4LA2-E-KsPQ0n.js +1 -0
  83. package/dist/assets/clike-B9uivgTg.js +1 -0
  84. package/dist/assets/clojure-BMjYHr_A.js +1 -0
  85. package/dist/assets/cmake-BQqOBYOt.js +1 -0
  86. package/dist/assets/cobol-CWcv1MsR.js +1 -0
  87. package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  88. package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  89. package/dist/assets/cose-bilkent-S5V4N54A-DO1d36Xa.js +1 -0
  90. package/dist/assets/crystal-SjHAIU92.js +1 -0
  91. package/dist/assets/css-BnMrqG3P.js +1 -0
  92. package/dist/assets/cypher-C_CwsFkJ.js +1 -0
  93. package/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  94. package/dist/assets/d-pRatUO7H.js +1 -0
  95. package/dist/assets/dagre-BM42HDAG-CtijaGW4.js +4 -0
  96. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  97. package/dist/assets/diagram-2AECGRRQ-CLuIxwuH.js +43 -0
  98. package/dist/assets/diagram-5GNKFQAL-C1SsZ_SX.js +10 -0
  99. package/dist/assets/diagram-KO2AKTUF-0Q55p83r.js +3 -0
  100. package/dist/assets/diagram-LMA3HP47-Cxe6Lxmi.js +24 -0
  101. package/dist/assets/diagram-OG6HWLK6-DdRxQD4N.js +24 -0
  102. package/dist/assets/diff-DbItnlRl.js +1 -0
  103. package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  104. package/dist/assets/dtd-DF_7sFjM.js +1 -0
  105. package/dist/assets/dylan-DwRh75JA.js +1 -0
  106. package/dist/assets/ebnf-CDyGwa7X.js +1 -0
  107. package/dist/assets/ecl-Cabwm37j.js +1 -0
  108. package/dist/assets/eiffel-CnydiIhH.js +1 -0
  109. package/dist/assets/elm-vLlmbW-K.js +1 -0
  110. package/dist/assets/erDiagram-TEJ5UH35-Br4OpnN3.js +85 -0
  111. package/dist/assets/erlang-BNw1qcRV.js +1 -0
  112. package/dist/assets/factor-kuTfRLto.js +1 -0
  113. package/dist/assets/fcl-Kvtd6kyn.js +1 -0
  114. package/dist/assets/flowDiagram-I6XJVG4X-BiWm4cMT.js +162 -0
  115. package/dist/assets/forth-Ffai-XNe.js +1 -0
  116. package/dist/assets/fortran-DYz_wnZ1.js +1 -0
  117. package/dist/assets/ganttDiagram-6RSMTGT7-Brtf61gk.js +292 -0
  118. package/dist/assets/gas-Bneqetm1.js +1 -0
  119. package/dist/assets/gherkin-heZmZLOM.js +1 -0
  120. package/dist/assets/gitGraphDiagram-PVQCEYII-B_3TyVGw.js +106 -0
  121. package/dist/assets/graph-DtBDQa9e.js +1 -0
  122. package/dist/assets/groovy-D9Dt4D0W.js +1 -0
  123. package/dist/assets/haskell-Cw1EW3IL.js +1 -0
  124. package/dist/assets/haxe-H-WmDvRZ.js +1 -0
  125. package/dist/assets/http-DBlCnlav.js +1 -0
  126. package/dist/assets/idl-BEugSyMb.js +1 -0
  127. package/dist/assets/index-2ky3dpil.js +1 -0
  128. package/dist/assets/index-4KK6bEqw.js +1 -0
  129. package/dist/assets/index-5ZUGo5ij.js +1 -0
  130. package/dist/assets/index-7FKqtxp0.js +13 -0
  131. package/dist/assets/index-B-96Htfw.js +6 -0
  132. package/dist/assets/index-BADfuR--.js +1 -0
  133. package/dist/assets/index-BD8MFdSo.js +1 -0
  134. package/dist/assets/index-BR9BrISk.js +1 -0
  135. package/dist/assets/index-BfnTu5JW.js +1 -0
  136. package/dist/assets/index-BgooUBR3.js +1 -0
  137. package/dist/assets/index-BkakCAI-.js +1 -0
  138. package/dist/assets/index-CBjB7xM7.js +1 -0
  139. package/dist/assets/index-CZY06TRX.js +1381 -0
  140. package/dist/assets/index-CiPTn2KW.js +2 -0
  141. package/dist/assets/index-CymWWySw.js +1 -0
  142. package/dist/assets/index-DKi-lLAL.js +1 -0
  143. package/dist/assets/index-Da6DydBE.js +1 -0
  144. package/dist/assets/index-Df2jy_bv.js +7 -0
  145. package/dist/assets/index-Ds7udwcB.js +3 -0
  146. package/dist/assets/index-MfTARqwV.css +1 -0
  147. package/dist/assets/index-OT6ZQvoz.js +1 -0
  148. package/dist/assets/index-Ot1nLBuC.js +1 -0
  149. package/dist/assets/index-PN8t7sGq.js +1 -0
  150. package/dist/assets/index-qn0Rsj7y.js +1 -0
  151. package/dist/assets/infoDiagram-5YYISTIA-DXpolhhW.js +2 -0
  152. package/dist/assets/init-Gi6I4Gst.js +1 -0
  153. package/dist/assets/ishikawaDiagram-YF4QCWOH-CaMQv6HD.js +70 -0
  154. package/dist/assets/javascript-iXu5QeM3.js +1 -0
  155. package/dist/assets/journeyDiagram-JHISSGLW-grFPpRCE.js +139 -0
  156. package/dist/assets/julia-DuME0IfC.js +1 -0
  157. package/dist/assets/kanban-definition-UN3LZRKU-BXTf-nUc.js +89 -0
  158. package/dist/assets/katex-HP8lGamR.js +257 -0
  159. package/dist/assets/layout-BfOOH1Wh.js +1 -0
  160. package/dist/assets/linear-DzLvWW3N.js +1 -0
  161. package/dist/assets/livescript-BwQOo05w.js +1 -0
  162. package/dist/assets/lua-VAEuO923.js +1 -0
  163. package/dist/assets/mathematica-DTrFuWx2.js +1 -0
  164. package/dist/assets/mbox-CNhZ1qSd.js +1 -0
  165. package/dist/assets/mindmap-definition-RKZ34NQL-DnhBS8H1.js +96 -0
  166. package/dist/assets/mirc-CjQqDB4T.js +1 -0
  167. package/dist/assets/mllike-CXdrOF99.js +1 -0
  168. package/dist/assets/modelica-Dc1JOy9r.js +1 -0
  169. package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  170. package/dist/assets/mumps-BT43cFF4.js +1 -0
  171. package/dist/assets/nginx-DdIZxoE0.js +1 -0
  172. package/dist/assets/nsis-LdVXkNf5.js +1 -0
  173. package/dist/assets/ntriples-BfvgReVJ.js +1 -0
  174. package/dist/assets/octave-Ck1zUtKM.js +1 -0
  175. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  176. package/dist/assets/oz-BzwKVEFT.js +1 -0
  177. package/dist/assets/pascal--L3eBynH.js +1 -0
  178. package/dist/assets/perl-CdXCOZ3F.js +1 -0
  179. package/dist/assets/pieDiagram-4H26LBE5-DjWB8MBQ.js +30 -0
  180. package/dist/assets/pig-CevX1Tat.js +1 -0
  181. package/dist/assets/powershell-CFHJl5sT.js +1 -0
  182. package/dist/assets/properties-C78fOPTZ.js +1 -0
  183. package/dist/assets/protobuf-ChK-085T.js +1 -0
  184. package/dist/assets/pug-DeIclll2.js +1 -0
  185. package/dist/assets/puppet-DMA9R1ak.js +1 -0
  186. package/dist/assets/python-BuPzkPfP.js +1 -0
  187. package/dist/assets/q-pXgVlZs6.js +1 -0
  188. package/dist/assets/quadrantDiagram-W4KKPZXB-AbXkFaom.js +7 -0
  189. package/dist/assets/r-B6wPVr8A.js +1 -0
  190. package/dist/assets/requirementDiagram-4Y6WPE33-Dmvesc_X.js +84 -0
  191. package/dist/assets/rpm-CTu-6PCP.js +1 -0
  192. package/dist/assets/ruby-B2Rjki9n.js +1 -0
  193. package/dist/assets/sankeyDiagram-5OEKKPKP-CTvY1DJo.js +40 -0
  194. package/dist/assets/sas-B4kiWyti.js +1 -0
  195. package/dist/assets/scheme-C41bIUwD.js +1 -0
  196. package/dist/assets/sequenceDiagram-3UESZ5HK-ejgTSjfJ.js +162 -0
  197. package/dist/assets/shell-CjFT_Tl9.js +1 -0
  198. package/dist/assets/sieve-C3Gn_uJK.js +1 -0
  199. package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  200. package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  201. package/dist/assets/solr-DehyRSwq.js +1 -0
  202. package/dist/assets/sparql-DkYu6x3z.js +1 -0
  203. package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  204. package/dist/assets/sql-D0XecflT.js +1 -0
  205. package/dist/assets/stateDiagram-AJRCARHV-Drx8IQcv.js +1 -0
  206. package/dist/assets/stateDiagram-v2-BHNVJYJU-BWYCpSzu.js +1 -0
  207. package/dist/assets/stex-C3f8Ysf7.js +1 -0
  208. package/dist/assets/stylus-B533Al4x.js +1 -0
  209. package/dist/assets/swift-BzpIVaGY.js +1 -0
  210. package/dist/assets/tcl-DVfN8rqt.js +1 -0
  211. package/dist/assets/textile-CnDTJFAw.js +1 -0
  212. package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  213. package/dist/assets/tiki-DGYXhP31.js +1 -0
  214. package/dist/assets/timeline-definition-PNZ67QCA-DXznZ8i_.js +120 -0
  215. package/dist/assets/toml-Bm5Em-hy.js +1 -0
  216. package/dist/assets/troff-wAsdV37c.js +1 -0
  217. package/dist/assets/ttcn-CfJYG6tj.js +1 -0
  218. package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  219. package/dist/assets/turtle-B1tBg_DP.js +1 -0
  220. package/dist/assets/vb-CmGdzxic.js +1 -0
  221. package/dist/assets/vbscript-BuJXcnF6.js +1 -0
  222. package/dist/assets/velocity-D8B20fx6.js +1 -0
  223. package/dist/assets/vennDiagram-CIIHVFJN-CLIExu7h.js +34 -0
  224. package/dist/assets/verilog-C6RDOZhf.js +1 -0
  225. package/dist/assets/vhdl-lSbBsy5d.js +1 -0
  226. package/dist/assets/wardley-L42UT6IY-CJGVDUi0.js +161 -0
  227. package/dist/assets/wardleyDiagram-YWT4CUSO-DB0Z_Z7a.js +78 -0
  228. package/dist/assets/webidl-ZXfAyPTL.js +1 -0
  229. package/dist/assets/xquery-DzFWVndE.js +1 -0
  230. package/dist/assets/xychartDiagram-2RQKCTM6-CLdNuotp.js +7 -0
  231. package/dist/assets/yacas-BJ4BC0dw.js +1 -0
  232. package/dist/assets/z80-Hz9HOZM7.js +1 -0
  233. package/dist/favicon.png +0 -0
  234. package/dist/index.html +14 -0
  235. package/dist/logo.png +0 -0
  236. package/dist-server/cli.mjs +725 -0
  237. package/package.json +79 -0
@@ -0,0 +1,725 @@
1
+ // server/cli.ts
2
+ import { exec } from "node:child_process";
3
+ import { stat as stat4 } from "node:fs/promises";
4
+ import { resolve as resolve5 } from "node:path";
5
+ import { Command } from "commander";
6
+
7
+ // server/fs/watcher.ts
8
+ import chokidar from "chokidar";
9
+ import { relative as relative2, resolve as resolve2 } from "node:path";
10
+
11
+ // server/fs/operations.ts
12
+ import { createHash } from "node:crypto";
13
+ import {
14
+ mkdir,
15
+ readFile as readFileFromDisk,
16
+ readdir,
17
+ stat,
18
+ unlink,
19
+ writeFile as writeFileToDisk
20
+ } from "node:fs/promises";
21
+ import { dirname, relative, resolve } from "node:path";
22
+
23
+ // server/types.ts
24
+ var FileOperationError = class extends Error {
25
+ status;
26
+ constructor(status, message) {
27
+ super(message);
28
+ this.name = "FileOperationError";
29
+ this.status = status;
30
+ }
31
+ };
32
+
33
+ // server/fs/operations.ts
34
+ function resolvePath(basePath, relativePath) {
35
+ const resolvedBase = resolve(basePath);
36
+ const resolvedPath = resolve(resolvedBase, relativePath);
37
+ const relativePathToBase = relative(resolvedBase, resolvedPath);
38
+ if (relativePathToBase.startsWith("..")) {
39
+ throw new FileOperationError(400, "Path escapes the proposals root.");
40
+ }
41
+ return resolvedPath;
42
+ }
43
+ function isTrackedProposalFile(path) {
44
+ return path.endsWith(".md") || path.endsWith(".comments.json");
45
+ }
46
+ async function walkFiles(basePath, currentPath = "") {
47
+ const directoryPath = resolvePath(basePath, currentPath || ".");
48
+ const entries = await readdir(directoryPath, { withFileTypes: true });
49
+ const files = [];
50
+ for (const entry of entries) {
51
+ const nextRelativePath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
52
+ if (entry.isDirectory()) {
53
+ files.push(...await walkFiles(basePath, nextRelativePath));
54
+ continue;
55
+ }
56
+ if (!entry.isFile() || !isTrackedProposalFile(nextRelativePath)) {
57
+ continue;
58
+ }
59
+ files.push({ path: nextRelativePath, type: "blob" });
60
+ }
61
+ return files;
62
+ }
63
+ function computeBlobSha(content) {
64
+ return createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
65
+ }
66
+ async function readFile(basePath, relativePath) {
67
+ const filePath = resolvePath(basePath, relativePath);
68
+ let content;
69
+ try {
70
+ content = await readFileFromDisk(filePath);
71
+ } catch (error) {
72
+ if (error.code === "ENOENT") {
73
+ throw new FileOperationError(404, `File not found: ${relativePath}`);
74
+ }
75
+ throw error;
76
+ }
77
+ return { content, sha: computeBlobSha(content) };
78
+ }
79
+ async function writeFile(basePath, relativePath, content, expectedSha) {
80
+ const filePath = resolvePath(basePath, relativePath);
81
+ const current = await readFile(basePath, relativePath);
82
+ if (expectedSha && expectedSha !== current.sha) {
83
+ throw new FileOperationError(409, "File SHA conflict.");
84
+ }
85
+ await writeFileToDisk(filePath, content);
86
+ return { sha: computeBlobSha(content) };
87
+ }
88
+ async function createFile(basePath, relativePath, content) {
89
+ const filePath = resolvePath(basePath, relativePath);
90
+ try {
91
+ await stat(filePath);
92
+ throw new FileOperationError(422, `File already exists: ${relativePath}`);
93
+ } catch (error) {
94
+ if (error instanceof FileOperationError) {
95
+ throw error;
96
+ }
97
+ if (error.code !== "ENOENT") {
98
+ throw error;
99
+ }
100
+ }
101
+ await mkdir(dirname(filePath), { recursive: true });
102
+ await writeFileToDisk(filePath, content);
103
+ return { sha: computeBlobSha(content) };
104
+ }
105
+ async function deleteFile(basePath, relativePath, expectedSha) {
106
+ const current = await readFile(basePath, relativePath);
107
+ if (expectedSha !== current.sha) {
108
+ throw new FileOperationError(409, "File SHA conflict.");
109
+ }
110
+ const filePath = resolvePath(basePath, relativePath);
111
+ await unlink(filePath);
112
+ }
113
+ async function listFiles(basePath) {
114
+ const files = await walkFiles(basePath);
115
+ return files.sort((left, right) => left.path.localeCompare(right.path));
116
+ }
117
+
118
+ // server/fs/watcher.ts
119
+ function isTrackedProposalFile2(path) {
120
+ return path.endsWith(".md") || path.endsWith(".comments.json");
121
+ }
122
+ function toRelativePath(basePath, filePath) {
123
+ const resolvedBase = resolve2(basePath);
124
+ const resolvedFile = resolve2(filePath);
125
+ const relativePath = relative2(resolvedBase, resolvedFile);
126
+ if (relativePath === "" || relativePath.startsWith("..")) {
127
+ return null;
128
+ }
129
+ return relativePath;
130
+ }
131
+ function startWatcher(basePath, onEvent) {
132
+ const watcher = chokidar.watch(basePath, {
133
+ ignoreInitial: true,
134
+ persistent: true
135
+ });
136
+ const pendingEvents = /* @__PURE__ */ new Map();
137
+ let flushTimer;
138
+ const queueEvent = (type, filePath) => {
139
+ const relativePath = toRelativePath(basePath, filePath);
140
+ if (!relativePath || !isTrackedProposalFile2(relativePath)) {
141
+ return;
142
+ }
143
+ const previousType = pendingEvents.get(relativePath);
144
+ if (previousType === "file:created" && type === "file:changed") {
145
+ pendingEvents.set(relativePath, previousType);
146
+ } else {
147
+ pendingEvents.set(relativePath, type);
148
+ }
149
+ clearTimeout(flushTimer);
150
+ flushTimer = setTimeout(async () => {
151
+ flushTimer = void 0;
152
+ const currentBatch = Array.from(pendingEvents.entries());
153
+ pendingEvents.clear();
154
+ for (const [path, eventType] of currentBatch) {
155
+ if (eventType === "file:deleted") {
156
+ onEvent({ type: eventType, path });
157
+ continue;
158
+ }
159
+ try {
160
+ const file = await readFile(basePath, path);
161
+ onEvent({ type: eventType, path, sha: file.sha });
162
+ } catch {
163
+ }
164
+ }
165
+ }, 100);
166
+ };
167
+ watcher.on("add", (filePath) => queueEvent("file:created", filePath));
168
+ watcher.on("change", (filePath) => queueEvent("file:changed", filePath));
169
+ watcher.on("unlink", (filePath) => queueEvent("file:deleted", filePath));
170
+ return () => {
171
+ if (flushTimer) {
172
+ clearTimeout(flushTimer);
173
+ flushTimer = void 0;
174
+ }
175
+ void watcher.close();
176
+ };
177
+ }
178
+
179
+ // server/app.ts
180
+ import {
181
+ createServer
182
+ } from "node:http";
183
+ import { access, readFile as readFile2, stat as stat3 } from "node:fs/promises";
184
+ import { extname, resolve as resolve4 } from "node:path";
185
+ import { Readable } from "node:stream";
186
+ import { fileURLToPath } from "node:url";
187
+
188
+ // server/routes/index.ts
189
+ import { Hono } from "hono";
190
+
191
+ // server/routes/commits.ts
192
+ import { stat as stat2 } from "node:fs/promises";
193
+ import { resolve as resolve3 } from "node:path";
194
+ function registerCommitsRoute(app, helpers) {
195
+ app.get("/api/github/repos/:owner/:repo/commits", async (c) => {
196
+ const apiPath = c.req.query("path");
197
+ if (!apiPath) {
198
+ return helpers.json([]);
199
+ }
200
+ const localPath = helpers.toLocalPath(apiPath);
201
+ const fileStats = await stat2(resolve3(helpers.basePath, localPath));
202
+ return helpers.json([
203
+ {
204
+ commit: {
205
+ message: "Local file update",
206
+ author: { date: fileStats.mtime.toISOString() }
207
+ },
208
+ author: {
209
+ login: "local-user",
210
+ avatar_url: ""
211
+ }
212
+ }
213
+ ]);
214
+ });
215
+ }
216
+
217
+ // server/routes/contents.ts
218
+ function decodeContent(body) {
219
+ if (!body.content) {
220
+ throw new FileOperationError(
221
+ 400,
222
+ "Request body must include base64 content."
223
+ );
224
+ }
225
+ return Buffer.from(body.content, "base64");
226
+ }
227
+ function requireApiPath(path) {
228
+ if (!path) {
229
+ throw new FileOperationError(400, "Request path is required.");
230
+ }
231
+ return path;
232
+ }
233
+ function registerContentsRoute(app, helpers) {
234
+ app.get("/api/github/repos/:owner/:repo/contents/:path{.+}", async (c) => {
235
+ const localPath = helpers.toLocalPath(requireApiPath(c.req.param("path")));
236
+ const file = await readFile(helpers.basePath, localPath);
237
+ return helpers.json({
238
+ type: "file",
239
+ sha: file.sha,
240
+ content: file.content.toString("base64")
241
+ });
242
+ });
243
+ app.put("/api/github/repos/:owner/:repo/contents/:path{.+}", async (c) => {
244
+ const localPath = helpers.toLocalPath(requireApiPath(c.req.param("path")));
245
+ const body = await c.req.json();
246
+ let result;
247
+ try {
248
+ result = await writeFile(
249
+ helpers.basePath,
250
+ localPath,
251
+ decodeContent(body),
252
+ body.sha ?? null
253
+ );
254
+ } catch (error) {
255
+ if (error instanceof FileOperationError && error.status === 404 && !body.sha) {
256
+ result = await createFile(
257
+ helpers.basePath,
258
+ localPath,
259
+ decodeContent(body)
260
+ );
261
+ } else {
262
+ throw error;
263
+ }
264
+ }
265
+ return helpers.json({ content: { sha: result.sha } });
266
+ });
267
+ app.post("/api/github/repos/:owner/:repo/contents/:path{.+}", async (c) => {
268
+ const localPath = helpers.toLocalPath(requireApiPath(c.req.param("path")));
269
+ const body = await c.req.json();
270
+ const result = await createFile(
271
+ helpers.basePath,
272
+ localPath,
273
+ decodeContent(body)
274
+ );
275
+ return helpers.json({ content: { sha: result.sha } }, 201);
276
+ });
277
+ app.delete("/api/github/repos/:owner/:repo/contents/:path{.+}", async (c) => {
278
+ const localPath = helpers.toLocalPath(requireApiPath(c.req.param("path")));
279
+ const body = await c.req.json();
280
+ if (!body.sha) {
281
+ throw new FileOperationError(400, "Request body must include a sha.");
282
+ }
283
+ await deleteFile(helpers.basePath, localPath, body.sha);
284
+ return helpers.json({ content: null });
285
+ });
286
+ }
287
+
288
+ // server/routes/git.ts
289
+ import { execFile } from "node:child_process";
290
+ import { realpath } from "node:fs/promises";
291
+ import { relative as relative3 } from "node:path";
292
+ import { promisify } from "node:util";
293
+ var execGit = promisify(execFile);
294
+ async function getRepoContext(basePath) {
295
+ try {
296
+ const { stdout } = await execGit("git", ["rev-parse", "--show-toplevel"], {
297
+ cwd: basePath
298
+ });
299
+ const repoRoot = await realpath(stdout.trim());
300
+ const resolvedBasePath = await realpath(basePath);
301
+ return {
302
+ repoRoot,
303
+ relativeScope: relative3(repoRoot, resolvedBasePath) || "."
304
+ };
305
+ } catch {
306
+ throw new FileOperationError(404, "Not a git repository.");
307
+ }
308
+ }
309
+ function mapStatus(code) {
310
+ if (code === "??") {
311
+ return "untracked";
312
+ }
313
+ if (code.includes("D")) {
314
+ return "deleted";
315
+ }
316
+ return "modified";
317
+ }
318
+ function defaultCommitMessage() {
319
+ return `Update proposals via ReDraft (${(/* @__PURE__ */ new Date()).toISOString()})`;
320
+ }
321
+ function registerGitRoute(app, helpers) {
322
+ app.get("/api/git/status", async () => {
323
+ const { repoRoot, relativeScope } = await getRepoContext(helpers.basePath);
324
+ const { stdout } = await execGit(
325
+ "git",
326
+ ["status", "--porcelain", "--", relativeScope],
327
+ {
328
+ cwd: repoRoot
329
+ }
330
+ );
331
+ const files = stdout.split("\n").map((line) => line.trimEnd()).filter(Boolean).map((line) => ({
332
+ path: line.slice(3).trim(),
333
+ status: mapStatus(line.slice(0, 2))
334
+ }));
335
+ return helpers.json({ dirty: files.length > 0, files });
336
+ });
337
+ app.post("/api/git/commit", async (c) => {
338
+ const body = await c.req.json();
339
+ const message = body.message?.trim() || defaultCommitMessage();
340
+ const { repoRoot, relativeScope } = await getRepoContext(helpers.basePath);
341
+ await execGit("git", ["add", "--", relativeScope], { cwd: repoRoot });
342
+ await execGit(
343
+ "git",
344
+ [
345
+ "-c",
346
+ "user.name=ReDraft",
347
+ "-c",
348
+ "user.email=redraft@local",
349
+ "commit",
350
+ "-m",
351
+ message
352
+ ],
353
+ { cwd: repoRoot }
354
+ );
355
+ const { stdout } = await execGit("git", ["rev-parse", "HEAD"], {
356
+ cwd: repoRoot
357
+ });
358
+ return helpers.json({ sha: stdout.trim(), message });
359
+ });
360
+ }
361
+
362
+ // server/routes/tree.ts
363
+ function registerTreeRoute(app, helpers) {
364
+ app.get("/api/github/repos/:owner/:repo/git/trees/:ref", async () => {
365
+ const tree = await listFiles(helpers.basePath);
366
+ return helpers.json({
367
+ tree: tree.map((entry) => ({
368
+ path: helpers.toApiPath(entry.path),
369
+ type: entry.type
370
+ }))
371
+ });
372
+ });
373
+ }
374
+
375
+ // server/routes/user.ts
376
+ function registerUserRoute(app, helpers) {
377
+ app.get("/api/github/user", () => {
378
+ return helpers.json({ login: "local-user", avatar_url: "" });
379
+ });
380
+ }
381
+
382
+ // server/routes/index.ts
383
+ var RATE_LIMIT_HEADERS = {
384
+ "x-ratelimit-limit": "1000000",
385
+ "x-ratelimit-remaining": "999999",
386
+ "x-ratelimit-reset": "4102444800"
387
+ };
388
+ function toLocalPath(apiPath) {
389
+ if (!apiPath.startsWith("proposals/")) {
390
+ throw new FileOperationError(404, `Unsupported path: ${apiPath}`);
391
+ }
392
+ return apiPath.slice("proposals/".length);
393
+ }
394
+ function toApiPath(localPath) {
395
+ return `proposals/${localPath}`;
396
+ }
397
+ function buildGitHubApiRouter(basePath) {
398
+ const app = new Hono();
399
+ const json = (body, status = 200) => {
400
+ const response = Response.json(body, { status });
401
+ for (const [header, value] of Object.entries(RATE_LIMIT_HEADERS)) {
402
+ response.headers.set(header, value);
403
+ }
404
+ return response;
405
+ };
406
+ app.onError((error) => {
407
+ if (error instanceof FileOperationError) {
408
+ return json({ message: error.message }, error.status);
409
+ }
410
+ return json(
411
+ { message: error instanceof Error ? error.message : "Unknown error" },
412
+ 500
413
+ );
414
+ });
415
+ const helpers = { basePath, json, toApiPath, toLocalPath };
416
+ registerUserRoute(app, helpers);
417
+ registerTreeRoute(app, helpers);
418
+ registerContentsRoute(app, helpers);
419
+ registerCommitsRoute(app, helpers);
420
+ registerGitRoute(app, helpers);
421
+ return app;
422
+ }
423
+
424
+ // server/ws/hub.ts
425
+ import { EventEmitter } from "node:events";
426
+ import { WebSocket, WebSocketServer } from "ws";
427
+ var WebSocketHub = class extends EventEmitter {
428
+ server = new WebSocketServer({ noServer: true });
429
+ clients = /* @__PURE__ */ new Set();
430
+ constructor() {
431
+ super();
432
+ this.server.on("connection", (client) => {
433
+ this.clients.add(client);
434
+ this.emit("connection-count", this.connectionCount);
435
+ const removeClient = () => {
436
+ this.clients.delete(client);
437
+ this.emit("connection-count", this.connectionCount);
438
+ };
439
+ client.on("close", removeClient);
440
+ client.on("error", removeClient);
441
+ });
442
+ }
443
+ get connectionCount() {
444
+ return this.clients.size;
445
+ }
446
+ handleUpgrade(request, socket, head) {
447
+ this.server.handleUpgrade(request, socket, head, (client) => {
448
+ this.server.emit("connection", client, request);
449
+ });
450
+ }
451
+ broadcast(event) {
452
+ const message = JSON.stringify(event);
453
+ for (const client of this.clients) {
454
+ if (client.readyState !== WebSocket.OPEN) {
455
+ this.clients.delete(client);
456
+ continue;
457
+ }
458
+ client.send(message);
459
+ }
460
+ }
461
+ async close() {
462
+ const { promise, resolve: resolve6, reject } = Promise.withResolvers();
463
+ this.server.close((error) => {
464
+ if (error) {
465
+ reject(error);
466
+ return;
467
+ }
468
+ resolve6();
469
+ });
470
+ await promise;
471
+ }
472
+ };
473
+
474
+ // server/app.ts
475
+ var CONTENT_TYPE_BY_EXTENSION = {
476
+ ".css": "text/css; charset=utf-8",
477
+ ".html": "text/html; charset=utf-8",
478
+ ".js": "text/javascript; charset=utf-8",
479
+ ".json": "application/json; charset=utf-8",
480
+ ".svg": "image/svg+xml",
481
+ ".ttf": "font/ttf",
482
+ ".woff": "font/woff",
483
+ ".woff2": "font/woff2"
484
+ };
485
+ function injectLocalModeMeta(html) {
486
+ const metaTag = '<meta name="redraft-mode" content="local">';
487
+ if (html.includes(metaTag)) {
488
+ return html;
489
+ }
490
+ if (html.includes("</head>")) {
491
+ return html.replace("</head>", ` ${metaTag}</head>`);
492
+ }
493
+ return `${metaTag}${html}`;
494
+ }
495
+ function contentTypeFor(path) {
496
+ return CONTENT_TYPE_BY_EXTENSION[extname(path)] ?? "application/octet-stream";
497
+ }
498
+ async function loadStaticResponse(uiRoot, requestPath) {
499
+ const resolvedUiRoot = resolve4(uiRoot);
500
+ const normalizedPath = requestPath === "/" ? "index.html" : requestPath.replace(/^\//, "");
501
+ const staticPath = resolve4(resolvedUiRoot, normalizedPath);
502
+ if (!staticPath.startsWith(resolvedUiRoot)) {
503
+ return null;
504
+ }
505
+ try {
506
+ const fileStats = await stat3(staticPath);
507
+ if (!fileStats.isFile()) {
508
+ return null;
509
+ }
510
+ } catch {
511
+ return null;
512
+ }
513
+ const file = await readFile2(staticPath);
514
+ const body = normalizedPath === "index.html" ? injectLocalModeMeta(file.toString("utf8")) : file;
515
+ return new Response(body, {
516
+ headers: { "content-type": contentTypeFor(normalizedPath) }
517
+ });
518
+ }
519
+ function buildReDraftApp(options) {
520
+ const app = buildGitHubApiRouter(options.basePath);
521
+ app.get("*", async (c) => {
522
+ if (options.noUi) {
523
+ return c.notFound();
524
+ }
525
+ const { pathname } = new URL(c.req.url);
526
+ const directFile = await loadStaticResponse(options.uiRoot, pathname);
527
+ if (directFile) {
528
+ return directFile;
529
+ }
530
+ if (extname(pathname) !== "") {
531
+ return c.notFound();
532
+ }
533
+ const indexFile = await loadStaticResponse(options.uiRoot, "/");
534
+ return indexFile ?? c.notFound();
535
+ });
536
+ return app;
537
+ }
538
+ function toRequest(request, fallbackOrigin) {
539
+ const url = new URL(request.url ?? "/", fallbackOrigin);
540
+ const headers = new Headers();
541
+ for (const [key, value] of Object.entries(request.headers)) {
542
+ if (Array.isArray(value)) {
543
+ headers.set(key, value.join(", "));
544
+ continue;
545
+ }
546
+ if (value !== void 0) {
547
+ headers.set(key, value);
548
+ }
549
+ }
550
+ const init = {
551
+ method: request.method,
552
+ headers
553
+ };
554
+ if (request.method && !["GET", "HEAD"].includes(request.method)) {
555
+ init.body = Readable.toWeb(request);
556
+ init.duplex = "half";
557
+ }
558
+ return new Request(url, init);
559
+ }
560
+ async function sendResponse(response, nodeResponse) {
561
+ nodeResponse.statusCode = response.status;
562
+ response.headers.forEach((value, key) => {
563
+ nodeResponse.setHeader(key, value);
564
+ });
565
+ if (!response.body) {
566
+ nodeResponse.end();
567
+ return;
568
+ }
569
+ const { promise, resolve: resolve6, reject } = Promise.withResolvers();
570
+ Readable.fromWeb(response.body).pipe(nodeResponse);
571
+ nodeResponse.once("finish", resolve6);
572
+ nodeResponse.once("error", reject);
573
+ await promise;
574
+ }
575
+ async function startReDraftServer(options) {
576
+ const host = options.host ?? "127.0.0.1";
577
+ const port = options.port ?? 4200;
578
+ const app = buildReDraftApp(options);
579
+ const hub = new WebSocketHub();
580
+ const server = createServer(async (request, response) => {
581
+ const honoResponse = await app.fetch(
582
+ toRequest(request, `http://${request.headers.host ?? `${host}:${port}`}`)
583
+ );
584
+ await sendResponse(honoResponse, response);
585
+ });
586
+ server.on("upgrade", (request, socket, head) => {
587
+ const url = new URL(
588
+ request.url ?? "/",
589
+ `http://${request.headers.host ?? `${host}:${port}`}`
590
+ );
591
+ if (url.pathname !== "/ws") {
592
+ socket.destroy();
593
+ return;
594
+ }
595
+ hub.handleUpgrade(request, socket, head);
596
+ });
597
+ const {
598
+ promise,
599
+ resolve: resolveListen,
600
+ reject: rejectListen
601
+ } = Promise.withResolvers();
602
+ server.listen(port, host, () => resolveListen());
603
+ server.once("error", rejectListen);
604
+ await promise;
605
+ return {
606
+ app,
607
+ hub,
608
+ server,
609
+ url: `http://${host}:${port}`,
610
+ close: async () => {
611
+ await hub.close();
612
+ const {
613
+ promise: closePromise,
614
+ resolve: resolveClose,
615
+ reject: rejectClose
616
+ } = Promise.withResolvers();
617
+ server.close((error) => {
618
+ if (error) {
619
+ rejectClose(error);
620
+ return;
621
+ }
622
+ resolveClose();
623
+ });
624
+ await closePromise;
625
+ }
626
+ };
627
+ }
628
+ function resolveUiRoot() {
629
+ return fileURLToPath(new URL("../dist", import.meta.url));
630
+ }
631
+ async function verifyUiBuild(uiRoot) {
632
+ await access(resolve4(uiRoot, "index.html"));
633
+ }
634
+
635
+ // server/cli.ts
636
+ async function ensureDirectoryExists(path) {
637
+ const fileStats = await stat4(path);
638
+ if (!fileStats.isDirectory()) {
639
+ throw new Error(`Not a directory: ${path}`);
640
+ }
641
+ }
642
+ function browserOpenCommand(url) {
643
+ if (process.platform === "darwin") {
644
+ return `open ${JSON.stringify(url)}`;
645
+ }
646
+ if (process.platform === "win32") {
647
+ return `start "" ${JSON.stringify(url)}`;
648
+ }
649
+ return `xdg-open ${JSON.stringify(url)}`;
650
+ }
651
+ function triggerBrowserOpen(url) {
652
+ exec(browserOpenCommand(url));
653
+ }
654
+ async function runServe(directory = "./proposals", options = {}) {
655
+ const basePath = resolve5(directory);
656
+ await ensureDirectoryExists(basePath);
657
+ const uiRoot = resolveUiRoot();
658
+ if (!options.noUi) {
659
+ await verifyUiBuild(uiRoot);
660
+ }
661
+ const runningServer = await startReDraftServer({
662
+ basePath,
663
+ uiRoot,
664
+ noUi: options.noUi,
665
+ host: options.host,
666
+ port: options.port
667
+ });
668
+ const stopWatcher = startWatcher(basePath, (event) => {
669
+ runningServer.hub.broadcast(event);
670
+ });
671
+ console.log(`ReDraft local server listening at ${runningServer.url}`);
672
+ if (options.open) {
673
+ triggerBrowserOpen(runningServer.url);
674
+ }
675
+ const shutdown = async (exitCode = 0) => {
676
+ stopWatcher();
677
+ await runningServer.close();
678
+ process.exit(exitCode);
679
+ };
680
+ process.once("SIGINT", () => {
681
+ void shutdown();
682
+ });
683
+ process.once("SIGTERM", () => {
684
+ void shutdown();
685
+ });
686
+ }
687
+ function registerServeOptions(command) {
688
+ return command.option(
689
+ "--port <number>",
690
+ "Port to listen on (default: 4200)",
691
+ (value) => Number(value)
692
+ ).option("--open", "Open the ReDraft UI in the default browser", false).option("--no-ui", "Skip serving the static frontend", false).option(
693
+ "--host <string>",
694
+ "Bind address (default: 127.0.0.1)",
695
+ "127.0.0.1"
696
+ );
697
+ }
698
+ var program = registerServeOptions(
699
+ new Command().name("redraft").description("ReDraft local tooling").argument("[directory]", "proposal directory for the default serve command").action(async function(directory) {
700
+ if (!directory) {
701
+ program.help();
702
+ return;
703
+ }
704
+ try {
705
+ await runServe(directory, this.optsWithGlobals());
706
+ } catch (error) {
707
+ console.error(error instanceof Error ? error.message : String(error));
708
+ process.exit(1);
709
+ }
710
+ })
711
+ );
712
+ registerServeOptions(
713
+ program.command("serve").argument("[directory]", "proposal directory").action(async function(directory) {
714
+ try {
715
+ await runServe(
716
+ directory ?? "./proposals",
717
+ this.optsWithGlobals()
718
+ );
719
+ } catch (error) {
720
+ console.error(error instanceof Error ? error.message : String(error));
721
+ process.exit(1);
722
+ }
723
+ })
724
+ );
725
+ program.parse(process.argv);