@jasy/cli 1.0.0-alpha.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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/jasy-pdf/jasy/main/docs/logo.png" width="120" alt="jasy">
3
+ </p>
4
+
5
+ <h1 align="center">@jasy/cli</h1>
6
+
7
+ <p align="center">
8
+ <b>The terminal for ZUGFeRD &amp; XRechnung. Read, validate and export any e-invoice PDF.</b><br>
9
+ Headless for scripts and CI, interactive when you want to look. 100% local - no upload, no account.
10
+ </p>
11
+
12
+ <p align="center"><code>npx @jasy/cli</code> &nbsp;·&nbsp; MIT</p>
13
+
14
+ ---
15
+
16
+ ## Don't believe us. Point it at your own invoice.
17
+
18
+ ```bash
19
+ npx @jasy/cli validate ./your-invoice.pdf
20
+ ```
21
+
22
+ <p align="center">
23
+ <img src="https://raw.githubusercontent.com/jasy-pdf/jasy/main/docs/validate.png" width="430" alt="jasy validate: VALID">
24
+ </p>
25
+
26
+ That `✓` runs the **same official KoSIT Schematron + veraPDF the big tools use** - the EN 16931 **and**
27
+ the German XRechnung (BR-DE) rules, plus PDF/A-3. It exits non-zero when invalid, so it drops straight
28
+ into CI. Your file, your terminal, in seconds.
29
+
30
+ > Java has **Mustang**. PHP has **horstoeko**. Python has **factur-x**.
31
+ > Node had nothing. Now it has jasy.
32
+
33
+ ## Everything it does
34
+
35
+ ```bash
36
+ jasy read invoice.pdf # identify + show: parties, line items, totals (CII and UBL)
37
+ jasy validate invoice.pdf # EN 16931 + XRechnung + PDF/A (exit 1 if invalid)
38
+ jasy export invoice.pdf -o x.xlsx # read it back to JSON, TXT or Excel
39
+ jasy # the interactive terminal: open, inspect, export
40
+ ```
41
+
42
+ <p align="center">
43
+ <img src="https://raw.githubusercontent.com/jasy-pdf/jasy/main/docs/read.png" width="520" alt="the jasy terminal">
44
+ </p>
45
+
46
+ Reads **CII and UBL**, real third-party invoices included - not just invoices we made ourselves.
47
+
48
+ ## The full ISO PDF/A check, optional and guided
49
+
50
+ Structural PDF/A-3 checks run built-in. For the **complete** ISO 19005 verdict, jasy wires up the
51
+ official **veraPDF** for you - no account, into `~/.jasy/verapdf`:
52
+
53
+ ```bash
54
+ jasy verapdf # a doctor: is Java here? is veraPDF here? what to run if not
55
+ jasy verapdf --install # downloads + installs it locally (the one requirement is a Java runtime)
56
+ ```
57
+
58
+ Once present, `jasy validate` adds the full ISO check automatically. It is never a gate - your own
59
+ structural checks carry the everyday case.
60
+
61
+ ## Under the hood
62
+
63
+ - **Schematron, local** - the official EN-16931 + XRechnung rules run via saxon-js (the real XSLT, in
64
+ pure JS). No upload, DSGVO-safe.
65
+ - **Excel by hand** - the `.xlsx` is a ZIP we build ourselves, deflated with our own writer and CRC32.
66
+ - **Reads multi-file PDFs** - pulls the right e-invoice XML out even when a tool embedded its own JSON too.
67
+ - Generates with [`@jasy/zugferd`](https://www.npmjs.com/package/@jasy/zugferd) on the hand-rolled
68
+ [`@jasy/pdf`](https://www.npmjs.com/package/@jasy/pdf) engine.
69
+
70
+ ---
71
+
72
+ <p align="center">MIT &nbsp;·&nbsp; part of <a href="https://github.com/jasy-pdf/jasy">jasy</a></p>
@@ -0,0 +1,23 @@
1
+
2
+ █████▄
3
+ ▄ ████████▄
4
+ ██ ███████████▄
5
+ ████▄ ██████████████▄
6
+ ███████▄ ▀███████████████▄ ▄██▄
7
+ ██████████▄ ▀█████████████ ████████▄
8
+ ██████████████▄ ▀█████████ ██████████▄
9
+ ██████████████████▄ ▀███████ ████████▀ ▀██▄
10
+ ▀██▄ ████████████████████▄ ▀█████ ███████
11
+ ▀███▄ █████████████████████▄ ▀▀███▄▄███████
12
+ ▀█████▄ █████████████████████▒▒ ▀▀███████
13
+ ███████▄ ▀████████████████████▒▒▒▒ ███████
14
+ ▀████████▄ ▀███████████████████▒▒▒▒██████
15
+ ▀██████████▄ ▒▒▒▒▒▒▒█████████████▒████████
16
+ ▀██████████▄ ▄███▒▒▒▒▒▒▒▒▒███████████████
17
+ ▀█████████████████▒▒▒▒▒▒▒▒▒▒▒▒█████████
18
+ ▀██████████████████▒▒▒▒▒████████████
19
+ ▀█████████████████▒███████████████
20
+ ▀█████████████ ███████████████
21
+ ▀█████████▀ ▀█████████▀ ▀█
22
+ ▀████▀ ▀████
23
+ ▀ ▀
@@ -0,0 +1,21 @@
1
+ # Vendored validation rules
2
+
3
+ The official **EN 16931 / XRechnung Schematron** business rules, compiled to SaxonJS SEF
4
+ (`.sef.json`, gzipped) so the CLI validates invoices **locally** via `saxon-js` — no upload, DSGVO-safe.
5
+ This covers the XML conformance (the legally decisive part); full ISO 19005 (PDF/A) is the optional
6
+ veraPDF adapter (`jasy verapdf`), with our own structural checks (`core/pdfa.ts`) as the default.
7
+
8
+ `validate.ts` picks the rule set from what `detect()` found. **XRechnung is a CIUS on top of EN 16931**,
9
+ so its files carry only the BR-DE delta — an XRechnung is validated against the EN 16931 base **and**
10
+ the XRechnung rules, and the findings are merged.
11
+
12
+ | File | Rules | Upstream |
13
+ | --------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
+ | `en16931-cii.sef.json.gz` | EN 16931 — UN/CEFACT **CII** (ZUGFeRD / Factur-X) | [ConnectingEurope/eInvoicing-EN16931](https://github.com/ConnectingEurope/eInvoicing-EN16931) (Apache-2.0), `cii/xslt/EN16931-CII-validation.xslt` |
15
+ | `en16931-ubl.sef.json.gz` | EN 16931 — OASIS **UBL** (PEPPOL) | same repo, `ubl/xslt/EN16931-UBL-validation.xslt` |
16
+ | `xrechnung-cii.sef.json.gz` | XRechnung 3.0 BR-DE delta — **CII** | [itplr-kosit/xrechnung-schematron](https://github.com/itplr-kosit/xrechnung-schematron) v2.5.0 (Apache-2.0), `schematron/cii/XRechnung-CII-validation.xsl` |
17
+ | `xrechnung-ubl.sef.json.gz` | XRechnung 3.0 BR-DE delta — **UBL** | same release, `schematron/ubl/XRechnung-UBL-validation.xsl` |
18
+
19
+ **To refresh / add a profile:** fetch the upstream `.xslt`/`.xsl`, then
20
+ `xslt3 -xsl:<file> -export:<out>.sef.json -nogo` and `gzip` the result into this folder.
21
+ (`xslt3` is the SaxonJS compiler, a devDependency.)
@@ -0,0 +1 @@
1
+ export declare function exportCommand(args: string[]): void;
@@ -0,0 +1,63 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve, extname } from "node:path";
3
+ import { readInvoice } from "../core/read.js";
4
+ import { exportInvoice } from "../core/export.js";
5
+ // `jasy export <file> [-f json|txt|xlsx] [-o out]` - read a ZUGFeRD/XRechnung PDF or XML and write the
6
+ // invoice out as JSON, plain text or a spreadsheet. Format comes from -f, else the -o extension, else json.
7
+ const FORMATS = {
8
+ json: "json",
9
+ txt: "txt",
10
+ text: "txt",
11
+ xlsx: "xlsx",
12
+ excel: "xlsx",
13
+ };
14
+ export function exportCommand(args) {
15
+ var _a, _b, _c;
16
+ let file;
17
+ let out;
18
+ let fmtArg;
19
+ for (let i = 0; i < args.length; i++) {
20
+ const a = args[i];
21
+ if (a === "-f" || a === "--format")
22
+ fmtArg = args[++i];
23
+ else if (a === "-o" || a === "--out")
24
+ out = args[++i];
25
+ else if (!a.startsWith("-") && !file)
26
+ file = a;
27
+ }
28
+ if (!file) {
29
+ console.error("usage: jasy export <file.pdf|file.xml> [-f json|txt|xlsx] [-o out]");
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ const format = (_b = (_a = FORMATS[fmtArg !== null && fmtArg !== void 0 ? fmtArg : ""]) !== null && _a !== void 0 ? _a : FORMATS[out ? extname(out).slice(1) : ""]) !== null && _b !== void 0 ? _b : "json";
34
+ if (format === "xlsx" && !out) {
35
+ console.error("✗ xlsx is binary - give an output path with -o <file.xlsx>");
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ const base = (_c = process.env.INIT_CWD) !== null && _c !== void 0 ? _c : process.cwd();
40
+ let bytes;
41
+ try {
42
+ bytes = readFileSync(resolve(base, file));
43
+ }
44
+ catch (e) {
45
+ console.error(`✗ could not read ${file}: ${e.message}`);
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ const r = readInvoice(bytes);
50
+ if (!r.invoice || !r.totals) {
51
+ console.error(`✗ could not parse the invoice (${r.meta.syntax}/${r.meta.profile})`);
52
+ process.exitCode = 1;
53
+ return;
54
+ }
55
+ const data = exportInvoice(r.invoice, r.totals, format);
56
+ if (out) {
57
+ writeFileSync(resolve(base, out), data);
58
+ console.log(`✓ wrote ${format} → ${out}`);
59
+ }
60
+ else {
61
+ process.stdout.write(data); // json/txt are strings (xlsx required -o above)
62
+ }
63
+ }
@@ -0,0 +1 @@
1
+ export declare function readCommand(args: string[]): void;
@@ -0,0 +1,73 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { resolve, basename } from "node:path";
3
+ import { readInvoiceFile } from "../core/read.js";
4
+ import { describeInvoice } from "../core/detect.js";
5
+ // `jasy read <file> [--xml] [-o out]` - read a ZUGFeRD / XRechnung PDF (or raw XML) and show the actual
6
+ // invoice (number, parties, lines, totals) for humans, or dump / save the embedded XML.
7
+ const tty = process.stdout.isTTY;
8
+ const paint = (c, s) => (tty ? `\x1b[${c}m${s}\x1b[0m` : s);
9
+ const bold = (s) => paint("1", s);
10
+ const dim = (s) => paint("2", s);
11
+ const money = (n) => n.toFixed(2);
12
+ export function readCommand(args) {
13
+ var _a, _b;
14
+ let file;
15
+ let out;
16
+ let dumpXml = false;
17
+ for (let i = 0; i < args.length; i++) {
18
+ const a = args[i];
19
+ if (a === "--xml")
20
+ dumpXml = true;
21
+ else if (a === "-o" || a === "--out")
22
+ out = args[++i];
23
+ else if (!a.startsWith("-") && !file)
24
+ file = a;
25
+ }
26
+ if (!file) {
27
+ console.error("usage: jasy read <file.pdf|file.xml> [--xml] [-o out.xml]");
28
+ process.exitCode = 1;
29
+ return;
30
+ }
31
+ // resolve against where the user actually stood - pnpm scripts cd into the package dir
32
+ const base = (_a = process.env.INIT_CWD) !== null && _a !== void 0 ? _a : process.cwd();
33
+ let r;
34
+ try {
35
+ r = readInvoiceFile(resolve(base, file));
36
+ }
37
+ catch (e) {
38
+ console.error(`✗ could not read ${file}: ${e.message}`);
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ if (out) {
43
+ writeFileSync(resolve(base, out), r.xml);
44
+ console.log(`✓ wrote ${r.xml.length} bytes of XML → ${out}`);
45
+ return;
46
+ }
47
+ if (dumpXml) {
48
+ process.stdout.write(r.xml.endsWith("\n") ? r.xml : r.xml + "\n");
49
+ return;
50
+ }
51
+ console.log(`\n✓ ${bold(basename(file))} ${dim("·")} ${describeInvoice(r.meta)}\n`);
52
+ const inv = r.invoice;
53
+ if (inv && r.totals) {
54
+ console.log(` invoice ${bold(inv.number)}`);
55
+ console.log(` date ${inv.issueDate}${inv.dueDate ? dim(` due ${inv.dueDate}`) : ""}`);
56
+ console.log(` from ${inv.seller.name}`);
57
+ console.log(` to ${inv.buyer.name}\n`);
58
+ inv.lines.forEach((l, i) => {
59
+ const left = ` ${l.quantity} ${l.unit} ${l.name}`;
60
+ console.log(left.padEnd(48) + money(r.totals.lineNets[i]).padStart(12));
61
+ });
62
+ const t = r.totals;
63
+ console.log(" " + dim("─".repeat(58)));
64
+ console.log(` ${dim("net")} ${money(t.taxBasisTotal)} ${dim("VAT")} ${money(t.taxTotal)} ${bold(`total ${money(t.grandTotal)} ${inv.currency}`)}`);
65
+ console.log(`\n ${dim("→ --xml print the XML · -o <file> save it · jasy validate <file>")}`);
66
+ }
67
+ else {
68
+ console.log(` source ${r.isPdf ? "PDF - embedded XML extracted" : "raw XML"}`);
69
+ console.log(` guideline ${(_b = r.meta.guideline) !== null && _b !== void 0 ? _b : "-"}`);
70
+ console.log(` XML ${r.xml.length} bytes`);
71
+ console.log(`\n ${dim("→ --xml print the XML · -o <file> save it")}`);
72
+ }
73
+ }
@@ -0,0 +1 @@
1
+ export declare function validateCommand(args: string[]): void;
@@ -0,0 +1,102 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve, basename } from "node:path";
3
+ import { readInvoice } from "../core/read.js";
4
+ import { describeInvoice } from "../core/detect.js";
5
+ import { validateInvoiceXml, profileFor } from "../core/validate.js";
6
+ import { checkPdfA3 } from "../core/pdfa.js";
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.
11
+ const tty = process.stdout.isTTY;
12
+ const paint = (code, s) => (tty ? `\x1b[${code}m${s}\x1b[0m` : s);
13
+ const green = (s) => paint("32", s);
14
+ const red = (s) => paint("31", s);
15
+ const dim = (s) => paint("2", s);
16
+ const bold = (s) => paint("1", s);
17
+ export function validateCommand(args) {
18
+ var _a, _b;
19
+ const file = args.find((a) => !a.startsWith("-"));
20
+ const verbose = args.includes("-v") || args.includes("--verbose");
21
+ if (!file) {
22
+ console.error("usage: jasy validate <file.pdf|file.xml> [-v]");
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ const base = (_a = process.env.INIT_CWD) !== null && _a !== void 0 ? _a : process.cwd();
27
+ let bytes;
28
+ try {
29
+ bytes = readFileSync(resolve(base, file));
30
+ }
31
+ catch (e) {
32
+ console.error(red(`✗ could not read ${file}: ${e.message}`));
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ const read = readInvoice(bytes);
37
+ // XML business rules (Schematron) - EN 16931, plus the XRechnung BR-DE delta when it's an XRechnung.
38
+ // The rule set is picked automatically from what detect() found (syntax + CIUS).
39
+ let rules = null;
40
+ if (read.meta.syntax !== "unknown") {
41
+ try {
42
+ rules = validateInvoiceXml(read.xml, profileFor(read.meta));
43
+ }
44
+ catch (_c) {
45
+ rules = null;
46
+ }
47
+ }
48
+ const pdfa = read.isPdf ? checkPdfA3(bytes) : null;
49
+ const label = (s) => s.padEnd(20);
50
+ console.log(`\n ${bold(basename(file))} ${dim("·")} ${describeInvoice(read.meta)}\n`);
51
+ // business rules
52
+ if (rules) {
53
+ const n = rules.errors.length;
54
+ const name = rules.profile.startsWith("xrechnung") ? "XRechnung rules" : "EN 16931 rules";
55
+ console.log(` ${label(name)}${rules.valid ? green("✓ valid") : red(`✗ ${n} error${n === 1 ? "" : "s"}`)}`);
56
+ for (const e of rules.errors)
57
+ console.log(` ${red("✗")} ${e.id ? bold(`[${e.id}]`) + " " : ""}${e.text}`);
58
+ if (verbose)
59
+ for (const w of rules.warnings)
60
+ console.log(` ${dim(`! ${w.id ? `[${w.id}] ` : ""}${w.text}`)}`);
61
+ }
62
+ else {
63
+ console.log(` ${label("business rules")}${dim("n/a (unrecognised XML)")}`);
64
+ }
65
+ // PDF/A-3 structure
66
+ if (pdfa) {
67
+ const passed = pdfa.checks.filter((c) => c.ok).length;
68
+ const tot = pdfa.checks.length;
69
+ console.log(` ${label("PDF/A-3 structure")}${pdfa.ok ? green(`✓ ${passed}/${tot}`) : red(`✗ ${passed}/${tot}`)}`);
70
+ for (const c of pdfa.checks) {
71
+ if (!c.ok || verbose)
72
+ console.log(` ${c.ok ? green("✓") : red("✗")} ${c.label}${c.detail ? dim(` ${c.detail}`) : ""}`);
73
+ }
74
+ }
75
+ else {
76
+ console.log(` ${label("PDF/A-3 structure")}${dim("n/a (raw XML)")}`);
77
+ }
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
+ }
88
+ if (vera) {
89
+ const n = (_b = vera.failedRules) !== null && _b !== void 0 ? _b : vera.failures.length;
90
+ console.log(` ${label("PDF/A (veraPDF)")}${vera.ok ? green("✓ compliant") : red(`✗ ${n} failed`)}`);
91
+ if (!vera.ok)
92
+ for (const f of vera.failures)
93
+ console.log(` ${red("✗")} ISO clause ${f.clause}${dim(` (${f.failedChecks} checks)`)}`);
94
+ }
95
+ else if (read.isPdf) {
96
+ console.log(` ${label("PDF/A (veraPDF)")}${dim("n/a - `jasy verapdf --install` for the full ISO check")}`);
97
+ }
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;
102
+ }
@@ -0,0 +1 @@
1
+ export declare function verapdfCommand(args: string[]): Promise<void>;
@@ -0,0 +1,96 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { resolve } from "node:path";
11
+ import { detectTools, installVeraPdf, runVeraPdf, findVerapdf } from "../core/verapdf.js";
12
+ // `jasy verapdf` - a guided doctor for the optional full-ISO PDF/A validator:
13
+ // jasy verapdf explain what veraPDF is + show Java / veraPDF status (✓ / ✗) + next steps
14
+ // jasy verapdf --install download + install it into ~/.jasy/verapdf (no admin, no account)
15
+ // jasy verapdf <file> validate a PDF with veraPDF and print the report
16
+ const tty = process.stdout.isTTY;
17
+ const paint = (c, s) => (tty ? `\x1b[${c}m${s}\x1b[0m` : s);
18
+ const green = (s) => paint("32", s);
19
+ const red = (s) => paint("31", s);
20
+ const dim = (s) => paint("2", s);
21
+ const bold = (s) => paint("1", s);
22
+ export function verapdfCommand(args) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ if (args.includes("--install"))
25
+ return install();
26
+ const file = args.find((a) => !a.startsWith("-"));
27
+ if (file)
28
+ return validateFile(file);
29
+ doctor();
30
+ });
31
+ }
32
+ function doctor() {
33
+ var _a, _b;
34
+ const t = detectTools();
35
+ console.log(`
36
+ ${bold("veraPDF")} - the official open-source PDF/A validator (PDF Association).
37
+ It checks a PDF against the full ISO 19005 (PDF/A) standard - the exact
38
+ conformance a ZUGFeRD / Factur-X invoice PDF must meet. Runs 100% locally;
39
+ your invoice never leaves the machine. Free, no account.
40
+ `);
41
+ const row = (label, ok, detail) => console.log(` ${label.padEnd(11)}${ok ? green("✓ " + detail) : red("✗ " + detail)}`);
42
+ row("Java", !!t.java, (_a = t.java) !== null && _a !== void 0 ? _a : "not found");
43
+ row("veraPDF", !!t.verapdf, t.verapdf ? `${t.verapdf} ${dim((_b = t.verapdfPath) !== null && _b !== void 0 ? _b : "")}` : "not found");
44
+ console.log("");
45
+ if (!t.java) {
46
+ console.log(` ${dim("Java is required - veraPDF is a Java app. Install a JRE 11+:")}`);
47
+ console.log(` ${dim("macOS")} brew install --cask temurin`);
48
+ console.log(` ${dim("Ubuntu")} sudo apt install default-jre`);
49
+ console.log(` ${dim("Windows")} winget install EclipseAdoptium.Temurin.21.JRE`);
50
+ }
51
+ if (!t.verapdf) {
52
+ console.log(` ${dim("Then install veraPDF (no admin, into ~/.jasy/verapdf):")} ${bold("jasy verapdf --install")}`);
53
+ }
54
+ else if (t.java) {
55
+ console.log(` ${green("Ready.")} ${dim("`jasy validate <file>` now adds the full ISO PDF/A check automatically.")}`);
56
+ }
57
+ }
58
+ function install() {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ try {
61
+ const bin = yield installVeraPdf((s) => console.log(` ${dim(s)}`));
62
+ console.log(` ${green("✓ veraPDF installed")} ${dim(bin)}`);
63
+ console.log(` ${dim("`jasy validate <file>` now runs the full ISO PDF/A check.")}`);
64
+ }
65
+ catch (e) {
66
+ console.error(` ${red("✗ " + e.message)}`);
67
+ process.exitCode = 1;
68
+ }
69
+ });
70
+ }
71
+ function validateFile(file) {
72
+ var _a, _b, _c;
73
+ if (!findVerapdf()) {
74
+ console.error(` ${red("✗ veraPDF is not installed")} - run ${bold("jasy verapdf --install")}`);
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ const base = (_a = process.env.INIT_CWD) !== null && _a !== void 0 ? _a : process.cwd();
79
+ try {
80
+ const r = runVeraPdf(resolve(base, file));
81
+ const head = r.ok
82
+ ? green("✓ compliant")
83
+ : red(`✗ ${(_b = r.failedRules) !== null && _b !== void 0 ? _b : r.failures.length} rule(s) failed`);
84
+ console.log(`\n ${(_c = r.profile) !== null && _c !== void 0 ? _c : "PDF/A"} ${head}`);
85
+ for (const f of r.failures) {
86
+ const n = f.failedChecks;
87
+ console.log(` ${red("✗")} ISO clause ${bold(f.clause)} ${dim(`(${n} check${n === 1 ? "" : "s"})`)}`);
88
+ }
89
+ if (!r.ok)
90
+ process.exitCode = 1;
91
+ }
92
+ catch (e) {
93
+ console.error(` ${red("✗ " + e.message)}`);
94
+ process.exitCode = 1;
95
+ }
96
+ }
@@ -0,0 +1,11 @@
1
+ export type Syntax = "CII" | "UBL" | "unknown";
2
+ export type Profile = "en16931" | "xrechnung" | "unknown";
3
+ export interface InvoiceMeta {
4
+ syntax: Syntax;
5
+ profile: Profile;
6
+ /** The raw guideline / customization identifier (BT-24). */
7
+ guideline: string | null;
8
+ }
9
+ export declare function detectInvoice(xml: string): InvoiceMeta;
10
+ /** A short human label, e.g. "ZUGFeRD · EN 16931 (CII)", "EN 16931 (UBL)" or "XRechnung (CII)". */
11
+ export declare function describeInvoice(meta: InvoiceMeta): string;
@@ -0,0 +1,33 @@
1
+ // Identifies an e-invoice XML at a glance: which syntax (UN/CEFACT CII vs OASIS UBL) and which
2
+ // profile (plain EN16931 vs the German XRechnung CIUS). Pure tag/regex inspection - no full parse
3
+ // needed (that comes later). Lets the CLI say "ZUGFeRD EN16931 (CII)" before doing anything heavy.
4
+ export function detectInvoice(xml) {
5
+ var _a, _b, _c, _d;
6
+ const syntax = /<(?:rsm:)?CrossIndustryInvoice/.test(xml)
7
+ ? "CII"
8
+ : /<Invoice\b[^>]*urn:oasis:names:specification:ubl/.test(xml) ||
9
+ /<(?:\w+:)?Invoice\b/.test(xml)
10
+ ? "UBL"
11
+ : "unknown";
12
+ // BT-24: CII carries it in GuidelineSpecified…/ram:ID, UBL in cbc:CustomizationID.
13
+ const guideline = (_d = (_b = (_a = xml.match(/GuidelineSpecifiedDocumentContextParameter>\s*<(?:ram:)?ID>([^<]+)</)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : (_c = xml.match(/<(?:cbc:)?CustomizationID>([^<]+)</)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : null;
14
+ const profile = !guideline
15
+ ? "unknown"
16
+ : /xrechnung/i.test(guideline)
17
+ ? "xrechnung"
18
+ : /en16931:2017/.test(guideline)
19
+ ? "en16931"
20
+ : "unknown";
21
+ return { syntax, profile, guideline };
22
+ }
23
+ /** A short human label, e.g. "ZUGFeRD · EN 16931 (CII)", "EN 16931 (UBL)" or "XRechnung (CII)". */
24
+ export function describeInvoice(meta) {
25
+ if (meta.profile === "xrechnung")
26
+ return `XRechnung (${meta.syntax})`;
27
+ if (meta.profile === "en16931") {
28
+ // ZUGFeRD / Factur-X is the CII flavour; a bare EN 16931 UBL is PEPPOL territory - don't mislabel it
29
+ const family = meta.syntax === "CII" ? "ZUGFeRD · " : "";
30
+ return `${family}EN 16931 (${meta.syntax})`;
31
+ }
32
+ return `unknown profile (${meta.syntax})`;
33
+ }
@@ -0,0 +1,10 @@
1
+ import type { Invoice, ComputedInvoice } from "@jasy/zugferd";
2
+ export type ExportFormat = "json" | "txt" | "xlsx";
3
+ /** Full invoice model + a computed totals block, pretty-printed. */
4
+ export declare function exportJson(inv: Invoice, t: ComputedInvoice): string;
5
+ /** A plain-text receipt - no ANSI, safe to pipe or save. */
6
+ export declare function exportText(inv: Invoice, t: ComputedInvoice): string;
7
+ /** A minimal single-sheet .xlsx with the invoice header, line items and totals. */
8
+ export declare function exportXlsx(inv: Invoice, t: ComputedInvoice): Buffer;
9
+ /** Export to the requested format - string for json/txt, Buffer for the binary xlsx. */
10
+ export declare function exportInvoice(inv: Invoice, t: ComputedInvoice, format: ExportFormat): string | Buffer;