document-cli 0.0.0 → 1.0.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 +157 -0
- package/dist/cli.js +615 -0
- package/dist/index.cjs +664 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +651 -0
- package/dist/io-Cl3MaB0L.js +81 -0
- package/dist/tui-CucYgqmL.js +5647 -0
- package/package.json +101 -2
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# document-cli
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/document-cli) [](https://www.npmjs.com/package/document-cli) [](https://github.com/ExaDev/document-cli/releases/latest) [](https://github.com/ExaDev/document-cli/actions)
|
|
4
|
+
|
|
5
|
+
> A command-line interface and an interactive terminal (Ink) app for [`documents.js`](https://github.com/ExaDev/documents.js): every docx/pptx/odt/odp/ods/odg/odf/odm/odb conversion, bridge, and editor documents.js exposes, wired up as a scriptable subcommand or a full-screen terminal editor. Installs as either `document-cli` or `doculi`.
|
|
6
|
+
|
|
7
|
+
`document-cli` adds no conversion or editing logic of its own — it is a dispatch layer over `documents.js`'s existing conversion functions, `DocumentConverter` port, live-view editors, and `.odb`/PDF readers. What it adds is two ways to drive them without writing TypeScript: a scriptable, Unix-shaped CLI (stdin/stdout, exit codes, `--json` diagnostics) for pipelines, and a full-screen Ink terminal app for browsing and editing a document interactively.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
`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 19 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) become a keyboard-driven terminal app that never needs a code editor open at all.
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
## Getting started
|
|
16
|
+
|
|
17
|
+
Requires Node.js `>=20`.
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm i -g document-cli
|
|
21
|
+
# or, identically:
|
|
22
|
+
npm i -g doculi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Both names install the exact same package and the exact same binary — `package.json`'s `bin` field declares both `document-cli` and `doculi` pointing at the one built entry point unconditionally, so there is no "real" name and an alias; pick whichever you find easier to type. This mirrors the alias-publishing pattern already established elsewhere in this package family (`documents.js`'s own scoped `@exadev/documents.js` republish to GitHub Packages) — a second name for the same build, not a second build.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Every conversion and extraction command reads one input and writes one output. Pass `-` for either to use stdin/stdout instead of a file — useful for piping through other tools without a temp file:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
document-cli docx-to-pdf report.docx report.pdf
|
|
33
|
+
cat report.docx | document-cli docx-to-pdf - - > report.pdf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Commands
|
|
37
|
+
|
|
38
|
+
**The 19 explicit `<source>-to-<target>` conversions** — one command per pair `createLocalDocumentConverter().conversions` declares in `documents.js`: `docx-to-pdf`, `pptx-to-pdf`, `odt-to-pdf`, `odp-to-pdf`, `ods-to-pdf`, `odg-to-pdf`, `odf-to-pdf` (one-way, no `pdf-to-odf` — see documents.js's own README), `pdf-to-docx`, `pdf-to-pptx`, `pdf-to-odt`, `pdf-to-odp`, `pdf-to-ods`, `pdf-to-odg`, and the six PDF-bypassing bridges `odt-to-docx`, `docx-to-odt`, `odp-to-pptx`, `pptx-to-odp`, `ods-to-xlsx`, `xlsx-to-ods`. Each takes `<input> [output]`:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
document-cli docx-to-pdf report.docx report.pdf
|
|
42
|
+
document-cli ods-to-xlsx budget.ods budget.xlsx
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**`convert <input> [output]`** — the same conversions through one generic command, inferring source format from the input's extension and target format from the output's extension (or `--to <format>` when the output path doesn't carry one, e.g. writing to stdout):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
document-cli convert report.docx report.pdf
|
|
49
|
+
document-cli convert report.docx - --to pdf > report.pdf
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**`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`).
|
|
53
|
+
|
|
54
|
+
**`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.
|
|
55
|
+
|
|
56
|
+
**`odb-to-xlsx <input> [output]`**, **`odb-to-csv <input> [output]`**, **`odb-tables <input>`** — extract table data from an embedded `.odb` database (HSQLDB TEXT script, HSQLDB CACHED binary row store, or Firebird gbak backup — detected automatically). `odb-to-xlsx` exports every table, one sheet each; `odb-to-csv` exports exactly one table (`--table <name>`, required whenever the database declares more than one); `odb-tables` lists every table's name, columns, and row count without writing anything:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
document-cli odb-tables customers.odb
|
|
60
|
+
document-cli odb-to-csv customers.odb --table CUSTOMERS customers.csv
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**`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:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
document-cli pdf-inspect report.pdf
|
|
67
|
+
document-cli pdf-inspect report.pdf --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**`tui [file]`** — launches the interactive terminal app; see [The TUI](#the-tui) below.
|
|
71
|
+
|
|
72
|
+
### Shared flags
|
|
73
|
+
|
|
74
|
+
The 19 explicit conversions, `convert`, `odm-to-pdf`, `odb-to-xlsx`, and `odb-to-csv` — every command that reads one file and writes one — share:
|
|
75
|
+
|
|
76
|
+
| Flag | Meaning |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `-o, --out <file>` | Output path; defaults to the input path with the target format's own extension. Conflicts with a positional output argument that names a different path. |
|
|
79
|
+
| `--timeout <ms>` | Abort the run after this many milliseconds. |
|
|
80
|
+
| `--json` | Emit diagnostics and the result summary as newline-delimited JSON on stderr, instead of human-readable lines. |
|
|
81
|
+
| `-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). |
|
|
82
|
+
| `--verbose` | Include a full stack trace in the error line when the run fails. |
|
|
83
|
+
|
|
84
|
+
`--dump-package <file>` is one flag further, registered only on the 19 explicit conversions and `convert` — it writes the intermediate `DocumentPackage` (content + layout) that conversion built to a JSON file. 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 six PDF-bypassing bridges 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`, `formats`, and `pdf-inspect` each take only their own `--json` (plus `pdf-inspect`'s own `--full`); `tui` takes no flags at all, only an optional positional file.
|
|
85
|
+
|
|
86
|
+
Diagnostics and the summary line always go to stderr; stdout is reserved for the converted bytes on any command writing to `-`.
|
|
87
|
+
|
|
88
|
+
### Exit codes
|
|
89
|
+
|
|
90
|
+
| Code | Meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `0` | Success. |
|
|
93
|
+
| `1` | The input was unusable — a malformed or encrypted PDF, or any other conversion failure not covered by the codes below. |
|
|
94
|
+
| `2` | A usage error — bad flags, conflicting output destinations, an unrecognised format, or (for a bare/`--help`/`--version` invocation) commander's own exit path. |
|
|
95
|
+
| `3` | documents.js needs more information to finish, not a different file — an unresolved `.odm` chapter, or a `.odb` table that wasn't specified (or wasn't found, or has no embedded engine at all, or uses an unsupported HSQLDB script serialisation). |
|
|
96
|
+
| `124` | The run's own `--timeout` elapsed before it finished. |
|
|
97
|
+
| `130` | Interrupted by `SIGINT` (Ctrl+C). |
|
|
98
|
+
|
|
99
|
+
## The TUI
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
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 live-view 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. Two 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), and a `.pdf` browses its pages and positioned items rather than being edited in place. `.xlsx` and standalone `.odf` have no TUI editor — documents.js has no xlsx editor at all, and a standalone `.odf` formula document has nothing to edit interactively, only a PDF conversion.
|
|
104
|
+
|
|
105
|
+
### Key bindings
|
|
106
|
+
|
|
107
|
+
The global bindings below apply everywhere; individual screens (a docx run's own bold/italic toggles, an ods cell's own value-kind picker) add their own on top:
|
|
108
|
+
|
|
109
|
+
| Keys | Action |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `↑` / `k` | Move the selection up |
|
|
112
|
+
| `↓` / `j` | Move the selection down |
|
|
113
|
+
| `Enter` / `→` / `l` | Open or edit the selected item |
|
|
114
|
+
| `Esc` / `←` / `h` | Go back to the previous screen |
|
|
115
|
+
| `PageUp` / `PageDown` | Scroll a page at a time |
|
|
116
|
+
| `Home` / `End` | Jump to the first or last item |
|
|
117
|
+
| `a` | Append a new item to the current list |
|
|
118
|
+
| `Ctrl+S` | Save the open document |
|
|
119
|
+
| `Ctrl+W` | Close the open document |
|
|
120
|
+
| `q` / `Ctrl+C` | Quit |
|
|
121
|
+
| `:` | Open the command palette |
|
|
122
|
+
| `/` | Search within the current screen |
|
|
123
|
+
| `?` | Show this help |
|
|
124
|
+
| `Ctrl+D` | Show the diagnostics panel |
|
|
125
|
+
|
|
126
|
+
## Architecture
|
|
127
|
+
|
|
128
|
+
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:
|
|
129
|
+
|
|
130
|
+
- **`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 19 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/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.
|
|
131
|
+
- **`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.
|
|
132
|
+
- **`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.
|
|
133
|
+
- **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.
|
|
134
|
+
|
|
135
|
+
## Build, test, and lint
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
pnpm build # tsdown -> dist/ (a library build for src/index.ts, and a separate ESM-only bin build for src/cli.ts)
|
|
139
|
+
pnpm typecheck # tsc --noEmit
|
|
140
|
+
pnpm lint # eslint . --max-warnings 0
|
|
141
|
+
pnpm test # vitest run --project unit
|
|
142
|
+
pnpm test:watch # vitest --project unit
|
|
143
|
+
pnpm test:smoke # tsdown, then vitest run --project smoke -- spawns the built dist/cli.js as a real child process
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Gotchas
|
|
147
|
+
|
|
148
|
+
- **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 19 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.
|
|
149
|
+
- **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.
|
|
150
|
+
- **`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.
|
|
151
|
+
- **`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.
|
|
152
|
+
- **A TUI screen must call an editor's own accessors fresh on every render, never cache them.** documents.js's live-view editors (`DocxRun`, `OdtParagraph`, `OdsCell`, ...) mutate the real XML tree in place — `editor.paragraphs()`/`slide.shapes()`/`sheet.cell(r, c)` called once and stored in `useState`/`useMemo` goes stale the instant any screen mutates the underlying document, with nothing in the type system or in React to catch it (see `src/tui/state/types.ts`'s own doc comment on `Screen`).
|
|
153
|
+
- **The conversions this CLI runs carry the same fidelity limits documents.js itself documents** — most notably that PDF-pivot conversions are not round-trip-lossless (line wrapping drifts under standard-14 font substitution, justified text renders left-aligned, tables and vector shapes don't reconstruct from a PDF), and that `pdf-to-ods` recovers only what a spreadsheet printed, never a real number/date/formula. None of that is specific to this CLI or the TUI; see documents.js's own README, particularly its [Fidelity](https://github.com/ExaDev/documents.js#fidelity) section, for the full, format-by-format account.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|