docrev 0.8.1 → 0.8.5

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 (306) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/PLAN-tables-and-postprocess.md +850 -0
  3. package/README.md +33 -0
  4. package/bin/rev.js +12 -131
  5. package/bin/rev.ts +145 -0
  6. package/dist/bin/rev.d.ts +9 -0
  7. package/dist/bin/rev.d.ts.map +1 -0
  8. package/dist/bin/rev.js +118 -0
  9. package/dist/bin/rev.js.map +1 -0
  10. package/dist/lib/annotations.d.ts +91 -0
  11. package/dist/lib/annotations.d.ts.map +1 -0
  12. package/dist/lib/annotations.js +554 -0
  13. package/dist/lib/annotations.js.map +1 -0
  14. package/dist/lib/build.d.ts +171 -0
  15. package/dist/lib/build.d.ts.map +1 -0
  16. package/dist/lib/build.js +755 -0
  17. package/dist/lib/build.js.map +1 -0
  18. package/dist/lib/citations.d.ts +34 -0
  19. package/dist/lib/citations.d.ts.map +1 -0
  20. package/dist/lib/citations.js +140 -0
  21. package/dist/lib/citations.js.map +1 -0
  22. package/dist/lib/commands/build.d.ts +13 -0
  23. package/dist/lib/commands/build.d.ts.map +1 -0
  24. package/dist/lib/commands/build.js +678 -0
  25. package/dist/lib/commands/build.js.map +1 -0
  26. package/dist/lib/commands/citations.d.ts +11 -0
  27. package/dist/lib/commands/citations.d.ts.map +1 -0
  28. package/dist/lib/commands/citations.js +428 -0
  29. package/dist/lib/commands/citations.js.map +1 -0
  30. package/dist/lib/commands/comments.d.ts +11 -0
  31. package/dist/lib/commands/comments.d.ts.map +1 -0
  32. package/dist/lib/commands/comments.js +883 -0
  33. package/dist/lib/commands/comments.js.map +1 -0
  34. package/dist/lib/commands/context.d.ts +35 -0
  35. package/dist/lib/commands/context.d.ts.map +1 -0
  36. package/dist/lib/commands/context.js +59 -0
  37. package/dist/lib/commands/context.js.map +1 -0
  38. package/dist/lib/commands/core.d.ts +11 -0
  39. package/dist/lib/commands/core.d.ts.map +1 -0
  40. package/dist/lib/commands/core.js +246 -0
  41. package/dist/lib/commands/core.js.map +1 -0
  42. package/dist/lib/commands/doi.d.ts +11 -0
  43. package/dist/lib/commands/doi.d.ts.map +1 -0
  44. package/dist/lib/commands/doi.js +373 -0
  45. package/dist/lib/commands/doi.js.map +1 -0
  46. package/dist/lib/commands/history.d.ts +11 -0
  47. package/dist/lib/commands/history.d.ts.map +1 -0
  48. package/dist/lib/commands/history.js +245 -0
  49. package/dist/lib/commands/history.js.map +1 -0
  50. package/dist/lib/commands/index.d.ts +28 -0
  51. package/dist/lib/commands/index.d.ts.map +1 -0
  52. package/dist/lib/commands/index.js +35 -0
  53. package/dist/lib/commands/index.js.map +1 -0
  54. package/dist/lib/commands/init.d.ts +11 -0
  55. package/dist/lib/commands/init.d.ts.map +1 -0
  56. package/dist/lib/commands/init.js +209 -0
  57. package/dist/lib/commands/init.js.map +1 -0
  58. package/dist/lib/commands/response.d.ts +11 -0
  59. package/dist/lib/commands/response.d.ts.map +1 -0
  60. package/dist/lib/commands/response.js +317 -0
  61. package/dist/lib/commands/response.js.map +1 -0
  62. package/dist/lib/commands/sections.d.ts +11 -0
  63. package/dist/lib/commands/sections.d.ts.map +1 -0
  64. package/dist/lib/commands/sections.js +1071 -0
  65. package/dist/lib/commands/sections.js.map +1 -0
  66. package/dist/lib/commands/utilities.d.ts +19 -0
  67. package/dist/lib/commands/utilities.d.ts.map +1 -0
  68. package/dist/lib/commands/utilities.js +2009 -0
  69. package/dist/lib/commands/utilities.js.map +1 -0
  70. package/dist/lib/comment-realign.d.ts +50 -0
  71. package/dist/lib/comment-realign.d.ts.map +1 -0
  72. package/dist/lib/comment-realign.js +372 -0
  73. package/dist/lib/comment-realign.js.map +1 -0
  74. package/dist/lib/config.d.ts +41 -0
  75. package/dist/lib/config.d.ts.map +1 -0
  76. package/dist/lib/config.js +76 -0
  77. package/dist/lib/config.js.map +1 -0
  78. package/dist/lib/crossref.d.ts +108 -0
  79. package/dist/lib/crossref.d.ts.map +1 -0
  80. package/dist/lib/crossref.js +597 -0
  81. package/dist/lib/crossref.js.map +1 -0
  82. package/dist/lib/dependencies.d.ts +30 -0
  83. package/dist/lib/dependencies.d.ts.map +1 -0
  84. package/dist/lib/dependencies.js +95 -0
  85. package/dist/lib/dependencies.js.map +1 -0
  86. package/dist/lib/doi-cache.d.ts +29 -0
  87. package/dist/lib/doi-cache.d.ts.map +1 -0
  88. package/dist/lib/doi-cache.js +104 -0
  89. package/dist/lib/doi-cache.js.map +1 -0
  90. package/dist/lib/doi.d.ts +65 -0
  91. package/dist/lib/doi.d.ts.map +1 -0
  92. package/dist/lib/doi.js +710 -0
  93. package/dist/lib/doi.js.map +1 -0
  94. package/dist/lib/equations.d.ts +61 -0
  95. package/dist/lib/equations.d.ts.map +1 -0
  96. package/dist/lib/equations.js +445 -0
  97. package/dist/lib/equations.js.map +1 -0
  98. package/dist/lib/errors.d.ts +60 -0
  99. package/dist/lib/errors.d.ts.map +1 -0
  100. package/dist/lib/errors.js +303 -0
  101. package/dist/lib/errors.js.map +1 -0
  102. package/dist/lib/format.d.ts +104 -0
  103. package/dist/lib/format.d.ts.map +1 -0
  104. package/dist/lib/format.js +416 -0
  105. package/dist/lib/format.js.map +1 -0
  106. package/dist/lib/git.d.ts +88 -0
  107. package/dist/lib/git.d.ts.map +1 -0
  108. package/dist/lib/git.js +304 -0
  109. package/dist/lib/git.js.map +1 -0
  110. package/dist/lib/grammar.d.ts +62 -0
  111. package/dist/lib/grammar.d.ts.map +1 -0
  112. package/dist/lib/grammar.js +244 -0
  113. package/dist/lib/grammar.js.map +1 -0
  114. package/dist/lib/image-registry.d.ts +68 -0
  115. package/dist/lib/image-registry.d.ts.map +1 -0
  116. package/dist/lib/image-registry.js +112 -0
  117. package/dist/lib/image-registry.js.map +1 -0
  118. package/dist/lib/import.d.ts +184 -0
  119. package/dist/lib/import.d.ts.map +1 -0
  120. package/dist/lib/import.js +1581 -0
  121. package/dist/lib/import.js.map +1 -0
  122. package/dist/lib/journals.d.ts +55 -0
  123. package/dist/lib/journals.d.ts.map +1 -0
  124. package/dist/lib/journals.js +417 -0
  125. package/dist/lib/journals.js.map +1 -0
  126. package/dist/lib/merge.d.ts +138 -0
  127. package/dist/lib/merge.d.ts.map +1 -0
  128. package/dist/lib/merge.js +603 -0
  129. package/dist/lib/merge.js.map +1 -0
  130. package/dist/lib/orcid.d.ts +36 -0
  131. package/dist/lib/orcid.d.ts.map +1 -0
  132. package/dist/lib/orcid.js +117 -0
  133. package/dist/lib/orcid.js.map +1 -0
  134. package/dist/lib/pdf-comments.d.ts +95 -0
  135. package/dist/lib/pdf-comments.d.ts.map +1 -0
  136. package/dist/lib/pdf-comments.js +192 -0
  137. package/dist/lib/pdf-comments.js.map +1 -0
  138. package/dist/lib/pdf-import.d.ts +118 -0
  139. package/dist/lib/pdf-import.d.ts.map +1 -0
  140. package/dist/lib/pdf-import.js +397 -0
  141. package/dist/lib/pdf-import.js.map +1 -0
  142. package/dist/lib/plugins.d.ts +76 -0
  143. package/dist/lib/plugins.d.ts.map +1 -0
  144. package/dist/lib/plugins.js +235 -0
  145. package/dist/lib/plugins.js.map +1 -0
  146. package/dist/lib/postprocess.d.ts +42 -0
  147. package/dist/lib/postprocess.d.ts.map +1 -0
  148. package/dist/lib/postprocess.js +138 -0
  149. package/dist/lib/postprocess.js.map +1 -0
  150. package/dist/lib/pptx-template.d.ts +59 -0
  151. package/dist/lib/pptx-template.d.ts.map +1 -0
  152. package/dist/lib/pptx-template.js +613 -0
  153. package/dist/lib/pptx-template.js.map +1 -0
  154. package/dist/lib/pptx-themes.d.ts +80 -0
  155. package/dist/lib/pptx-themes.d.ts.map +1 -0
  156. package/dist/lib/pptx-themes.js +818 -0
  157. package/dist/lib/pptx-themes.js.map +1 -0
  158. package/dist/lib/protect-restore.d.ts +137 -0
  159. package/dist/lib/protect-restore.d.ts.map +1 -0
  160. package/dist/lib/protect-restore.js +394 -0
  161. package/dist/lib/protect-restore.js.map +1 -0
  162. package/dist/lib/rate-limiter.d.ts +27 -0
  163. package/dist/lib/rate-limiter.d.ts.map +1 -0
  164. package/dist/lib/rate-limiter.js +79 -0
  165. package/dist/lib/rate-limiter.js.map +1 -0
  166. package/dist/lib/response.d.ts +41 -0
  167. package/dist/lib/response.d.ts.map +1 -0
  168. package/dist/lib/response.js +150 -0
  169. package/dist/lib/response.js.map +1 -0
  170. package/dist/lib/review.d.ts +35 -0
  171. package/dist/lib/review.d.ts.map +1 -0
  172. package/dist/lib/review.js +263 -0
  173. package/dist/lib/review.js.map +1 -0
  174. package/dist/lib/schema.d.ts +66 -0
  175. package/dist/lib/schema.d.ts.map +1 -0
  176. package/dist/lib/schema.js +339 -0
  177. package/dist/lib/schema.js.map +1 -0
  178. package/dist/lib/scientific-words.d.ts +6 -0
  179. package/dist/lib/scientific-words.d.ts.map +1 -0
  180. package/dist/lib/scientific-words.js +66 -0
  181. package/dist/lib/scientific-words.js.map +1 -0
  182. package/dist/lib/sections.d.ts +40 -0
  183. package/dist/lib/sections.d.ts.map +1 -0
  184. package/dist/lib/sections.js +288 -0
  185. package/dist/lib/sections.js.map +1 -0
  186. package/dist/lib/slides.d.ts +86 -0
  187. package/dist/lib/slides.d.ts.map +1 -0
  188. package/dist/lib/slides.js +676 -0
  189. package/dist/lib/slides.js.map +1 -0
  190. package/dist/lib/spelling.d.ts +76 -0
  191. package/dist/lib/spelling.d.ts.map +1 -0
  192. package/dist/lib/spelling.js +272 -0
  193. package/dist/lib/spelling.js.map +1 -0
  194. package/dist/lib/templates.d.ts +30 -0
  195. package/dist/lib/templates.d.ts.map +1 -0
  196. package/dist/lib/templates.js +504 -0
  197. package/dist/lib/templates.js.map +1 -0
  198. package/dist/lib/themes.d.ts +85 -0
  199. package/dist/lib/themes.d.ts.map +1 -0
  200. package/dist/lib/themes.js +652 -0
  201. package/dist/lib/themes.js.map +1 -0
  202. package/dist/lib/trackchanges.d.ts +51 -0
  203. package/dist/lib/trackchanges.d.ts.map +1 -0
  204. package/dist/lib/trackchanges.js +202 -0
  205. package/dist/lib/trackchanges.js.map +1 -0
  206. package/dist/lib/tui.d.ts +76 -0
  207. package/dist/lib/tui.d.ts.map +1 -0
  208. package/dist/lib/tui.js +377 -0
  209. package/dist/lib/tui.js.map +1 -0
  210. package/dist/lib/types.d.ts +447 -0
  211. package/dist/lib/types.d.ts.map +1 -0
  212. package/dist/lib/types.js +6 -0
  213. package/dist/lib/types.js.map +1 -0
  214. package/dist/lib/undo.d.ts +57 -0
  215. package/dist/lib/undo.d.ts.map +1 -0
  216. package/dist/lib/undo.js +185 -0
  217. package/dist/lib/undo.js.map +1 -0
  218. package/dist/lib/utils.d.ts +16 -0
  219. package/dist/lib/utils.d.ts.map +1 -0
  220. package/dist/lib/utils.js +40 -0
  221. package/dist/lib/utils.js.map +1 -0
  222. package/dist/lib/variables.d.ts +42 -0
  223. package/dist/lib/variables.d.ts.map +1 -0
  224. package/dist/lib/variables.js +141 -0
  225. package/dist/lib/variables.js.map +1 -0
  226. package/dist/lib/word.d.ts +80 -0
  227. package/dist/lib/word.d.ts.map +1 -0
  228. package/dist/lib/word.js +360 -0
  229. package/dist/lib/word.js.map +1 -0
  230. package/dist/lib/wordcomments.d.ts +51 -0
  231. package/dist/lib/wordcomments.d.ts.map +1 -0
  232. package/dist/lib/wordcomments.js +587 -0
  233. package/dist/lib/wordcomments.js.map +1 -0
  234. package/eslint.config.js +27 -0
  235. package/lib/annotations.ts +622 -0
  236. package/lib/apply-buildup-colors.py +88 -0
  237. package/lib/build.ts +1013 -0
  238. package/lib/{citations.js → citations.ts} +38 -27
  239. package/lib/commands/{build.js → build.ts} +80 -27
  240. package/lib/commands/{citations.js → citations.ts} +36 -18
  241. package/lib/commands/{comments.js → comments.ts} +187 -54
  242. package/lib/commands/{context.js → context.ts} +18 -8
  243. package/lib/commands/{core.js → core.ts} +34 -20
  244. package/lib/commands/{doi.js → doi.ts} +32 -16
  245. package/lib/commands/{history.js → history.ts} +25 -12
  246. package/lib/commands/{index.js → index.ts} +9 -5
  247. package/lib/commands/{init.js → init.ts} +20 -8
  248. package/lib/commands/{response.js → response.ts} +47 -20
  249. package/lib/commands/{sections.js → sections.ts} +273 -68
  250. package/lib/commands/{utilities.js → utilities.ts} +338 -158
  251. package/lib/{comment-realign.js → comment-realign.ts} +117 -45
  252. package/lib/config.ts +84 -0
  253. package/lib/{crossref.js → crossref.ts} +213 -138
  254. package/lib/dependencies.ts +106 -0
  255. package/lib/doi-cache.ts +115 -0
  256. package/lib/{doi.js → doi.ts} +115 -281
  257. package/lib/{equations.js → equations.ts} +60 -64
  258. package/lib/{errors.js → errors.ts} +56 -48
  259. package/lib/{format.js → format.ts} +137 -63
  260. package/lib/{git.js → git.ts} +66 -63
  261. package/lib/{grammar.js → grammar.ts} +45 -32
  262. package/lib/image-registry.ts +180 -0
  263. package/lib/import.ts +2060 -0
  264. package/lib/journals.ts +505 -0
  265. package/lib/{merge.js → merge.ts} +185 -135
  266. package/lib/{orcid.js → orcid.ts} +17 -22
  267. package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
  268. package/lib/{pdf-import.js → pdf-import.ts} +148 -70
  269. package/lib/{plugins.js → plugins.ts} +82 -39
  270. package/lib/postprocess.ts +188 -0
  271. package/lib/pptx-color-filter.lua +37 -0
  272. package/lib/pptx-template.ts +625 -0
  273. package/lib/pptx-themes/academic.pptx +0 -0
  274. package/lib/pptx-themes/corporate.pptx +0 -0
  275. package/lib/pptx-themes/dark.pptx +0 -0
  276. package/lib/pptx-themes/default.pptx +0 -0
  277. package/lib/pptx-themes/minimal.pptx +0 -0
  278. package/lib/pptx-themes/plant.pptx +0 -0
  279. package/lib/pptx-themes.ts +896 -0
  280. package/lib/protect-restore.ts +516 -0
  281. package/lib/rate-limiter.ts +94 -0
  282. package/lib/{response.js → response.ts} +36 -21
  283. package/lib/{review.js → review.ts} +53 -43
  284. package/lib/{schema.js → schema.ts} +70 -25
  285. package/lib/{sections.js → sections.ts} +71 -76
  286. package/lib/slides.ts +793 -0
  287. package/lib/{spelling.js → spelling.ts} +43 -59
  288. package/lib/{templates.js → templates.ts} +20 -17
  289. package/lib/themes.ts +742 -0
  290. package/lib/{trackchanges.js → trackchanges.ts} +52 -23
  291. package/lib/types.ts +509 -0
  292. package/lib/{undo.js → undo.ts} +75 -52
  293. package/lib/utils.ts +41 -0
  294. package/lib/{variables.js → variables.ts} +60 -54
  295. package/lib/word.ts +428 -0
  296. package/lib/{wordcomments.js → wordcomments.ts} +94 -40
  297. package/package.json +15 -5
  298. package/skill/REFERENCE.md +67 -0
  299. package/tsconfig.json +26 -0
  300. package/lib/annotations.js +0 -414
  301. package/lib/build.js +0 -639
  302. package/lib/config.js +0 -79
  303. package/lib/import.js +0 -1145
  304. package/lib/journals.js +0 -629
  305. package/lib/word.js +0 -225
  306. /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
