bun-docx 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +51 -4
  2. package/dist/index.js +49191 -7357
  3. package/package.json +7 -2
package/README.md CHANGED
@@ -78,11 +78,25 @@ docx insert FILE --after p3 --text "..." [--style HeadingN] [--color HEX] [--bol
78
78
  docx insert FILE --after p3 --runs '[{"type":"text","text":"X","bold":true}]'
79
79
  docx insert FILE --after p3 --page-break | --column-break
80
80
  docx insert FILE --after p3 --section [--columns N] [--type continuous|nextPage|evenPage|oddPage|nextColumn]
81
+ docx insert FILE --after p3 --table --rows N --cols N [--widths "A,B,C"] [--table-width 100%] [--borders single|none|double] [--layout autofit|fixed]
82
+ docx insert FILE --after p3 --image SRC [--alt TEXT] [--width INCHES] [--height INCHES] # SRC = path, data: URI, or http(s) URL
83
+ docx insert FILE --after p3 --code "..." [--language LANG] # one CodeBlock paragraph per \n; --language → syntax highlight
84
+ docx insert FILE --after p3 --code-file PATH [--language LANG] | --code-file - # same, from file or stdin
85
+ docx insert FILE --after p3 --task checked|unchecked --text "..." [--list-level N] # GFM task list item
86
+ docx insert FILE --after p3 --list bullet|ordered --text "..." [--list-level N] # plain list item
87
+ docx insert FILE --after p3 --equation "x^2 + y^2" [--display] # LaTeX → OMML via temml + own MathML→OMML adapter
88
+ docx edit FILE --at p3 --task checked|unchecked # flip an existing task's state; track-changes emits checkboxToggle
89
+ docx edit FILE --at eq3 --equation "x^3" [--display|--inline] # replace equation content and/or toggle display mode
81
90
  docx edit FILE --at p3 --text "..." [--no-formatting] # word-level diff preserves bold/italic on unchanged words
82
91
  docx edit FILE --at p3 --runs '[...]'
83
- docx edit FILE --at s0 [--columns N] [--type T] # mutate section properties
92
+ docx edit FILE --at p3 --code "..." [--language LANG] # replace paragraph with a code block (expands to N lines)
93
+ docx edit FILE --at p3 --code-file PATH [--language LANG]
94
+ docx edit FILE --at p2-p5 --text "..." # range replace: collapse paragraph range to one paragraph
95
+ docx edit FILE --at p2-p5 --code-file new.go --language go # range replace with a fresh code block
96
+ docx edit FILE --at s0 [--columns N] [--type T] # mutate section properties
84
97
  docx delete FILE --at p3
85
- docx delete FILE --at s0 # strip an inline sectPr
98
+ docx delete FILE --at p2-p5 # range delete: drop a contiguous paragraph span
99
+ docx delete FILE --at s0 # strip an inline sectPr
86
100
 
87
101
  docx find FILE QUERY [--regex] [--ignore-case] [--all] [--nth N] [--current | --baseline] [--exact]
88
102
  docx replace FILE PATTERN REPLACEMENT [--regex] [--ignore-case] [--all] [--limit N] [--current | --baseline] [--exact] [--dry-run]
@@ -102,15 +116,34 @@ docx comments delete FILE --id c1 --id c3 # repeatable
102
116
  docx comments delete FILE --batch removals.jsonl
103
117
  docx comments list FILE [--include-resolved] [--thread c0]
104
118
 
119
+ docx footnotes add FILE --at pN[:offset] --text "..."
120
+ docx footnotes edit FILE --id fnN --text "..."
121
+ docx footnotes delete FILE --id fnN
122
+ docx footnotes list FILE
123
+ docx endnotes add FILE --at pN[:offset] --text "..."
124
+ docx endnotes edit FILE --id enN --text "..."
125
+ docx endnotes delete FILE --id enN
126
+ docx endnotes list FILE
127
+
105
128
  docx images list FILE
106
129
  docx images extract FILE --to ./media [--id imgN]
107
130
  docx images replace FILE --at imgN --with ./new.png
131
+ docx images delete FILE --at imgN
108
132
 
109
133
  docx hyperlinks list FILE
110
134
  docx hyperlinks add FILE --at pN:S-E --url URL
111
135
  docx hyperlinks replace FILE --at linkN --with URL
112
136
  docx hyperlinks delete FILE --at linkN
113
137
 
138
+ docx tables insert-row FILE --at tN [--position INDEX] [--cells "a,b,c"]
139
+ docx tables delete-row FILE --at tN:rR
140
+ docx tables insert-column FILE --at tN [--position INDEX] [--width TWIPS]
141
+ docx tables delete-column FILE --at tN:cC
142
+ docx tables set-widths FILE --at tN --widths "25%,25%,50%" | "1440,..." | auto
143
+ docx tables merge FILE --at tN:rR1cC1-rR2cC2
144
+ docx tables unmerge FILE --at tN:rRcC
145
+ docx tables borders FILE --at tN [--style single|double|none] [--size N] [--color HEX]
146
+
114
147
  docx track-changes FILE on|off
115
148
  docx track-changes list FILE
116
149
  docx track-changes accept FILE (--at tcN [--at tcM ...] | --all)
@@ -121,7 +154,7 @@ docx info locators [--json]
121
154
 
122
155
  Every command has `--help`. Mutating commands accept `--dry-run`, `-o/--output PATH` (write to a parallel file instead of overwriting `FILE`), and `-v/--verbose` (print the JSON ack — see "Quiet by default" below).
123
156
 
124
- **Quiet by default.** Mutators (`create`, `insert`, `edit`, `delete`, `replace`, `comments add/reply/resolve/delete`, `images replace`, `hyperlinks add/replace/delete`, `track-changes` toggle/accept/reject) print nothing on success and exit 0. Errors always print as `{ok: false, code, error, hint}`. Pass `-v`/`--verbose` to get the full JSON ack. Read commands (`read`, `find`, `wc`, `outline`, `info *`, `*-list`) print their data unconditionally. Batch operations that mint new ids — `comments add --batch`, `comments delete --batch`, `comments resolve --batch`, and `comments delete/resolve` with multiple `--id` — always print the affected ids since the agent can't reconstruct them otherwise.
157
+ **Quiet by default.** Mutators (`create`, `insert`, `edit`, `delete`, `replace`, `comments add/reply/resolve/delete`, `footnotes add/edit/delete`, `endnotes add/edit/delete`, `images replace/delete`, `hyperlinks add/replace/delete`, `tables *`, `track-changes` toggle/accept/reject) print nothing on success and exit 0. Errors always print as `{ok: false, code, error, hint}`. Pass `-v`/`--verbose` to get the full JSON ack. Read commands (`read`, `find`, `wc`, `outline`, `info *`, `*-list`) print their data unconditionally. Batch operations that mint new ids — `comments add --batch`, `comments delete --batch`, `comments resolve --batch`, and `comments delete/resolve` with multiple `--id` — always print the affected ids since the agent can't reconstruct them otherwise.
125
158
 
126
159
  ### Markdown rendering
127
160
 
@@ -161,8 +194,9 @@ The same `--current`/`--baseline` flags apply to `find`, `replace`, `wc`, and `c
161
194
 
162
195
  ```
163
196
  pN paragraph N (e.g., p3)
197
+ pN-pM paragraph range (whole paragraphs pN..pM as a unit)
164
198
  pN:S-E characters S..E within paragraph N
165
- pN:S-pM:E cross-paragraph range
199
+ pN:S-pM:E cross-paragraph character range
166
200
  tN table N; tN:rRcC for cell at row R, col C
167
201
  sN section break N — column count, type (continuous /
168
202
  nextPage / nextColumn / evenPage / oddPage)
@@ -183,12 +217,25 @@ Run `docx info locators` for the full reference.
183
217
 
184
218
  **Cross-format image replacement.** `images replace --at img0 --with new.png` detects the new MIME type via `Bun.file().type`, renames the part (`word/media/image1.jpeg` → `word/media/image1.png`), rewrites the relationship `Target`, and ensures `[Content_Types].xml` has a `<Default>` for the new extension.
185
219
 
220
+ **GFM task lists.** Paragraphs in a list whose leading content is a Word checkbox content control (`<w:sdt><w14:checkbox/></w:sdt>`) surface as `paragraph.taskState: "checked" | "unchecked"` in the AST and render as `- [ ]` / `- [x]` in markdown. The reader strips the SDT subtree and its trailing space run from `runs` so the AST carries only the task text; the SDT survives untouched in the underlying `XmlNode` tree so a read→edit→save round-trip preserves every checkbox. The reader also recognizes the **Word-for-Web Checklist** shape — a bulleted list whose level-0 bullet character is Wingdings ☐ (U+F0A8), with `<w:strike>` on the paragraph-mark marking "done" — since Web silently strips SDT content controls when it authors new task lists. Both shapes were confirmed empirically against Microsoft Word for Mac desktop, Word for Web, and LibreOffice. On the emit side we always produce SDT. **Authoring**: `insert --task checked|unchecked --text "..."` creates a fresh task line (inherits the anchor's numId if it's already a list, otherwise allocates a new bullet list); `--list-level N` nests; `--list bullet|ordered` makes a plain list item without a checkbox. `edit --at pN --task checked|unchecked` flips an existing task's state in place. **Tracked toggles** (checking or unchecking under track-changes) surface as the `checkboxToggle` tracked-change kind in `track-changes list`; accept keeps the new state, reject restores the prior glyph AND flips `w14:checked` back (inferred from the kept glyph, since Word stores no separate prior-value record).
221
+
222
+ **Math equations.** `<m:oMath>` and `<m:oMathPara>` (the Office Math markup Word, Pandoc, and LibreOffice all emit) surface as `EquationRun.latex` in the AST — reconstructed LaTeX, not the legacy plaintext concatenation. Markdown render emits `$…$` inline and `$$…$$` for display equations, so a doc full of academic equations round-trips through Pandoc with high fidelity. The walker handles fractions, super/sub/mixed scripts, roots (incl. nth roots), n-ary operators (sum/product/integral/contour), accents (hat/bar/vec — both Word's combining-diacritic and Pandoc's spacing-overscript encodings), delimited expressions, matrices, aligned equation arrays, and function operators (`\sin`/`\lim`/`\log` promoted from upright-styled runs). Unrecognized OMML constructs degrade per-subtree to plaintext (`EquationRun.text` is kept as a fallback) so a niche construct doesn't corrupt the surrounding equation. **Authoring**: `insert --equation "x^2 + y^2 = r^2" [--display]` inserts an inline or display equation from LaTeX (parsed by [temml](https://github.com/ronkok/Temml) for the LaTeX-side spec coverage, then walked into OMML by our own MathML → OMML adapter — no LGPL deps). `edit --at eqN --equation NEW_LATEX` replaces the content; `--display` / `--inline` toggle the mode. `eqN` locators address equations in document order.
223
+
224
+ **Code blocks + inline code.** `insert --code TEXT` (or `--code-file PATH`, `-` for stdin) splits content on `\n` and emits one `<w:p>` per source line, all styled `CodeBlock` (Courier New, indent, adjacent-paragraph spacing collapse) with runs styled `Code` (monospace character style — defensive in case Word doesn't cascade the paragraph font). Both styles get provisioned in `styles.xml` automatically. `--language LANG` syntax-highlights via [lowlight](https://github.com/wooorm/lowlight) (highlight.js); 37 common languages are bundled — `bash`, `c`, `cpp`, `csharp`, `css`, `diff`, `go`, `graphql`, `ini`, `java`, `javascript`, `json`, `kotlin`, `less`, `lua`, `makefile`, `markdown`, `objectivec`, `perl`, `php`, `php-template`, `plaintext`, `python`, `python-repl`, `r`, `ruby`, `rust`, `scss`, `shell`, `sql`, `swift`, `typescript`, `vbnet`, `wasm`, `xml`, `yaml`. Unknown languages degrade silently to uncolored runs. The palette is GitHub-light inspired (keywords red, strings dark-blue, comments gray, …) and unmapped highlight.js classes fall through with no color. For an inline ``code`` span inside a normal paragraph, use `--runs` JSON with `runStyle: "Code"` (the S8 markdown walker will make this ergonomic via the `\`code\`` shorthand). On `docx read`, consecutive `CodeBlock` paragraphs collapse into one GFM fenced block (`` ``` `` … `` ``` ``); inline `runStyle: "Code"` runs render with backticks.
225
+
226
+ **Image insertion.** `insert --image SRC` resolves SRC from a file path, a `data:` URI, or an `http(s)` URL (bounded fetch: 10s timeout, 25 MB cap streamed per-chunk so the limit holds even if `Content-Length` lies), writes the bytes to `word/media/imageN.ext`, mints an `image` relationship, and registers the extension's content-type `<Default>`. Pixel dimensions are read from the PNG/JPEG/GIF header and converted to EMU (1px = 9525 EMU at 96 dpi) for `<wp:extent>`; `--width`/`--height` override in inches, and supplying one alone scales the other to preserve aspect. The drawing is a standard inline `<w:drawing><wp:inline>` picture (`a:`/`pic:` namespaces declared on the subtree). Under track-changes the inserted run is wrapped in `<w:ins>` like any other inserted content. **HEIC/HEIF** input (common from iPhones) is transcoded to JPEG before embedding — Word can't render HEIC — so students can drop a `.heic` straight in; detection is by file header, not just extension. **SVG input is sanitized** before embedding (`<script>`, `on*` handlers, `<foreignObject>`, animation events, external `href`/`xlink:href`, and `data:image/svg+xml` self-references are stripped; XXE is rejected at parse time by `fast-xml-parser`) so an attacker-controlled SVG can't smuggle active content into the doc. **Remote fetches block non-public addresses** — private, loopback, link-local, and cloud-metadata ranges are refused, and HTTP redirects are followed manually with the same check at every hop, so an agent steered into `http://169.254.169.254/...` or `http://10.0.0.1/admin` is short-circuited.
227
+
228
+ **Image deletion.** `images delete --at imgN` removes the inline drawing and its run, pruning the media part and relationship when nothing else references them. Under track-changes it wraps the run in a real `<w:del>` instead (accept removes, reject restores), keeping the part until the change is accepted.
229
+
186
230
  **Hyperlink CRUD.** `hyperlinks list` enumerates `<w:hyperlink>` elements with positional `linkN` ids; `hyperlinks add --at p3:5-20 --url URL` wraps an existing span (splitting runs at offsets); `hyperlinks replace --at link0 --with URL` updates the rels `Target`, allocating a new rId if the existing one is shared so siblings stay pointed at the original URL; `hyperlinks delete --at link0` unwraps the link (text survives) and prunes the rels entry when no longer referenced.
187
231
 
232
+ **Table restructuring.** The `tables` verbs operate on a merge-aware logical grid: `gridSpan` (horizontal) and `vMerge` (vertical) cells map to physical `<w:tc>` elements so row/column locators (`tN:rR`, `tN:cC`, region `tN:rR1cC1-rR2cC2`) resolve correctly. `merge`/`unmerge` reshape `gridSpan`/`vMerge`; `set-widths` rewrites `<w:tblGrid>` plus per-cell `<w:tcW>`; structural edits refuse to bisect or orphan an existing merge (with a hint to `unmerge` first). Under track-changes, the tracked representation was verified against Microsoft Word (accept/reject), since Word — not the ECMA-376 schema — decides what actually round-trips. Row insert/delete emit native `<w:trPr><w:ins>`/`<w:del>`; column insert/delete emit per-cell `<w:tcPr><w:cellIns>`/`<w:cellDel>` (paired with a `<w:tblGridChange>` on insert); `set-widths` emits `<w:tblGridChange>` plus a per-cell `<w:tcPrChange>` (which is what Word's reject actually reverts) — all addressable as `tcN` and resolvable via `track-changes accept`/`reject` (which resyncs the grid). Cell merges and border changes are *not* tracked by Word (it warns "this action won't be marked as a change" and applies them immediately), so `merge`/`unmerge`/`borders` match that — applied in place with a `[docx-cli]` audit comment (mirroring hyperlink/image edits).
233
+
188
234
  ## Stack
189
235
 
190
236
  - **Runtime**: Bun (`node:util` parseArgs, JSX with custom factory, native zlib)
191
237
  - **Parser**: [`jszip`](https://www.npmjs.com/package/jszip) + [`fast-xml-parser`](https://www.npmjs.com/package/fast-xml-parser) + [`fast-xml-builder`](https://www.npmjs.com/package/fast-xml-builder)
238
+ - **Images**: [`heic-convert`](https://www.npmjs.com/package/heic-convert) (wasm libheif) transcodes HEIC/HEIF input to JPEG on insert
192
239
  - **Quality**: Biome + Knip + tsc; LibreOffice headless for round-trip integration tests
193
240
  - **Standard**: ECMA-376 Part 1 §17 (WordprocessingML), Transitional profile
194
241