@jasy/cli 1.0.0-alpha.1 → 1.0.0-alpha.3

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.
@@ -5,9 +5,9 @@ import { describeInvoice } from "../core/detect.js";
5
5
  import { validateInvoiceXml, profileFor } from "../core/validate.js";
6
6
  import { checkPdfA3 } from "../core/pdfa.js";
7
7
  import { findVerapdf, runVeraPdf } from "../core/verapdf.js";
8
- // `jasy validate <file> [-v]` - runs the full local check (EN 16931 business rules + structural
9
- // PDF/A-3) on a ZUGFeRD/XRechnung PDF or raw XML, prints a report, and exits non-zero when invalid
10
- // (so it slots into scripts/CI). Same UI-agnostic core as the TUI.
8
+ // `jasy validate <file> [--json] [-v]` - runs the full local check (EN 16931 business rules +
9
+ // structural PDF/A-3, plus veraPDF when installed) on a ZUGFeRD/XRechnung PDF or raw XML, prints a
10
+ // report (or `--json` for scripts/CI/services), and exits non-zero when invalid. Same core as the TUI.
11
11
  const tty = process.stdout.isTTY;
12
12
  const paint = (code, s) => (tty ? `\x1b[${code}m${s}\x1b[0m` : s);
13
13
  const green = (s) => paint("32", s);
@@ -15,12 +15,19 @@ const red = (s) => paint("31", s);
15
15
  const dim = (s) => paint("2", s);
16
16
  const bold = (s) => paint("1", s);
