document-cli 1.4.0 → 1.6.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxExtras, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
2
+ import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodeMarkdownText, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readOdfFormulaContent, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, writePdf, xlsxToPdf } from "documents.js";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
4
  import { Command, InvalidArgumentError } from "commander";
5
5
  import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
@@ -683,7 +683,7 @@ function registerFontsCommand(program) {
683
683
  }
684
684
  //#endregion
685
685
  //#region src/commands/formats.ts
686
- const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras";
686
+ const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
687
687
  function registerFormatsCommand(program) {
688
688
  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) => {
689
689
  const { conversions } = createLocalDocumentConverter();
@@ -776,6 +776,78 @@ function registerFromPackageCommand(program) {
776
776
  });
777
777
  }
778
778
  //#endregion
779
+ //#region src/runtime/metadata-format.ts
780
+ const METADATA_KEYS = [
781
+ "title",
782
+ "author",
783
+ "subject",
784
+ "keywords",
785
+ "creator",
786
+ "producer",
787
+ "createdIso",
788
+ "modifiedIso"
789
+ ];
790
+ function isPresent(entry) {
791
+ return entry[1] !== void 0;
792
+ }
793
+ function formatMetadataValue(value) {
794
+ return typeof value === "string" ? value : value.join(", ");
795
+ }
796
+ function presentMetadataEntries(metadata) {
797
+ return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
798
+ }
799
+ function formatMetadataLines(metadata) {
800
+ return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
801
+ }
802
+ //#endregion
803
+ //#region src/commands/metadata.ts
804
+ function readMetadataForFormat(format, bytes, signal) {
805
+ switch (format) {
806
+ case "docx": return readDocxContent(decodePackage(bytes)).metadata;
807
+ case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
808
+ case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
809
+ case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
810
+ case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
811
+ case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
812
+ case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
813
+ case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
814
+ case "pdf": return readPdf(bytes, { signal }).metadata;
815
+ case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
816
+ }
817
+ }
818
+ async function runMetadata(input, options) {
819
+ const command = "metadata";
820
+ const source = inferFormatFromExtension(input);
821
+ if (source === void 0) {
822
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
823
+ return 2;
824
+ }
825
+ const { signal, getAbortReason } = createRuntimeSignal({});
826
+ try {
827
+ const inputBytes = await readInput(input, { signal });
828
+ const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
829
+ if (options.json) {
830
+ process.stdout.write(`${JSON.stringify(metadata)}\n`);
831
+ return 0;
832
+ }
833
+ const lines = formatMetadataLines(metadata);
834
+ if (lines.length === 0) {
835
+ process.stdout.write("This document carries no metadata.\n");
836
+ return 0;
837
+ }
838
+ for (const line of lines) process.stdout.write(`${line}\n`);
839
+ return 0;
840
+ } catch (error) {
841
+ process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
842
+ return mapErrorToExit(error, getAbortReason());
843
+ }
844
+ }
845
+ function registerMetadataCommand(program) {
846
+ program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
847
+ process.exitCode = await runMetadata(input, options);
848
+ });
849
+ }
850
+ //#endregion
779
851
  //#region src/odb-structure.ts
780
852
  const INDENT = " ";
