apex-ruby 1.0.18 → 1.0.20

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 (207) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -3
  3. data/ext/apex_ext/apex_ext.c +260 -91
  4. data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
  5. data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
  6. data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
  7. data/ext/apex_ext/apex_src/Package.swift +15 -4
  8. data/ext/apex_ext/apex_src/README.md +94 -35
  9. data/ext/apex_ext/apex_src/VERSION +1 -1
  10. data/ext/apex_ext/apex_src/apex-debug.log +4 -0
  11. data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
  12. data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
  13. data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
  14. data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
  15. data/ext/apex_ext/apex_src/cli/main.c +262 -33
  16. data/ext/apex_ext/apex_src/debug.log +7 -0
  17. data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
  18. data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
  19. data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
  20. data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
  21. data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
  22. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
  23. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
  24. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
  25. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
  26. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
  27. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
  28. data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
  29. data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
  30. data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
  31. data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
  32. data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
  33. data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
  34. data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
  35. data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
  36. data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
  37. data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
  38. data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
  39. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
  40. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
  41. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
  42. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
  43. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
  44. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
  45. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
  46. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
  47. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
  48. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
  49. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
  50. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
  51. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
  52. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
  53. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
  54. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
  55. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
  56. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
  57. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
  58. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
  59. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
  60. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
  61. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
  62. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
  63. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
  64. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
  65. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
  66. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
  67. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
  68. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
  69. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
  70. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
  71. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
  72. data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
  73. data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
  74. data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
  75. data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
  76. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
  77. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
  78. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
  79. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
  80. data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
  81. data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
  82. data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
  83. data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
  84. data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
  85. data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
  86. data/ext/apex_ext/apex_src/include/apexc.h +1 -0
  87. data/ext/apex_ext/apex_src/man/apex.1 +82 -78
  88. data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
  89. data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
  90. data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
  91. data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
  92. data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
  93. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
  94. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
  95. data/ext/apex_ext/apex_src/output.html +87 -0
  96. data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
  97. data/ext/apex_ext/apex_src/src/_README.md +85 -32
  98. data/ext/apex_ext/apex_src/src/apex.c +770 -200
  99. data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
  100. data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
  101. data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
  102. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
  103. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
  104. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
  105. data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
  106. data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
  107. data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
  108. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
  109. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
  110. data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
  111. data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
  112. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
  113. data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
  114. data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
  115. data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
  116. data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
  117. data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
  118. data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
  119. data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
  120. data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
  121. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
  122. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
  123. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
  124. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
  125. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
  126. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
  127. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
  128. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
  129. data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
  130. data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
  131. data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
  132. data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
  133. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
  134. data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
  135. data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
  136. data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
  137. data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
  138. data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
  139. data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
  140. data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
  141. data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
  142. data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
  143. data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
  144. data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
  145. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
  146. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
  147. data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
  148. data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
  149. data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
  150. data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
  151. data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
  152. data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
  153. data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
  154. data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
  155. data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
  156. data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
  157. data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
  158. data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
  159. data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
  160. data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
  161. data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
  162. data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
  163. data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
  164. data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
  165. data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
  166. data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
  167. data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
  168. data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
  169. data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
  170. data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
  171. data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
  172. data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
  173. data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
  174. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
  175. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
  176. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
  177. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
  178. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
  179. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
  180. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
  181. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
  182. data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
  183. data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
  184. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
  185. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
  186. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
  187. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
  188. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  189. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  190. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
  191. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
  192. data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
  193. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
  194. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
  195. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
  196. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  197. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
  198. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
  199. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
  200. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
  201. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
  202. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
  203. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
  204. data/ext/apex_ext/extconf.rb +35 -0
  205. data/lib/apex/version.rb +2 -2
  206. metadata +117 -2
  207. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
