@md2nativedocx/cli 0.0.0-stage → 0.2.1
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.
- package/README.md +114 -2
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/assets/README.md +65 -0
- package/assets/md2nativedocx.opendocument +171 -0
- package/assets/reference.docx +0 -0
- package/assets/reference.odt +0 -0
- package/bin/md2nativedocx.mjs +209 -0
- package/package.json +62 -4
- package/src/chartParts.mjs +177 -0
- package/src/cliSupport.mjs +30 -0
- package/src/convert.d.mts +97 -0
- package/src/convert.mjs +274 -0
- package/src/envOptions.mjs +88 -0
- package/src/postprocess.mjs +628 -0
- package/src/pptxExport.mjs +102 -0
- package/src/referenceDocBuilder.mjs +521 -0
- package/src/unclosedFences.mjs +48 -0
- package/src/wordCheck.mjs +125 -0
- package/src/writeOutputSafely.mjs +69 -0
- package/src/zipUtils.mjs +38 -0
package/README.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @md2nativedocx/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Convert **Markdown to native Word (`.docx`)** — a complete document conversion (text, tables, footnotes, math)
|
|
4
|
+
in which every embedded **Mermaid** diagram becomes real Word objects (editable OOXML shapes, SmartArt, charts),
|
|
5
|
+
not flattened PNGs. (`md` = Markdown, the input; *native* describes the Word output.) Standalone CLI counterpart to the
|
|
6
|
+
[md2nativedocx VS Code extension](https://marketplace.visualstudio.com/items?itemName=md2nativedocx.md2nativedocx),
|
|
7
|
+
useful for CI pipelines, scripts, Git hooks, or any editor other than VS Code.
|
|
8
|
+
|
|
9
|
+
Full project: <https://github.com/nicolasbridelance/md2nativedocx>
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js 18+
|
|
14
|
+
- [Pandoc](https://pandoc.org) 3.1.3+ on `PATH` (or point `MD2NATIVEDOCX_PANDOC_BIN` at a specific
|
|
15
|
+
binary). Unlike the VS Code extension, this standalone CLI does not auto-download Pandoc for you.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g @md2nativedocx/cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
md2nativedocx report.md -o report.docx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Usage: md2nativedocx <input.md> -o <output.docx|output.odt|output.pptx> [options]
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
-o, --output <file> Output .docx, .odt or .pptx path (required)
|
|
34
|
+
--show-source .pptx only: show each diagram's Mermaid source beside it
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Every ` ```mermaid ` code block in the Markdown becomes native, editable Word objects, never an embedded
|
|
39
|
+
image: a SmartArt graphic when the diagram fits one (processes, cycles, org charts, mindmaps, timelines,
|
|
40
|
+
kanban boards…), a Word chart with editable data for `pie`, `xychart` and `radar`, and individually
|
|
41
|
+
selectable shapes otherwise. Everything else (headings, tables, lists, LaTeX math, ...) is handled by
|
|
42
|
+
Pandoc as usual. Per-type detail:
|
|
43
|
+
[`docs/coverage.md`](https://github.com/nicolasbridelance/md2nativedocx/blob/main/docs/coverage.md).
|
|
44
|
+
|
|
45
|
+
| Variable | Effect |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `MD2NATIVEDOCX_ENABLE_SMARTART=0` | No SmartArt: every diagram as shapes |
|
|
48
|
+
| `MD2NATIVEDOCX_NATIVE_CHARTS=0` (or `pie,xychart`) | No Word charts (or only those types) |
|
|
49
|
+
| `MD2NATIVEDOCX_SMARTART_STYLE` | SmartArt look: `simple`, `subtle`, `moderate`, `intense-accent`, `colorful` (default), `colorful-moderate`, `intense` |
|
|
50
|
+
|
|
51
|
+
### LibreOffice (`.odt`)
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
md2nativedocx report.md -o report.odt
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Writes an OpenDocument text for LibreOffice: the document as Pandoc converts it, and each flowchart as one
|
|
58
|
+
group of native LibreOffice shapes (boxes, diamonds, cylinders…) with connectors attached to them, which
|
|
59
|
+
follow a box you move (*F3* enters the group), subgraphs as containers, `classDef`/`style`/`linkStyle`
|
|
60
|
+
colours kept. Other diagram types become a note in their place for now. The page is A4 with 2.54 cm
|
|
61
|
+
margins; the page and typography variables below, SmartArt and charts apply to `.docx` only. Pandoc
|
|
62
|
+
writes the whole file, with a reference document and template shipped in `assets/` (see
|
|
63
|
+
`THIRD_PARTY_NOTICES.md` for the template's BSD-3 notice).
|
|
64
|
+
|
|
65
|
+
From code: `convert(markdown, { format: 'odt' })`.
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Page layout, typography, table of contents, and other export options are set via environment
|
|
70
|
+
variables (the same ones the VS Code extension's settings map to) — see
|
|
71
|
+
[`docs/specs/export_customization_SPEC.md`](https://github.com/nicolasbridelance/md2nativedocx/blob/main/docs/specs/export_customization_SPEC.md)
|
|
72
|
+
in the main repository for the full list.
|
|
73
|
+
|
|
74
|
+
## Exit codes
|
|
75
|
+
|
|
76
|
+
| Code | Meaning |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `0` | Written. Diagram warnings, if any, are on stderr and in the `.log` next to the output. |
|
|
79
|
+
| `1` | No document: input file not found, Pandoc missing or failed, post-processing failed. |
|
|
80
|
+
| `2` | Bad command line: unknown option, missing input or `-o`, a path outside the working directory. |
|
|
81
|
+
|
|
82
|
+
## Very large diagrams
|
|
83
|
+
|
|
84
|
+
A diagram with more than 500 nodes (subgraph boxes included) or 800 edges is not laid out: the document
|
|
85
|
+
still converts, the diagram is replaced by a grey note saying why, and a warning goes to stderr and the
|
|
86
|
+
`.log`. This keeps one pathological block from running for minutes. In `@md2nativedocx/core`, `renderDiagram()`
|
|
87
|
+
throws a `DiagramTooLargeError` instead, and takes `maxNodes`, `maxEdges` and `maxSourceLength` to change the limits.
|
|
88
|
+
|
|
89
|
+
## From Node: `convert()`
|
|
90
|
+
|
|
91
|
+
The same conversion as a function, for a Node application, a build script or a server, without spawning
|
|
92
|
+
the CLI. Pandoc must still be installed.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { writeFile } from 'node:fs/promises';
|
|
96
|
+
import { convert, ConversionError } from '@md2nativedocx/cli';
|
|
97
|
+
|
|
98
|
+
const { document, warnings } = await convert('# Report\n\n```mermaid\ngraph LR\n A --> B\n```\n', {
|
|
99
|
+
layout: { pageSize: 'A4', margins: 'narrow' },
|
|
100
|
+
toc: true,
|
|
101
|
+
});
|
|
102
|
+
await writeFile('report.docx', document);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`convert()` takes Markdown text, or `{ path }` of a file, and resolves to `{ document, warnings,
|
|
106
|
+
pandocStderr }`; `document` is the `.docx` as a `Buffer`. It reads no environment variable: every setting
|
|
107
|
+
is an option (types in `src/convert.d.mts`, mirroring the variables above). A failure is a
|
|
108
|
+
`ConversionError` whose `stage` is `setup`, `pandoc` or `postprocess`. To render a single diagram without
|
|
109
|
+
Pandoc, use `renderDiagram()` from `@md2nativedocx/core`.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
CC0-1.0 (public domain). See the [main repository](https://github.com/nicolasbridelance/md2nativedocx)
|
|
114
|
+
for compliance/licensing details, including third-party notices for Pandoc (GPL-2.0-or-later,
|
|
115
|
+
invoked as a separate subprocess, never bundled).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
Everything in this package is CC0-1.0 except the file below.
|
|
4
|
+
|
|
5
|
+
## `assets/md2nativedocx.opendocument`
|
|
6
|
+
|
|
7
|
+
Derived from Pandoc's `opendocument` template (Pandoc 3.1.3, `data/templates/default.opendocument`).
|
|
8
|
+
Pandoc's templates are dual-licensed under the GNU GPL (version 2 or later) and the BSD 3-Clause
|
|
9
|
+
license; this derived file is distributed under the BSD 3-Clause license. The only change is a loop
|
|
10
|
+
added inside `office:automatic-styles`, filled by this project's Lua filter with the styles of the
|
|
11
|
+
diagrams it draws. That change is distributed under the same license.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Copyright (c) 2014-2023, John MacFarlane <jgm@berkeley.edu>
|
|
15
|
+
All rights reserved.
|
|
16
|
+
|
|
17
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted
|
|
18
|
+
provided that the following conditions are met:
|
|
19
|
+
|
|
20
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions
|
|
21
|
+
and the following disclaimer.
|
|
22
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of
|
|
23
|
+
conditions and the following disclaimer in the documentation and/or other materials provided
|
|
24
|
+
with the distribution.
|
|
25
|
+
* Neither the name of John MacFarlane nor the names of other contributors may be used to endorse
|
|
26
|
+
or promote products derived from this software without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
|
29
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
31
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
32
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
33
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
34
|
+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
35
|
+
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Pandoc itself is not part of this package: it is run as a separate program (GPL-2.0-or-later).
|
package/assets/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# `reference.docx`
|
|
2
|
+
|
|
3
|
+
Passed to Pandoc via `--reference-doc` so generated documents use Word's current default look
|
|
4
|
+
(Aptos font scheme, the modern "Office" colour scheme, accent1 `#4472C4`, flat non-bold heading
|
|
5
|
+
hierarchy, 11 pt body text with 1.08 line spacing and 8 pt after) instead of Pandoc's own bundled
|
|
6
|
+
default, which is the 2007-2010 Office look (Calibri/Cambria, bold coloured headings).
|
|
7
|
+
|
|
8
|
+
**Written by this project, from scratch (2026-10-08).** Every part comes from
|
|
9
|
+
`packages/cli/reference-docx-src/` and is assembled by:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
node scripts/build-reference-docx.mjs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Edit a part there, rebuild, and commit both. The build is reproducible (fixed timestamps).
|
|
16
|
+
|
|
17
|
+
Why: the previous file was derived from Pandoc's default `reference.docx`, which Pandoc distributes
|
|
18
|
+
under GPL-2+ only (its templates are dual GPL-2+/BSD-3, its other data files are not), while this
|
|
19
|
+
project is CC0. The maintainer chose to rebuild it (ADR 0013, `docs/adr/0013-odf-output-phase-0.md`).
|
|
20
|
+
The new parts keep the same visual values: a LibreOffice render of a document using every Pandoc
|
|
21
|
+
style (title block, abstract, headings, lists, footnote, code, table with caption, definition list,
|
|
22
|
+
diagram) is pixel-identical to the previous one. They contain no sample text and no relationship to
|
|
23
|
+
anything outside the package (the old file carried an unused `http://example.com` link from
|
|
24
|
+
Pandoc's sample document).
|
|
25
|
+
|
|
26
|
+
What the rest of the code relies on (`src/referenceDocBuilder.mjs`, tested in
|
|
27
|
+
`test/reference-doc-builder.test.mjs`):
|
|
28
|
+
|
|
29
|
+
- `word/theme/theme1.xml`: `<a:majorFont><a:latin typeface="…" />`, the same for `minorFont`, and
|
|
30
|
+
`<a:accent1><a:srgbClr val="RRGGBB"/></a:accent1>`;
|
|
31
|
+
- `word/styles.xml`: `w:docDefaults` with `w:sz`/`w:szCs` and a `w:pPrDefault`; accent colours as
|
|
32
|
+
`w:themeColor="accent1"` with a literal `w:val` fallback (LibreOffice does not resolve theme
|
|
33
|
+
colours); a `Table` style with a `firstRow` conditional `w:tcPr`;
|
|
34
|
+
- `word/document.xml`: an empty `<w:sectPr />`, which `patchSectPr` fills.
|
|
35
|
+
|
|
36
|
+
Styles Pandoc adds at conversion time (syntax highlighting `SourceCode`, `*Tok`) are not in this
|
|
37
|
+
file: Pandoc writes them itself.
|
|
38
|
+
|
|
39
|
+
**To verify in a real Word** (no Word in this environment; checked with LibreOffice and the Open XML
|
|
40
|
+
validator): heading styling and whether Word's current built-in template still matches.
|
|
41
|
+
|
|
42
|
+
# `reference.odt` and `md2nativedocx.opendocument`
|
|
43
|
+
|
|
44
|
+
The two files handed to Pandoc for a `.odt` (ADR 0013; `AGENTS.md` rule 7: nothing touches the
|
|
45
|
+
`.odt` Pandoc writes). Both are built by:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
node scripts/build-odt-assets.mjs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **`reference.odt`**, written by this project (CC0) from `packages/cli/reference-odt-src/`, never
|
|
52
|
+
Pandoc's own (GPL-2+). Pandoc keeps its `styles.xml` and writes the rest. It defines the styles
|
|
53
|
+
Pandoc's ODT writer refers to (headings, title block, code, quotations, tables, captions,
|
|
54
|
+
definition lists, footnotes, links), an A4 page with 2.54 cm margins, and the arrow markers and
|
|
55
|
+
dash pattern the diagrams use (`ODF_GRAPHIC_DEFINITIONS` in `@md2nativedocx/core`, kept equal by
|
|
56
|
+
`test/odt.test.mjs`). LibreOffice does **not** supply its built-in look for a style a document
|
|
57
|
+
references without defining (headings came out as plain text with a minimal `styles.xml`,
|
|
58
|
+
2026-10-08), so each one is written here.
|
|
59
|
+
- **`md2nativedocx.opendocument`**, Pandoc 3.1.3's `opendocument` template plus one loop inside
|
|
60
|
+
`office:automatic-styles` for the diagram styles. BSD-3-Clause, see `../THIRD_PARTY_NOTICES.md`.
|
|
61
|
+
The build refuses any other Pandoc version, and `test/odt.test.mjs` checks the file still differs
|
|
62
|
+
from `pandoc -D opendocument` by that loop only.
|
|
63
|
+
|
|
64
|
+
Known Pandoc 3.1.3 limitation, not ours: a front-matter `abstract` is written as bare text outside
|
|
65
|
+
any paragraph, which the ODF validator rejects (same with Pandoc's own reference document).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
$-- md2nativedocx: Pandoc's opendocument template (Pandoc 3.1.3), with one addition: the
|
|
2
|
+
$-- md2n-automatic-styles loop in office:automatic-styles, filled by the md2nativedocx Lua filter.
|
|
3
|
+
$-- Copyright (c) 2014-2023, John MacFarlane. BSD-3-Clause, see THIRD_PARTY_NOTICES.md.
|
|
4
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
5
|
+
<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" office:version="1.3">
|
|
6
|
+
<office:font-face-decls>
|
|
7
|
+
<style:font-face style:name="Courier New" style:font-family-generic="modern" style:font-pitch="fixed" svg:font-family="'Courier New'" />
|
|
8
|
+
</office:font-face-decls>
|
|
9
|
+
<office:automatic-styles>
|
|
10
|
+
$automatic-styles$
|
|
11
|
+
$for(md2n-automatic-styles)$
|
|
12
|
+
$md2n-automatic-styles$
|
|
13
|
+
$endfor$
|
|
14
|
+
</office:automatic-styles>
|
|
15
|
+
$for(header-includes)$
|
|
16
|
+
$header-includes$
|
|
17
|
+
$endfor$
|
|
18
|
+
<office:body>
|
|
19
|
+
<office:text>
|
|
20
|
+
$if(title)$
|
|
21
|
+
<text:p text:style-name="Title">$title$</text:p>
|
|
22
|
+
$endif$
|
|
23
|
+
$if(subtitle)$
|
|
24
|
+
<text:p text:style-name="Subtitle">$subtitle$</text:p>
|
|
25
|
+
$endif$
|
|
26
|
+
$for(author)$
|
|
27
|
+
<text:p text:style-name="Author">$author$</text:p>
|
|
28
|
+
$endfor$
|
|
29
|
+
$if(date)$
|
|
30
|
+
<text:p text:style-name="Date">$date$</text:p>
|
|
31
|
+
$endif$
|
|
32
|
+
$if(abstract)$
|
|
33
|
+
$abstract$
|
|
34
|
+
$endif$
|
|
35
|
+
$for(include-before)$
|
|
36
|
+
$include-before$
|
|
37
|
+
$endfor$
|
|
38
|
+
$if(toc)$
|
|
39
|
+
<text:table-of-content>
|
|
40
|
+
<text:table-of-content-source text:outline-level="$toc-depth$">
|
|
41
|
+
<text:index-title-template text:style-name="Contents_20_Heading">$toc-title$</text:index-title-template>
|
|
42
|
+
<text:table-of-content-entry-template text:outline-level="1"
|
|
43
|
+
text:style-name="Contents_20_1">
|
|
44
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
45
|
+
<text:index-entry-chapter />
|
|
46
|
+
<text:index-entry-text />
|
|
47
|
+
<text:index-entry-link-end />
|
|
48
|
+
<text:index-entry-tab-stop style:type="right"
|
|
49
|
+
style:leader-char="." />
|
|
50
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
51
|
+
<text:index-entry-page-number />
|
|
52
|
+
<text:index-entry-link-end />
|
|
53
|
+
</text:table-of-content-entry-template>
|
|
54
|
+
<text:table-of-content-entry-template text:outline-level="2"
|
|
55
|
+
text:style-name="Contents_20_2">
|
|
56
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
57
|
+
<text:index-entry-chapter />
|
|
58
|
+
<text:index-entry-text />
|
|
59
|
+
<text:index-entry-link-end />
|
|
60
|
+
<text:index-entry-tab-stop style:type="right"
|
|
61
|
+
style:leader-char="." />
|
|
62
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
63
|
+
<text:index-entry-page-number />
|
|
64
|
+
<text:index-entry-link-end />
|
|
65
|
+
</text:table-of-content-entry-template>
|
|
66
|
+
<text:table-of-content-entry-template text:outline-level="3"
|
|
67
|
+
text:style-name="Contents_20_3">
|
|
68
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
69
|
+
<text:index-entry-chapter />
|
|
70
|
+
<text:index-entry-text />
|
|
71
|
+
<text:index-entry-link-end />
|
|
72
|
+
<text:index-entry-tab-stop style:type="right"
|
|
73
|
+
style:leader-char="." />
|
|
74
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
75
|
+
<text:index-entry-page-number />
|
|
76
|
+
<text:index-entry-link-end />
|
|
77
|
+
</text:table-of-content-entry-template>
|
|
78
|
+
<text:table-of-content-entry-template text:outline-level="4"
|
|
79
|
+
text:style-name="Contents_20_4">
|
|
80
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
81
|
+
<text:index-entry-chapter />
|
|
82
|
+
<text:index-entry-text />
|
|
83
|
+
<text:index-entry-link-end />
|
|
84
|
+
<text:index-entry-tab-stop style:type="right"
|
|
85
|
+
style:leader-char="." />
|
|
86
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
87
|
+
<text:index-entry-page-number />
|
|
88
|
+
<text:index-entry-link-end />
|
|
89
|
+
</text:table-of-content-entry-template>
|
|
90
|
+
<text:table-of-content-entry-template text:outline-level="5"
|
|
91
|
+
text:style-name="Contents_20_5">
|
|
92
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
93
|
+
<text:index-entry-chapter />
|
|
94
|
+
<text:index-entry-text />
|
|
95
|
+
<text:index-entry-link-end />
|
|
96
|
+
<text:index-entry-tab-stop style:type="right"
|
|
97
|
+
style:leader-char="." />
|
|
98
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
99
|
+
<text:index-entry-page-number />
|
|
100
|
+
<text:index-entry-link-end />
|
|
101
|
+
</text:table-of-content-entry-template>
|
|
102
|
+
<text:table-of-content-entry-template text:outline-level="6"
|
|
103
|
+
text:style-name="Contents_20_6">
|
|
104
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
105
|
+
<text:index-entry-chapter />
|
|
106
|
+
<text:index-entry-text />
|
|
107
|
+
<text:index-entry-link-end />
|
|
108
|
+
<text:index-entry-tab-stop style:type="right"
|
|
109
|
+
style:leader-char="." />
|
|
110
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
111
|
+
<text:index-entry-page-number />
|
|
112
|
+
<text:index-entry-link-end />
|
|
113
|
+
</text:table-of-content-entry-template>
|
|
114
|
+
<text:table-of-content-entry-template text:outline-level="7"
|
|
115
|
+
text:style-name="Contents_20_7">
|
|
116
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
117
|
+
<text:index-entry-chapter />
|
|
118
|
+
<text:index-entry-text />
|
|
119
|
+
<text:index-entry-link-end />
|
|
120
|
+
<text:index-entry-tab-stop style:type="right"
|
|
121
|
+
style:leader-char="." />
|
|
122
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
123
|
+
<text:index-entry-page-number />
|
|
124
|
+
<text:index-entry-link-end />
|
|
125
|
+
</text:table-of-content-entry-template>
|
|
126
|
+
<text:table-of-content-entry-template text:outline-level="8"
|
|
127
|
+
text:style-name="Contents_20_8">
|
|
128
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
129
|
+
<text:index-entry-chapter />
|
|
130
|
+
<text:index-entry-text />
|
|
131
|
+
<text:index-entry-link-end />
|
|
132
|
+
<text:index-entry-tab-stop style:type="right"
|
|
133
|
+
style:leader-char="." />
|
|
134
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
135
|
+
<text:index-entry-page-number />
|
|
136
|
+
<text:index-entry-link-end />
|
|
137
|
+
</text:table-of-content-entry-template>
|
|
138
|
+
<text:table-of-content-entry-template text:outline-level="9"
|
|
139
|
+
text:style-name="Contents_20_9">
|
|
140
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
141
|
+
<text:index-entry-chapter />
|
|
142
|
+
<text:index-entry-text />
|
|
143
|
+
<text:index-entry-link-end />
|
|
144
|
+
<text:index-entry-tab-stop style:type="right"
|
|
145
|
+
style:leader-char="." />
|
|
146
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
147
|
+
<text:index-entry-page-number />
|
|
148
|
+
<text:index-entry-link-end />
|
|
149
|
+
</text:table-of-content-entry-template>
|
|
150
|
+
<text:table-of-content-entry-template text:outline-level="10"
|
|
151
|
+
text:style-name="Contents_20_10">
|
|
152
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
153
|
+
<text:index-entry-chapter />
|
|
154
|
+
<text:index-entry-text />
|
|
155
|
+
<text:index-entry-link-end />
|
|
156
|
+
<text:index-entry-tab-stop style:type="right"
|
|
157
|
+
style:leader-char="." />
|
|
158
|
+
<text:index-entry-link-start text:style-name="Internet_20_link" />
|
|
159
|
+
<text:index-entry-page-number />
|
|
160
|
+
<text:index-entry-link-end />
|
|
161
|
+
</text:table-of-content-entry-template>
|
|
162
|
+
</text:table-of-content-source>
|
|
163
|
+
</text:table-of-content>
|
|
164
|
+
$endif$
|
|
165
|
+
$body$
|
|
166
|
+
$for(include-after)$
|
|
167
|
+
$include-after$
|
|
168
|
+
$endfor$
|
|
169
|
+
</office:text>
|
|
170
|
+
</office:body>
|
|
171
|
+
</office:document-content>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* md2nativedocx CLI.
|
|
4
|
+
*
|
|
5
|
+
* `npx md2nativedocx rapport.md -o rapport.docx` packages the Pandoc invocation
|
|
6
|
+
* with the md2nativedocx Lua filter, producing a .docx with native, editable
|
|
7
|
+
* OOXML vector shapes for every ```mermaid block — or, for a flowchart shape
|
|
8
|
+
* `classifyTopology()` accepts (chain/tree/cycle, spec §4), an editable native
|
|
9
|
+
* SmartArt diagram instead (spec §7 step 5). See postprocess.mjs's
|
|
10
|
+
* `injectSmartArtParts` doc comment for how that dispatch is wired end to end.
|
|
11
|
+
*
|
|
12
|
+
* The conversion itself is `convert()` (src/convert.mjs), the package's
|
|
13
|
+
* library entry point; this file only reads the `MD2NATIVEDOCX_*` variables
|
|
14
|
+
* into its options (src/envOptions.mjs), handles files and arguments, and
|
|
15
|
+
* reports warnings, errors and the Word compatibility check.
|
|
16
|
+
*
|
|
17
|
+
* Security (AGENTS.md):
|
|
18
|
+
* * Rule #4: Pandoc is invoked via execFile with an argument array — never a
|
|
19
|
+
* shell string that interpolates a file path (src/convert.mjs).
|
|
20
|
+
* * Path traversal: input/output paths are resolved and validated against the
|
|
21
|
+
* expected root before any file operation.
|
|
22
|
+
* * Errors are typed (ParseError/TranslationError) and mapped to exit codes.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { basename, extname } from 'node:path';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { CliError, resolveSafePath } from '../src/cliSupport.mjs';
|
|
28
|
+
import { ConversionError, convert } from '../src/convert.mjs';
|
|
29
|
+
import { writeOutputSafely } from '../src/writeOutputSafely.mjs';
|
|
30
|
+
import { formatWordCompatibility, runWordCompatibilityCheck } from '../src/wordCheck.mjs';
|
|
31
|
+
import { hasLayoutOptions, readConvertOptionsFromEnv } from '../src/envOptions.mjs';
|
|
32
|
+
|
|
33
|
+
// `-o something.pptx` takes the Pandoc-free deck path (packages/pptx), dispatched before anything a
|
|
34
|
+
// .docx export needs.
|
|
35
|
+
{
|
|
36
|
+
const argv = process.argv.slice(2);
|
|
37
|
+
const outIndex = argv.findIndex((a) => a === '-o' || a === '--output');
|
|
38
|
+
if (outIndex !== -1 && /\.pptx$/i.test(argv[outIndex + 1] ?? '')) {
|
|
39
|
+
const { runPptxCli } = await import('../src/pptxExport.mjs');
|
|
40
|
+
process.exit(await runPptxCli(argv, process.cwd()));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const USAGE = `Usage: md2nativedocx <input.md> -o <output.docx|output.odt|output.pptx> [options]
|
|
45
|
+
|
|
46
|
+
A .odt output is for LibreOffice: flowcharts become native, editable shapes and connectors;
|
|
47
|
+
other diagram types become a note for now.
|
|
48
|
+
|
|
49
|
+
A .pptx output is a slide deck: one slide per \`\`\`mermaid block, titled with the nearest
|
|
50
|
+
preceding heading (text outside diagrams is not exported).
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-o, --output <file> Output .docx, .odt or .pptx path (required)
|
|
54
|
+
--show-source .pptx only: show each diagram's Mermaid source beside it
|
|
55
|
+
-h, --help Show this help
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
function parseArgs(argv) {
|
|
59
|
+
const args = { input: null, output: null };
|
|
60
|
+
for (let i = 0; i < argv.length; i++) {
|
|
61
|
+
const a = argv[i];
|
|
62
|
+
if (a === '-h' || a === '--help') {
|
|
63
|
+
args.help = true;
|
|
64
|
+
} else if (a === '-o' || a === '--output') {
|
|
65
|
+
args.output = argv[++i];
|
|
66
|
+
if (!args.output) throw new CliError('Missing value for --output', 2);
|
|
67
|
+
} else if (a.startsWith('-')) {
|
|
68
|
+
throw new CliError(`Unknown option: ${a}`, 2);
|
|
69
|
+
} else if (args.input === null) {
|
|
70
|
+
args.input = a;
|
|
71
|
+
} else {
|
|
72
|
+
throw new CliError(`Unexpected argument: ${a}`, 2);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return args;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function main() {
|
|
79
|
+
const cwd = process.cwd();
|
|
80
|
+
const options = readConvertOptionsFromEnv(process.env);
|
|
81
|
+
// A custom reference document wins over page/typography options (spec §2.1, option (a)). Said as an
|
|
82
|
+
// info line, not with the `md2nativedocx: ` prefix that counts as a warning: nothing is wrong with
|
|
83
|
+
// the export, the user should just know.
|
|
84
|
+
if (options.referenceDoc && hasLayoutOptions(options.layout)) {
|
|
85
|
+
process.stderr.write(
|
|
86
|
+
'md2nativedocx (info): page/typography options are ignored because a custom reference document (MD2NATIVEDOCX_REFERENCE_DOC) is set.\n',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let args;
|
|
91
|
+
try {
|
|
92
|
+
args = parseArgs(process.argv.slice(2));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (err instanceof CliError) {
|
|
95
|
+
process.stderr.write(`${err.message}\n${USAGE}`);
|
|
96
|
+
process.exit(err.exitCode);
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (args.help) {
|
|
102
|
+
process.stdout.write(USAGE);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
if (!args.input) {
|
|
106
|
+
process.stderr.write(`Missing input file.\n${USAGE}`);
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
if (!args.output) {
|
|
110
|
+
process.stderr.write(`Missing output file (-o).\n${USAGE}`);
|
|
111
|
+
process.exit(2);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate paths (anti path traversal): a path outside the working directory is a usage error (exit 2),
|
|
115
|
+
// as on the .pptx path.
|
|
116
|
+
let input;
|
|
117
|
+
let output;
|
|
118
|
+
try {
|
|
119
|
+
input = resolveSafePath(args.input, cwd);
|
|
120
|
+
output = resolveSafePath(args.output, cwd);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (err instanceof CliError) {
|
|
123
|
+
process.stderr.write(`${err.message}\n`);
|
|
124
|
+
process.exit(err.exitCode);
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
if (!existsSync(input)) {
|
|
129
|
+
process.stderr.write(`Input file not found: ${args.input}\n`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const format = extname(output).toLowerCase() === '.odt' ? 'odt' : 'docx';
|
|
134
|
+
if (format === 'odt' && hasLayoutOptions(options.layout)) {
|
|
135
|
+
process.stderr.write('md2nativedocx (info): page/typography options apply to .docx only; the .odt uses A4 with 2.54 cm margins.\n');
|
|
136
|
+
}
|
|
137
|
+
// MD2NATIVEDOCX_REFERENCE_DOC names a Word template; a .odt keeps the bundled reference.odt.
|
|
138
|
+
const formatOptions = format === 'odt' && options.referenceDoc && extname(options.referenceDoc).toLowerCase() !== '.odt'
|
|
139
|
+
? { ...options, referenceDoc: undefined }
|
|
140
|
+
: options;
|
|
141
|
+
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
result = await convert({ path: input }, { ...formatOptions, cwd, format });
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err instanceof ConversionError) {
|
|
147
|
+
process.stderr.write(`md2nativedocx: ${err.message}\n`);
|
|
148
|
+
// `exportService.ts` (VS Code) reads Pandoc's own message, e.g. to tell a missing Pandoc apart.
|
|
149
|
+
if (err.stage === 'pandoc' && err.pandocStderr) process.stderr.write(err.pandocStderr);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
writeOutputSafely(output, result.document);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err instanceof ConversionError) {
|
|
158
|
+
process.stderr.write(`md2nativedocx: ${err.message}\n`);
|
|
159
|
+
process.exit(2);
|
|
160
|
+
}
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Surface the diagrams' non-fatal notices: never let a successful export silently hide something the
|
|
165
|
+
// author should know about.
|
|
166
|
+
const warnings = result.warnings.map((w) => `md2nativedocx: ${w}`);
|
|
167
|
+
const wordCompatibility = format === 'docx' ? runWordCompatibilityCheck(output) : null;
|
|
168
|
+
const logPath = writeExportLog({ input, output, warnings, rawStderr: result.pandocStderr, wordCompatibility });
|
|
169
|
+
if (warnings.length > 0) {
|
|
170
|
+
process.stdout.write(`Warnings: ${warnings.length} (see ${basename(logPath)})\n`);
|
|
171
|
+
for (const warning of warnings) process.stderr.write(`${warning}\n`);
|
|
172
|
+
}
|
|
173
|
+
process.stdout.write(`Wrote ${basename(output)}\n`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Write a plain-text log next to the output .docx (same basename, `.log`
|
|
177
|
+
* extension) so a click-right export (no VS Code Problems panel in view) has
|
|
178
|
+
* somewhere durable to point to — spec §10, "surface warnings". Written on
|
|
179
|
+
* every successful export, not just when there are warnings, for a
|
|
180
|
+
* consistent, discoverable location. */
|
|
181
|
+
function writeExportLog({ input, output, warnings, rawStderr, wordCompatibility }) {
|
|
182
|
+
const logPath = ['.docx', '.odt'].includes(extname(output).toLowerCase())
|
|
183
|
+
? output.slice(0, -extname(output).length) + '.log'
|
|
184
|
+
: `${output}.log`;
|
|
185
|
+
const lines = [
|
|
186
|
+
'md2nativedocx export log',
|
|
187
|
+
`Date: ${new Date().toISOString()}`,
|
|
188
|
+
`Input: ${input}`,
|
|
189
|
+
`Output: ${output}`,
|
|
190
|
+
`Warnings: ${warnings.length}`,
|
|
191
|
+
'',
|
|
192
|
+
];
|
|
193
|
+
if (warnings.length > 0) {
|
|
194
|
+
lines.push(...warnings.map((w) => `- ${w.replace(/^md2nativedocx:\s*(warning:\s*)?/, '')}`), '');
|
|
195
|
+
} else {
|
|
196
|
+
lines.push('No warnings.', '');
|
|
197
|
+
}
|
|
198
|
+
lines.push('--- Word compatibility check ---', ...formatWordCompatibility(wordCompatibility), '');
|
|
199
|
+
if (rawStderr && rawStderr.trim().length > 0) {
|
|
200
|
+
lines.push('--- Raw Pandoc/tool output ---', rawStderr.trimEnd(), '');
|
|
201
|
+
}
|
|
202
|
+
writeOutputSafely(logPath, lines.join('\n'));
|
|
203
|
+
return logPath;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
main().catch((err) => {
|
|
207
|
+
process.stderr.write(`md2nativedocx: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|