document-cli 0.0.0 → 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,615 @@
1
+ #!/usr/bin/env node
2
+ import { a as inferFormatFromExtension, n as resolveDefaultOutputPath, o as isDocumentFormat, r as writeOutput, t as readInput } from "./io-Cl3MaB0L.js";
3
+ import { Command, CommanderError, InvalidArgumentError } from "commander";
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";
6
+ import { basename, dirname, extname, join } from "node:path";
7
+ import { decodePackage } from "odf.js";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ //#region src/runtime/abort.ts
10
+ function combineSignals(a, b) {
11
+ const controller = new AbortController();
12
+ const forward = (signal) => {
13
+ controller.abort(signal.reason);
14
+ };
15
+ if (a.aborted) forward(a);
16
+ else a.addEventListener("abort", () => forward(a), { once: true });
17
+ if (b.aborted) forward(b);
18
+ else b.addEventListener("abort", () => forward(b), { once: true });
19
+ return controller.signal;
20
+ }
21
+ function createRuntimeSignal(options) {
22
+ let abortReason;
23
+ const interruptController = new AbortController();
24
+ process.on("SIGINT", () => {
25
+ abortReason ??= "interrupt";
26
+ interruptController.abort(/* @__PURE__ */ new Error("Interrupted by SIGINT"));
27
+ });
28
+ const getAbortReason = () => abortReason;
29
+ if (options.timeoutMs === void 0) return {
30
+ signal: interruptController.signal,
31
+ getAbortReason
32
+ };
33
+ const timeoutController = new AbortController();
34
+ setTimeout(() => {
35
+ abortReason ??= "timeout";
36
+ timeoutController.abort(/* @__PURE__ */ new Error(`Timed out after ${options.timeoutMs}ms`));
37
+ }, options.timeoutMs).unref();
38
+ return {
39
+ signal: combineSignals(interruptController.signal, timeoutController.signal),
40
+ getAbortReason
41
+ };
42
+ }
43
+ //#endregion
44
+ //#region src/runtime/diagnostics.ts
45
+ function createDiagnosticReporter(options) {
46
+ const { json, quiet, command } = options;
47
+ return {
48
+ report(diagnostic) {
49
+ if (quiet) return;
50
+ if (json) {
51
+ process.stderr.write(`${JSON.stringify({
52
+ type: "diagnostic",
53
+ command,
54
+ ...diagnostic
55
+ })}\n`);
56
+ return;
57
+ }
58
+ const pageClause = diagnostic.pageIndex === void 0 ? "" : ` (page ${diagnostic.pageIndex})`;
59
+ process.stderr.write(`[${command}] ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${pageClause}\n`);
60
+ },
61
+ summarize(result) {
62
+ if (json) {
63
+ process.stderr.write(`${JSON.stringify({
64
+ type: "result",
65
+ ...result
66
+ })}\n`);
67
+ return;
68
+ }
69
+ if (quiet) return;
70
+ const diagnosticClause = `${result.diagnosticCount} diagnostic${result.diagnosticCount === 1 ? "" : "s"}`;
71
+ process.stderr.write(`[${command}] wrote ${result.bytes} bytes to ${result.output} (${diagnosticClause})\n`);
72
+ }
73
+ };
74
+ }
75
+ function substitutionToDiagnostic(substitution, pageIndex) {
76
+ return {
77
+ severity: "warning",
78
+ code: "win-ansi-substitution",
79
+ message: `Character '${substitution.from}' has no glyph in the standard font; substituted with '${substitution.to}'`,
80
+ pageIndex
81
+ };
82
+ }
83
+ function pdfDiagnosticToDiagnostic(diagnostic) {
84
+ return {
85
+ severity: diagnostic.severity,
86
+ code: diagnostic.code,
87
+ message: diagnostic.message,
88
+ pageIndex: diagnostic.pageIndex
89
+ };
90
+ }
91
+ function mapErrorToExit(error, abortReason) {
92
+ if (abortReason === "interrupt") return 130;
93
+ if (abortReason === "timeout") return 124;
94
+ if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) return 3;
95
+ if (error instanceof PdfEncryptedError || error instanceof PdfParseError) return 1;
96
+ return 1;
97
+ }
98
+ //#endregion
99
+ //#region src/commands/shared.ts
100
+ function formatError(error, verbose) {
101
+ if (!(error instanceof Error)) return `error: ${String(error)}`;
102
+ const stackClause = verbose && error.stack !== void 0 ? `\n${error.stack}` : "";
103
+ return `error: ${error.message}${stackClause}`;
104
+ }
105
+ function buildConversionAction(source, target) {
106
+ const command = `${source}-to-${target}`;
107
+ return async (input, output, options) => {
108
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
109
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
110
+ return 2;
111
+ }
112
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target));
113
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeoutMs });
114
+ try {
115
+ const inputBytes = await readInput(input, { signal });
116
+ const result = await createLocalDocumentConverter().convert({
117
+ source: {
118
+ format: source,
119
+ bytes: new Uint8Array(inputBytes)
120
+ },
121
+ targetFormat: target
122
+ }, { signal });
123
+ await writeOutput(resolvedOutput, result.document.bytes);
124
+ const reporter = createDiagnosticReporter({
125
+ json: options.json,
126
+ quiet: options.quiet,
127
+ command
128
+ });
129
+ for (const diagnostic of result.diagnostics) reporter.report(diagnostic);
130
+ 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));
132
+ reporter.summarize({
133
+ output: resolvedOutput,
134
+ bytes: result.document.bytes.byteLength,
135
+ diagnosticCount: result.diagnostics.length
136
+ });
137
+ return 0;
138
+ } catch (error) {
139
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
140
+ return mapErrorToExit(error, getAbortReason());
141
+ }
142
+ };
143
+ }
144
+ //#endregion
145
+ //#region src/commands/options.ts
146
+ function addOutOption(command) {
147
+ return command.option("-o, --out <file>", "output file path (defaults to the input path with the target format's extension); use - for stdout");
148
+ }
149
+ function addTimeoutOption(command) {
150
+ return command.option("--timeout <ms>", "abort the run after this many milliseconds", (value) => Number.parseInt(value, 10));
151
+ }
152
+ function addJsonOption(command) {
153
+ return command.option("--json", "emit diagnostics and the result summary as newline-delimited JSON on stderr", false);
154
+ }
155
+ function addQuietOption(command) {
156
+ return command.option("-q, --quiet", "suppress diagnostic and summary output", false);
157
+ }
158
+ function addVerboseOption(command) {
159
+ return command.option("--verbose", "include a full stack trace when the run fails", false);
160
+ }
161
+ function addDumpPackageOption(command) {
162
+ return command.option("--dump-package <file>", "write the intermediate DocumentPackage (content + layout) this conversion built to a JSON file");
163
+ }
164
+ function addConversionFlags(command) {
165
+ addOutOption(command);
166
+ addTimeoutOption(command);
167
+ addJsonOption(command);
168
+ addQuietOption(command);
169
+ addVerboseOption(command);
170
+ return command;
171
+ }
172
+ //#endregion
173
+ //#region src/commands/convert.ts
174
+ function toConversionCommandOptions(options) {
175
+ return {
176
+ out: options.out,
177
+ timeoutMs: options.timeout,
178
+ json: options.json,
179
+ quiet: options.quiet,
180
+ verbose: options.verbose,
181
+ dumpPackage: options.dumpPackage
182
+ };
183
+ }
184
+ const KNOWN_FORMATS = "docx, pptx, xlsx, odt, odp, ods, odg, odf, 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
+ async function runGenericConvert(input, output, options) {
197
+ const extension = extname(input).toLowerCase();
198
+ if (extension === ".odm") {
199
+ process.stderr.write("convert: '.odm' master documents are not supported by the generic convert command -- use 'odm-to-pdf' instead\n");
200
+ return 2;
201
+ }
202
+ if (extension === ".odb") {
203
+ process.stderr.write("convert: '.odb' embedded databases are not supported by the generic convert command -- use 'odb-to-csv', 'odb-to-xlsx', or 'odb-tables' instead\n");
204
+ return 2;
205
+ }
206
+ const source = inferFormatFromExtension(input);
207
+ if (source === void 0) {
208
+ process.stderr.write(`convert: cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_FORMATS}) or use one of the explicit '<source>-to-<target>' commands\n`);
209
+ return 2;
210
+ }
211
+ const target = resolveGenericTarget(output, options);
212
+ if ("errorMessage" in target) {
213
+ process.stderr.write(`convert: ${target.errorMessage}\n`);
214
+ return 2;
215
+ }
216
+ return buildConversionAction(source, target.format)(input, output, toConversionCommandOptions(options));
217
+ }
218
+ function registerConversionCommands(program) {
219
+ const { conversions } = createLocalDocumentConverter();
220
+ for (const { source, target } of conversions) {
221
+ const commandName = `${source}-to-${target}`;
222
+ const command = program.command(`${commandName} <input> [output]`).description(`convert a ${source} document to ${target}`);
223
+ addConversionFlags(command);
224
+ addDumpPackageOption(command);
225
+ command.action(async (input, output, options) => {
226
+ process.exitCode = await buildConversionAction(source, target)(input, output, toConversionCommandOptions(options));
227
+ });
228
+ }
229
+ const generic = program.command("convert <input> [output]").description("convert between any two supported document formats, inferring source/target from file extensions where possible");
230
+ addConversionFlags(generic);
231
+ addDumpPackageOption(generic);
232
+ generic.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_FORMATS})`);
233
+ generic.action(async (input, output, options) => {
234
+ process.exitCode = await runGenericConvert(input, output, options);
235
+ });
236
+ }
237
+ //#endregion
238
+ //#region src/commands/formats.ts
239
+ const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, pdf-inspect";
240
+ function registerFormatsCommand(program) {
241
+ 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
+ const { conversions } = createLocalDocumentConverter();
243
+ if (options.json) {
244
+ process.stdout.write(`${JSON.stringify(conversions)}\n`);
245
+ return;
246
+ }
247
+ for (const { source, target } of conversions) process.stdout.write(`${source} -> ${target}\n`);
248
+ process.stdout.write(`\nnot covered by this list, each its own command: ${COMMANDS_NOT_LISTED}\n`);
249
+ });
250
+ }
251
+ //#endregion
252
+ //#region src/commands/odb.ts
253
+ function reportOdbError(command, error, verbose, abortReason) {
254
+ if (error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) {
255
+ process.stderr.write(`[${command}] ${error.message}\n`);
256
+ return mapErrorToExit(error, abortReason);
257
+ }
258
+ process.stderr.write(`[${command}] ${formatError(error, verbose)}\n`);
259
+ return mapErrorToExit(error, abortReason);
260
+ }
261
+ function reportOdbCsvError(command, error, verbose, abortReason) {
262
+ if (error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError) {
263
+ process.stderr.write(`[${command}] ${error.message}\nrun 'odb-tables' first to see the available tables\n`);
264
+ return mapErrorToExit(error, abortReason);
265
+ }
266
+ return reportOdbError(command, error, verbose, abortReason);
267
+ }
268
+ function resolveDefaultCsvOutputPath(inputPath) {
269
+ const directory = dirname(inputPath);
270
+ const stem = basename(inputPath, extname(inputPath));
271
+ return join(directory, `${stem}.csv`);
272
+ }
273
+ async function runOdbToXlsx(input, output, options) {
274
+ const command = "odb-to-xlsx";
275
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
276
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
277
+ return 2;
278
+ }
279
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, "xlsx"));
280
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
281
+ try {
282
+ const inputBytes = await readInput(input, { signal });
283
+ const bytes = odbToXlsx(new Uint8Array(inputBytes), { signal });
284
+ await writeOutput(resolvedOutput, bytes);
285
+ createDiagnosticReporter({
286
+ json: options.json,
287
+ quiet: options.quiet,
288
+ command
289
+ }).summarize({
290
+ output: resolvedOutput,
291
+ bytes: bytes.byteLength,
292
+ diagnosticCount: 0
293
+ });
294
+ return 0;
295
+ } catch (error) {
296
+ return reportOdbError(command, error, options.verbose, getAbortReason());
297
+ }
298
+ }
299
+ async function runOdbToCsv(input, output, options) {
300
+ const command = "odb-to-csv";
301
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
302
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
303
+ return 2;
304
+ }
305
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultCsvOutputPath(input));
306
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
307
+ try {
308
+ const inputBytes = await readInput(input, { signal });
309
+ const bytes = odbToCsv(new Uint8Array(inputBytes), {
310
+ signal,
311
+ table: options.table
312
+ });
313
+ await writeOutput(resolvedOutput, bytes);
314
+ createDiagnosticReporter({
315
+ json: options.json,
316
+ quiet: options.quiet,
317
+ command
318
+ }).summarize({
319
+ output: resolvedOutput,
320
+ bytes: bytes.byteLength,
321
+ diagnosticCount: 0
322
+ });
323
+ return 0;
324
+ } catch (error) {
325
+ return reportOdbCsvError(command, error, options.verbose, getAbortReason());
326
+ }
327
+ }
328
+ async function runOdbTables(input, options) {
329
+ const command = "odb-tables";
330
+ const { signal, getAbortReason } = createRuntimeSignal({});
331
+ try {
332
+ const inputBytes = await readInput(input, { signal });
333
+ const pkg = decodePackage(new Uint8Array(inputBytes));
334
+ const tables = readOdbTables(pkg);
335
+ if (options.json) {
336
+ const summary = tables.map((table) => ({
337
+ tableName: table.tableName,
338
+ columns: table.columns,
339
+ rowCount: table.rows.length
340
+ }));
341
+ process.stdout.write(`${JSON.stringify(summary)}\n`);
342
+ return 0;
343
+ }
344
+ for (const table of tables) {
345
+ process.stdout.write(`${table.tableName} (${table.rows.length} row${table.rows.length === 1 ? "" : "s"})\n`);
346
+ for (const column of table.columns) process.stdout.write(` ${column.name}: ${column.type}\n`);
347
+ }
348
+ return 0;
349
+ } catch (error) {
350
+ return reportOdbError(command, error, false, getAbortReason());
351
+ }
352
+ }
353
+ function registerOdbToXlsxCommand(program) {
354
+ 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
+ addOutOption(command);
356
+ addTimeoutOption(command);
357
+ addJsonOption(command);
358
+ addQuietOption(command);
359
+ addVerboseOption(command);
360
+ command.action(async (input, output, options) => {
361
+ process.exitCode = await runOdbToXlsx(input, output, options);
362
+ });
363
+ }
364
+ function registerOdbToCsvCommand(program) {
365
+ const command = program.command("odb-to-csv <input> [output]").description("extract exactly one named table from an embedded .odb database as CSV");
366
+ addOutOption(command);
367
+ addTimeoutOption(command);
368
+ addJsonOption(command);
369
+ addQuietOption(command);
370
+ addVerboseOption(command);
371
+ command.option("--table <name>", "the table to export -- required when the .odb declares more than one table");
372
+ command.action(async (input, output, options) => {
373
+ process.exitCode = await runOdbToCsv(input, output, options);
374
+ });
375
+ }
376
+ function registerOdbTablesCommand(program) {
377
+ program.command("odb-tables <input>").description("list every table an embedded .odb database declares, with column names/types and row counts").option("--json", "emit the table list as a JSON array instead of a human-readable report", false).action(async (input, options) => {
378
+ process.exitCode = await runOdbTables(input, options);
379
+ });
380
+ }
381
+ function registerOdbCommands(program) {
382
+ registerOdbToXlsxCommand(program);
383
+ registerOdbToCsvCommand(program);
384
+ registerOdbTablesCommand(program);
385
+ }
386
+ //#endregion
387
+ //#region src/commands/odm.ts
388
+ function collectChapterOverride(value, previous) {
389
+ const separatorIndex = value.indexOf("=");
390
+ if (separatorIndex === -1) throw new InvalidArgumentError(`--chapter must be formatted as <href>=<file>, got '${value}'`);
391
+ const next = new Map(previous);
392
+ next.set(value.slice(0, separatorIndex), value.slice(separatorIndex + 1));
393
+ return next;
394
+ }
395
+ function createResolveSubDocument(overrides, chaptersDir) {
396
+ return (href) => {
397
+ const overridePath = overrides.get(href);
398
+ if (overridePath !== void 0) return new Uint8Array(readFileSync(overridePath));
399
+ if (chaptersDir === void 0) return;
400
+ const candidate = join(chaptersDir, basename(href));
401
+ if (!existsSync(candidate)) return;
402
+ return new Uint8Array(readFileSync(candidate));
403
+ };
404
+ }
405
+ async function runOdmToPdf(input, output, options) {
406
+ const command = "odm-to-pdf";
407
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
408
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
409
+ return 2;
410
+ }
411
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, "pdf"));
412
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
413
+ const resolveSubDocument = createResolveSubDocument(options.chapter, options.chaptersDir);
414
+ const reporter = createDiagnosticReporter({
415
+ json: options.json,
416
+ quiet: options.quiet,
417
+ command
418
+ });
419
+ let diagnosticCount = 0;
420
+ try {
421
+ const inputBytes = await readInput(input, { signal });
422
+ const bytes = odmToPdf(new Uint8Array(inputBytes), {
423
+ signal,
424
+ resolveSubDocument,
425
+ onSubstitution: (substitution, context) => {
426
+ diagnosticCount += 1;
427
+ reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
428
+ }
429
+ });
430
+ await writeOutput(resolvedOutput, bytes);
431
+ reporter.summarize({
432
+ output: resolvedOutput,
433
+ bytes: bytes.byteLength,
434
+ diagnosticCount
435
+ });
436
+ return 0;
437
+ } catch (error) {
438
+ if (error instanceof OdmUnresolvedSectionError) {
439
+ process.stderr.write(`${error.message}\npass --chapters-dir <dir> containing these files, or --chapter <href>=<file>\n`);
440
+ return mapErrorToExit(error, getAbortReason());
441
+ }
442
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
443
+ return mapErrorToExit(error, getAbortReason());
444
+ }
445
+ }
446
+ function registerOdmCommand(program) {
447
+ const command = program.command("odm-to-pdf <input> [output]").description("convert a .odm master document to pdf, resolving each chapter's external .odt reference via --chapters-dir and/or --chapter");
448
+ addOutOption(command);
449
+ addTimeoutOption(command);
450
+ addJsonOption(command);
451
+ addQuietOption(command);
452
+ addVerboseOption(command);
453
+ command.option("--chapters-dir <dir>", "directory to search for each unresolved chapter href, matched by the href's own basename");
454
+ command.option("--chapter <href>=<file>", "resolve one chapter href to a local file explicitly; repeatable", collectChapterOverride, /* @__PURE__ */ new Map());
455
+ command.action(async (input, output, options) => {
456
+ process.exitCode = await runOdmToPdf(input, output, options);
457
+ });
458
+ }
459
+ //#endregion
460
+ //#region src/commands/pdf-inspect.ts
461
+ function buildItemKindHistogram(items) {
462
+ const histogram = /* @__PURE__ */ new Map();
463
+ for (const item of items) histogram.set(item.kind, (histogram.get(item.kind) ?? 0) + 1);
464
+ return histogram;
465
+ }
466
+ function countImagesByFormat(images) {
467
+ const counts = /* @__PURE__ */ new Map();
468
+ for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
469
+ return counts;
470
+ }
471
+ function isPresent(entry) {
472
+ return entry[1] !== void 0;
473
+ }
474
+ function formatMetadataValue(value) {
475
+ return typeof value === "string" ? value : value.join(", ");
476
+ }
477
+ async function runPdfInspect(input, options) {
478
+ const command = "pdf-inspect";
479
+ const { signal, getAbortReason } = createRuntimeSignal({});
480
+ const reporter = createDiagnosticReporter({
481
+ json: options.json,
482
+ quiet: false,
483
+ command
484
+ });
485
+ try {
486
+ const inputBytes = await readInput(input, { signal });
487
+ const layout = readPdf(new Uint8Array(inputBytes), {
488
+ signal,
489
+ sink: (diagnostic) => {
490
+ reporter.report(pdfDiagnosticToDiagnostic(diagnostic));
491
+ }
492
+ });
493
+ if (options.full) {
494
+ process.stdout.write(`${JSON.stringify(layout, void 0, 2)}\n`);
495
+ return 0;
496
+ }
497
+ const imagesByFormat = countImagesByFormat(layout.images);
498
+ if (options.json) {
499
+ const summary = {
500
+ pageCount: layout.pages.length,
501
+ pages: layout.pages.map((page) => ({
502
+ widthPt: page.widthPt,
503
+ heightPt: page.heightPt,
504
+ itemKinds: Object.fromEntries(buildItemKindHistogram(page.items))
505
+ })),
506
+ metadata: layout.metadata,
507
+ imagesByFormat: Object.fromEntries(imagesByFormat)
508
+ };
509
+ process.stdout.write(`${JSON.stringify(summary)}\n`);
510
+ return 0;
511
+ }
512
+ process.stdout.write(`${layout.pages.length} page${layout.pages.length === 1 ? "" : "s"}\n`);
513
+ layout.pages.forEach((page, index) => {
514
+ const histogram = buildItemKindHistogram(page.items);
515
+ const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
516
+ process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
517
+ });
518
+ const presentMetadata = [
519
+ ["title", layout.metadata.title],
520
+ ["author", layout.metadata.author],
521
+ ["subject", layout.metadata.subject],
522
+ ["keywords", layout.metadata.keywords],
523
+ ["creator", layout.metadata.creator],
524
+ ["producer", layout.metadata.producer],
525
+ ["createdIso", layout.metadata.createdIso],
526
+ ["modifiedIso", layout.metadata.modifiedIso]
527
+ ].filter(isPresent);
528
+ if (presentMetadata.length > 0) {
529
+ process.stdout.write("metadata:\n");
530
+ for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
531
+ }
532
+ if (imagesByFormat.size > 0) {
533
+ process.stdout.write("images:\n");
534
+ for (const [format, count] of imagesByFormat) process.stdout.write(` ${format}: ${count}\n`);
535
+ }
536
+ return 0;
537
+ } catch (error) {
538
+ process.stderr.write(`${formatError(error, false)}\n`);
539
+ return mapErrorToExit(error, getAbortReason());
540
+ }
541
+ }
542
+ function registerPdfInspectCommand(program) {
543
+ program.command("pdf-inspect <input>").description("inspect a PDF: page count, per-page size and item-kind histogram, document metadata, and embedded image formats").option("--json", "emit the summary as JSON instead of a human-readable report", false).option("--full", "dump the entire parsed LayoutDocument as JSON instead of a summary", false).action(async (input, options) => {
544
+ process.exitCode = await runPdfInspect(input, options);
545
+ });
546
+ }
547
+ //#endregion
548
+ //#region package.json
549
+ var version = "1.0.1";
550
+ //#endregion
551
+ //#region src/program.ts
552
+ function createProgram() {
553
+ const program = new Command("document-cli");
554
+ program.description("every documents.js docx/pptx/odt/odp/ods/odg/odf/odm/odb conversion, bridge, and inspector as a scriptable command");
555
+ program.version(version);
556
+ program.exitOverride((error) => {
557
+ process.exitCode = error.exitCode === 0 ? 0 : 2;
558
+ throw error;
559
+ });
560
+ registerConversionCommands(program);
561
+ registerFormatsCommand(program);
562
+ registerOdmCommand(program);
563
+ registerOdbCommands(program);
564
+ registerPdfInspectCommand(program);
565
+ return program;
566
+ }
567
+ //#endregion
568
+ //#region src/cli.ts
569
+ async function launchTui(startPath, signal) {
570
+ try {
571
+ const { runTui } = await import("./tui-CucYgqmL.js");
572
+ await runTui({
573
+ startPath,
574
+ signal
575
+ });
576
+ process.exitCode = 0;
577
+ } catch (error) {
578
+ process.stderr.write(`${formatError(error, false)}\n`);
579
+ process.exitCode = 1;
580
+ }
581
+ }
582
+ function findStartPath(args) {
583
+ return args.find((arg) => !arg.startsWith("-"));
584
+ }
585
+ async function main() {
586
+ const { signal } = createRuntimeSignal({});
587
+ const args = process.argv.slice(2);
588
+ const [dispatchToken] = args;
589
+ if (dispatchToken === void 0 || dispatchToken === "tui") {
590
+ if (process.stdout.isTTY !== true) {
591
+ if (dispatchToken === "tui") {
592
+ process.stderr.write("the TUI requires an interactive terminal (TTY); stdout is currently redirected\n");
593
+ process.exitCode = 2;
594
+ return;
595
+ }
596
+ createProgram().outputHelp();
597
+ process.exitCode = 0;
598
+ return;
599
+ }
600
+ await launchTui(findStartPath(dispatchToken === "tui" ? args.slice(1) : args), signal);
601
+ return;
602
+ }
603
+ const program = createProgram();
604
+ program.command("tui [file]").description("launch the interactive terminal UI, optionally opening a file immediately").action(async (file) => {
605
+ await launchTui(file, signal);
606
+ });
607
+ try {
608
+ await program.parseAsync(process.argv);
609
+ } catch (error) {
610
+ if (!(error instanceof CommanderError)) throw error;
611
+ }
612
+ }
613
+ await main();
614
+ //#endregion
615
+ export {};