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.
- checksums.yaml +4 -4
- data/README.md +17 -3
- data/ext/apex_ext/apex_ext.c +260 -91
- data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
- data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
- data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
- data/ext/apex_ext/apex_src/Package.swift +15 -4
- data/ext/apex_ext/apex_src/README.md +94 -35
- data/ext/apex_ext/apex_src/VERSION +1 -1
- data/ext/apex_ext/apex_src/apex-debug.log +4 -0
- data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
- data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
- data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
- data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
- data/ext/apex_ext/apex_src/cli/main.c +262 -33
- data/ext/apex_ext/apex_src/debug.log +7 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
- data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
- data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
- data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
- data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
- data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
- data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
- data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
- data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
- data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
- data/ext/apex_ext/apex_src/include/apexc.h +1 -0
- data/ext/apex_ext/apex_src/man/apex.1 +82 -78
- data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
- data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
- data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
- data/ext/apex_ext/apex_src/output.html +87 -0
- data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
- data/ext/apex_ext/apex_src/src/_README.md +85 -32
- data/ext/apex_ext/apex_src/src/apex.c +770 -200
- data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
- data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
- data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
- data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
- data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
- data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
- data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
- data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
- data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
- data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
- data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
- data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
- data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
- data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
- data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
- data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
- data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
- data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
- data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
- data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
- data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
- data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
- data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
- data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
- data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
- data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
- data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
- data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
- data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
- data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
- data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
- data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
- data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
- data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
- data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
- data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
- data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
- data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
- data/ext/apex_ext/extconf.rb +35 -0
- data/lib/apex/version.rb +2 -2
- metadata +117 -2
- 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.
|