781
853
  function indent(depth) {
@@ -1379,12 +1451,6 @@ function countImagesByFormat(images) {
1379
1451
  for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
1380
1452
  return counts;
1381
1453
  }
1382
- function isPresent(entry) {
1383
- return entry[1] !== void 0;
1384
- }
1385
- function formatMetadataValue(value) {
1386
- return typeof value === "string" ? value : value.join(", ");
1387
- }
1388
1454
  async function runPdfInspect(input, options) {
1389
1455
  const command = "pdf-inspect";
1390
1456
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -1426,19 +1492,9 @@ async function runPdfInspect(input, options) {
1426
1492
  const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
1427
1493
  process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
1428
1494
  });
1429
- const presentMetadata = [
1430
- ["title", layout.metadata.title],
1431
- ["author", layout.metadata.author],
1432
- ["subject", layout.metadata.subject],
1433
- ["keywords", layout.metadata.keywords],
1434
- ["creator", layout.metadata.creator],
1435
- ["producer", layout.metadata.producer],
1436
- ["createdIso", layout.metadata.createdIso],
1437
- ["modifiedIso", layout.metadata.modifiedIso]
1438
- ].filter(isPresent);
1439
- if (presentMetadata.length > 0) {
1495
+ if (presentMetadataEntries(layout.metadata).length > 0) {
1440
1496
  process.stdout.write("metadata:\n");
1441
- for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
1497
+ for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
1442
1498
  }
1443
1499
  if (imagesByFormat.size > 0) {
1444
1500
  process.stdout.write("images:\n");
@@ -1456,8 +1512,155 @@ function registerPdfInspectCommand(program) {
1456
1512
  });
1457
1513
  }
1458
1514
  //#endregion
1515
+ //#region src/commands/set-metadata.ts
1516
+ const REBUILD_FORMATS = {
1517
+ docx: true,
1518
+ pptx: true,
1519
+ odt: true,
1520
+ odp: true,
1521
+ ods: true,
1522
+ odg: true,
1523
+ markdown: true
1524
+ };
1525
+ function isRebuildFormat(format) {
1526
+ return format in REBUILD_FORMATS;
1527
+ }
1528
+ function readContentForFormat(format, bytes) {
1529
+ switch (format) {
1530
+ case "docx": return readDocxContent(decodePackage(bytes));
1531
+ case "pptx": return readPptxContent(decodePackage(bytes));
1532
+ case "odt": return readOdtContent(decodePackage$1(bytes));
1533
+ case "odp": return readOdpContent(decodePackage$1(bytes));
1534
+ case "ods": return readOdsContent(decodePackage$1(bytes));
1535
+ case "odg": return readOdgContent(decodePackage$1(bytes));
1536
+ case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
1537
+ }
1538
+ }
1539
+ function buildBytesForRebuildFormat(format, content) {
1540
+ switch (format) {
1541
+ case "docx": return encodePackage(buildDocxPackage(content));
1542
+ case "pptx": return encodePackage(buildPptxPackage(content));
1543
+ case "odt": return encodePackage$1(buildOdtPackage(content));
1544
+ case "odp": return encodePackage$1(buildOdpPackage(content));
1545
+ case "ods": return encodePackage$1(buildOdsPackage(content));
1546
+ case "odg": return encodePackage$1(buildOdgPackage(content));
1547
+ case "markdown": return encodeMarkdownText(buildMarkdownText(content));
1548
+ }
1549
+ }
1550
+ function mergeMetadata(current, overrides) {
1551
+ return {
1552
+ ...current,
1553
+ ...overrides.title !== void 0 ? { title: overrides.title } : {},
1554
+ ...overrides.author !== void 0 ? { author: overrides.author } : {},
1555
+ ...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
1556
+ ...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
1557
+ };
1558
+ }
1559
+ function parseKeywords(csv) {
1560
+ return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1561
+ }
1562
+ function classifyWritePath(source, target) {
1563
+ if (source === "pdf" && target === "pdf") return { kind: "pdf" };
1564
+ if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
1565
+ if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
1566
+ if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
1567
+ if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
1568
+ return {
1569
+ kind: "rebuild",
1570
+ format: source
1571
+ };
1572
+ }
1573
+ async function runSetMetadata(input, output, options) {
1574
+ const command = "set-metadata";
1575
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1576
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1577
+ return 2;
1578
+ }
1579
+ const target = resolveTargetFormat(output, options.out, options.to);
1580
+ if ("errorMessage" in target) {
1581
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1582
+ return 2;
1583
+ }
1584
+ const source = inferFormatFromExtension(input);
1585
+ if (source === void 0) {
1586
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
1587
+ return 2;
1588
+ }
1589
+ const writePath = classifyWritePath(source, target.format);
1590
+ if ("errorMessage" in writePath) {
1591
+ process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
1592
+ return 2;
1593
+ }
1594
+ const overrides = {
1595
+ title: options.setTitle,
1596
+ author: options.setAuthor,
1597
+ subject: options.setSubject,
1598
+ keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
1599
+ };
1600
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
1601
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1602
+ try {
1603
+ const inputBytes = await readInput(input, { signal });
1604
+ const bytes = writePath.kind === "pdf" ? (() => {
1605
+ const layout = readPdf(new Uint8Array(inputBytes), { signal });
1606
+ const patched = {
1607
+ ...layout,
1608
+ metadata: mergeMetadata(layout.metadata, overrides)
1609
+ };
1610
+ return writePdf(patched, { signal });
1611
+ })() : (() => {
1612
+ const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
1613
+ const nextContent = {
1614
+ ...content,
1615
+ metadata: mergeMetadata(content.metadata, overrides)
1616
+ };
1617
+ return buildBytesForRebuildFormat(writePath.format, nextContent);
1618
+ })();
1619
+ await writeOutput(resolvedOutput, bytes);
1620
+ createDiagnosticReporter({
1621
+ json: options.json,
1622
+ quiet: options.quiet,
1623
+ command
1624
+ }).summarize({
1625
+ output: resolvedOutput,
1626
+ bytes: bytes.byteLength,
1627
+ diagnosticCount: 0
1628
+ });
1629
+ return 0;
1630
+ } catch (error) {
1631
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
1632
+ return mapErrorToExit(error, getAbortReason());
1633
+ }
1634
+ }
1635
+ function registerSetMetadataCommand(program) {
1636
+ const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
1637
+ "",
1638
+ "Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
1639
+ "involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
1640
+ "odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
1641
+ "this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
1642
+ "since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
1643
+ "",
1644
+ "set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
1645
+ "set-metadata on the result, if you need a different target format."
1646
+ ].join("\n"));
1647
+ addOutOption(command);
1648
+ addTimeoutOption(command);
1649
+ addJsonOption(command);
1650
+ addQuietOption(command);
1651
+ addVerboseOption(command);
1652
+ command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
1653
+ command.option("--set-title <text>", "set the title field");
1654
+ command.option("--set-author <text>", "set the author field");
1655
+ command.option("--set-subject <text>", "set the subject field");
1656
+ command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
1657
+ command.action(async (input, output, options) => {
1658
+ process.exitCode = await runSetMetadata(input, output, options);
1659
+ });
1660
+ }
1661
+ //#endregion
1459
1662
  //#region package.json