@@ -0,0 +1,755 @@
1
+ /**
2
+ * Build system - combines sections → paper.md → PDF/DOCX/TEX
3
+ *
4
+ * Features:
5
+ * - Reads rev.yaml config
6
+ * - Combines section files into paper.md (persisted)
7
+ * - Strips annotations appropriately per output format
8
+ * - Runs pandoc with crossref filter
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { spawn } from 'child_process';
13
+ import YAML from 'yaml';
14
+ import { stripAnnotations } from './annotations.js';
15
+ import { buildRegistry, labelToDisplay, detectDynamicRefs, resolveForwardRefs } from './crossref.js';
16
+ import { processVariables, hasVariables } from './variables.js';
17
+ import { processSlideMarkdown, hasSlideSyntax } from './slides.js';
18
+ import { injectMediaIntoPptx, injectSlideNumbers, applyBuildupColors } from './pptx-template.js';
19
+ import { getThemePath } from './pptx-themes.js';
20
+ import { runPostprocess } from './postprocess.js';
21
+ import { hasPandoc, hasPandocCrossref, hasLatex } from './dependencies.js';
22
+ import { buildImageRegistry, writeImageRegistry } from './image-registry.js';
23
+ // =============================================================================
24
+ // Constants
25
+ // =============================================================================
26
+ /** Supported output formats */
27
+ const SUPPORTED_FORMATS = ['pdf', 'docx', 'tex', 'beamer', 'pptx'];
28
+ /** Maximum title length for output filename */
29
+ const MAX_TITLE_FILENAME_LENGTH = 50;
30
+ /**
31
+ * Default rev.yaml configuration
32
+ */
33
+ export const DEFAULT_CONFIG = {
34
+ title: 'Untitled Document',
35
+ authors: [],
36
+ sections: [],
37
+ bibliography: null,
38
+ csl: null,
39
+ crossref: {
40
+ figureTitle: 'Figure',
41
+ tableTitle: 'Table',
42
+ figPrefix: ['Fig.', 'Figs.'],
43
+ tblPrefix: ['Table', 'Tables'],
44
+ secPrefix: ['Section', 'Sections'],
45
+ linkReferences: true,
46
+ },
47
+ pdf: {
48
+ template: null,
49
+ documentclass: 'article',
50
+ fontsize: '12pt',
51
+ geometry: 'margin=1in',
52
+ linestretch: 1.5,
53
+ numbersections: false,
54
+ toc: false,
55
+ },
56
+ docx: {
57
+ reference: null,
58
+ keepComments: true,
59
+ toc: false,
60
+ },
61
+ tex: {
62
+ standalone: true,
63
+ },
64
+ // Slide formats
65
+ beamer: {
66
+ theme: 'default',
67
+ colortheme: null,
68
+ fonttheme: null,
69
+ aspectratio: null, // '169' for 16:9, '43' for 4:3
70
+ navigation: null, // 'horizontal', 'vertical', 'frame', 'empty'
71
+ section: true, // section divider slides
72
+ notes: 'show', // 'show' (presenter view), 'only' (notes only), 'hide', or false
73
+ fit_images: true, // scale images to fit within slide bounds
74
+ },
75
+ pptx: {
76
+ theme: 'default', // Built-in theme: default, dark, academic, minimal, corporate
77
+ reference: null, // Custom reference-doc (overrides theme)
78
+ media: null, // directory with logo images (e.g., logo-left.png, logo-right.png)
79
+ },
80
+ // Table formatting
81
+ tables: {
82
+ nowrap: [], // Column headers to apply nowrap formatting (converts Normal() → $\mathcal{N}()$ etc.)
83
+ },
84
+ // Postprocess scripts
85
+ postprocess: {
86
+ pdf: null,
87
+ docx: null,
88
+ tex: null,
89
+ pptx: null,
90
+ beamer: null,
91
+ all: null, // Runs after any format
92
+ },
93
+ };
94
+ // =============================================================================
95
+ // Public API
96
+ // =============================================================================
97
+ /**
98
+ * Load rev.yaml config from directory
99
+ * @param directory - Project directory path
100
+ * @returns Merged config with defaults
101
+ * @throws {TypeError} If directory is not a string
102
+ * @throws {Error} If rev.yaml exists but cannot be parsed
103
+ */
104
+ export function loadConfig(directory) {
105
+ if (typeof directory !== 'string') {
106
+ throw new TypeError(`directory must be a string, got ${typeof directory}`);
107
+ }
108
+ const configPath = path.join(directory, 'rev.yaml');
109
+ if (!fs.existsSync(configPath)) {
110
+ return { ...DEFAULT_CONFIG, _configPath: null };
111
+ }
112
+ try {
113
+ const content = fs.readFileSync(configPath, 'utf-8');
114
+ const userConfig = YAML.parse(content) || {};
115
+ // Deep merge with defaults
116
+ const config = {
117
+ ...DEFAULT_CONFIG,
118
+ ...userConfig,
119
+ crossref: { ...DEFAULT_CONFIG.crossref, ...userConfig.crossref },
120
+ pdf: { ...DEFAULT_CONFIG.pdf, ...userConfig.pdf },
121
+ docx: { ...DEFAULT_CONFIG.docx, ...userConfig.docx },
122
+ tex: { ...DEFAULT_CONFIG.tex, ...userConfig.tex },
123
+ beamer: { ...DEFAULT_CONFIG.beamer, ...userConfig.beamer },
124
+ pptx: { ...DEFAULT_CONFIG.pptx, ...userConfig.pptx },
125
+ tables: { ...DEFAULT_CONFIG.tables, ...userConfig.tables },
126
+ postprocess: { ...DEFAULT_CONFIG.postprocess, ...userConfig.postprocess },
127
+ _configPath: configPath,
128
+ };
129
+ return config;
130
+ }
131
+ catch (err) {
132
+ const error = err;
133
+ throw new Error(`Failed to parse rev.yaml: ${error.message}`);
134
+ }
135
+ }
136
+ /**
137
+ * Find section files in directory
138
+ * @param directory - Project directory path
139
+ * @param configSections - Sections from rev.yaml (optional)
140
+ * @returns Ordered list of section file names
141
+ * @throws {TypeError} If directory is not a string
142
+ */
143
+ export function findSections(directory, configSections = []) {
144
+ if (typeof directory !== 'string') {
145
+ throw new TypeError(`directory must be a string, got ${typeof directory}`);
146
+ }
147
+ // If sections specified in config, use that order
148
+ if (configSections.length > 0) {
149
+ const sections = [];
150
+ for (const section of configSections) {
151
+ const filePath = path.join(directory, section);
152
+ if (fs.existsSync(filePath)) {
153
+ sections.push(section);
154
+ }
155
+ else {
156
+ console.warn(`Warning: Section file not found: ${section}`);
157
+ }
158
+ }
159
+ return sections;
160
+ }
161
+ // Try sections.yaml
162
+ const sectionsYamlPath = path.join(directory, 'sections.yaml');
163
+ if (fs.existsSync(sectionsYamlPath)) {
164
+ try {
165
+ const sectionsConfig = YAML.parse(fs.readFileSync(sectionsYamlPath, 'utf-8'));
166
+ if (sectionsConfig.sections) {
167
+ return Object.entries(sectionsConfig.sections)
168
+ .sort((a, b) => (a[1].order ?? 999) - (b[1].order ?? 999))
169
+ .map(([file]) => file)
170
+ .filter((f) => fs.existsSync(path.join(directory, f)));
171
+ }
172
+ }
173
+ catch (e) {
174
+ if (process.env.DEBUG) {
175
+ const error = e;
176
+ console.warn('build: YAML parse error in sections.yaml:', error.message);
177
+ }
178
+ }
179
+ }
180
+ // Default: find all .md files except special ones
181
+ const exclude = ['paper.md', 'readme.md', 'claude.md'];
182
+ const files = fs.readdirSync(directory).filter((f) => {
183
+ if (!f.endsWith('.md'))
184
+ return false;
185
+ if (exclude.includes(f.toLowerCase()))
186
+ return false;
187
+ return true;
188
+ });
189
+ // Sort alphabetically as fallback
190
+ return files.sort();
191
+ }
192
+ /**
193
+ * Combine section files into paper.md
194
+ */
195
+ export function combineSections(directory, config, options = {}) {
196
+ const sections = findSections(directory, config.sections);
197
+ if (sections.length === 0) {
198
+ throw new Error('No section files found. Create .md files or specify sections in rev.yaml');
199
+ }
200
+ const parts = [];
201
+ // Add YAML frontmatter
202
+ const frontmatter = buildFrontmatter(config);
203
+ parts.push('---');
204
+ parts.push(YAML.stringify(frontmatter).trim());
205
+ parts.push('---');
206
+ parts.push('');
207
+ // Read all section contents for variable processing
208
+ const sectionContents = [];
209
+ // Check if we need to auto-inject references before supplementary
210
+ // Pandoc places refs at the end by default, which breaks when supplementary follows
211
+ const hasRefsSection = sections.some(s => s.toLowerCase().includes('reference') || s.toLowerCase().includes('refs'));
212
+ const suppIndex = sections.findIndex(s => s.toLowerCase().includes('supp') || s.toLowerCase().includes('appendix'));
213
+ const hasBibliography = config.bibliography && fs.existsSync(path.join(directory, config.bibliography));
214
+ // Track if we find an explicit refs div in any section
215
+ let hasExplicitRefsDiv = false;
216
+ // Combine sections
217
+ for (let i = 0; i < sections.length; i++) {
218
+ const section = sections[i];
219
+ if (!section)
220
+ continue;
221
+ const filePath = path.join(directory, section);
222
+ let content = fs.readFileSync(filePath, 'utf-8');
223
+ // Remove any existing frontmatter from section files
224
+ content = stripFrontmatter(content);
225
+ sectionContents.push(content);
226
+ // Check if this section has an explicit refs div
227
+ if (content.includes('::: {#refs}') || content.includes('::: {#refs}')) {
228
+ hasExplicitRefsDiv = true;
229
+ }
230
+ // Auto-inject references before supplementary if needed
231
+ if (i === suppIndex && hasBibliography && !hasRefsSection && !hasExplicitRefsDiv) {
232
+ parts.push('# References\n');
233
+ parts.push('::: {#refs}');
234
+ parts.push(':::');
235
+ parts.push('');
236
+ parts.push('');
237
+ options._refsAutoInjected = true;
238
+ }
239
+ parts.push(content.trim());
240
+ parts.push('');
241
+ parts.push(''); // Double newline between sections
242
+ }
243
+ let paperContent = parts.join('\n');
244
+ // Process template variables if any exist
245
+ if (hasVariables(paperContent)) {
246
+ paperContent = processVariables(paperContent, config, { sectionContents });
247
+ }
248
+ // Resolve forward references (refs that appear before their anchor definition)
249
+ // This fixes pandoc-crossref limitation with multi-file documents
250
+ if (hasPandocCrossref()) {
251
+ const registry = buildRegistry(directory, sections);
252
+ const { text, resolved } = resolveForwardRefs(paperContent, registry);
253
+ if (resolved.length > 0) {
254
+ paperContent = text;
255
+ // Store resolved count for optional reporting
256
+ options._forwardRefsResolved = resolved.length;
257
+ }
258
+ }
259
+ const paperPath = path.join(directory, 'paper.md');
260
+ fs.writeFileSync(paperPath, paperContent, 'utf-8');
261
+ return paperPath;
262
+ }
263
+ /**
264
+ * Build YAML frontmatter from config
265
+ */
266
+ function buildFrontmatter(config) {
267
+ const fm = {};
268
+ if (config.title)
269
+ fm.title = config.title;
270
+ if (config.authors && config.authors.length > 0) {
271
+ fm.author = config.authors;
272
+ }
273
+ if (config.bibliography) {
274
+ fm.bibliography = config.bibliography;
275
+ }
276
+ if (config.csl) {
277
+ fm.csl = config.csl;
278
+ }
279
+ return fm;
280
+ }
281
+ /**
282
+ * Strip YAML frontmatter from content
283
+ */
284
+ function stripFrontmatter(content) {
285
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
286
+ if (match) {
287
+ return content.slice(match[0].length);
288
+ }
289
+ return content;
290
+ }
291
+ /**
292
+ * Process markdown tables to apply nowrap formatting to specified columns.
293
+ * Converts distribution notation (Normal, Student-t, Gamma) to LaTeX math.
294
+ * @param content - Markdown content
295
+ * @param tablesConfig - tables config from rev.yaml
296
+ * @param format - output format (pdf, docx, etc.)
297
+ * @returns processed content
298
+ */
299
+ export function processTablesForFormat(content, tablesConfig, format) {
300
+ // Only process for PDF/TeX output
301
+ if (format !== 'pdf' && format !== 'tex') {
302
+ return content;
303
+ }
304
+ // Check if we have nowrap columns configured
305
+ if (!tablesConfig?.nowrap?.length) {
306
+ return content;
307
+ }
308
+ const nowrapPatterns = tablesConfig.nowrap.map((p) => p.toLowerCase());
309
+ // Match pipe tables: header row, separator row, body rows
310
+ // Header: | Col1 | Col2 | Col3 |
311
+ // Separator: |:-----|:-----|:-----|
312
+ // Body: | val1 | val2 | val3 |
313
+ const tableRegex = /^(\|[^\n]+\|\r?\n\|[-:| ]+\|\r?\n)((?:\|[^\n]+\|\r?\n?)+)/gm;
314
+ return content.replace(tableRegex, (match, headerAndSep, body) => {
315
+ // Split header from separator
316
+ const lines = headerAndSep.split(/\r?\n/);
317
+ const headerLine = lines[0] ?? '';
318
+ // Parse header cells to find nowrap column indices
319
+ const headerCells = headerLine
320
+ .split('|')
321
+ .slice(1, -1)
322
+ .map((c) => c.trim().toLowerCase());
323
+ const nowrapCols = [];
324
+ headerCells.forEach((cell, i) => {
325
+ if (nowrapPatterns.some((p) => cell.includes(p))) {
326
+ nowrapCols.push(i);
327
+ }
328
+ });
329
+ // If no nowrap columns found in this table, return unchanged
330
+ if (nowrapCols.length === 0) {
331
+ return match;
332
+ }
333
+ // Process body rows
334
+ const bodyLines = body.split(/\r?\n/).filter((l) => l.trim());
335
+ const processedBody = bodyLines
336
+ .map((row) => {
337
+ // Split row into cells, keeping the pipe structure
338
+ const cells = row.split('|');
339
+ // cells[0] is empty (before first |), cells[last] is empty (after last |)
340
+ nowrapCols.forEach((colIdx) => {
341
+ const cellIdx = colIdx + 1; // Account for empty first element
342
+ if (cells[cellIdx] !== undefined) {
343
+ const cellContent = cells[cellIdx].trim();
344
+ // Skip if empty, already math, or already has LaTeX commands
345
+ if (!cellContent || cellContent.startsWith('$') || cellContent.startsWith('\\')) {
346
+ return;
347
+ }
348
+ // Convert distribution notation to LaTeX math
349
+ // Order matters: compound names (Half-Normal) must come before simple names (Normal)
350
+ let processed = cellContent;
351
+ // Half-Normal(x) → $\text{Half-Normal}(x)$ (must come before Normal)
352
+ processed = processed.replace(/Half-Normal\(([^)]+)\)/g, '$\\text{Half-Normal}($1)$');
353
+ // Normal(x, y) → $\mathcal{N}(x, y)$
354
+ processed = processed.replace(/Normal\(([^)]+)\)/g, '$\\mathcal{N}($1)$');
355
+ // Student-t(df, loc, scale) → $t_{df}(loc, scale)$
356
+ processed = processed.replace(/Student-t\((\d+),\s*([^)]+)\)/g, '$t_{$1}($2)$');
357
+ // Gamma(a, b) → $\text{Gamma}(a, b)$
358
+ processed = processed.replace(/Gamma\(([^)]+)\)/g, '$\\text{Gamma}($1)$');
359
+ // Exponential(x) → $\text{Exp}(x)$
360
+ processed = processed.replace(/Exponential\(([^)]+)\)/g, '$\\text{Exp}($1)$');
361
+ // Update cell with padding
362
+ cells[cellIdx] = ` ${processed} `;
363
+ }
364
+ });
365
+ return cells.join('|');
366
+ })
367
+ .join('\n');
368
+ return headerAndSep + processedBody + '\n';
369
+ });
370
+ }
371
+ /**
372
+ * Prepare paper.md for specific output format
373
+ */
374
+ export function prepareForFormat(paperPath, format, config, options = {}) {
375
+ const directory = path.dirname(paperPath);
376
+ let content = fs.readFileSync(paperPath, 'utf-8');
377
+ // Build crossref registry for reference conversion
378
+ // Pass sections from config to ensure correct file ordering
379
+ const registry = buildRegistry(directory, config.sections);
380
+ if (format === 'pdf' || format === 'tex') {
381
+ // Strip all annotations for clean output
382
+ content = stripAnnotations(content);
383
+ // Process tables for nowrap columns (convert Normal() → $\mathcal{N}()$ etc.)
384
+ content = processTablesForFormat(content, config.tables, format);
385
+ }
386
+ else if (format === 'docx') {
387
+ // Strip track changes, optionally keep comments
388
+ content = stripAnnotations(content, { keepComments: config.docx.keepComments });
389
+ // Convert @fig:label to "Figure 1" for Word readers
390
+ content = convertDynamicRefsToDisplay(content, registry);
391
+ }
392
+ else if (format === 'beamer' || format === 'pptx') {
393
+ // Strip annotations for slide output
394
+ content = stripAnnotations(content);
395
+ // Process slide syntax (::: step, ::: notes)
396
+ if (hasSlideSyntax(content)) {
397
+ content = processSlideMarkdown(content, format);
398
+ }
399
+ }
400
+ // Write to temporary file
401
+ const preparedPath = path.join(directory, `.paper-${format}.md`);
402
+ fs.writeFileSync(preparedPath, content, 'utf-8');
403
+ return preparedPath;
404
+ }
405
+ /**
406
+ * Convert @fig:label references to display format (Figure 1)
407
+ */
408
+ function convertDynamicRefsToDisplay(text, registry) {
409
+ const refs = detectDynamicRefs(text);
410
+ // Process in reverse order to preserve positions
411
+ let result = text;
412
+ for (let i = refs.length - 1; i >= 0; i--) {
413
+ const ref = refs[i];
414
+ if (!ref)
415
+ continue;
416
+ const display = labelToDisplay(ref.type, ref.label, registry);
417
+ if (display) {
418
+ result = result.slice(0, ref.position) + display + result.slice(ref.position + ref.match.length);
419
+ }
420
+ }
421
+ return result;
422
+ }
423
+ /**
424
+ * Build pandoc arguments for format
425
+ */
426
+ export function buildPandocArgs(format, config, outputPath) {
427
+ const args = [];
428
+ // Output format
429
+ if (format === 'tex') {
430
+ args.push('-t', 'latex');
431
+ if (config.tex.standalone) {
432
+ args.push('-s');
433
+ }
434
+ }
435
+ else if (format === 'pdf') {
436
+ args.push('-t', 'pdf');
437
+ }
438
+ else if (format === 'docx') {
439
+ args.push('-t', 'docx');
440
+ }
441
+ else if (format === 'beamer') {
442
+ args.push('-t', 'beamer');
443
+ }
444
+ else if (format === 'pptx') {
445
+ args.push('-t', 'pptx');
446
+ }
447
+ // Output file (use basename since we set cwd to directory in runPandoc)
448
+ args.push('-o', path.basename(outputPath));
449
+ // Crossref filter (if available) - skip for slides
450
+ if (hasPandocCrossref() && format !== 'beamer' && format !== 'pptx') {
451
+ args.push('--filter', 'pandoc-crossref');
452
+ }
453
+ // Bibliography
454
+ if (config.bibliography) {
455
+ args.push('--citeproc');
456
+ }
457
+ // Format-specific options
458
+ if (format === 'pdf') {
459
+ if (config.pdf.template) {
460
+ args.push('--template', config.pdf.template);
461
+ }
462
+ args.push('-V', `documentclass=${config.pdf.documentclass}`);
463
+ args.push('-V', `fontsize=${config.pdf.fontsize}`);
464
+ args.push('-V', `geometry:${config.pdf.geometry}`);
465
+ if (config.pdf.linestretch !== 1) {
466
+ args.push('-V', `linestretch=${config.pdf.linestretch}`);
467
+ }
468
+ if (config.pdf.numbersections) {
469
+ args.push('--number-sections');
470
+ }
471
+ if (config.pdf.toc) {
472
+ args.push('--toc');
473
+ }
474
+ }
475
+ else if (format === 'docx') {
476
+ if (config.docx.reference) {
477
+ args.push('--reference-doc', config.docx.reference);
478
+ }
479
+ if (config.docx.toc) {
480
+ args.push('--toc');
481
+ }
482
+ }
483
+ else if (format === 'beamer') {
484
+ // Beamer slide options
485
+ const beamer = config.beamer || {};
486
+ if (beamer.theme) {
487
+ args.push('-V', `theme=${beamer.theme}`);
488
+ }
489
+ if (beamer.colortheme) {
490
+ args.push('-V', `colortheme=${beamer.colortheme}`);
491
+ }
492
+ if (beamer.fonttheme) {
493
+ args.push('-V', `fonttheme=${beamer.fonttheme}`);
494
+ }
495
+ if (beamer.aspectratio) {
496
+ args.push('-V', `aspectratio=${beamer.aspectratio}`);
497
+ }
498
+ if (beamer.navigation) {
499
+ args.push('-V', `navigation=${beamer.navigation}`);
500
+ }
501
+ // Speaker notes - default to 'show' which creates presenter view PDF
502
+ // Options: 'show' (dual screen), 'only' (notes only), 'hide' (no notes), false (disabled)
503
+ const notesMode = beamer.notes !== undefined ? beamer.notes : 'show';
504
+ if (notesMode && notesMode !== 'hide') {
505
+ args.push('-V', `classoption=notes=${notesMode}`);
506
+ }
507
+ // Fit images within slide bounds (default: true)
508
+ if (beamer.fit_images !== false) {
509
+ const fitImagesHeader = `\\makeatletter
510
+ \\def\\maxwidth{\\ifdim\\Gin@nat@width>\\linewidth\\linewidth\\else\\Gin@nat@width\\fi}
511
+ \\def\\maxheight{\\ifdim\\Gin@nat@height>0.75\\textheight 0.75\\textheight\\else\\Gin@nat@height\\fi}
512
+ \\makeatother
513
+ \\setkeys{Gin}{width=\\maxwidth,height=\\maxheight,keepaspectratio}`;
514
+ args.push('-V', `header-includes=${fitImagesHeader}`);
515
+ }
516
+ // Slides need standalone
517
+ args.push('-s');
518
+ }
519
+ else if (format === 'pptx') {
520
+ // PowerPoint options - handled separately in preparePptxTemplate
521
+ // Reference doc is set by caller after template generation
522
+ }
523
+ return args;
524
+ }
525
+ /**
526
+ * Write crossref.yaml if needed
527
+ */
528
+ function ensureCrossrefConfig(directory, config) {
529
+ const crossrefPath = path.join(directory, 'crossref.yaml');
530
+ if (!fs.existsSync(crossrefPath) && hasPandocCrossref()) {
531
+ fs.writeFileSync(crossrefPath, YAML.stringify(config.crossref), 'utf-8');
532
+ }
533
+ }
534
+ /**
535
+ * Get install instructions for missing dependency
536
+ */
537
+ function getInstallInstructions(tool) {
538
+ const instructions = {
539
+ pandoc: 'https://pandoc.org/installing.html',
540
+ latex: 'https://www.latex-project.org/get/',
541
+ };
542
+ return instructions[tool] || 'Check documentation';
543
+ }
544
+ /**
545
+ * Run pandoc build
546
+ */
547
+ export async function runPandoc(inputPath, format, config, options = {}) {
548
+ const directory = path.dirname(inputPath);
549
+ const baseName = config.title
550
+ ? config.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50)
551
+ : 'paper';
552
+ // Map format to file extension
553
+ const extMap = {
554
+ tex: '.tex',
555
+ pdf: '.pdf',
556
+ docx: '.docx',
557
+ beamer: '.pdf', // beamer outputs PDF
558
+ pptx: '.pptx',
559
+ };
560
+ const ext = extMap[format] || '.pdf';
561
+ // For beamer, use -slides suffix to distinguish from regular PDF
562
+ const suffix = format === 'beamer' ? '-slides' : '';
563
+ // Allow custom output path via options
564
+ const outputPath = options.outputPath || path.join(directory, `${baseName}${suffix}${ext}`);
565
+ // Ensure crossref.yaml exists
566
+ ensureCrossrefConfig(directory, config);
567
+ const args = buildPandocArgs(format, config, outputPath);
568
+ // Handle PPTX reference template and themes
569
+ let pptxMediaDir = null;
570
+ if (format === 'pptx') {
571
+ const pptx = config.pptx || {};
572
+ // Determine media directory (default: pptx/media or slides/media)
573
+ let mediaDir = pptx.media;
574
+ if (!mediaDir) {
575
+ if (fs.existsSync(path.join(directory, 'pptx', 'media'))) {
576
+ mediaDir = path.join(directory, 'pptx', 'media');
577
+ }
578
+ else if (fs.existsSync(path.join(directory, 'slides', 'media'))) {
579
+ mediaDir = path.join(directory, 'slides', 'media');
580
+ }
581
+ }
582
+ else if (!path.isAbsolute(mediaDir)) {
583
+ mediaDir = path.join(directory, mediaDir);
584
+ }
585
+ pptxMediaDir = mediaDir || null;
586
+ // Determine reference doc: custom reference overrides theme
587
+ let referenceDoc = null;
588
+ if (pptx.reference && fs.existsSync(path.join(directory, pptx.reference))) {
589
+ // Custom reference doc takes precedence
590
+ referenceDoc = path.join(directory, pptx.reference);
591
+ }
592
+ else {
593
+ // Use built-in theme (default: 'default')
594
+ const themeName = pptx.theme || 'default';
595
+ const themePath = getThemePath(themeName);
596
+ if (themePath && fs.existsSync(themePath)) {
597
+ referenceDoc = themePath;
598
+ }
599
+ }
600
+ if (referenceDoc) {
601
+ args.push('--reference-doc', referenceDoc);
602
+ }
603
+ // Add color filter for PPTX (handles [text]{color=#RRGGBB} syntax)
604
+ const colorFilterPath = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')), 'pptx-color-filter.lua');
605
+ if (fs.existsSync(colorFilterPath)) {
606
+ args.push('--lua-filter', colorFilterPath);
607
+ }
608
+ }
609
+ // Add crossref metadata file if exists (skip for slides - they don't use crossref)
610
+ if (format !== 'beamer' && format !== 'pptx') {
611
+ const crossrefPath = path.join(directory, 'crossref.yaml');
612
+ if (fs.existsSync(crossrefPath) && hasPandocCrossref()) {
613
+ // Use basename since we set cwd to directory
614
+ args.push('--metadata-file', 'crossref.yaml');
615
+ }
616
+ }
617
+ // Input file (use basename since we set cwd to directory)
618
+ args.push(path.basename(inputPath));
619
+ return new Promise((resolve) => {
620
+ const pandoc = spawn('pandoc', args, {
621
+ cwd: directory,
622
+ stdio: ['ignore', 'pipe', 'pipe'],
623
+ });
624
+ let stderr = '';
625
+ pandoc.stderr?.on('data', (data) => {
626
+ stderr += data.toString();
627
+ });
628
+ pandoc.on('close', async (code) => {
629
+ if (code === 0) {
630
+ // For PPTX, post-process to add slide numbers, buildup colors, and logos
631
+ if (format === 'pptx') {
632
+ try {
633
+ // Inject slide numbers into content slides only
634
+ await injectSlideNumbers(outputPath);
635
+ }
636
+ catch (e) {
637
+ // Slide number injection failed but output was created
638
+ }
639
+ try {
640
+ // Apply colors (default text color, title color, buildup greying)
641
+ const pptxConfig = config.pptx || {};
642
+ const colorsConfig = pptxConfig.colors || {};
643
+ const buildupConfig = pptxConfig.buildup || {};
644
+ // Merge colors and buildup config for applyBuildupColors
645
+ const colorConfig = {
646
+ default: colorsConfig.default,
647
+ title: colorsConfig.title,
648
+ grey: buildupConfig.grey,
649
+ accent: buildupConfig.accent,
650
+ enabled: buildupConfig.enabled
651
+ };
652
+ await applyBuildupColors(outputPath, colorConfig);
653
+ }
654
+ catch (e) {
655
+ // Color application failed but output was created
656
+ }
657
+ // Inject logos into cover slide (if media dir configured)
658
+ if (pptxMediaDir) {
659
+ try {
660
+ await injectMediaIntoPptx(outputPath, pptxMediaDir);
661
+ }
662
+ catch (e) {
663
+ // Logo injection failed but output was created
664
+ }
665
+ }
666
+ }
667
+ // Run user postprocess scripts
668
+ const postResult = await runPostprocess(outputPath, format, config, options);
669
+ if (!postResult.success && options.verbose) {
670
+ console.error(`Postprocess warning: ${postResult.error}`);
671
+ }
672
+ resolve({ outputPath, success: true });
673
+ }
674
+ else {
675
+ resolve({ outputPath, success: false, error: stderr || `Exit code ${code}` });
676
+ }
677
+ });
678
+ pandoc.on('error', (err) => {
679
+ resolve({ outputPath, success: false, error: err.message });
680
+ });
681
+ });
682
+ }
683
+ /**
684
+ * Full build pipeline
685
+ */
686
+ export async function build(directory, formats = ['pdf', 'docx'], options = {}) {
687
+ const warnings = [];
688
+ let forwardRefsResolved = 0;
689
+ // Check pandoc
690
+ if (!hasPandoc()) {
691
+ const instruction = getInstallInstructions('pandoc');
692
+ throw new Error(`Pandoc not found. Install with: ${instruction}\nOr run: rev doctor`);
693
+ }
694
+ // Check LaTeX if PDF is requested
695
+ if ((formats.includes('pdf') || formats.includes('all')) && !hasLatex()) {
696
+ warnings.push(`LaTeX not found - PDF generation may fail. Install with: ${getInstallInstructions('latex')}`);
697
+ }
698
+ // Check pandoc-crossref
699
+ if (!hasPandocCrossref()) {
700
+ warnings.push('pandoc-crossref not found - figure/table numbering will not work');
701
+ }
702
+ // Load config (use passed config if provided, otherwise load from file)
703
+ const config = options.config || loadConfig(directory);
704
+ // Combine sections → paper.md
705
+ const buildOptions = { ...options };
706
+ const paperPath = combineSections(directory, config, buildOptions);
707
+ forwardRefsResolved = buildOptions._forwardRefsResolved || 0;
708
+ const refsAutoInjected = buildOptions._refsAutoInjected || false;
709
+ // Expand 'all' to all formats
710
+ if (formats.includes('all')) {
711
+ formats = ['pdf', 'docx', 'tex'];
712
+ }
713
+ // Build and save image registry when DOCX is being built
714
+ // This allows import to restore proper image syntax from Word documents
715
+ if (formats.includes('docx')) {
716
+ const paperContent = fs.readFileSync(paperPath, 'utf-8');
717
+ const crossrefReg = buildRegistry(directory, config.sections);
718
+ const imageReg = buildImageRegistry(paperContent, crossrefReg);
719
+ if (imageReg.figures?.length > 0) {
720
+ writeImageRegistry(directory, imageReg);
721
+ }
722
+ }
723
+ const results = [];
724
+ for (const format of formats) {
725
+ // Prepare format-specific version
726
+ const preparedPath = prepareForFormat(paperPath, format, config, options);
727
+ // Run pandoc
728
+ const result = await runPandoc(preparedPath, format, config, options);
729
+ results.push({ format, ...result });
730
+ // Clean up temp file
731
+ try {
732
+ fs.unlinkSync(preparedPath);
733
+ }
734
+ catch {
735
+ // Ignore cleanup errors
736
+ }
737
+ }
738
+ return { results, paperPath, warnings, forwardRefsResolved, refsAutoInjected };
739
+ }
740
+ /**
741
+ * Get build status summary
742
+ */
743
+ export function formatBuildResults(results) {
744
+ const lines = [];
745
+ for (const r of results) {
746
+ if (r.success) {
747
+ lines.push(` ${r.format.toUpperCase()}: ${path.basename(r.outputPath)}`);
748
+ }
749
+ else {
750
+ lines.push(` ${r.format.toUpperCase()}: FAILED - ${r.error}`);
751
+ }
752
+ }
753
+ return lines.join('\n');
754
+ }
755
+ //# sourceMappingURL=build.js.map