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/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as odbFormSummary, c as resolveDefaultOutputPath, f as inferFormatFromExtension, i as formatOdbReportLines, l as writeOutput, n as describeOdbReport, o as formatDocxExtrasLines, p as isDocumentFormat, r as formatOdbFormLines, s as readInput, t as describeOdbForm, u as loadProvidedFonts } from "./odb-structure-DkfNyHGR.js";
2
+ import { a as odbFormSummary, c as formatDocxExtrasLines, d as writeOutput, f as loadProvidedFonts, h as isDocumentFormat, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, r as formatOdbFormLines, s as presentMetadataEntries, t as describeOdbForm, u as resolveDefaultOutputPath } from "./odb-structure-Du1w_hOU.js";
3
3
  import { Command, CommanderError, InvalidArgumentError } from "commander";
4
4
  import { writeFile } from "node:fs/promises";
5
- 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";
5
+ 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";
6
6
  import { basename, dirname, extname, join } from "node:path";
7
7
  import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
8
8
  import { existsSync, readFileSync } from "node:fs";
@@ -373,7 +373,7 @@ function registerFontsCommand(program) {
373
373
  }
374
374
  //#endregion
375
375
  //#region src/commands/formats.ts
376
- 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";
376
+ 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";
377
377
  function registerFormatsCommand(program) {
378
378
  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) => {
379
379
  const { conversions } = createLocalDocumentConverter();
@@ -466,6 +466,54 @@ function registerFromPackageCommand(program) {
466
466
  });
467
467
  }
468
468
  //#endregion
469
+ //#region src/commands/metadata.ts
470
+ function readMetadataForFormat(format, bytes, signal) {
471
+ switch (format) {
472
+ case "docx": return readDocxContent(decodePackage(bytes)).metadata;
473
+ case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
474
+ case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
475
+ case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
476
+ case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
477
+ case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
478
+ case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
479
+ case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
480
+ case "pdf": return readPdf(bytes, { signal }).metadata;
481
+ case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
482
+ }
483
+ }
484
+ async function runMetadata(input, options) {
485
+ const command = "metadata";
486
+ const source = inferFormatFromExtension(input);
487
+ if (source === void 0) {
488
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
489
+ return 2;
490
+ }
491
+ const { signal, getAbortReason } = createRuntimeSignal({});
492
+ try {
493
+ const inputBytes = await readInput(input, { signal });
494
+ const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
495
+ if (options.json) {
496
+ process.stdout.write(`${JSON.stringify(metadata)}\n`);
497
+ return 0;
498
+ }
499
+ const lines = formatMetadataLines(metadata);
500
+ if (lines.length === 0) {
501
+ process.stdout.write("This document carries no metadata.\n");
502
+ return 0;
503
+ }
504
+ for (const line of lines) process.stdout.write(`${line}\n`);
505
+ return 0;
506
+ } catch (error) {
507
+ process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
508
+ return mapErrorToExit(error, getAbortReason());
509
+ }
510
+ }
511
+ function registerMetadataCommand(program) {
512
+ 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) => {
513
+ process.exitCode = await runMetadata(input, options);
514
+ });
515
+ }
516
+ //#endregion
469
517
  //#region src/sql-result-format.ts
470
518
  const COLUMN_GAP = " ";