1460
- var version = "1.4.0";
1663
+ var version = "1.6.0";
1461
1664
  //#endregion
1462
1665
  //#region src/program.ts
1463
1666
  function createProgram() {
@@ -1476,6 +1679,8 @@ function createProgram() {
1476
1679
  registerPdfInspectCommand(program);
1477
1680
  registerFontsCommand(program);
1478
1681
  registerDocxExtrasCommand(program);
1682
+ registerMetadataCommand(program);
1683
+ registerSetMetadataCommand(program);
1479
1684
  return program;
1480
1685
  }
1481
1686
  //#endregion
@@ -305,6 +305,30 @@ function formatDocxExtrasLines(extras) {
305
305
  return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
306
306
  }
307
307
  //#endregion
308
+ //#region src/runtime/metadata-format.ts
309
+ const METADATA_KEYS = [
310
+ "title",
311
+ "author",
312
+ "subject",
313
+ "keywords",
314
+ "creator",
315
+ "producer",
316
+ "createdIso",
317
+ "modifiedIso"
318
+ ];
319
+ function isPresent(entry) {
320
+ return entry[1] !== void 0;
321
+ }
322
+ function formatMetadataValue(value) {
323
+ return typeof value === "string" ? value : value.join(", ");
324
+ }
325
+ function presentMetadataEntries(metadata) {
326
+ return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
327
+ }
328
+ function formatMetadataLines(metadata) {
329
+ return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
330
+ }
331
+ //#endregion
308
332
  //#region src/odb-structure.ts
309
333
  const INDENT = " ";
310
334
  function indent(depth) {
@@ -437,4 +461,4 @@ function formatOdbReportLines(report) {
437
461
  return lines;
438
462
  }
439
463
  //#endregion
440
- export { odbFormSummary as a, resolveDefaultOutputPath as c, formatToExtension as d, inferFormatFromExtension as f, formatOdbReportLines as i, writeOutput as l, describeOdbReport as n, formatDocxExtrasLines as o, isDocumentFormat as p, formatOdbFormLines as r, readInput as s, describeOdbForm as t, loadProvidedFonts as u };
464
+ export { odbFormSummary as a, formatDocxExtrasLines as c, writeOutput as d, loadProvidedFonts as f, isDocumentFormat as h, formatOdbReportLines as i, readInput as l, inferFormatFromExtension as m, describeOdbReport as n, formatMetadataLines as o, formatToExtension as p, formatOdbFormLines as r, presentMetadataEntries as s, describeOdbForm as t, resolveDefaultOutputPath as u };