document-cli 1.1.0 → 1.2.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.
- package/README.md +46 -8
- package/dist/cli.js +223 -24
- package/dist/index.cjs +517 -20
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +519 -22
- package/dist/odb-structure-CX_0zL8_.js +382 -0
- package/dist/{tui-Dywpifqx.js → tui-DD4bQp39.js} +464 -68
- package/package.json +3 -3
- package/dist/io-CKOE9Xda.js +0 -84
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ graph TD
|
|
|
41
41
|
|
|
42
42
|
## Why
|
|
43
43
|
|
|
44
|
-
`documents.js` is a library, not a tool — everything it does happens through function calls from TypeScript/JavaScript. Most people who want to convert a docx to a PDF, extract an `.odb` table to CSV, or poke at a PDF's structure from a terminal don't want to write a script to do it. `document-cli` is that missing entry point: every one of documents.js's
|
|
44
|
+
`documents.js` is a library, not a tool — everything it does happens through function calls from TypeScript/JavaScript. Most people who want to convert a docx to a PDF, extract an `.odb` table to CSV, or poke at a PDF's structure from a terminal don't want to write a script to do it. `document-cli` is that missing entry point: every one of documents.js's 27 direct conversion pairs, its generic converter, its `.odm`/`.odb` extraction functions, and its PDF inspector become a single command-line invocation, and its six live-view editors (docx/pptx/odt/odp/ods/odg) plus its line-based markdown editor become a keyboard-driven terminal app that never needs a code editor open at all.
|
|
45
45
|
|
|
46
46
|
The CLI and the TUI are deliberately not two separate implementations of the same logic. The TUI's own document-opening, saving, and PDF-export code (`src/tui/format/`) calls the identical `documents.js` functions the CLI commands call — `openDocx`/`createDocx`/`docxToPdf` and their five siblings per format, plus `readOdbTables`/`readPdf` for the two read-only sources — so there is exactly one place either surface can drift from what documents.js itself does: nowhere.
|
|
47
47
|
|
|
@@ -68,7 +68,7 @@ cat report.docx | document-cli docx-to-pdf - - > report.pdf
|
|
|
68
68
|
|
|
69
69
|
### Commands
|
|
70
70
|
|
|
71
|
-
**The
|
|
71
|
+
**The 27 explicit `<source>-to-<target>` conversions** — one command per pair `createLocalDocumentConverter().conversions` declares in `documents.js` (confirmed by running the built CLI's own `formats` command, not assumed): the nine `<format>-to-pdf` conversions `docx-to-pdf`, `pptx-to-pdf`, `odt-to-pdf`, `odp-to-pdf`, `ods-to-pdf`, `odg-to-pdf`, `odf-to-pdf`, `xlsx-to-pdf`, `markdown-to-pdf`; the eight `pdf-to-<format>` reverse conversions `pdf-to-docx`, `pdf-to-pptx`, `pdf-to-odt`, `pdf-to-odp`, `pdf-to-ods`, `pdf-to-odg`, `pdf-to-xlsx`, `pdf-to-markdown` (`odf-to-pdf` is one-way — there is no `pdf-to-odf` — see documents.js's own README); and ten further PDF-bypassing bridges, `odt-to-docx`, `docx-to-odt`, `odp-to-pptx`, `pptx-to-odp`, `ods-to-xlsx`, `xlsx-to-ods`, `markdown-to-docx`, `docx-to-markdown`, `markdown-to-odt`, `odt-to-markdown`. Each takes `<input> [output]`:
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
74
|
document-cli docx-to-pdf report.docx report.pdf
|
|
@@ -82,7 +82,15 @@ document-cli convert report.docx report.pdf
|
|
|
82
82
|
document-cli convert report.docx - --to pdf > report.pdf
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
**`formats`** — lists every `source -> target` pair the commands above support (`--json` for a machine-readable array), plus a pointer to the commands not on that list because they don't fit the source/target shape (`odm-to-pdf`, `odb-to-csv`, `odb-to-xlsx`, `odb-tables`, `pdf-inspect`).
|
|
85
|
+
**`formats`** — lists every `source -> target` pair the commands above support (`--json` for a machine-readable array), plus a pointer to the commands not on that list because they don't fit the source/target shape (`odm-to-pdf`, `odb-to-csv`, `odb-to-xlsx`, `odb-tables`, `odb-forms`, `odb-reports`, `pdf-inspect`, `from-package`).
|
|
86
|
+
|
|
87
|
+
**`from-package <input> [output]`** — reads back a `DocumentPackage` JSON file a previous conversion wrote via `--dump-package` (below) and exports it to a real target format, closing the round trip `--dump-package` otherwise has no return path for. Target resolution matches `convert`: an output path's own extension, or `--to <format>` when it doesn't have one. `pdf` writes the package's own `layout` half directly (`writePdf`); every other format builds a fresh package from the `content` half through the identical `buildXPackage` function the matching `pdf-to-X`/bridge conversion already uses. `xlsx` and `odf` are rejected outright — documents.js exposes no `ContentDocument`-to-xlsx builder (convert to `ods` and run `ods-to-xlsx` instead) and a formula document has no write path from `ContentDocument` at all. Only a file genuinely written by `--dump-package` round-trips here; anything else fails with its `$schema` mismatch named:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
document-cli docx-to-pdf report.docx report.pdf --dump-package report.package.json
|
|
91
|
+
document-cli from-package report.package.json report2.pdf
|
|
92
|
+
document-cli from-package report.package.json report.odt --to odt
|
|
93
|
+
```
|
|
86
94
|
|
|
87
95
|
**`odm-to-pdf <input> [output]`** — converts a `.odm` master document to PDF. A `.odm`'s chapters are external references to standalone `.odt` files, never inlined in the master document itself, so this command needs to be told where to find them: `--chapters-dir <dir>` (matched by each chapter's own basename) and/or repeatable `--chapter <href>=<file>` overrides. An unresolved chapter fails with every missing `href` named at once, not just the first.
|
|
88
96
|
|
|
@@ -93,6 +101,13 @@ document-cli odb-tables customers.odb
|
|
|
93
101
|
document-cli odb-to-csv customers.odb --table CUSTOMERS customers.csv
|
|
94
102
|
```
|
|
95
103
|
|
|
104
|
+
**`odb-forms <input>`**, **`odb-reports <input>`** — read a `.odb`'s form and report *structure* rather than its table data. A form or report is a static ODF sub-document inside the package, so neither command consults the embedded database at all: they work on an `.odb` whose connection points at an external server just as well as on an embedded one. `odb-forms` prints each form's own data source (table or saved query) and its field-bound controls, sub-forms nested under their parent with their own separate command; `odb-reports` prints each report's data-source command, its band structure (report/page headers and footers, groups, detail), every `rpt:` formula expression (`field:[AMOUNT]`, `rpt:SUM([AMOUNT])`), and any user-defined report functions. `--json` emits the same structure machine-readably — for a form that is the structure only, with the form's own parsed sub-document dropped:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
document-cli odb-forms sales.odb
|
|
108
|
+
document-cli odb-reports sales.odb --json
|
|
109
|
+
```
|
|
110
|
+
|
|
96
111
|
**`pdf-inspect <input>`** — reports a PDF's page count, per-page size and item-kind histogram, document metadata, and embedded image formats, without converting it to anything. `--full` dumps the entire parsed `LayoutDocument` as JSON instead of the summary:
|
|
97
112
|
|
|
98
113
|
```sh
|
|
@@ -104,7 +119,7 @@ document-cli pdf-inspect report.pdf --json
|
|
|
104
119
|
|
|
105
120
|
### Shared flags
|
|
106
121
|
|
|
107
|
-
The
|
|
122
|
+
The 27 explicit conversions, `convert`, `odm-to-pdf`, `odb-to-xlsx`, and `odb-to-csv` — every command that reads one file and writes one — share:
|
|
108
123
|
|
|
109
124
|
| Flag | Meaning |
|
|
110
125
|
|---|---|
|
|
@@ -114,7 +129,24 @@ The 19 explicit conversions, `convert`, `odm-to-pdf`, `odb-to-xlsx`, and `odb-to
|
|
|
114
129
|
| `-q, --quiet` | Suppress diagnostic and summary output (the JSON result-summary line still prints in `--json` mode, so a script consuming NDJSON always gets a terminating record). |
|
|
115
130
|
| `--verbose` | Include a full stack trace in the error line when the run fails. |
|
|
116
131
|
|
|
117
|
-
`--dump-package <file>` is one flag further, registered only on the
|
|
132
|
+
`--dump-package <file>` is one flag further, registered only on the 27 explicit conversions and `convert` — it writes the intermediate `DocumentPackage` (content + layout) that conversion built to a JSON file, tagged with its own `$schema` so `from-package` (above) can read it back in. Every conversion populates one except `odf-to-pdf`, which accepts but never invokes its own `onDocument` callback (a standalone formula document has no `ContentDocument`/`LayoutDocument` pivot behind it); the ten PDF-bypassing bridges (`odt-to-docx`/`docx-to-odt`, `odp-to-pptx`/`pptx-to-odp`, `ods-to-xlsx`/`xlsx-to-ods`, `markdown-to-docx`/`docx-to-markdown`, `markdown-to-odt`/`odt-to-markdown`) populate one too, just with `layout` always `undefined`, since a bridge never runs a layout engine. `odm-to-pdf`/`odb-*` don't expose the flag at all, since neither goes through `DocumentConverter.convert` in the first place. `odb-tables`, `odb-forms`, `odb-reports`, `formats`, and `pdf-inspect` each take only their own `--json` (plus `pdf-inspect`'s own `--full`); `from-package` takes `--to <format>` alongside the shared flags in this table; `tui` takes no flags at all, only an optional positional file.
|
|
133
|
+
|
|
134
|
+
### Real fonts
|
|
135
|
+
|
|
136
|
+
By default a conversion renders through whatever `documents.js` resolves for itself: the source document's own embedded faces first, then `pdf-codec`'s vendored Carlito/Caladea substitutes (metric-compatible with Calibri/Cambria), then the standard 14 PDF fonts. `--font-file <path>` inserts your own faces between the first two steps — used wherever the document asks for the family a font file declares, and ignored where it doesn't.
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
document-cli docx-to-pdf report.docx report.pdf \
|
|
140
|
+
--font-file ~/fonts/Calibri.ttf \
|
|
141
|
+
--font-file ~/fonts/Calibri-Bold.ttf \
|
|
142
|
+
--report-font-substitutions
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The flag is repeatable, takes a `.ttf`/`.otf` path, and needs **no accompanying family flag**: the family, weight, and slope are read from the font file's own `name` and `OS/2` tables. That is a deliberate choice over the alternative of a parallel `--font-family`/`--font-bold`/`--font-italic` set — three repeatable flags whose values must stay index-aligned with a fourth is a silent-misalignment hazard (pass two font files and one `--font-family` and the second face is mis-declared, with nothing reporting it), and every real font already states all three facts itself. The consequence to know about: a font file can only be supplied *as the family it says it is*. There is no way to say "draw Calibri using this file instead" — for that, the family in the document has to match the family in the font. A file that is not a readable font (a `.woff`, a `.ttc` collection, a mistyped path pointing at something else) fails the run outright, naming the file, rather than being quietly skipped.
|
|
146
|
+
|
|
147
|
+
`--report-font-substitutions` prints each face that resolved to something other than what the document asked for, as it happens, with its structured fields intact (`--json` makes it one more NDJSON record: `{"type":"font-substitution","requestedFamily":"Calibri",…}`). Without it, the same fallbacks are still reported — the `font/substituted` diagnostic lines every conversion already emits — just as rendered messages after the fact rather than structured events as they occur.
|
|
148
|
+
|
|
149
|
+
Both flags are registered only where they can do something: the nine `<format>-to-pdf` conversions, `convert`, and `odm-to-pdf`. A `pdf-to-<format>` reconstruction reads a PDF's own already-positioned glyphs and a format-to-format bridge runs no layout engine at all, so neither resolves a typeface and neither advertises the flags.
|
|
118
150
|
|
|
119
151
|
Diagnostics and the summary line always go to stderr; stdout is reserved for the converted bytes on any command writing to `-`.
|
|
120
152
|
|
|
@@ -133,7 +165,13 @@ Diagnostics and the summary line always go to stderr; stdout is reserved for the
|
|
|
133
165
|
|
|
134
166
|
Launch it either bare (`document-cli`, with no arguments) or explicitly with `document-cli tui [file]` — both open the same app; the explicit form additionally opens `file` immediately, skipping the launcher screen. The TUI needs an interactive terminal: a bare invocation with redirected stdout prints help text instead, and an explicit `tui` invocation with redirected stdout fails outright, since there's no terminal for Ink to draw into.
|
|
135
167
|
|
|
136
|
-
It supports the same six formats documents.js's editors cover — docx, pptx, odt, odp, ods, odg — each with a full navigate/edit/save experience built on that format's
|
|
168
|
+
It supports the same six formats documents.js's live-view editors cover — docx, pptx, odt, odp, ods, odg — each with a full navigate/edit/save experience built on that format's editor (paragraphs and runs for docx/odt, slides and shapes for pptx/odp, sheets and cells for ods, pages and vectors/shapes for odg), plus undo (whole-document snapshots taken before each committed mutation), search, a command palette, and PDF export straight from the open document. On a pptx or odp slide, `a` from the shape list also adds a real table (rows then columns, a two-step prompt) alongside the existing textbox/image choices, and `n` opens the slide's own speaker notes for either format — `PptxSlide` and `OdpSlide` both carry a real `.notes` getter/setter, so notes editing was never odp-specific, only gated that way until this phase removed the gate.
|
|
169
|
+
|
|
170
|
+
Markdown (`.md`/`.markdown`) is also a fully supported TUI format, but a structurally different one: documents.js has no `MarkdownEditor` the way it has a `DocxEditor`/`OdtEditor` (its own markdown support is a thin read/write pair over a plain string, not an `XmlElement` tree to hold a live view into), so the TUI edits a markdown document as its own raw source text rather than through a live-view editor object. Opening a `.md` file loads its bytes verbatim as a source string (`decodeMarkdownText`); the root screen lists that source split into lines, one row each, with search filtering by line content; selecting a line opens a line editor that replaces its text in place; `Ctrl+S` writes the (possibly edited) source straight back to disk (`encodeMarkdownText`), the identical byte↔text boundary opening it went through in reverse — never through `readMarkdownContent`/`buildMarkdownText`. PDF export is the one place the `ContentDocument` pivot enters at all: exporting calls `markdownToPdf` directly on the current source text, run fresh on every export rather than kept in sync with in-progress edits. There is no "create a new markdown document" flow (documents.js has no `createMarkdown()`), so a markdown document can only be opened from an existing file, never created fresh from the TUI's new-document screen the way docx/pptx/odt/odp/ods/odg can.
|
|
171
|
+
|
|
172
|
+
Three further formats open read-only: a `.odb` browses its tables and rows with no write path at all (documents.js's own `.odb` support has no write direction to offer), a `.pdf` browses its pages and positioned items rather than being edited in place, and a `.xlsx` opens as a converted PDF preview — documents.js has no xlsx editor to hold a live view into, so opening one runs `xlsxToPdf` once at open time and browses the result through the identical page-list/page-items/item-detail screens a real `.pdf` uses, with the original bytes kept alongside so a later export re-runs `xlsxToPdf` with the caller's own fonts and diagnostics rather than reusing the fixed preview conversion. A `.odb` additionally browses its *structure* alongside its data: `f` from the table list opens the form browser and `r` the report browser, each listing what the database declares and opening one to show it in full — a form's own data source and field-bound controls (sub-forms nested under their parent), a report's data-source command, band and group structure, and every `rpt:` formula. Both are rendered through the same `src/odb-structure.ts` the `odb-forms`/`odb-reports` commands print, so the two views cannot drift apart, and search filters by line (`/SUM` narrows a long report to its aggregate expressions). A standalone `.odf` formula document has no TUI editor either — nothing to edit interactively, only a PDF conversion.
|
|
173
|
+
|
|
174
|
+
The export-to-PDF screen (`e` from any editor screen) is a two-field form: a destination path, then an optional comma-separated list of local `.ttf`/`.otf` paths, which are the same `--font-file` faces the CLI takes and are derived the same way — each font's family, weight, and slope come from the file itself. `Enter` on the path field moves to the fonts field and `Enter` there exports, so leaving fonts empty is still "type a path, press Enter twice". Comma-separated rather than space-separated because a font path routinely contains spaces and almost never a comma. A face falling back to a substitute is reported into the same diagnostics panel a character substitution already is, and a bad font path fails the export with the file named, before anything is written to the destination.
|
|
137
175
|
|
|
138
176
|
### Key bindings
|
|
139
177
|
|
|
@@ -160,7 +198,7 @@ The global bindings below apply everywhere; individual screens (a docx run's own
|
|
|
160
198
|
|
|
161
199
|
The package splits into two independent layers sharing one thin format-detection module, `src/format.ts` (extension ⇄ `DocumentFormat` inference), so a change to how a format is recognised from a path never needs making twice:
|
|
162
200
|
|
|
163
|
-
- **`src/commands/` + `src/runtime/`** is the CLI proper. `commands/shared.ts`'s `buildConversionAction(source, target)` is the one implementation behind every `<source>-to-<target>` command and the generic `convert` — it partially applies a format pair and hands back a ready commander action, so the conversion-running logic (read input, call `createLocalDocumentConverter().convert`, write output, report diagnostics, map errors to exit codes) exists exactly once regardless of which of the
|
|
201
|
+
- **`src/commands/` + `src/runtime/`** is the CLI proper. `commands/shared.ts`'s `buildConversionAction(source, target)` is the one implementation behind every `<source>-to-<target>` command and the generic `convert` — it partially applies a format pair and hands back a ready commander action, so the conversion-running logic (read input, call `createLocalDocumentConverter().convert`, write output, report diagnostics, map errors to exit codes) exists exactly once regardless of which of the 27 pairs is invoked. `commands/{odm,odb,pdf-inspect}.ts` each call their own documents.js function directly instead, since none of the three fits the generic `DocumentConverter` port's bytes-in/bytes-out shape (`odmToPdf` needs a `resolveSubDocument` callback, `.odb` extraction has no PDF conversion or port entry at all, and `pdf-inspect` reads without converting). `src/odb-structure.ts` sits alongside `src/format.ts` as the second module both layers share: it turns an `OdbForm`/`OdbReport` into a flat array of already-indented lines, which the `odb-forms`/`odb-reports` commands join with newlines and the TUI's own form/report detail screens render one per list row. `src/runtime/` holds the process-level concerns every command shares: `abort.ts`'s `createRuntimeSignal` (one `SIGINT` listener and an optional timeout, combined into a single signal), `io.ts`'s stdin/stdout/file `-`-aware read and write helpers, `exit-codes.ts`'s exit-code constants and `mapErrorToExit`, and `diagnostics.ts`'s stderr reporter.
|
|
164
202
|
- **`src/tui/`** is the Ink app, entered lazily. `src/cli.ts` only imports `./tui/index.js` inside a dynamic `import()`, called just once dispatch has already decided the TUI is actually running — a plain `document-cli docx-to-pdf a b` invocation never loads React, Ink, or any TUI screen module at all, and `tsdown.config.ts`'s bin build correctly code-splits the TUI into its own lazily-loaded chunk as a result. Inside the TUI, `state/reducer.ts` and `state/types.ts` hold the single `AppState` (a screen stack, the open document, undo history, overlays), `format/open-document.ts` is the one place bytes become an open document for every format, and `screens/editors/<format>/` holds each format's own screen components — reusing shared building blocks (`screens/shared/paragraph-family.tsx`, `slide-family.tsx`) between docx/odt and pptx/odp respectively, the same way documents.js's own odp editor reuses its odt paragraph/run classes.
|
|
165
203
|
- **`src/index.ts`** re-exports the CLI's command-layer, format, and exit-code logic (not the TUI, which stays behind its own lazy import) as this package's `"."` library export, for a caller that wants `document-cli`'s conversion-running logic directly rather than spawning the bin as a subprocess.
|
|
166
204
|
- **One package, two npm names.** `package.json`'s `bin` field lists `document-cli` and `doculi` unconditionally, both pointing at the same built entry point — there is no separate build, alias package, or npm alias mechanism involved, just two keys in one `bin` object.
|
|
@@ -178,7 +216,7 @@ pnpm test:smoke # tsdown, then vitest run --project smoke -- spawns the built
|
|
|
178
216
|
|
|
179
217
|
## Gotchas
|
|
180
218
|
|
|
181
|
-
- **The lazy TUI import is load-bearing, not incidental.** `src/cli.ts` computes the dispatch token before doing anything else and only reaches `await import('./tui/index.js')` on the bare/`tui` branch — every other command path (all
|
|
219
|
+
- **The lazy TUI import is load-bearing, not incidental.** `src/cli.ts` computes the dispatch token before doing anything else and only reaches `await import('./tui/index.js')` on the bare/`tui` branch — every other command path (all 27 explicit conversions, `convert`, `formats`, `odm-to-pdf`, `odb-*`, `pdf-inspect`) never touches that import at all. This is what keeps a scripted, high-frequency CLI invocation from paying React/Ink's module-load cost on every call.
|
|
182
220
|
- **A bare invocation and an explicit `tui` invocation fail differently on non-interactive stdout.** `document-cli` with no arguments and redirected stdout prints help and exits `0`, on the assumption that a bare invocation piped somewhere was more likely a forgotten argument than a deliberate TUI request. `document-cli tui` with redirected stdout is unambiguous, so it fails outright (exit `2`) rather than silently reinterpreting it as a help request.
|
|
183
221
|
- **`tsdown.config.ts` disables `fixedExtension` on both build passes.** `platform: 'node'` defaults tsdown's `fixedExtension` to `true`, which would emit `dist/cli.mjs`/`dist/index.mjs` regardless of `package.json`'s own `"type": "module"` — mismatching the `.js`/`.cjs` paths `bin` and `exports` actually name. Both entries set `fixedExtension: false` explicitly so the build output matches what's published.
|
|
184
222
|
- **`readOdbTables` needs `decodePackage` from `odf.js`, not from `documents.js`.** `documents.js` re-exports `ooxml.js`'s own `decodePackage` under the same name for OOXML packages, and that function cannot read an ODF `.odb` container at all — `commands/odb.ts` and the TUI's `format/open-document.ts` both import `decodePackage` from `odf.js` directly for exactly this reason. Importing it from the wrong package fails silently different, not loudly: it throws trying to parse a `.odb`'s manifest as an OOXML relationship file.
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as inferFormatFromExtension, n as
|
|
2
|
+
import { a as odbFormSummary, c as writeOutput, d as inferFormatFromExtension, f as isDocumentFormat, i as formatOdbReportLines, l as loadProvidedFonts, n as describeOdbReport, o as readInput, r as formatOdbFormLines, s as resolveDefaultOutputPath, t as describeOdbForm } from "./odb-structure-CX_0zL8_.js";
|
|
3
3
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
4
4
|
import { writeFile } from "node:fs/promises";
|
|
5
|
-
import { OdbNoEmbeddedDataSourceError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, createLocalDocumentConverter, odbToCsv, odbToXlsx, odmToPdf, readOdbTables, readPdf } from "documents.js";
|
|
5
|
+
import { OdbNoEmbeddedDataSourceError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, createLocalDocumentConverter, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, odbToCsv, odbToXlsx, odmToPdf, readOdbForms, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
|
|
6
6
|
import { basename, dirname, extname, join } from "node:path";
|
|
7
|
-
import { decodePackage } from "odf.js";
|
|
7
|
+
import { decodePackage, encodePackage as encodePackage$1 } from "odf.js";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
//#region src/runtime/abort.ts
|
|
10
10
|
function combineSignals(a, b) {
|
|
@@ -72,6 +72,22 @@ function createDiagnosticReporter(options) {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
function createFontSubstitutionReporter(options) {
|
|
76
|
+
const { json, quiet, command } = options;
|
|
77
|
+
return (substitution) => {
|
|
78
|
+
if (quiet) return;
|
|
79
|
+
if (json) {
|
|
80
|
+
process.stderr.write(`${JSON.stringify({
|
|
81
|
+
type: "font-substitution",
|
|
82
|
+
command,
|
|
83
|
+
...substitution
|
|
84
|
+
})}\n`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const styleClause = `${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
|
|
88
|
+
process.stderr.write(`[${command}] font substitution: "${substitution.requestedFamily}"${styleClause} -> "${substitution.resolvedFamily}" (${substitution.reason})\n`);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
75
91
|
function substitutionToDiagnostic(substitution, pageIndex) {
|
|
76
92
|
return {
|
|
77
93
|
severity: "warning",
|
|
@@ -80,6 +96,13 @@ function substitutionToDiagnostic(substitution, pageIndex) {
|
|
|
80
96
|
pageIndex
|
|
81
97
|
};
|
|
82
98
|
}
|
|
99
|
+
function fontSubstitutionToDiagnostic(substitution) {
|
|
100
|
+
return {
|
|
101
|
+
severity: "info",
|
|
102
|
+
code: "font/substituted",
|
|
103
|
+
message: `"${`${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`}" is not available; ${substitution.reason === "vendored-substitute" ? `substituted the metric-compatible "${substitution.resolvedFamily}"` : `substituted another face of "${substitution.resolvedFamily}"`}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
83
106
|
function pdfDiagnosticToDiagnostic(diagnostic) {
|
|
84
107
|
return {
|
|
85
108
|
severity: diagnostic.severity,
|
|
@@ -97,6 +120,18 @@ function mapErrorToExit(error, abortReason) {
|
|
|
97
120
|
}
|
|
98
121
|
//#endregion
|
|
99
122
|
//#region src/commands/shared.ts
|
|
123
|
+
const KNOWN_DOCUMENT_FORMATS = "docx, pptx, xlsx, odt, odp, ods, odg, odf, markdown, pdf";
|
|
124
|
+
function resolveTargetFormat(output, out, to) {
|
|
125
|
+
if (to !== void 0) {
|
|
126
|
+
if (!isDocumentFormat(to)) return { errorMessage: `unknown --to format '${to}'; expected one of ${KNOWN_DOCUMENT_FORMATS}` };
|
|
127
|
+
return { format: to };
|
|
128
|
+
}
|
|
129
|
+
const destination = output ?? out;
|
|
130
|
+
if (destination === void 0) return { errorMessage: "cannot infer a target format -- pass an output path with a recognised extension, --out with one, or --to <format>" };
|
|
131
|
+
const inferred = inferFormatFromExtension(destination);
|
|
132
|
+
if (inferred === void 0) return { errorMessage: `cannot infer a target format from '${destination}'; pass --to <format> instead` };
|
|
133
|
+
return { format: inferred };
|
|
134
|
+
}
|
|
100
135
|
function formatError(error, verbose) {
|
|
101
136
|
if (!(error instanceof Error)) return `error: ${String(error)}`;
|
|
102
137
|
const stackClause = verbose && error.stack !== void 0 ? `\n${error.stack}` : "";
|
|
@@ -113,13 +148,22 @@ function buildConversionAction(source, target) {
|
|
|
113
148
|
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeoutMs });
|
|
114
149
|
try {
|
|
115
150
|
const inputBytes = await readInput(input, { signal });
|
|
151
|
+
const fonts = await loadProvidedFonts(options.fontFiles ?? [], { signal });
|
|
116
152
|
const result = await createLocalDocumentConverter().convert({
|
|
117
153
|
source: {
|
|
118
154
|
format: source,
|
|
119
155
|
bytes: new Uint8Array(inputBytes)
|
|
120
156
|
},
|
|
121
157
|
targetFormat: target
|
|
122
|
-
}, {
|
|
158
|
+
}, {
|
|
159
|
+
signal,
|
|
160
|
+
fonts,
|
|
161
|
+
onFontSubstitution: options.reportFontSubstitutions === true ? createFontSubstitutionReporter({
|
|
162
|
+
json: options.json,
|
|
163
|
+
quiet: options.quiet,
|
|
164
|
+
command
|
|
165
|
+
}) : void 0
|
|
166
|
+
});
|
|
123
167
|
await writeOutput(resolvedOutput, result.document.bytes);
|
|
124
168
|
const reporter = createDiagnosticReporter({
|
|
125
169
|
json: options.json,
|
|
@@ -128,7 +172,7 @@ function buildConversionAction(source, target) {
|
|
|
128
172
|
});
|
|
129
173
|
for (const diagnostic of result.diagnostics) reporter.report(diagnostic);
|
|
130
174
|
if (options.dumpPackage !== void 0) if (result.package === void 0) process.stderr.write(`[${command}] this conversion does not produce an intermediate DocumentPackage\n`);
|
|
131
|
-
else await writeFile(options.dumpPackage, JSON.stringify(result.package, void 0, 2));
|
|
175
|
+
else await writeFile(options.dumpPackage, JSON.stringify(documentPackageWithSchema(result.package), void 0, 2));
|
|
132
176
|
reporter.summarize({
|
|
133
177
|
output: resolvedOutput,
|
|
134
178
|
bytes: result.document.bytes.byteLength,
|
|
@@ -161,6 +205,14 @@ function addVerboseOption(command) {
|
|
|
161
205
|
function addDumpPackageOption(command) {
|
|
162
206
|
return command.option("--dump-package <file>", "write the intermediate DocumentPackage (content + layout) this conversion built to a JSON file");
|
|
163
207
|
}
|
|
208
|
+
function collectFontFile(value, previous) {
|
|
209
|
+
return [...previous, value];
|
|
210
|
+
}
|
|
211
|
+
function addFontOptions(command) {
|
|
212
|
+
command.option("--font-file <path>", "embed this font file (.ttf/.otf) when the document asks for the family it declares; repeatable. The family, weight, and slope are read from the font's own 'name'/'OS/2' tables, so no accompanying family flag is needed", collectFontFile, []);
|
|
213
|
+
command.option("--report-font-substitutions", "print each font face that resolved to something other than what the document asked for to stderr, as it happens", false);
|
|
214
|
+
return command;
|
|
215
|
+
}
|
|
164
216
|
function addConversionFlags(command) {
|
|
165
217
|
addOutOption(command);
|
|
166
218
|
addTimeoutOption(command);
|
|
@@ -178,21 +230,11 @@ function toConversionCommandOptions(options) {
|
|
|
178
230
|
json: options.json,
|
|
179
231
|
quiet: options.quiet,
|
|
180
232
|
verbose: options.verbose,
|
|
181
|
-
dumpPackage: options.dumpPackage
|
|
233
|
+
dumpPackage: options.dumpPackage,
|
|
234
|
+
fontFiles: options.fontFile,
|
|
235
|
+
reportFontSubstitutions: options.reportFontSubstitutions
|
|
182
236
|
};
|
|
183
237
|
}
|
|
184
|
-
const KNOWN_FORMATS = "docx, pptx, xlsx, odt, odp, ods, odg, odf, markdown, pdf";
|
|
185
|
-
function resolveGenericTarget(output, options) {
|
|
186
|
-
if (options.to !== void 0) {
|
|
187
|
-
if (!isDocumentFormat(options.to)) return { errorMessage: `unknown --to format '${options.to}'; expected one of ${KNOWN_FORMATS}` };
|
|
188
|
-
return { format: options.to };
|
|
189
|
-
}
|
|
190
|
-
const destination = output ?? options.out;
|
|
191
|
-
if (destination === void 0) return { errorMessage: "cannot infer a target format -- pass an output path with a recognised extension, --out with one, or --to <format>" };
|
|
192
|
-
const inferred = inferFormatFromExtension(destination);
|
|
193
|
-
if (inferred === void 0) return { errorMessage: `cannot infer a target format from '${destination}'; pass --to <format> instead` };
|
|
194
|
-
return { format: inferred };
|
|
195
|
-
}
|
|
196
238
|
async function runGenericConvert(input, output, options) {
|
|
197
239
|
const extension = extname(input).toLowerCase();
|
|
198
240
|
if (extension === ".odm") {
|
|
@@ -205,10 +247,10 @@ async function runGenericConvert(input, output, options) {
|
|
|
205
247
|
}
|
|
206
248
|
const source = inferFormatFromExtension(input);
|
|
207
249
|
if (source === void 0) {
|
|
208
|
-
process.stderr.write(`convert: cannot infer a source format from '${input}'; rename the file with a recognised extension (${
|
|
250
|
+
process.stderr.write(`convert: cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS}) or use one of the explicit '<source>-to-<target>' commands\n`);
|
|
209
251
|
return 2;
|
|
210
252
|
}
|
|
211
|
-
const target =
|
|
253
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
212
254
|
if ("errorMessage" in target) {
|
|
213
255
|
process.stderr.write(`convert: ${target.errorMessage}\n`);
|
|
214
256
|
return 2;
|
|
@@ -222,6 +264,7 @@ function registerConversionCommands(program) {
|
|
|
222
264
|
const command = program.command(`${commandName} <input> [output]`).description(`convert a ${source} document to ${target}`);
|
|
223
265
|
addConversionFlags(command);
|
|
224
266
|
addDumpPackageOption(command);
|
|
267
|
+
if (target === "pdf") addFontOptions(command);
|
|
225
268
|
command.action(async (input, output, options) => {
|
|
226
269
|
process.exitCode = await buildConversionAction(source, target)(input, output, toConversionCommandOptions(options));
|
|
227
270
|
});
|
|
@@ -229,14 +272,15 @@ function registerConversionCommands(program) {
|
|
|
229
272
|
const generic = program.command("convert <input> [output]").description("convert between any two supported document formats, inferring source/target from file extensions where possible");
|
|
230
273
|
addConversionFlags(generic);
|
|
231
274
|
addDumpPackageOption(generic);
|
|
232
|
-
generic
|
|
275
|
+
addFontOptions(generic);
|
|
276
|
+
generic.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
233
277
|
generic.action(async (input, output, options) => {
|
|
234
278
|
process.exitCode = await runGenericConvert(input, output, options);
|
|
235
279
|
});
|
|
236
280
|
}
|
|
237
281
|
//#endregion
|
|
238
282
|
//#region src/commands/formats.ts
|
|
239
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, pdf-inspect";
|
|
283
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package";
|
|
240
284
|
function registerFormatsCommand(program) {
|
|
241
285
|
program.command("formats").description("list every source -> target conversion this CLI supports via a <source>-to-<target> command").option("--json", "emit the conversion list as a JSON array instead of a human-readable table", false).action((options) => {
|
|
242
286
|
const { conversions } = createLocalDocumentConverter();
|
|
@@ -249,6 +293,86 @@ function registerFormatsCommand(program) {
|
|
|
249
293
|
});
|
|
250
294
|
}
|
|
251
295
|
//#endregion
|
|
296
|
+
//#region src/commands/from-package.ts
|
|
297
|
+
function buildBytesForTarget(pkg, target) {
|
|
298
|
+
if (target === "pdf") {
|
|
299
|
+
if (pkg.layout === void 0) throw new Error("this DocumentPackage has no layout -- only a package dumped from a <format>-to-pdf or pdf-to-<format> conversion carries one; a bridge conversion's own dump (e.g. odt-to-docx) never does, so 'pdf' is not a reachable target from it");
|
|
300
|
+
return writePdf(pkg.layout);
|
|
301
|
+
}
|
|
302
|
+
switch (target) {
|
|
303
|
+
case "docx": return encodePackage(buildDocxPackage(pkg.content));
|
|
304
|
+
case "pptx": return encodePackage(buildPptxPackage(pkg.content));
|
|
305
|
+
case "odt": return encodePackage$1(buildOdtPackage(pkg.content));
|
|
306
|
+
case "odp": return encodePackage$1(buildOdpPackage(pkg.content));
|
|
307
|
+
case "ods": return encodePackage$1(buildOdsPackage(pkg.content));
|
|
308
|
+
case "odg": return encodePackage$1(buildOdgPackage(pkg.content));
|
|
309
|
+
case "markdown": return encodeMarkdownText(buildMarkdownText(pkg.content));
|
|
310
|
+
case "xlsx": throw new Error("'xlsx' cannot be built from a DocumentPackage directly -- documents.js does not re-export a ContentDocument-to-xlsx builder; convert to 'ods' here, then run 'ods-to-xlsx' on the result instead");
|
|
311
|
+
case "odf": throw new Error("'odf' (a standalone formula document) cannot be built from a DocumentPackage -- there is no ContentDocument-to-odf builder");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function runFromPackage(input, output, options) {
|
|
315
|
+
const command = "from-package";
|
|
316
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
317
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
318
|
+
return 2;
|
|
319
|
+
}
|
|
320
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
321
|
+
if ("errorMessage" in target) {
|
|
322
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
323
|
+
return 2;
|
|
324
|
+
}
|
|
325
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
326
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
327
|
+
try {
|
|
328
|
+
const inputBytes = await readInput(input, { signal });
|
|
329
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(inputBytes);
|
|
330
|
+
let parsed;
|
|
331
|
+
try {
|
|
332
|
+
parsed = JSON.parse(text);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
process.stderr.write(`[${command}] '${input}' is not valid JSON: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
335
|
+
return 1;
|
|
336
|
+
}
|
|
337
|
+
const result = documentFromJson(parsed);
|
|
338
|
+
if (result.kind !== "DocumentPackage") {
|
|
339
|
+
process.stderr.write(`[${command}] '${input}' is a ${result.kind}, not a DocumentPackage -- only a file written by --dump-package can be read back by this command\n`);
|
|
340
|
+
return 2;
|
|
341
|
+
}
|
|
342
|
+
const bytes = buildBytesForTarget(result.value, target.format);
|
|
343
|
+
await writeOutput(resolvedOutput, bytes);
|
|
344
|
+
createDiagnosticReporter({
|
|
345
|
+
json: options.json,
|
|
346
|
+
quiet: options.quiet,
|
|
347
|
+
command
|
|
348
|
+
}).summarize({
|
|
349
|
+
output: resolvedOutput,
|
|
350
|
+
bytes: bytes.byteLength,
|
|
351
|
+
diagnosticCount: 0
|
|
352
|
+
});
|
|
353
|
+
return 0;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (error instanceof UnrecognizedDocumentSchemaError) {
|
|
356
|
+
process.stderr.write(`[${command}] '${input}' has no recognised $schema -- only a file written by --dump-package can be read back by this command\n`);
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
360
|
+
return mapErrorToExit(error, getAbortReason());
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function registerFromPackageCommand(program) {
|
|
364
|
+
const command = program.command("from-package <input> [output]").description("read a DocumentPackage previously written by --dump-package and export it to a real target format");
|
|
365
|
+
addOutOption(command);
|
|
366
|
+
addTimeoutOption(command);
|
|
367
|
+
addJsonOption(command);
|
|
368
|
+
addQuietOption(command);
|
|
369
|
+
addVerboseOption(command);
|
|
370
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
371
|
+
command.action(async (input, output, options) => {
|
|
372
|
+
process.exitCode = await runFromPackage(input, output, options);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
252
376
|
//#region src/commands/odb.ts
|
|
253
377
|
function reportOdbError(command, error, verbose, abortReason) {
|
|
254
378
|
if (error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) {
|
|
@@ -350,6 +474,52 @@ async function runOdbTables(input, options) {
|
|
|
350
474
|
return reportOdbError(command, error, false, getAbortReason());
|
|
351
475
|
}
|
|
352
476
|
}
|
|
477
|
+
async function runOdbForms(input, options) {
|
|
478
|
+
const command = "odb-forms";
|
|
479
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
480
|
+
try {
|
|
481
|
+
const inputBytes = await readInput(input, { signal });
|
|
482
|
+
const forms = readOdbForms(decodePackage(new Uint8Array(inputBytes)));
|
|
483
|
+
if (options.json) {
|
|
484
|
+
process.stdout.write(`${JSON.stringify(forms.map((form) => odbFormSummary(form)))}\n`);
|
|
485
|
+
return 0;
|
|
486
|
+
}
|
|
487
|
+
if (forms.length === 0) {
|
|
488
|
+
process.stdout.write("This database declares no forms.\n");
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
for (const form of forms) {
|
|
492
|
+
process.stdout.write(`${describeOdbForm(form)}\n`);
|
|
493
|
+
for (const line of formatOdbFormLines(form)) process.stdout.write(` ${line}\n`);
|
|
494
|
+
}
|
|
495
|
+
return 0;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return reportOdbError(command, error, false, getAbortReason());
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async function runOdbReports(input, options) {
|
|
501
|
+
const command = "odb-reports";
|
|
502
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
503
|
+
try {
|
|
504
|
+
const inputBytes = await readInput(input, { signal });
|
|
505
|
+
const reports = readOdbReports(decodePackage(new Uint8Array(inputBytes)));
|
|
506
|
+
if (options.json) {
|
|
507
|
+
process.stdout.write(`${JSON.stringify(reports)}\n`);
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
if (reports.length === 0) {
|
|
511
|
+
process.stdout.write("This database declares no reports.\n");
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
for (const report of reports) {
|
|
515
|
+
process.stdout.write(`${describeOdbReport(report)}\n`);
|
|
516
|
+
for (const line of formatOdbReportLines(report)) process.stdout.write(` ${line}\n`);
|
|
517
|
+
}
|
|
518
|
+
return 0;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return reportOdbError(command, error, false, getAbortReason());
|
|
521
|
+
}
|
|
522
|
+
}
|
|
353
523
|
function registerOdbToXlsxCommand(program) {
|
|
354
524
|
const command = program.command("odb-to-xlsx <input> [output]").description("extract every table an embedded .odb database declares into one xlsx workbook, one sheet per table");
|
|
355
525
|
addOutOption(command);
|
|
@@ -378,10 +548,22 @@ function registerOdbTablesCommand(program) {
|
|
|
378
548
|
process.exitCode = await runOdbTables(input, options);
|
|
379
549
|
});
|
|
380
550
|
}
|
|
551
|
+
function registerOdbFormsCommand(program) {
|
|
552
|
+
program.command("odb-forms <input>").description("list every form an .odb declares, with each form's own data source and its field-bound controls").option("--json", "emit the form structure as a JSON array instead of a human-readable report", false).action(async (input, options) => {
|
|
553
|
+
process.exitCode = await runOdbForms(input, options);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function registerOdbReportsCommand(program) {
|
|
557
|
+
program.command("odb-reports <input>").description("list every report an .odb declares, with each report's own data-source command, band/group structure, and rpt: formula expressions").option("--json", "emit the report structure as a JSON array instead of a human-readable report", false).action(async (input, options) => {
|
|
558
|
+
process.exitCode = await runOdbReports(input, options);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
381
561
|
function registerOdbCommands(program) {
|
|
382
562
|
registerOdbToXlsxCommand(program);
|
|
383
563
|
registerOdbToCsvCommand(program);
|
|
384
564
|
registerOdbTablesCommand(program);
|
|
565
|
+
registerOdbFormsCommand(program);
|
|
566
|
+
registerOdbReportsCommand(program);
|
|
385
567
|
}
|
|
386
568
|
//#endregion
|
|
387
569
|
//#region src/commands/odm.ts
|
|
@@ -417,11 +599,26 @@ async function runOdmToPdf(input, output, options) {
|
|
|
417
599
|
command
|
|
418
600
|
});
|
|
419
601
|
let diagnosticCount = 0;
|
|
602
|
+
const reportFontSubstitution = options.reportFontSubstitutions === true ? createFontSubstitutionReporter({
|
|
603
|
+
json: options.json,
|
|
604
|
+
quiet: options.quiet,
|
|
605
|
+
command
|
|
606
|
+
}) : void 0;
|
|
420
607
|
try {
|
|
421
608
|
const inputBytes = await readInput(input, { signal });
|
|
609
|
+
const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
|
|
422
610
|
const bytes = odmToPdf(new Uint8Array(inputBytes), {
|
|
423
611
|
signal,
|
|
424
612
|
resolveSubDocument,
|
|
613
|
+
fonts,
|
|
614
|
+
onFontSubstitution: (substitution) => {
|
|
615
|
+
if (reportFontSubstitution !== void 0) {
|
|
616
|
+
reportFontSubstitution(substitution);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
diagnosticCount += 1;
|
|
620
|
+
reporter.report(fontSubstitutionToDiagnostic(substitution));
|
|
621
|
+
},
|
|
425
622
|
onSubstitution: (substitution, context) => {
|
|
426
623
|
diagnosticCount += 1;
|
|
427
624
|
reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
|
|
@@ -450,6 +647,7 @@ function registerOdmCommand(program) {
|
|
|
450
647
|
addJsonOption(command);
|
|
451
648
|
addQuietOption(command);
|
|
452
649
|
addVerboseOption(command);
|
|
650
|
+
addFontOptions(command);
|
|
453
651
|
command.option("--chapters-dir <dir>", "directory to search for each unresolved chapter href, matched by the href's own basename");
|
|
454
652
|
command.option("--chapter <href>=<file>", "resolve one chapter href to a local file explicitly; repeatable", collectChapterOverride, /* @__PURE__ */ new Map());
|
|
455
653
|
command.action(async (input, output, options) => {
|
|
@@ -546,7 +744,7 @@ function registerPdfInspectCommand(program) {
|
|
|
546
744
|
}
|
|
547
745
|
//#endregion
|
|
548
746
|
//#region package.json
|
|
549
|
-
var version = "1.
|
|
747
|
+
var version = "1.2.0";
|
|
550
748
|
//#endregion
|
|
551
749
|
//#region src/program.ts
|
|
552
750
|
function createProgram() {
|
|
@@ -559,6 +757,7 @@ function createProgram() {
|
|
|
559
757
|
});
|
|
560
758
|
registerConversionCommands(program);
|
|
561
759
|
registerFormatsCommand(program);
|
|
760
|
+
registerFromPackageCommand(program);
|
|
562
761
|
registerOdmCommand(program);
|
|
563
762
|
registerOdbCommands(program);
|
|
564
763
|
registerPdfInspectCommand(program);
|
|
@@ -568,7 +767,7 @@ function createProgram() {
|
|
|
568
767
|
//#region src/cli.ts
|
|
569
768
|
async function launchTui(startPath, signal) {
|
|
570
769
|
try {
|
|
571
|
-
const { runTui } = await import("./tui-
|
|
770
|
+
const { runTui } = await import("./tui-DD4bQp39.js");
|
|
572
771
|
await runTui({
|
|
573
772
|
startPath,
|
|
574
773
|
signal
|