471
519
  function columnWidths(columns, cells) {
@@ -937,12 +985,6 @@ function countImagesByFormat(images) {
937
985
  for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
938
986
  return counts;
939
987
  }
940
- function isPresent(entry) {
941
- return entry[1] !== void 0;
942
- }
943
- function formatMetadataValue(value) {
944
- return typeof value === "string" ? value : value.join(", ");
945
- }
946
988
  async function runPdfInspect(input, options) {
947
989
  const command = "pdf-inspect";
948
990
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -984,19 +1026,9 @@ async function runPdfInspect(input, options) {
984
1026
  const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
985
1027
  process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
986
1028
  });
987
- const presentMetadata = [
988
- ["title", layout.metadata.title],
989
- ["author", layout.metadata.author],
990
- ["subject", layout.metadata.subject],
991
- ["keywords", layout.metadata.keywords],
992
- ["creator", layout.metadata.creator],
993
- ["producer", layout.metadata.producer],
994
- ["createdIso", layout.metadata.createdIso],
995
- ["modifiedIso", layout.metadata.modifiedIso]
996
- ].filter(isPresent);
997
- if (presentMetadata.length > 0) {
1029
+ if (presentMetadataEntries(layout.metadata).length > 0) {
998
1030
  process.stdout.write("metadata:\n");
999
- for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
1031
+ for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
1000
1032
  }
1001
1033
  if (imagesByFormat.size > 0) {
1002
1034
  process.stdout.write("images:\n");
@@ -1014,8 +1046,155 @@ function registerPdfInspectCommand(program) {
1014
1046
  });
1015
1047
  }
1016
1048
  //#endregion
1049
+ //#region src/commands/set-metadata.ts
1050
+ const REBUILD_FORMATS = {
1051
+ docx: true,
1052
+ pptx: true,
1053
+ odt: true,
1054
+ odp: true,
1055
+ ods: true,
1056
+ odg: true,
1057
+ markdown: true
1058
+ };
1059
+ function isRebuildFormat(format) {
1060
+ return format in REBUILD_FORMATS;
1061
+ }
1062
+ function readContentForFormat(format, bytes) {
1063
+ switch (format) {
1064
+ case "docx": return readDocxContent(decodePackage(bytes));
1065
+ case "pptx": return readPptxContent(decodePackage(bytes));
1066
+ case "odt": return readOdtContent(decodePackage$1(bytes));
1067
+ case "odp": return readOdpContent(decodePackage$1(bytes));
1068
+ case "ods": return readOdsContent(decodePackage$1(bytes));
1069
+ case "odg": return readOdgContent(decodePackage$1(bytes));
1070
+ case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
1071
+ }
1072
+ }
1073
+ function buildBytesForRebuildFormat(format, content) {
1074
+ switch (format) {
1075
+ case "docx": return encodePackage(buildDocxPackage(content));
1076
+ case "pptx": return encodePackage(buildPptxPackage(content));
1077
+ case "odt": return encodePackage$1(buildOdtPackage(content));
1078
+ case "odp": return encodePackage$1(buildOdpPackage(content));
1079
+ case "ods": return encodePackage$1(buildOdsPackage(content));
1080
+ case "odg": return encodePackage$1(buildOdgPackage(content));
1081
+ case "markdown": return encodeMarkdownText(buildMarkdownText(content));
1082
+ }
1083
+ }
1084
+ function mergeMetadata(current, overrides) {
1085
+ return {
1086
+ ...current,
1087
+ ...overrides.title !== void 0 ? { title: overrides.title } : {},
1088
+ ...overrides.author !== void 0 ? { author: overrides.author } : {},
1089
+ ...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
1090
+ ...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
1091
+ };
1092
+ }
1093
+ function parseKeywords(csv) {
1094
+ return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1095
+ }
1096
+ function classifyWritePath(source, target) {
1097
+ if (source === "pdf" && target === "pdf") return { kind: "pdf" };
1098
+ 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" };
1099
+ 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" };
1100
+ 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.` };
1101
+ 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.` };
1102
+ return {
1103
+ kind: "rebuild",
1104
+ format: source
1105
+ };
1106
+ }
1107
+ async function runSetMetadata(input, output, options) {
1108
+ const command = "set-metadata";
1109
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1110
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1111
+ return 2;
1112
+ }
1113
+ const target = resolveTargetFormat(output, options.out, options.to);
1114
+ if ("errorMessage" in target) {
1115
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1116
+ return 2;
1117
+ }
1118
+ const source = inferFormatFromExtension(input);
1119
+ if (source === void 0) {
1120
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
1121
+ return 2;
1122
+ }
1123
+ const writePath = classifyWritePath(source, target.format);
1124
+ if ("errorMessage" in writePath) {
1125
+ process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
1126
+ return 2;
1127
+ }
1128
+ const overrides = {
1129
+ title: options.setTitle,
1130
+ author: options.setAuthor,
1131
+ subject: options.setSubject,
1132
+ keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
1133
+ };
1134
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
1135
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1136
+ try {
1137
+ const inputBytes = await readInput(input, { signal });
1138
+ const bytes = writePath.kind === "pdf" ? (() => {
1139
+ const layout = readPdf(new Uint8Array(inputBytes), { signal });
1140
+ const patched = {
1141
+ ...layout,
1142
+ metadata: mergeMetadata(layout.metadata, overrides)
1143
+ };
1144
+ return writePdf(patched, { signal });
1145
+ })() : (() => {
1146
+ const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
1147
+ const nextContent = {
1148
+ ...content,
1149
+ metadata: mergeMetadata(content.metadata, overrides)
1150
+ };
1151
+ return buildBytesForRebuildFormat(writePath.format, nextContent);
1152
+ })();
1153
+ await writeOutput(resolvedOutput, bytes);
1154
+ createDiagnosticReporter({
1155
+ json: options.json,
1156
+ quiet: options.quiet,
1157
+ command
1158
+ }).summarize({
1159
+ output: resolvedOutput,
1160
+ bytes: bytes.byteLength,
1161
+ diagnosticCount: 0
1162
+ });
1163
+ return 0;
1164
+ } catch (error) {
1165
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
1166
+ return mapErrorToExit(error, getAbortReason());
1167
+ }
1168
+ }
1169
+ function registerSetMetadataCommand(program) {
1170
+ 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", [
1171
+ "",
1172
+ "Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
1173
+ "involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
1174
+ "odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
1175
+ "this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
1176
+ "since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
1177
+ "",
1178
+ "set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
1179
+ "set-metadata on the result, if you need a different target format."
1180
+ ].join("\n"));
1181
+ addOutOption(command);
1182
+ addTimeoutOption(command);
1183
+ addJsonOption(command);
1184
+ addQuietOption(command);
1185
+ addVerboseOption(command);
1186
+ command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
1187
+ command.option("--set-title <text>", "set the title field");
1188
+ command.option("--set-author <text>", "set the author field");
1189
+ command.option("--set-subject <text>", "set the subject field");
1190
+ command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
1191
+ command.action(async (input, output, options) => {
1192
+ process.exitCode = await runSetMetadata(input, output, options);
1193
+ });
1194
+ }
1195
+ //#endregion
1017
1196
  //#region package.json