@@ -0,0 +1,1148 @@
1
+ # Apex Node.js npm Package Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use
4
+ > superpowers:subagent-driven-development (recommended) or
5
+ > superpowers:executing-plans to implement this plan task-by-task. Steps use
6
+ > checkbox (`- [ ]`) syntax for tracking.
7
+
8
+ **Goal:** Ship `@apexmarkdown/apex` as a Node.js native binding that converts
9
+ Markdown with Apex modes and serializable options on macOS/Linux without
10
+ system C libraries.
11
+
12
+ **Architecture:** Create a sibling `apex-node` repository that vendors Apex as
13
+ a submodule, builds a CMake Node-API addon linking bundled cmark-gfm and
14
+ libyaml, and distributes platform `.node` binaries as optional npm packages
15
+ selected by a small JavaScript loader.
16
+
17
+ **Tech Stack:** Node.js 20+, Node-API (`node-addon-api`), CMake, C++17,
18
+ vendored Apex/cmark-gfm/libyaml, npm optionalDependencies, GitHub Actions.
19
+
20
+ ## Global Constraints
21
+
22
+ - Package name is `@apexmarkdown/apex`.
23
+ - Repository path is `/Users/ttscoff/Desktop/Code/apex-node`.
24
+ - Supported platforms for v1: macOS and Linux, x64 and arm64.
25
+ - Supported Node.js: 20 or newer.
26
+ - Bundle Apex, cmark-gfm, and libyaml; do not require system libraries.
27
+ - Enable `APEX_HAVE_LIBYAML=1`.
28
+ - Public API is synchronous `convert(markdown, options?)` and `version`.
29
+ - Option names are camelCase only.
30
+ - Expose every serializable user-facing `apex_options` field.
31
+ - Exclude callbacks and TOC output pointers.
32
+ - Unknown names or wrong types throw `TypeError`.
33
+ - No WASM, Windows, async workers, or structured TOC API in v1.
34
+ - Work and commits happen in `apex-node`, not in the Apex core tree
35
+ (except this plan/spec already stored under Apex docs).
36
+
37
+ ---
38
+
39
+ ## File Structure
40
+
41
+ All paths below are relative to `/Users/ttscoff/Desktop/Code/apex-node`
42
+ unless noted.
43
+
44
+ - Create `package.json`: public package metadata, scripts, optionalDeps.
45
+ - Create `README.md`, `LICENSE`, `.gitignore`, `.npmrc`.
46
+ - Create `vendor/apex`: Apex git submodule.
47
+ - Create `binding/CMakeLists.txt`: build Apex static lib + Node addon.
48
+ - Create `binding/src/addon.cpp`: Node-API `convert` / `version`.
49
+ - Create `binding/src/options.h` / `options.cpp`: JS object → `apex_options`.
50
+ - Create `lib/index.js`: platform loader and re-exports.
51
+ - Create `lib/index.d.ts`: TypeScript declarations.
52
+ - Create `lib/load-native.js`: resolve platform optional package.
53
+ - Create `test/*.test.js`: conversion, options, loader, errors.
54
+ - Create `scripts/build.js`: cmake-js wrapper for local builds.
55
+ - Create `scripts/package-prebuild.js`: emit a platform package tarball dir.
56
+ - Create `platforms/package.template.json`: template for platform packages.
57
+ - Create `.github/workflows/ci.yml` and `publish-prebuilds.yml`.
58
+
59
+ ### Task 1: Scaffold Repository and Failing Smoke Tests
60
+
61
+ **Files:**
62
+
63
+ - Create: `/Users/ttscoff/Desktop/Code/apex-node/` (new git repo)
64
+ - Create: `package.json`
65
+ - Create: `.gitignore`
66
+ - Create: `LICENSE`
67
+ - Create: `test/convert.test.js`
68
+ - Create: `test/options-validation.test.js`
69
+ - Create: `vendor/apex` (submodule)
70
+
71
+ **Interfaces:**
72
+
73
+ - Produces: npm package skeleton named `@apexmarkdown/apex` at version
74
+ `0.1.0`
75
+ - Produces: tests that import `{ convert, version }` from `../lib/index.js`
76
+ - Consumes: Apex submodule at GitHub `ApexMarkdown/apex`
77
+
78
+ - [ ] **Step 1: Create the repository and submodule**
79
+
80
+ ```bash
81
+ mkdir -p /Users/ttscoff/Desktop/Code/apex-node
82
+ cd /Users/ttscoff/Desktop/Code/apex-node
83
+ git init
84
+ git submodule add https://github.com/ApexMarkdown/apex.git vendor/apex
85
+ git submodule update --init --recursive
86
+ ```
87
+
88
+ Expected: `vendor/apex/include/apex/apex.h` exists and
89
+ `vendor/apex/vendor/cmark-gfm` / `vendor/apex/vendor/libyaml` are populated.
90
+
91
+ - [ ] **Step 2: Add package metadata and ignore rules**
92
+
93
+ Create `.gitignore`:
94
+
95
+ ```gitignore
96
+ node_modules/
97
+ build/
98
+ prebuilds/
99
+ *.node
100
+ .DS_Store
101
+ coverage/
102
+ npm-debug.log*
103
+ ```
104
+
105
+ Create `package.json`:
106
+
107
+ ```json
108
+ {
109
+ "name": "@apexmarkdown/apex",
110
+ "version": "0.1.0",
111
+ "description": "Node.js bindings for the Apex unified Markdown processor",
112
+ "license": "MIT",
113
+ "type": "commonjs",
114
+ "main": "./lib/index.js",
115
+ "types": "./lib/index.d.ts",
116
+ "exports": {
117
+ ".": {
118
+ "types": "./lib/index.d.ts",
119
+ "require": "./lib/index.js",
120
+ "import": "./lib/index.js"
121
+ }
122
+ },
123
+ "engines": {
124
+ "node": ">=20"
125
+ },
126
+ "files": [
127
+ "lib/",
128
+ "binding/",
129
+ "scripts/",
130
+ "README.md",
131
+ "LICENSE"
132
+ ],
133
+ "scripts": {
134
+ "build": "node scripts/build.js",
135
+ "test": "node --test test/**/*.test.js",
136
+ "package:prebuild": "node scripts/package-prebuild.js"
137
+ },
138
+ "optionalDependencies": {
139
+ "@apexmarkdown/apex-darwin-arm64": "0.1.0",
140
+ "@apexmarkdown/apex-darwin-x64": "0.1.0",
141
+ "@apexmarkdown/apex-linux-arm64": "0.1.0",
142
+ "@apexmarkdown/apex-linux-x64": "0.1.0"
143
+ },
144
+ "devDependencies": {
145
+ "cmake-js": "^7.3.0",
146
+ "node-addon-api": "^8.3.0"
147
+ }
148
+ }
149
+ ```
150
+
151
+ Create `LICENSE` as MIT, copyright Brett Terpstra.
152
+
153
+ - [ ] **Step 3: Write failing smoke tests**
154
+
155
+ Create `test/convert.test.js`:
156
+
157
+ ```js
158
+ const { describe, it } = require("node:test");
159
+ const assert = require("node:assert/strict");
160
+ const { convert, version } = require("../lib/index.js");
161
+
162
+ describe("convert", () => {
163
+ it("renders a heading by default", () => {
164
+ const html = convert("# Hello");
165
+ assert.match(html, /<h1[^>]*>Hello<\/h1>/);
166
+ });
167
+
168
+ it("honors gfm mode for strikethrough", () => {
169
+ const html = convert("~~x~~", { mode: "gfm" });
170
+ assert.match(html, /<del>x<\/del>/);
171
+ });
172
+
173
+ it("returns empty string for empty input", () => {
174
+ assert.equal(convert(""), "");
175
+ });
176
+
177
+ it("preserves utf8", () => {
178
+ const html = convert("café 漢字");
179
+ assert.match(html, /café 漢字/);
180
+ });
181
+ });
182
+
183
+ describe("version", () => {
184
+ it("exposes a non-empty version string", () => {
185
+ assert.equal(typeof version, "string");
186
+ assert.notEqual(version.length, 0);
187
+ });
188
+ });
189
+ ```
190
+
191
+ Create `test/options-validation.test.js`:
192
+
193
+ ```js
194
+ const { describe, it } = require("node:test");
195
+ const assert = require("node:assert/strict");
196
+ const { convert } = require("../lib/index.js");
197
+
198
+ describe("option validation", () => {
199
+ it("rejects unknown option names", () => {
200
+ assert.throws(
201
+ () => convert("# x", { notARealOption: true }),
202
+ (err) => err instanceof TypeError && /Unknown option/.test(err.message)
203
+ );
204
+ });
205
+
206
+ it("rejects wrong boolean types", () => {
207
+ assert.throws(
208
+ () => convert("# x", { unsafe: "yes" }),
209
+ (err) => err instanceof TypeError && /unsafe/.test(err.message)
210
+ );
211
+ });
212
+
213
+ it("rejects invalid mode strings", () => {
214
+ assert.throws(
215
+ () => convert("# x", { mode: "pandoc" }),
216
+ (err) => err instanceof TypeError && /mode/.test(err.message)
217
+ );
218
+ });
219
+
220
+ it("rejects non-string markdown", () => {
221
+ assert.throws(
222
+ () => convert(42),
223
+ (err) => err instanceof TypeError
224
+ );
225
+ });
226
+ });
227
+ ```
228
+
229
+ - [ ] **Step 4: Install deps and confirm tests fail**
230
+
231
+ ```bash
232
+ cd /Users/ttscoff/Desktop/Code/apex-node
233
+ npm install
234
+ npm test
235
+ ```
236
+
237
+ Expected: FAIL because `lib/index.js` does not exist / native addon missing.
238
+
239
+ - [ ] **Step 5: Commit scaffold**
240
+
241
+ ```bash
242
+ cd /Users/ttscoff/Desktop/Code/apex-node
243
+ git add .
244
+ git commit -m "$(cat <<'EOF'
245
+ Scaffold Apex Node package and failing smoke tests.
246
+
247
+ @new **Node package scaffold** for @apexmarkdown/apex with Apex submodule and smoke tests.
248
+
249
+ EOF
250
+ )"
251
+ ```
252
+
253
+ ### Task 2: CMake Build and Minimal Native Convert
254
+
255
+ **Files:**
256
+
257
+ - Create: `binding/CMakeLists.txt`
258
+ - Create: `binding/src/addon.cpp`
259
+ - Create: `binding/src/options.h`
260
+ - Create: `binding/src/options.cpp` (stub that only accepts empty/undefined options)
261
+ - Create: `scripts/build.js`
262
+ - Create: `lib/load-native.js`
263
+ - Create: `lib/index.js`
264
+ - Create: `lib/index.d.ts` (minimal)
265
+
266
+ **Interfaces:**
267
+
268
+ - Produces native exports:
269
+ - `convert(markdown: string, options?: object): string`
270
+ - `version: string`
271
+ - Produces:
272
+ `bool apex_node_options_from_js(Napi::Env, Napi::Value, apex_options *,
273
+ ApexNodeOptionStorage *, std::string *error)`
274
+ - Produces loader:
275
+ `function loadNative(): { convert, version }`
276
+
277
+ - [ ] **Step 1: Add CMake that builds bundled Apex and the addon**
278
+
279
+ Create `binding/CMakeLists.txt`:
280
+
281
+ ```cmake
282
+ cmake_minimum_required(VERSION 3.15)
283
+ project(apex_node LANGUAGES C CXX)
284
+
285
+ set(CMAKE_CXX_STANDARD 17)
286
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
287
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
288
+
289
+ set(APEX_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../vendor/apex")
290
+
291
+ set(CMARK_TESTS OFF CACHE BOOL "" FORCE)
292
+ set(CMARK_SHARED OFF CACHE BOOL "" FORCE)
293
+ set(CMARK_STATIC ON CACHE BOOL "" FORCE)
294
+ add_subdirectory("${APEX_ROOT}/vendor/cmark-gfm" "${CMAKE_BINARY_DIR}/cmark-gfm")
295
+
296
+ set(YAML_SRC
297
+ ${APEX_ROOT}/vendor/libyaml/src/api.c
298
+ ${APEX_ROOT}/vendor/libyaml/src/dumper.c
299
+ ${APEX_ROOT}/vendor/libyaml/src/emitter.c
300
+ ${APEX_ROOT}/vendor/libyaml/src/loader.c
301
+ ${APEX_ROOT}/vendor/libyaml/src/parser.c
302
+ ${APEX_ROOT}/vendor/libyaml/src/reader.c
303
+ ${APEX_ROOT}/vendor/libyaml/src/scanner.c
304
+ ${APEX_ROOT}/vendor/libyaml/src/writer.c
305
+ )
306
+ add_library(apex_yaml STATIC ${YAML_SRC})
307
+ target_include_directories(apex_yaml PUBLIC
308
+ ${APEX_ROOT}/vendor/libyaml/include
309
+ ${APEX_ROOT}/vendor/libyaml/src
310
+ )
311
+ target_compile_definitions(apex_yaml PUBLIC
312
+ YAML_VERSION_MAJOR=0
313
+ YAML_VERSION_MINOR=2
314
+ YAML_VERSION_PATCH=5
315
+ YAML_VERSION_STRING="0.2.5"
316
+ HAVE_CONFIG_H=0
317
+ )
318
+
319
+ set(APEX_LIB_SOURCES
320
+ ${APEX_ROOT}/src/apex.c
321
+ ${APEX_ROOT}/src/ast_json.c
322
+ ${APEX_ROOT}/src/ast_markdown.c
323
+ ${APEX_ROOT}/src/ast_terminal.c
324
+ ${APEX_ROOT}/src/ast_man.c
325
+ ${APEX_ROOT}/src/filters_ast.c
326
+ ${APEX_ROOT}/src/plugins_env.c
327
+ ${APEX_ROOT}/src/plugins.c
328
+ ${APEX_ROOT}/src/plugins_remote.c
329
+ ${APEX_ROOT}/src/plugin_catalog.c
330
+ ${APEX_ROOT}/src/html_renderer.c
331
+ ${APEX_ROOT}/src/extensions/metadata.c
332
+ ${APEX_ROOT}/src/extensions/wiki_links.c
333
+ ${APEX_ROOT}/src/extensions/math.c
334
+ ${APEX_ROOT}/src/extensions/critic.c
335
+ ${APEX_ROOT}/src/extensions/callouts.c
336
+ ${APEX_ROOT}/src/extensions/raw_content.c
337
+ ${APEX_ROOT}/src/extensions/code_fence_attrs.c
338
+ ${APEX_ROOT}/src/extensions/quarto_diagrams.c
339
+ ${APEX_ROOT}/src/extensions/quarto_shortcodes.c
340
+ ${APEX_ROOT}/src/extensions/quarto_polish.c
341
+ ${APEX_ROOT}/src/extensions/quarto_lists.c
342
+ ${APEX_ROOT}/src/extensions/includes.c
343
+ ${APEX_ROOT}/src/extensions/inline_tables.c
344
+ ${APEX_ROOT}/src/extensions/toc.c
345
+ ${APEX_ROOT}/src/extensions/abbreviations.c
346
+ ${APEX_ROOT}/src/extensions/emoji.c
347
+ ${APEX_ROOT}/src/extensions/special_markers.c
348
+ ${APEX_ROOT}/src/extensions/ial.c
349
+ ${APEX_ROOT}/src/extensions/bear_image_attrs.c
350
+ ${APEX_ROOT}/src/extensions/definition_list.c
351
+ ${APEX_ROOT}/src/extensions/advanced_footnotes.c
352
+ ${APEX_ROOT}/src/extensions/advanced_tables.c
353
+ ${APEX_ROOT}/src/extensions/html_markdown.c
354
+ ${APEX_ROOT}/src/extensions/fenced_divs.c
355
+ ${APEX_ROOT}/src/extensions/table_html_postprocess.c
356
+ ${APEX_ROOT}/src/extensions/inline_footnotes.c
357
+ ${APEX_ROOT}/src/extensions/highlight.c
358
+ ${APEX_ROOT}/src/extensions/insert.c
359
+ ${APEX_ROOT}/src/extensions/sup_sub.c
360
+ ${APEX_ROOT}/src/extensions/header_ids.c
361
+ ${APEX_ROOT}/src/extensions/relaxed_tables.c
362
+ ${APEX_ROOT}/src/extensions/grid_tables.c
363
+ ${APEX_ROOT}/src/extensions/citations.c
364
+ ${APEX_ROOT}/src/extensions/index.c
365
+ ${APEX_ROOT}/src/extensions/syntax_highlight.c
366
+ ${APEX_ROOT}/src/pretty_html.c
367
+ ${APEX_ROOT}/src/buffer.c
368
+ ${APEX_ROOT}/src/parser.c
369
+ ${APEX_ROOT}/src/renderer.c
370
+ ${APEX_ROOT}/src/utf8.c
371
+ )
372
+
373
+ add_library(apex_static STATIC ${APEX_LIB_SOURCES})
374
+ target_include_directories(apex_static PUBLIC
375
+ ${APEX_ROOT}/include
376
+ ${APEX_ROOT}/include/apex
377
+ ${APEX_ROOT}/vendor/cmark-gfm/src
378
+ ${APEX_ROOT}/vendor/cmark-gfm/extensions
379
+ ${APEX_ROOT}/vendor/libyaml/include
380
+ )
381
+ target_compile_definitions(apex_static PUBLIC APEX_HAVE_LIBYAML=1)
382
+ target_link_libraries(apex_static PUBLIC
383
+ libcmark-gfm-extensions_static
384
+ libcmark-gfm_static
385
+ apex_yaml
386
+ )
387
+
388
+ add_library(${PROJECT_NAME} SHARED
389
+ src/addon.cpp
390
+ src/options.cpp
391
+ )
392
+ set_target_properties(${PROJECT_NAME} PROPERTIES
393
+ PREFIX ""
394
+ SUFFIX ".node"
395
+ )
396
+ target_include_directories(${PROJECT_NAME} PRIVATE
397
+ ${CMAKE_JS_INC}
398
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
399
+ )
400
+ target_link_libraries(${PROJECT_NAME} PRIVATE apex_static ${CMAKE_JS_LIB})
401
+ target_compile_definitions(${PROJECT_NAME} PRIVATE NAPI_VERSION=8)
402
+ ```
403
+
404
+ If cmake-js does not inject `CMAKE_JS_INC` / `CMAKE_JS_LIB` correctly on the
405
+ host, pass them explicitly from `scripts/build.js` using
406
+ `require("node-addon-api").include` and Node’s `process.execPath` include
407
+ paths.
408
+
409
+ - [ ] **Step 2: Implement options stub and addon entry points**
410
+
411
+ Create `binding/src/options.h`:
412
+
413
+ ```cpp
414
+ #pragma once
415
+
416
+ #include <napi.h>
417
+ #include <string>
418
+ #include <vector>
419
+ #include "apex/apex.h"
420
+
421
+ struct ApexNodeOptionStorage {
422
+ std::vector<std::string> owned_strings;
423
+ std::vector<std::vector<char>> owned_cstrings;
424
+ std::vector<const char *> stylesheet_ptrs;
425
+ std::vector<char *> bibliography_ptrs;
426
+ std::vector<char *> script_tag_ptrs;
427
+ std::vector<const char *> ast_filter_ptrs;
428
+ };
429
+
430
+ bool apex_node_options_from_js(Napi::Env env,
431
+ Napi::Value value,
432
+ apex_options *out,
433
+ ApexNodeOptionStorage *storage,
434
+ std::string *error);
435
+ ```
436
+
437
+ Create `binding/src/options.cpp` stub:
438
+
439
+ ```cpp
440
+ #include "options.h"
441
+
442
+ bool apex_node_options_from_js(Napi::Env env,
443
+ Napi::Value value,
444
+ apex_options *out,
445
+ ApexNodeOptionStorage *storage,
446
+ std::string *error) {
447
+ (void)storage;
448
+ if (value.IsUndefined() || value.IsNull()) {
449
+ *out = apex_options_default();
450
+ return true;
451
+ }
452
+ if (!value.IsObject()) {
453
+ *error = "options must be an object";
454
+ return false;
455
+ }
456
+ Napi::Object obj = value.As<Napi::Object>();
457
+ Napi::Array names = obj.GetPropertyNames();
458
+ if (names.Length() > 0) {
459
+ *error = "Unknown option: " +
460
+ names.Get(uint32_t(0)).As<Napi::String>().Utf8Value();
461
+ return false;
462
+ }
463
+ *out = apex_options_default();
464
+ return true;
465
+ }
466
+ ```
467
+
468
+ Create `binding/src/addon.cpp`:
469
+
470
+ ```cpp
471
+ #include <napi.h>
472
+ #include <string>
473
+ #include "apex/apex.h"
474
+ #include "options.h"
475
+
476
+ static Napi::Value Convert(const Napi::CallbackInfo &info) {
477
+ Napi::Env env = info.Env();
478
+ if (info.Length() < 1 || !info[0].IsString()) {
479
+ Napi::TypeError::New(env, "markdown must be a string").ThrowAsJavaScriptException();
480
+ return env.Null();
481
+ }
482
+
483
+ std::string markdown = info[0].As<Napi::String>().Utf8Value();
484
+ ApexNodeOptionStorage storage;
485
+ apex_options options;
486
+ std::string error;
487
+ Napi::Value opts_val = info.Length() >= 2 ? info[1] : env.Undefined();
488
+ if (!apex_node_options_from_js(env, opts_val, &options, &storage, &error)) {
489
+ Napi::TypeError::New(env, error).ThrowAsJavaScriptException();
490
+ return env.Null();
491
+ }
492
+
493
+ char *html = apex_markdown_to_html(markdown.c_str(), markdown.size(), &options);
494
+ if (!html) {
495
+ Napi::Error::New(env, "Apex conversion failed").ThrowAsJavaScriptException();
496
+ return env.Null();
497
+ }
498
+ Napi::String result = Napi::String::New(env, html);
499
+ apex_free_string(html);
500
+ return result;
501
+ }
502
+
503
+ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
504
+ exports.Set("convert", Napi::Function::New(env, Convert));
505
+ exports.Set("version", Napi::String::New(env, apex_version_string()));
506
+ return exports;
507
+ }
508
+
509
+ NODE_API_MODULE(apex_node, Init)
510
+ ```
511
+
512
+ - [ ] **Step 3: Add build script and development loader**
513
+
514
+ Create `scripts/build.js`:
515
+
516
+ ```js
517
+ const { spawnSync } = require("node:child_process");
518
+ const path = require("node:path");
519
+
520
+ const root = path.join(__dirname, "..");
521
+ const result = spawnSync(
522
+ "npx",
523
+ ["cmake-js", "compile", "--directory", "binding"],
524
+ { cwd: root, stdio: "inherit", shell: process.platform === "win32" }
525
+ );
526
+ process.exit(result.status ?? 1);
527
+ ```
528
+
529
+ Create `lib/load-native.js`:
530
+
531
+ ```js
532
+ const fs = require("node:fs");
533
+ const path = require("node:path");
534
+ const os = require("node:os");
535
+
536
+ function platformPackageName() {
537
+ const platform = process.platform;
538
+ const arch = process.arch;
539
+ if (platform === "darwin" && arch === "arm64") return "@apexmarkdown/apex-darwin-arm64";
540
+ if (platform === "darwin" && arch === "x64") return "@apexmarkdown/apex-darwin-x64";
541
+ if (platform === "linux" && arch === "arm64") return "@apexmarkdown/apex-linux-arm64";
542
+ if (platform === "linux" && arch === "x64") return "@apexmarkdown/apex-linux-x64";
543
+ return null;
544
+ }
545
+
546
+ function loadNative() {
547
+ const candidates = [];
548
+ const pkg = platformPackageName();
549
+ if (pkg) {
550
+ try {
551
+ const resolved = require.resolve(`${pkg}/apex.node`);
552
+ candidates.push(resolved);
553
+ } catch (_) {
554
+ // optional dependency may be absent during local development
555
+ }
556
+ }
557
+
558
+ candidates.push(
559
+ path.join(__dirname, "..", "build", "Release", "apex_node.node"),
560
+ path.join(__dirname, "..", "binding", "build", "Release", "apex_node.node")
561
+ );
562
+
563
+ for (const candidate of candidates) {
564
+ if (fs.existsSync(candidate)) {
565
+ return require(candidate);
566
+ }
567
+ }
568
+
569
+ const detected = `${process.platform}-${process.arch}`;
570
+ throw new Error(
571
+ `Apex native addon is unavailable for ${detected}. ` +
572
+ `Install a supported optional dependency or run npm run build.`
573
+ );
574
+ }
575
+
576
+ module.exports = { loadNative, platformPackageName };
577
+ ```
578
+
579
+ Create `lib/index.js`:
580
+
581
+ ```js
582
+ const { loadNative } = require("./load-native.js");
583
+ const native = loadNative();
584
+
585
+ module.exports = {
586
+ convert: native.convert,
587
+ version: native.version
588
+ };
589
+ ```
590
+
591
+ Create minimal `lib/index.d.ts`:
592
+
593
+ ```ts
594
+ export interface ApexOptions {
595
+ [key: string]: unknown;
596
+ }
597
+
598
+ export declare function convert(markdown: string, options?: ApexOptions): string;
599
+ export declare const version: string;
600
+ ```
601
+
602
+ - [ ] **Step 4: Build and run convert smoke tests**
603
+
604
+ ```bash
605
+ cd /Users/ttscoff/Desktop/Code/apex-node
606
+ npm run build
607
+ node --test test/convert.test.js
608
+ ```
609
+
610
+ Expected: PASS for convert/version. Validation tests still FAIL for
611
+ non-empty options because the stub rejects every property.
612
+
613
+ - [ ] **Step 5: Commit native convert path**
614
+
615
+ ```bash
616
+ git add binding scripts lib
617
+ git commit -m "$(cat <<'EOF'
618
+ Add CMake Node-API convert binding for Apex.
619
+
620
+ @new **Native convert()** binding that bundles Apex, cmark-gfm, and libyaml.
621
+
622
+ EOF
623
+ )"
624
+ ```
625
+
626
+ ### Task 3: Complete Serializable Options Mapper
627
+
628
+ **Files:**
629
+
630
+ - Modify: `binding/src/options.cpp`
631
+ - Modify: `binding/src/options.h` if storage helpers need expansion
632
+ - Modify: `lib/index.d.ts`
633
+ - Modify: `test/options-validation.test.js`
634
+ - Create: `test/options-behavior.test.js`
635
+
636
+ **Interfaces:**
637
+
638
+ - Produces complete `apex_node_options_from_js(...)` covering every
639
+ serializable field listed below
640
+ - Produces typed `ApexOptions` in `lib/index.d.ts`
641
+
642
+ Serializable camelCase fields to implement (exclude callbacks / TOC outs):
643
+
644
+ **Booleans:**
645
+ `enablePlugins`, `allowExternalPluginDetection`, `enableTables`,
646
+ `enableFootnotes`, `enableDefinitionLists`, `enableSmartTypography`,
647
+ `enableMath`, `enableCriticMarkup`, `enableWikiLinks`, `enableTaskLists`,
648
+ `enableAttributes`, `enableCallouts`, `enablePyCallouts`,
649
+ `enableQuartoCallouts`, `enableQuartoExtensions`, `enableQuartoRaw`,
650
+ `enableQuartoExampleLists`, `enableQuartoLineBlocks`,
651
+ `enableQuartoRomanLists`, `enableQuartoCodeAttrs`, `enableQuartoDiagrams`,
652
+ `enableQuartoShortcodes`, `enableQuartoStrictLists`, `enableQuartoXrefs`,
653
+ `enableMarkedExtensions`, `enableDivs`, `enableSpans`, `enableGridTables`,
654
+ `stripMetadata`, `enableMetadataVariables`, `enableMetadataTransforms`,
655
+ `enableFileIncludes`, `unsafe`, `validateUtf8`, `githubPreLang`,
656
+ `standalone`, `pretty`, `xhtml`, `strictXhtml`, `paginate`,
657
+ `paginateSymbols`, `terminalInlineImages`, `hardbreaks`, `nobreaks`,
658
+ `generateHeaderIds`, `headerAnchors`, `relaxedTables`, `perCellAlignment`,
659
+ `allowMixedListMarkers`, `allowAlphaLists`, `enableSupSub`,
660
+ `enableStrikethrough`, `enableAutolink`, `obfuscateEmails`, `embedImages`,
661
+ `enableImageCaptions`, `titleCaptionsOnly`, `enableCitations`,
662
+ `suppressBibliography`, `linkCitations`, `showTooltips`, `enableIndices`,
663
+ `enableMmarkIndexSyntax`, `enableTextindexSyntax`,
664
+ `enableLeanpubIndexSyntax`, `suppressIndex`, `groupIndexByLetter`,
665
+ `wikilinkSanitize`, `embedStylesheet`, `enableAria`,
666
+ `enableEmojiAutocorrect`, `codeLineNumbers`, `highlightLanguageOnly`,
667
+ `enableWidont`, `codeIsPoetry`, `enableMarkdownInHtml`,
668
+ `randomFootnoteIds`, `enableHashtags`, `styleHashtags`, `proofreaderMode`,
669
+ `hrPageBreak`, `titleFromH1`, `pageBreakBeforeFootnotes`,
670
+ `astFilterStrict`
671
+
672
+ **Integers:**
673
+ `maxIncludeDepth`, `terminalWidth`, `terminalImageWidth`, `tocMin`, `tocMax`
674
+
675
+ **Strings:**
676
+ `baseDirectory`, `documentTitle`, `themeName`, `cslFile`, `nocite`,
677
+ `wikilinkExtension`, `codeHighlighter`, `codeHighlightTheme`,
678
+ `inputFilePath`
679
+
680
+ **Enums (string unions):**
681
+ `mode`, `outputFormat`, `criticMode`, `idFormat`, `captionPosition`,
682
+ `wikilinkSpace`
683
+
684
+ **String arrays:**
685
+ `stylesheetPaths` → `stylesheet_paths` + `stylesheet_count`
686
+ `bibliographyFiles` → NULL-terminated `bibliography_files`
687
+ `scriptTags` → NULL-terminated `script_tags`
688
+ `astFilterCommands` → `ast_filter_commands` + `ast_filter_count`
689
+
690
+ - [ ] **Step 1: Expand validation/behavior tests first**
691
+
692
+ Append to `test/options-validation.test.js`:
693
+
694
+ ```js
695
+ it("accepts camelCase mode aliases only as documented strings", () => {
696
+ assert.doesNotThrow(() => convert("# x", { mode: "unified" }));
697
+ assert.doesNotThrow(() => convert("# x", { mode: "commonmark" }));
698
+ assert.doesNotThrow(() => convert("# x", { mode: "quarto" }));
699
+ });
700
+
701
+ it("rejects snake_case option names", () => {
702
+ assert.throws(
703
+ () => convert("# x", { enable_tables: true }),
704
+ TypeError
705
+ );
706
+ });
707
+ ```
708
+
709
+ Create `test/options-behavior.test.js`:
710
+
711
+ ```js
712
+ const { describe, it } = require("node:test");
713
+ const assert = require("node:assert/strict");
714
+ const { convert } = require("../lib/index.js");
715
+
716
+ describe("option behavior", () => {
717
+ it("disables header ids when generateHeaderIds is false", () => {
718
+ const withIds = convert("# Hello", { mode: "gfm", generateHeaderIds: true });
719
+ const withoutIds = convert("# Hello", { mode: "gfm", generateHeaderIds: false });
720
+ assert.match(withIds, /id=/);
721
+ assert.doesNotMatch(withoutIds, /id=/);
722
+ });
723
+
724
+ it("applies criticMode accept", () => {
725
+ const html = convert("{++added++}", {
726
+ mode: "unified",
727
+ enableCriticMarkup: true,
728
+ criticMode: "accept"
729
+ });
730
+ assert.match(html, /added/);
731
+ assert.doesNotMatch(html, /ins/);
732
+ });
733
+
734
+ it("accepts stylesheetPaths array without throwing", () => {
735
+ const html = convert("# Hi", {
736
+ standalone: true,
737
+ documentTitle: "Doc",
738
+ stylesheetPaths: []
739
+ });
740
+ assert.match(html, /<html/i);
741
+ });
742
+ });
743
+ ```
744
+
745
+ Run:
746
+
747
+ ```bash
748
+ npm test
749
+ ```
750
+
751
+ Expected: FAIL on unknown-option / unimplemented mapper behavior.
752
+
753
+ - [ ] **Step 2: Implement table-driven options parsing**
754
+
755
+ Replace `binding/src/options.cpp` with a complete mapper:
756
+
757
+ 1. Read optional `mode` first.
758
+ 2. Initialize with `apex_options_for_mode(mode)` (or
759
+ `apex_options_default()` when mode omitted).
760
+ 3. Iterate `Object.getOwnPropertyNames`.
761
+ 4. For each name, look up a handler in a `std::unordered_map`.
762
+ 5. Unknown names set `error = "Unknown option: " + name` and return false.
763
+ 6. Copy strings into `storage->owned_strings` and point `const char *`
764
+ fields at `c_str()`.
765
+ 7. For arrays, fill pointer vectors and NULL-terminate
766
+ `bibliography_files` / `script_tags`; set counts for stylesheets and
767
+ AST filters.
768
+
769
+ Enum maps:
770
+
771
+ ```cpp
772
+ // mode
773
+ {"commonmark", APEX_MODE_COMMONMARK},
774
+ {"gfm", APEX_MODE_GFM},
775
+ {"multimarkdown", APEX_MODE_MULTIMARKDOWN},
776
+ {"kramdown", APEX_MODE_KRAMDOWN},
777
+ {"unified", APEX_MODE_UNIFIED},
778
+ {"quarto", APEX_MODE_QUARTO}
779
+
780
+ // outputFormat
781
+ {"html", APEX_OUTPUT_HTML},
782
+ {"json", APEX_OUTPUT_JSON},
783
+ {"jsonFiltered", APEX_OUTPUT_JSON_FILTERED},
784
+ {"markdown", APEX_OUTPUT_MARKDOWN},
785
+ {"mmd", APEX_OUTPUT_MMD},
786
+ {"commonmark", APEX_OUTPUT_COMMONMARK},
787
+ {"kramdown", APEX_OUTPUT_KRAMDOWN},
788
+ {"gfm", APEX_OUTPUT_GFM},
789
+ {"terminal", APEX_OUTPUT_TERMINAL},
790
+ {"terminal256", APEX_OUTPUT_TERMINAL256},
791
+ {"man", APEX_OUTPUT_MAN},
792
+ {"manHtml", APEX_OUTPUT_MAN_HTML},
793
+ {"toc", APEX_OUTPUT_TOC}
794
+
795
+ // criticMode
796
+ {"markup", 0}, {"accept", 1}, {"reject", 2}
797
+
798
+ // idFormat
799
+ {"gfm", 0}, {"mmd", 1}
800
+
801
+ // captionPosition
802
+ {"above", 0}, {"below", 1}
803
+
804
+ // wikilinkSpace
805
+ {"dash", 0}, {"none", 1}, {"underscore", 2}, {"space", 3}
806
+ ```
807
+
808
+ Leave these C fields unset / NULL always:
809
+
810
+ `plugin_register`, `progress_callback`, `progress_user_data`,
811
+ `cmark_init`, `cmark_done`, `cmark_user_data`,
812
+ `toc_entries_out`, `toc_entries_count_out`.
813
+
814
+ - [ ] **Step 3: Write complete TypeScript declarations**
815
+
816
+ Replace `lib/index.d.ts` with an explicit `ApexOptions` interface listing
817
+ every supported property and the enum string unions from the design spec.
818
+ Do not include an index signature that would allow unknown keys.
819
+
820
+ - [ ] **Step 4: Rebuild and verify all option tests**
821
+
822
+ ```bash
823
+ npm run build
824
+ npm test
825
+ ```
826
+
827
+ Expected: all tests PASS.
828
+
829
+ - [ ] **Step 5: Commit options mapper**
830
+
831
+ ```bash
832
+ git add binding/src/options.cpp binding/src/options.h lib/index.d.ts test
833
+ git commit -m "$(cat <<'EOF'
834
+ Map complete camelCase Apex options in the Node binding.
835
+
836
+ @new **Complete ApexOptions mapping** for serializable camelCase Node options with strict validation.
837
+
838
+ EOF
839
+ )"
840
+ ```
841
+
842
+ ### Task 4: Platform Package Packaging
843
+
844
+ **Files:**
845
+
846
+ - Create: `scripts/package-prebuild.js`
847
+ - Create: `platforms/package.template.json`
848
+ - Create: `test/load-native.test.js`
849
+ - Modify: `package.json` scripts if needed
850
+
851
+ **Interfaces:**
852
+
853
+ - Produces directory
854
+ `prebuilds/@apexmarkdown/apex-<platform>-<arch>/` containing:
855
+ - `package.json`
856
+ - `apex.node`
857
+ - `README.md`
858
+ - Produces npm package name matching `platformPackageName()`
859
+
860
+ - [ ] **Step 1: Add loader unit test for unsupported platforms**
861
+
862
+ Create `test/load-native.test.js`:
863
+
864
+ ```js
865
+ const { describe, it } = require("node:test");
866
+ const assert = require("node:assert/strict");
867
+ const { platformPackageName } = require("../lib/load-native.js");
868
+
869
+ describe("platformPackageName", () => {
870
+ it("returns a scoped package for the current host when supported", () => {
871
+ const name = platformPackageName();
872
+ if (process.platform === "darwin" || process.platform === "linux") {
873
+ if (process.arch === "arm64" || process.arch === "x64") {
874
+ assert.match(name, /^@apexmarkdown\/apex-(darwin|linux)-(arm64|x64)$/);
875
+ return;
876
+ }
877
+ }
878
+ assert.equal(name, null);
879
+ });
880
+ });
881
+ ```
882
+
883
+ - [ ] **Step 2: Implement package-prebuild script**
884
+
885
+ Create `platforms/package.template.json`:
886
+
887
+ ```json
888
+ {
889
+ "name": "@apexmarkdown/apex-PLATFORM_ARCH",
890
+ "version": "PACKAGE_VERSION",
891
+ "description": "Apex native addon for PLATFORM_ARCH",
892
+ "os": ["OS"],
893
+ "cpu": ["CPU"],
894
+ "main": "apex.node",
895
+ "files": ["apex.node", "README.md"],
896
+ "license": "MIT",
897
+ "engines": {
898
+ "node": ">=20"
899
+ }
900
+ }
901
+ ```
902
+
903
+ Create `scripts/package-prebuild.js`:
904
+
905
+ ```js
906
+ const fs = require("node:fs");
907
+ const path = require("node:path");
908
+ const { platformPackageName } = require("../lib/load-native.js");
909
+
910
+ const root = path.join(__dirname, "..");
911
+ const pkg = require("../package.json");
912
+ const name = platformPackageName();
913
+ if (!name) {
914
+ console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
915
+ process.exit(1);
916
+ }
917
+
918
+ const addonCandidates = [
919
+ path.join(root, "build", "Release", "apex_node.node"),
920
+ path.join(root, "binding", "build", "Release", "apex_node.node")
921
+ ];
922
+ const addon = addonCandidates.find((p) => fs.existsSync(p));
923
+ if (!addon) {
924
+ console.error("Built addon not found; run npm run build first");
925
+ process.exit(1);
926
+ }
927
+
928
+ const outDir = path.join(root, "prebuilds", name);
929
+ fs.rmSync(outDir, { recursive: true, force: true });
930
+ fs.mkdirSync(outDir, { recursive: true });
931
+
932
+ const os = process.platform;
933
+ const cpu = process.arch;
934
+ const rendered = fs
935
+ .readFileSync(path.join(root, "platforms", "package.template.json"), "utf8")
936
+ .replaceAll("PLATFORM_ARCH", `${os === "darwin" ? "darwin" : "linux"}-${cpu}`)
937
+ .replaceAll("PACKAGE_VERSION", pkg.version)
938
+ .replaceAll("\"OS\"", `"${os}"`)
939
+ .replaceAll("\"CPU\"", `"${cpu}"`);
940
+
941
+ fs.writeFileSync(path.join(outDir, "package.json"), rendered);
942
+ fs.copyFileSync(addon, path.join(outDir, "apex.node"));
943
+ fs.writeFileSync(
944
+ path.join(outDir, "README.md"),
945
+ `# ${name}\n\nNative Apex addon for ${os}-${cpu}.\n`
946
+ );
947
+ console.log(`Wrote ${outDir}`);
948
+ ```
949
+
950
+ - [ ] **Step 3: Build and package on the current host**
951
+
952
+ ```bash
953
+ npm run build
954
+ npm run package:prebuild
955
+ node -e "console.log(require('./prebuilds/'+require('./lib/load-native').platformPackageName()+'/package.json').name)"
956
+ npm test
957
+ ```
958
+
959
+ Expected: platform directory exists; tests still PASS.
960
+
961
+ - [ ] **Step 4: Commit packaging scripts**
962
+
963
+ ```bash
964
+ git add scripts/package-prebuild.js platforms test/load-native.test.js
965
+ git commit -m "$(cat <<'EOF'
966
+ Add platform prebuild packaging for optional native deps.
967
+
968
+ @new **Platform prebuild packaging** for @apexmarkdown/apex optional native packages.
969
+
970
+ EOF
971
+ )"
972
+ ```
973
+
974
+ ### Task 5: CI Workflows and README
975
+
976
+ **Files:**
977
+
978
+ - Create: `.github/workflows/ci.yml`
979
+ - Create: `.github/workflows/publish-prebuilds.yml`
980
+ - Create: `README.md`
981
+ - Modify: `package.json` repository/homepage fields
982
+
983
+ **Interfaces:**
984
+
985
+ - CI matrix builds and tests:
986
+ - `macos-14` (arm64)
987
+ - `macos-13` or `macos-15-intel` equivalent for x64 if available; otherwise
988
+ document cross-compile/skip with an explicit matrix entry that uses
989
+ `cmake` targeting `x86_64-apple-darwin` only if reliable
990
+ - `ubuntu-24.04` (x64)
991
+ - `ubuntu-24.04-arm` (arm64) when available on the org plan
992
+ - Publish workflow uploads platform package artifacts on tag `v*`
993
+
994
+ Practical v1 matrix if ARM Linux runners are unavailable:
995
+
996
+ ```yaml
997
+ strategy:
998
+ matrix:
999
+ include:
1000
+ - os: macos-14
1001
+ package: "@apexmarkdown/apex-darwin-arm64"
1002
+ - os: macos-13
1003
+ package: "@apexmarkdown/apex-darwin-x64"
1004
+ - os: ubuntu-24.04
1005
+ package: "@apexmarkdown/apex-linux-x64"
1006
+ ```
1007
+
1008
+ If Linux arm64 CI is unavailable at implementation time, keep the optional
1009
+ dependency declared, build it manually when needed, and note the gap in
1010
+ README. Do not silently drop the package name from `package.json`.
1011
+
1012
+ - [ ] **Step 1: Write CI workflow**
1013
+
1014
+ Create `.github/workflows/ci.yml` that:
1015
+
1016
+ 1. Checks out with `submodules: recursive`
1017
+ 2. Sets up Node 20
1018
+ 3. Runs `npm ci`
1019
+ 4. Runs `npm run build`
1020
+ 5. Runs `npm test`
1021
+ 6. Runs `npm run package:prebuild`
1022
+ 7. Uploads `prebuilds/**` as artifacts named by matrix package
1023
+
1024
+ - [ ] **Step 2: Write publish workflow**
1025
+
1026
+ Create `.github/workflows/publish-prebuilds.yml` triggered on `push: tags: ['v*']`:
1027
+
1028
+ 1. Build matrix packages
1029
+ 2. `npm publish` each platform package from its `prebuilds/...` directory
1030
+ 3. Publish `@apexmarkdown/apex` from the repo root
1031
+
1032
+ Require `NPM_TOKEN` repository secret. Use
1033
+ `npm publish --access public` for scoped packages.
1034
+
1035
+ - [ ] **Step 3: Write README**
1036
+
1037
+ Create `README.md` covering:
1038
+
1039
+ ```md
1040
+ # @apexmarkdown/apex
1041
+
1042
+ Node.js bindings for [Apex](https://github.com/ApexMarkdown/apex).
1043
+
1044
+ ## Install
1045
+
1046
+ npm install @apexmarkdown/apex
1047
+
1048
+ ## Usage
1049
+
1050
+ const { convert, version } = require("@apexmarkdown/apex");
1051
+ const html = convert("# Hello", { mode: "gfm", generateHeaderIds: true });
1052
+
1053
+ ## Supported platforms
1054
+
1055
+ - macOS arm64 / x64
1056
+ - Linux arm64 / x64
1057
+ - Node.js 20+
1058
+
1059
+ No system cmark-gfm or libyaml install is required.
1060
+
1061
+ ## Options
1062
+
1063
+ camelCase options matching Apex's serializable apex_options fields.
1064
+ Unknown options throw TypeError.
1065
+ ```
1066
+
1067
+ Also document local development:
1068
+
1069
+ ```bash
1070
+ git submodule update --init --recursive
1071
+ npm install
1072
+ npm run build
1073
+ npm test
1074
+ ```
1075
+
1076
+ - [ ] **Step 4: Fill package repository metadata**
1077
+
1078
+ In `package.json` set:
1079
+
1080
+ ```json
1081
+ "repository": {
1082
+ "type": "git",
1083
+ "url": "git+https://github.com/ApexMarkdown/apex-node.git"
1084
+ },
1085
+ "homepage": "https://github.com/ApexMarkdown/apex-node",
1086
+ "bugs": {
1087
+ "url": "https://github.com/ApexMarkdown/apex-node/issues"
1088
+ }
1089
+ ```
1090
+
1091
+ - [ ] **Step 5: Final verification on the development machine**
1092
+
1093
+ ```bash
1094
+ npm run build
1095
+ npm test
1096
+ npm run package:prebuild
1097
+ node -e "const {convert,version}=require('.'); console.log(version); console.log(convert('**hi**'))"
1098
+ ```
1099
+
1100
+ Expected: version prints, HTML contains `<strong>hi</strong>`.
1101
+
1102
+ - [ ] **Step 6: Commit docs and CI**
1103
+
1104
+ ```bash
1105
+ git add .github README.md package.json
1106
+ git commit -m "$(cat <<'EOF'
1107
+ Add CI, publish workflow, and Node package README.
1108
+
1109
+ @new **Node package CI and docs** for building and publishing @apexmarkdown/apex prebuilds.
1110
+
1111
+ EOF
1112
+ )"
1113
+ ```
1114
+
1115
+ - [ ] **Step 7: Create GitHub repository and push**
1116
+
1117
+ ```bash
1118
+ cd /Users/ttscoff/Desktop/Code/apex-node
1119
+ gh repo create ApexMarkdown/apex-node --public --source=. --remote=origin --push
1120
+ ```
1121
+
1122
+ Only run this step when the user asks to publish the repository.
1123
+
1124
+ ---
1125
+
1126
+ ## Spec Coverage Checklist
1127
+
1128
+ | Spec requirement | Task |
1129
+ | --- | --- |
1130
+ | Sibling `apex-node` repo + submodule | Task 1 |
1131
+ | `@apexmarkdown/apex` package identity | Task 1 |
1132
+ | CMake + bundled cmark-gfm/libyaml | Task 2 |
1133
+ | Sync `convert` / `version` | Task 2 |
1134
+ | Complete serializable camelCase options | Task 3 |
1135
+ | Strict TypeError validation | Task 3 |
1136
+ | Exclude callbacks / TOC outs | Task 3 |
1137
+ | Platform optional packages | Task 4 |
1138
+ | macOS/Linux x64/arm64 distribution | Tasks 4–5 |
1139
+ | Tests listed in design | Tasks 1, 3, 4 |
1140
+ | No WASM / Windows / async / TOC API | Global Constraints |
1141
+
1142
+ ## Placeholder / Ambiguity Scan
1143
+
1144
+ - No TBD/TODO left in task steps.
1145
+ - Linux arm64 CI availability is handled with an explicit fallback note,
1146
+ not a silent omission of the package name.
1147
+ - cmake-js include/lib injection has an explicit fallback instruction.
1148
+ - Repository creation/push is gated on explicit user request.