17
17
  export function validateCommand(args) {
18
- var _a, _b;
18
+ var _a;
19
19
  const file = args.find((a) => !a.startsWith("-"));
20
20
  const verbose = args.includes("-v") || args.includes("--verbose");
21
- if (!file) {
22
- console.error("usage: jasy validate <file.pdf|file.xml> [-v]");
21
+ const json = args.includes("--json");
22
+ const fail = (msg) => {
23
+ if (json)
24
+ console.log(JSON.stringify({ error: msg }));
25
+ else
26
+ console.error(red(`✗ ${msg}`));
23
27
  process.exitCode = 1;
28
+ };
29
+ if (!file) {
30
+ fail("usage: jasy validate <file.pdf|file.xml> [--json] [-v]");
24
31
  return;
25
32
  }
26
33
  const base = (_a = process.env.INIT_CWD) !== null && _a !== void 0 ? _a : process.cwd();
@@ -29,8 +36,7 @@ export function validateCommand(args) {
29
36
  bytes = readFileSync(resolve(base, file));
30
37
  }
31
38
  catch (e) {
32
- console.error(red(`✗ could not read ${file}: ${e.message}`));
33
- process.exitCode = 1;
39
+ fail(`could not read ${file}: ${e.message}`);
34
40
  return;
35
41
  }
36
42
  const read = readInvoice(bytes);
@@ -41,11 +47,71 @@ export function validateCommand(args) {
41
47
  try {
42
48
  rules = validateInvoiceXml(read.xml, profileFor(read.meta));
43
49
  }
44
- catch (_c) {
50
+ catch (_b) {
45
51
  rules = null;
46
52
  }
47
53
  }
48
54
  const pdfa = read.isPdf ? checkPdfA3(bytes) : null;
55
+ // Full ISO 19005 (PDF/A) via veraPDF - only when installed; never blocks, just adds the official seal.
56
+ let vera = null;
57
+ if (read.isPdf && findVerapdf()) {
58
+ try {
59
+ vera = runVeraPdf(resolve(base, file));
60
+ }
61
+ catch (_c) {
62
+ vera = null;
63
+ }
64
+ }
65
+ // VALID requires that we actually recognised EN 16931 invoice data: a file we could not parse as an
66
+ // invoice (random bytes, or a plain PDF with no embedded XML) is "not an invoice" - never "valid".
67
+ const recognized = read.meta.syntax !== "unknown";
68
+ const ok = recognized && (!rules || rules.valid) && (!pdfa || pdfa.ok) && (!vera || vera.ok);
69
+ if (!ok)
70
+ process.exitCode = 1;
71
+ if (json) {
72
+ console.log(JSON.stringify(toJson(file, read, rules, pdfa, vera, recognized, ok)));
73
+ return;
74
+ }
75
+ printReport(file, read, rules, pdfa, vera, recognized, ok, verbose);
76
+ }
77
+ /** The machine-readable report (`--json`): the exact same data the printed report shows. */
78
+ function toJson(file, read, rules, pdfa, vera, recognized, ok) {
79
+ var _a;
80
+ return {
81
+ file: basename(file),
82
+ summary: describeInvoice(read.meta),
83
+ recognized,
84
+ valid: ok,
85
+ businessRules: rules && {
86
+ kind: rules.profile.startsWith("xrechnung") ? "XRechnung" : "EN 16931",
87
+ profile: rules.profile,
88
+ valid: rules.valid,
89
+ errors: rules.errors,
90
+ warnings: rules.warnings,
91
+ },
92
+ pdfA3: pdfa && {
93
+ valid: pdfa.ok,
94
+ passed: pdfa.checks.filter((c) => c.ok).length,
95
+ total: pdfa.checks.length,
96
+ checks: pdfa.checks,
97
+ },
98
+ // null when it isn't a PDF; { available: false } when it is but veraPDF isn't installed.
99
+ veraPdf: read.isPdf
100
+ ? vera
101
+ ? {
102
+ available: true,
103
+ valid: vera.ok,
104
+ profile: vera.profile,
105
+ failedRules: (_a = vera.failedRules) !== null && _a !== void 0 ? _a : vera.failures.length,
106
+ failures: vera.failures,
107
+ }
108
+ : { available: false }
109
+ : null,
110
+ };
111
+ }
112
+ /** The human-readable report (default): a coloured summary line + per-check detail. */
113
+ function printReport(file, read, rules, pdfa, vera, recognized, ok, verbose) {
114
+ var _a;
49
115
  const label = (s) => s.padEnd(20);
50
116
  console.log(`\n ${bold(basename(file))} ${dim("·")} ${describeInvoice(read.meta)}\n`);
51
117
  // business rules
@@ -75,18 +141,9 @@ export function validateCommand(args) {
75
141
  else {
76
142
  console.log(` ${label("PDF/A-3 structure")}${dim("n/a (raw XML)")}`);
77
143
  }
78
- // full ISO 19005 (PDF/A) via veraPDF - only when installed; never blocks, just adds the official seal
79
- let vera = null;
80
- if (read.isPdf && findVerapdf()) {
81
- try {
82
- vera = runVeraPdf(resolve(base, file));
83
- }
84
- catch (_d) {
85
- vera = null;
86
- }
87
- }
144
+ // full ISO 19005 (PDF/A) via veraPDF
88
145
  if (vera) {
89
- const n = (_b = vera.failedRules) !== null && _b !== void 0 ? _b : vera.failures.length;
146
+ const n = (_a = vera.failedRules) !== null && _a !== void 0 ? _a : vera.failures.length;
90
147
  console.log(` ${label("PDF/A (veraPDF)")}${vera.ok ? green("✓ compliant") : red(`✗ ${n} failed`)}`);
91
148
  if (!vera.ok)
92
149
  for (const f of vera.failures)
@@ -95,8 +152,8 @@ export function validateCommand(args) {
95
152
  else if (read.isPdf) {
96
153
  console.log(` ${label("PDF/A (veraPDF)")}${dim("n/a - `jasy verapdf --install` for the full ISO check")}`);
97
154
  }
98
- const ok = (!rules || rules.valid) && (!pdfa || pdfa.ok) && (!vera || vera.ok);
99
- console.log(`\n → ${ok ? green(bold("VALID")) : red(bold("INVALID"))}\n`);
100
- if (!ok)
101
- process.exitCode = 1;
155
+ if (!recognized)
156
+ console.log(`\n → ${red(bold("NOT A ZUGFeRD / XRECHNUNG INVOICE"))} ${dim("(no EN 16931 data found)")}\n`);
157
+ else
158
+ console.log(`\n → ${ok ? green(bold("VALID")) : red(bold("INVALID"))}\n`);
102
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jasy/cli",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "Interactive terminal to validate, read and export ZUGFeRD / XRechnung e-invoices.",
5
5
  "keywords": [
6
6
  "cli",