1018
- var version = "1.4.0";
1197
+ var version = "1.6.0";
1019
1198
  //#endregion
1020
1199
  //#region src/program.ts
1021
1200
  function createProgram() {
@@ -1034,13 +1213,15 @@ function createProgram() {
1034
1213
  registerPdfInspectCommand(program);
1035
1214
  registerFontsCommand(program);
1036
1215
  registerDocxExtrasCommand(program);
1216
+ registerMetadataCommand(program);
1217
+ registerSetMetadataCommand(program);
1037
1218
  return program;
1038
1219
  }
1039
1220
  //#endregion
1040
1221
  //#region src/cli.ts
1041
1222
  async function launchTui(startPath, signal) {
1042
1223
  try {
1043
- const { runTui } = await import("./tui-CQCLalYe.js");
1224
+ const { runTui } = await import("./tui-CqYlr4nN.js");
1044
1225
  await runTui({
1045
1226
  startPath,
1046
1227
  signal
package/dist/index.cjs CHANGED
@@ -684,7 +684,7 @@ function registerFontsCommand(program) {
684
684
  }
685
685
  //#endregion
686
686
  //#region src/commands/formats.ts
687
- 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";
687
+ 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";
688
688
  function registerFormatsCommand(program) {
689
689
  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) => {
690
690
  const { conversions } = (0, documents_js.createLocalDocumentConverter)();
@@ -777,6 +777,78 @@ function registerFromPackageCommand(program) {
777
777
  });
778
778
  }
779
779
  //#endregion
780
+ //#region src/runtime/metadata-format.ts
781
+ const METADATA_KEYS = [
782
+ "title",
783
+ "author",
784
+ "subject",
785
+ "keywords",
786
+ "creator",
787
+ "producer",
788
+ "createdIso",
789
+ "modifiedIso"
790
+ ];
791
+ function isPresent(entry) {
792
+ return entry[1] !== void 0;
793
+ }
794
+ function formatMetadataValue(value) {
795
+ return typeof value === "string" ? value : value.join(", ");
796
+ }
797
+ function presentMetadataEntries(metadata) {
798
+ return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
799
+ }
800
+ function formatMetadataLines(metadata) {
801
+ return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
802
+ }
803
+ //#endregion
804
+ //#region src/commands/metadata.ts
805
+ function readMetadataForFormat(format, bytes, signal) {
806
+ switch (format) {
807
+ case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes)).metadata;
808
+ case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes)).metadata;
809
+ case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes)).metadata;
810
+ case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes)).metadata;
811
+ case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes)).metadata;
812
+ case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes)).metadata;
813
+ case "odf": return (0, documents_js.readOdfFormulaContent)((0, odf_js.decodePackage)(bytes)).metadata;
814
+ case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes)).metadata;
815
+ case "pdf": return (0, documents_js.readPdf)(bytes, { signal }).metadata;
816
+ case "xlsx": return (0, documents_js.readPdf)((0, documents_js.xlsxToPdf)(bytes, { signal }), { signal }).metadata;
817
+ }
818
+ }
819
+ async function runMetadata(input, options) {
820
+ const command = "metadata";
821
+ const source = inferFormatFromExtension(input);
822
+ if (source === void 0) {
823
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
824
+ return 2;
825
+ }
826
+ const { signal, getAbortReason } = createRuntimeSignal({});
827
+ try {
828
+ const inputBytes = await readInput(input, { signal });
829
+ const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
830
+ if (options.json) {
831
+ process.stdout.write(`${JSON.stringify(metadata)}\n`);
832
+ return 0;
833
+ }
834
+ const lines = formatMetadataLines(metadata);
835
+ if (lines.length === 0) {
836
+ process.stdout.write("This document carries no metadata.\n");
837
+ return 0;
838
+ }
839
+ for (const line of lines) process.stdout.write(`${line}\n`);
840
+ return 0;
841
+ } catch (error) {
842
+ process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
843
+ return mapErrorToExit(error, getAbortReason());
844
+ }
845
+ }
846
+ function registerMetadataCommand(program) {
847
+ 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) => {
848
+ process.exitCode = await runMetadata(input, options);
849
+ });
850
+ }
851
+ //#endregion
780
852
  //#region src/odb-structure.ts
781
853
  const INDENT = " ";
782
854
  function indent(depth) {
@@ -1380,12 +1452,6 @@ function countImagesByFormat(images) {
1380
1452
  for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
1381
1453
  return counts;
1382
1454
  }
1383
- function isPresent(entry) {
1384
- return entry[1] !== void 0;
1385
- }
1386
- function formatMetadataValue(value) {
1387
- return typeof value === "string" ? value : value.join(", ");
1388
- }
1389
1455
  async function runPdfInspect(input, options) {
1390
1456
  const command = "pdf-inspect";
1391
1457
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -1427,19 +1493,9 @@ async function runPdfInspect(input, options) {
1427
1493
  const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
1428
1494
  process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
1429
1495
  });
1430
- const presentMetadata = [
1431
- ["title", layout.metadata.title],
1432
- ["author", layout.metadata.author],
1433
- ["subject", layout.metadata.subject],
1434
- ["keywords", layout.metadata.keywords],
1435
- ["creator", layout.metadata.creator],
1436
- ["producer", layout.metadata.producer],
1437
- ["createdIso", layout.metadata.createdIso],
1438
- ["modifiedIso", layout.metadata.modifiedIso]
1439
- ].filter(isPresent);
1440
- if (presentMetadata.length > 0) {
1496
+ if (presentMetadataEntries(layout.metadata).length > 0) {
1441
1497
  process.stdout.write("metadata:\n");
1442
- for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
1498
+ for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
1443
1499
  }
1444
1500
  if (imagesByFormat.size > 0) {
1445
1501
  process.stdout.write("images:\n");
@@ -1457,8 +1513,155 @@ function registerPdfInspectCommand(program) {
1457
1513
  });
1458
1514
  }
1459
1515
  //#endregion
1516
+ //#region src/commands/set-metadata.ts
1517
+ const REBUILD_FORMATS = {
1518
+ docx: true,
1519
+ pptx: true,
1520
+ odt: true,
1521
+ odp: true,
1522
+ ods: true,
1523
+ odg: true,
1524
+ markdown: true
1525
+ };
1526
+ function isRebuildFormat(format) {
1527
+ return format in REBUILD_FORMATS;
1528
+ }
1529
+ function readContentForFormat(format, bytes) {
1530
+ switch (format) {
1531
+ case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes));
1532
+ case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes));
1533
+ case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes));
1534
+ case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes));
1535
+ case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes));
1536
+ case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes));
1537
+ case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes));
1538
+ }
1539
+ }
1540
+ function buildBytesForRebuildFormat(format, content) {
1541
+ switch (format) {
1542
+ case "docx": return (0, documents_js.encodePackage)((0, documents_js.buildDocxPackage)(content));
1543
+ case "pptx": return (0, documents_js.encodePackage)((0, documents_js.buildPptxPackage)(content));
1544
+ case "odt": return (0, odf_js.encodePackage)((0, documents_js.buildOdtPackage)(content));
1545
+ case "odp": return (0, odf_js.encodePackage)((0, documents_js.buildOdpPackage)(content));
1546
+ case "ods": return (0, odf_js.encodePackage)((0, documents_js.buildOdsPackage)(content));
1547
+ case "odg": return (0, odf_js.encodePackage)((0, documents_js.buildOdgPackage)(content));
1548
+ case "markdown": return (0, documents_js.encodeMarkdownText)((0, documents_js.buildMarkdownText)(content));
1549
+ }
1550
+ }
1551
+ function mergeMetadata(current, overrides) {
1552
+ return {
1553
+ ...current,
1554
+ ...overrides.title !== void 0 ? { title: overrides.title } : {},
1555
+ ...overrides.author !== void 0 ? { author: overrides.author } : {},
1556
+ ...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
1557
+ ...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
1558
+ };
1559
+ }
1560
+ function parseKeywords(csv) {
1561
+ return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1562
+ }
1563
+ function classifyWritePath(source, target) {
1564
+ if (source === "pdf" && target === "pdf") return { kind: "pdf" };
1565
+ 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" };
1566
+ 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" };
1567
+ 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.` };
1568
+ 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.` };
1569
+ return {
1570
+ kind: "rebuild",
1571
+ format: source
1572
+ };
1573
+ }
1574
+ async function runSetMetadata(input, output, options) {
1575
+ const command = "set-metadata";
1576
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1577
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1578
+ return 2;
1579
+ }
1580
+ const target = resolveTargetFormat(output, options.out, options.to);
1581
+ if ("errorMessage" in target) {
1582
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1583
+ return 2;
1584
+ }
1585
+ const source = inferFormatFromExtension(input);
1586
+ if (source === void 0) {
1587
+ process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
1588
+ return 2;
1589
+ }
1590
+ const writePath = classifyWritePath(source, target.format);
1591
+ if ("errorMessage" in writePath) {
1592
+ process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
1593
+ return 2;
1594
+ }
1595
+ const overrides = {
1596
+ title: options.setTitle,
1597
+ author: options.setAuthor,
1598
+ subject: options.setSubject,
1599
+ keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
1600
+ };
1601
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
1602
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1603
+ try {
1604
+ const inputBytes = await readInput(input, { signal });
1605
+ const bytes = writePath.kind === "pdf" ? (() => {
1606
+ const layout = (0, documents_js.readPdf)(new Uint8Array(inputBytes), { signal });
1607
+ const patched = {
1608
+ ...layout,
1609
+ metadata: mergeMetadata(layout.metadata, overrides)
1610
+ };
1611
+ return (0, documents_js.writePdf)(patched, { signal });
1612
+ })() : (() => {
1613
+ const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
1614
+ const nextContent = {
1615
+ ...content,
1616
+ metadata: mergeMetadata(content.metadata, overrides)
1617
+ };
1618
+ return buildBytesForRebuildFormat(writePath.format, nextContent);
1619
+ })();
1620
+ await writeOutput(resolvedOutput, bytes);
1621
+ createDiagnosticReporter({
1622
+ json: options.json,
1623
+ quiet: options.quiet,
1624
+ command
1625
+ }).summarize({
1626
+ output: resolvedOutput,
1627
+ bytes: bytes.byteLength,
1628
+ diagnosticCount: 0
1629
+ });
1630
+ return 0;
1631
+ } catch (error) {
1632
+ process.stderr.write(`${formatError(error, options.verbose)}\n`);
1633
+ return mapErrorToExit(error, getAbortReason());
1634
+ }
1635
+ }
1636
+ function registerSetMetadataCommand(program) {
1637
+ 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", [
1638
+ "",
1639
+ "Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
1640
+ "involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
1641
+ "odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
1642
+ "this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
1643
+ "since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
1644
+ "",
1645
+ "set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
1646
+ "set-metadata on the result, if you need a different target format."
1647
+ ].join("\n"));
1648
+ addOutOption(command);
1649
+ addTimeoutOption(command);
1650
+ addJsonOption(command);
1651
+ addQuietOption(command);
1652
+ addVerboseOption(command);
1653
+ command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
1654
+ command.option("--set-title <text>", "set the title field");
1655
+ command.option("--set-author <text>", "set the author field");
1656
+ command.option("--set-subject <text>", "set the subject field");
1657
+ command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
1658
+ command.action(async (input, output, options) => {
1659
+ process.exitCode = await runSetMetadata(input, output, options);
1660
+ });
1661
+ }
1662
+ //#endregion
1460
1663
  //#region package.json
1461
- var version = "1.4.0";
1664
+ var version = "1.6.0";
1462
1665
  //#endregion
1463
1666
  //#region src/program.ts
1464
1667
  function createProgram() {
@@ -1477,6 +1680,8 @@ function createProgram() {
1477
1680
  registerPdfInspectCommand(program);
1478
1681
  registerFontsCommand(program);
1479
1682
  registerDocxExtrasCommand(program);
1683
+ registerMetadataCommand(program);
1684
+ registerSetMetadataCommand(program);
1480
1685
  return program;
1481
1686
  }
1482
1687
  //#endregion