@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.
@@ -0,0 +1,47 @@
1
+ // Structural PDF/A-3 + ZUGFeRD/Factur-X conformance checks - pure Node. This is NOT full ISO 19005-3
2
+ // validation (that's veraPDF, Java - offered as an optional adapter). It's the structural / metadata
3
+ // layer that actually trips e-invoice PDFs in practice: the rules our own writer satisfies, read
4
+ // backwards. Great as a regression guard for us and a fast local signal for foreign PDFs.
5
+ export function checkPdfA3(pdf) {
6
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
7
+ const s = Buffer.from(pdf).toString("latin1");
8
+ const xmp = (_b = (_a = s.match(/<x:xmpmeta[\s\S]*?<\/x:xmpmeta>/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : "";
9
+ const checks = [];
10
+ const add = (id, label, ok, detail) => {
11
+ checks.push({ id, label, ok, detail });
12
+ };
13
+ // header + binary marker (≥4 bytes > 127 on the comment line) - required for PDF/A
14
+ const version = (_c = s.match(/^%PDF-(\d\.\d)/)) === null || _c === void 0 ? void 0 : _c[1];
15
+ add("header", "PDF header & version", !!version, version ? `%PDF-${version}` : "missing");
16
+ const afterHeader = s.slice(s.indexOf("\n") + 1, s.indexOf("\n") + 9);
17
+ const binaryMarker = afterHeader.startsWith("%") &&
18
+ Array.from(afterHeader.slice(1, 5)).some((c) => c.charCodeAt(0) > 127);
19
+ add("binary-marker", "binary marker comment", binaryMarker);
20
+ // forbidden in PDF/A
21
+ add("no-encrypt", "not encrypted", !/\/Encrypt\b/.test(s));
22
+ add("no-javascript", "no JavaScript actions", !/\/JavaScript\b|\/JS\s/.test(s));
23
+ add("no-lzw", "no LZW compression", !/\/LZWDecode\b/.test(s));
24
+ // trailer /ID (required)
25
+ add("file-id", "document /ID present", /\/ID\s*\[\s*<[0-9A-Fa-f]/.test(s));
26
+ // XMP metadata + PDF/A identification
27
+ add("xmp", "XMP metadata present", xmp.length > 0);
28
+ const part = (_d = xmp.match(/pdfaid:part[^0-9]{0,4}(\d)/)) === null || _d === void 0 ? void 0 : _d[1];
29
+ add("pdfa-part", "declares PDF/A part 3", part === "3", part ? `part ${part}` : "missing");
30
+ const conformance = (_e = xmp.match(/pdfaid:conformance[^ABU]{0,4}([ABU])/)) === null || _e === void 0 ? void 0 : _e[1];
31
+ add("pdfa-conformance", "PDF/A conformance level", !!conformance, conformance ? `level ${conformance}` : "missing");
32
+ // Factur-X / ZUGFeRD XMP extension schema (the e-invoice fingerprint)
33
+ const fxFile = (_g = (_f = xmp.match(/DocumentFileName[^>]*>\s*([^<\s]+\.xml)/i)) === null || _f === void 0 ? void 0 : _f[1]) !== null && _g !== void 0 ? _g : (/DocumentFileName/i.test(xmp) ? "?" : undefined);
34
+ add("facturx-xmp", "Factur-X/ZUGFeRD XMP extension", /ConformanceLevel/i.test(xmp) && /DocumentType/i.test(xmp), fxFile);
35
+ // PDF/A OutputIntent with an embedded ICC profile (needed once colour/transparency is used)
36
+ add("output-intent", "PDF/A OutputIntent (sRGB ICC)", /\/OutputIntent/.test(s) && /\/DestOutputProfile/.test(s) && /\/S\s*\/GTS_PDFA1/.test(s));
37
+ // the embedded XML attached as an Associated File with a relationship (ZUGFeRD requirement)
38
+ add("associated-file", "embedded XML as Associated File", /\/AF\b/.test(s) &&
39
+ /\/AFRelationship\s*\/(Alternative|Data|Source)/.test(s) &&
40
+ /\/EF\s*<</.test(s));
41
+ // every font must be embedded (no font descriptor without a font program)
42
+ // count the descriptor objects (/Type /FontDescriptor), not the /FontDescriptor ref entries
43
+ const descriptors = ((_h = s.match(/\/Type\s*\/FontDescriptor\b/g)) !== null && _h !== void 0 ? _h : []).length;
44
+ const programs = ((_j = s.match(/\/FontFile[23]?\b/g)) !== null && _j !== void 0 ? _j : []).length;
45
+ add("fonts-embedded", "all fonts embedded", descriptors === 0 || programs >= descriptors, `${programs}/${descriptors} programs`);
46
+ return { ok: checks.every((c) => c.ok), checks };
47
+ }
@@ -0,0 +1,14 @@
1
+ import { type Invoice, type ComputedInvoice } from "@jasy/zugferd";
2
+ import { type InvoiceMeta } from "./detect.js";
3
+ export interface ReadResult {
4
+ isPdf: boolean;
5
+ xml: string;
6
+ meta: InvoiceMeta;
7
+ /** The parsed invoice (CII supported; UBL coming) - undefined if the syntax can't be parsed yet. */
8
+ invoice?: Invoice;
9
+ /** Totals derived from `invoice` (net, VAT, gross …). */
10
+ totals?: ComputedInvoice;
11
+ }
12
+ /** Read an e-invoice from raw bytes - a PDF (embedded XML extracted) or raw XML (used as-is). */
13
+ export declare function readInvoice(data: Uint8Array): ReadResult;
14
+ export declare function readInvoiceFile(path: string): ReadResult;
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { computeInvoice } from "@jasy/zugferd";
3
+ import { extractEmbeddedXml } from "./extract.js";
4
+ import { detectInvoice } from "./detect.js";
5
+ import { parseInvoice } from "./parse.js";
6
+ /** Read an e-invoice from raw bytes - a PDF (embedded XML extracted) or raw XML (used as-is). */
7
+ export function readInvoice(data) {
8
+ const buf = Buffer.from(data);
9
+ const isPdf = buf.subarray(0, 5).toString("latin1") === "%PDF-";
10
+ const xml = isPdf ? extractEmbeddedXml(buf) : buf.toString("utf-8");
11
+ const meta = detectInvoice(xml);
12
+ let invoice;
13
+ let totals;
14
+ try {
15
+ invoice = parseInvoice(xml); // throws for not-yet-supported syntaxes
16
+ totals = computeInvoice(invoice);
17
+ }
18
+ catch (_a) {
19
+ /* leave the parsed view empty - the raw XML + detection still work */
20
+ }
21
+ return { isPdf, xml, meta, invoice, totals };
22
+ }
23
+ export function readInvoiceFile(path) {
24
+ return readInvoice(readFileSync(path));
25
+ }
@@ -0,0 +1,18 @@
1
+ import type { InvoiceMeta } from "./detect.js";
2
+ export type ValidationProfile = "en16931-cii" | "en16931-ubl" | "xrechnung-cii" | "xrechnung-ubl";
3
+ export interface Violation {
4
+ id?: string;
5
+ test?: string;
6
+ location?: string;
7
+ text: string;
8
+ }
9
+ export interface ValidationReport {
10
+ profile: ValidationProfile;
11
+ valid: boolean;
12
+ errors: Violation[];
13
+ warnings: Violation[];
14
+ }
15
+ /** The profile that matches what detect() found - picks the right syntax + CIUS automatically. */
16
+ export declare function profileFor(meta: InvoiceMeta): ValidationProfile;
17
+ /** Validate invoice XML against a profile's Schematron rules → a structured pass/fail report. */
18
+ export declare function validateInvoiceXml(xml: string, profile: ValidationProfile): ValidationReport;
@@ -0,0 +1,65 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { gunzipSync } from "node:zlib";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import SaxonJS from "saxon-js";
6
+ // Which rule sets to run for a profile. XRechnung is a CIUS *on top of* EN 16931, so its files carry
7
+ // only the BR-DE delta - we run the EN 16931 base AND the XRechnung rules and merge the findings.
8
+ const RULE_SETS = {
9
+ "en16931-cii": ["en16931-cii"],
10
+ "en16931-ubl": ["en16931-ubl"],
11
+ "xrechnung-cii": ["en16931-cii", "xrechnung-cii"],
12
+ "xrechnung-ubl": ["en16931-ubl", "xrechnung-ubl"],
13
+ };
14
+ const RULES_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "validation");
15
+ /** The profile that matches what detect() found - picks the right syntax + CIUS automatically. */
16
+ export function profileFor(meta) {
17
+ const ubl = meta.syntax === "UBL";
18
+ if (meta.profile === "xrechnung")
19
+ return ubl ? "xrechnung-ubl" : "xrechnung-cii";
20
+ return ubl ? "en16931-ubl" : "en16931-cii";
21
+ }
22
+ function runRuleSet(set, xml) {
23
+ var _a;
24
+ const sef = gunzipSync(readFileSync(join(RULES_DIR, `${set}.sef.json.gz`))).toString("utf-8");
25
+ const out = SaxonJS.transform({ stylesheetText: sef, sourceText: xml, destination: "serialized" }, "sync");
26
+ return parseSvrl((_a = out.principalResult) !== null && _a !== void 0 ? _a : "");
27
+ }
28
+ /** Validate invoice XML against a profile's Schematron rules → a structured pass/fail report. */
29
+ export function validateInvoiceXml(xml, profile) {
30
+ const errors = [];
31
+ const warnings = [];
32
+ for (const set of RULE_SETS[profile]) {
33
+ const r = runRuleSet(set, xml);
34
+ errors.push(...r.errors);
35
+ warnings.push(...r.warnings);
36
+ }
37
+ return { profile, valid: errors.length === 0, errors, warnings };
38
+ }
39
+ // The rules emit SVRL (Schematron Validation Report Language). Each <svrl:failed-assert> is a violation;
40
+ // flag="warning" downgrades it. Regex is enough for this well-formed machine output.
41
+ function parseSvrl(svrl) {
42
+ var _a, _b, _c, _d, _e;
43
+ const errors = [];
44
+ const warnings = [];
45
+ const re = /<svrl:failed-assert\b([^>]*)>([\s\S]*?)<\/svrl:failed-assert>/g;
46
+ for (let m = re.exec(svrl); m; m = re.exec(svrl)) {
47
+ const flag = (_b = (_a = attr(m[1], "flag")) !== null && _a !== void 0 ? _a : attr(m[1], "role")) !== null && _b !== void 0 ? _b : "fatal";
48
+ const text = ((_d = (_c = m[2].match(/<svrl:text>([\s\S]*?)<\/svrl:text>/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "")
49
+ .replace(/\s+/g, " ")
50
+ .trim();
51
+ const violation = {
52
+ id: (_e = text.match(/\[((?:BR|BG)-?[A-Z0-9-]+)\]/)) === null || _e === void 0 ? void 0 : _e[1],
53
+ test: attr(m[1], "test"),
54
+ location: attr(m[1], "location"),
55
+ text,
56
+ };
57
+ // only fatal asserts fail validity; warning/information are advisory (Schematron flag levels)
58
+ (/warn|info/i.test(flag) ? warnings : errors).push(violation);
59
+ }
60
+ return { errors, warnings };
61
+ }
62
+ function attr(s, name) {
63
+ var _a;
64
+ return (_a = s.match(new RegExp(`\\b${name}="([^"]*)"`))) === null || _a === void 0 ? void 0 : _a[1];
65
+ }
@@ -0,0 +1,26 @@
1
+ export declare const VERAPDF_HOME: string;
2
+ export interface Tools {
3
+ java?: string;
4
+ verapdf?: string;
5
+ verapdfPath?: string;
6
+ }
7
+ /** The veraPDF binary we'd use: our managed install first, then any system install on PATH. */
8
+ export declare function findVerapdf(): string | undefined;
9
+ /** What's available right now - drives the `jasy verapdf` doctor. */
10
+ export declare function detectTools(): Tools;
11
+ export interface VeraReport {
12
+ ok: boolean;
13
+ profile?: string;
14
+ passedRules?: number;
15
+ failedRules?: number;
16
+ failures: {
17
+ clause: string;
18
+ failedChecks: number;
19
+ }[];
20
+ }
21
+ /** Parse veraPDF's `--format xml` report (the real 1.30 shape - verified against live output). */
22
+ export declare function parseVeraReport(xml: string): VeraReport;
23
+ /** Run veraPDF on a PDF (auto-detecting the PDF/A flavour) and return the parsed report. */
24
+ export declare function runVeraPdf(file: string, bin?: string | undefined): VeraReport;
25
+ /** Download + headless-install veraPDF into ~/.jasy/verapdf. `log` streams progress to the UI. */
26
+ export declare function installVeraPdf(log: (s: string) => void): Promise<string>;
@@ -0,0 +1,161 @@
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 { spawnSync } from "node:child_process";
11
+ import { existsSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { homedir, tmpdir } from "node:os";
14
+ import { inflateRawSync } from "node:zlib";
15
+ // Optional veraPDF adapter. veraPDF (verapdf.org) is the official open-source PDF/A validator from the
16
+ // PDF Association - we shell out to a local copy for the FULL ISO 19005 (PDF/A) check. It is NEVER a
17
+ // gate: our own structural checks (core/pdfa.ts) carry the everyday case; this is the opt-in "official
18
+ // seal". Self-contained: `--install` drops veraPDF into ~/.jasy/verapdf (no admin, no account). The one
19
+ // real requirement is a Java runtime - veraPDF is a Java app - which the doctor flags clearly.
20
+ const WIN = process.platform === "win32";
21
+ export const VERAPDF_HOME = join(homedir(), ".jasy", "verapdf");
22
+ const MANAGED_BIN = join(VERAPDF_HOME, WIN ? "verapdf.bat" : "verapdf");
23
+ const INSTALLER_URL = "https://software.verapdf.org/releases/verapdf-installer.zip";
24
+ /** The veraPDF binary we'd use: our managed install first, then any system install on PATH. */
25
+ export function findVerapdf() {
26
+ if (existsSync(MANAGED_BIN))
27
+ return MANAGED_BIN;
28
+ const r = spawnSync(WIN ? "where" : "which", ["verapdf"], { encoding: "utf-8" });
29
+ const p = r.status === 0 ? r.stdout.split(/\r?\n/)[0].trim() : "";
30
+ return p || undefined;
31
+ }
32
+ /** What's available right now - drives the `jasy verapdf` doctor. */
33
+ export function detectTools() {
34
+ var _a, _b, _c, _d;
35
+ const jv = spawnSync("java", ["-version"], { encoding: "utf-8" }); // java prints the version to stderr
36
+ const java = jv.error
37
+ ? undefined
38
+ : ((_b = (_a = (jv.stderr || jv.stdout).match(/version "([^"]+)"/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "found");
39
+ const verapdfPath = findVerapdf();
40
+ let verapdf;
41
+ if (verapdfPath) {
42
+ const vv = spawnSync(verapdfPath, ["--version"], { encoding: "utf-8" });
43
+ verapdf = vv.error ? undefined : ((_d = (_c = vv.stdout.match(/veraPDF (\S+)/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "found");
44
+ }
45
+ return { java, verapdf, verapdfPath };
46
+ }
47
+ /** Parse veraPDF's `--format xml` report (the real 1.30 shape - verified against live output). */
48
+ export function parseVeraReport(xml) {
49
+ var _a;
50
+ const ok = /isCompliant="true"/.test(xml);
51
+ const det = xml.match(/passedRules="(\d+)"\s+failedRules="(\d+)"/);
52
+ const failures = [
53
+ ...xml.matchAll(/<rule\b[^>]*\bclause="([^"]*)"[^>]*\bstatus="failed"[^>]*\bfailedChecks="(\d+)"/g),
54
+ ].map((m) => ({ clause: m[1], failedChecks: Number(m[2]) }));
55
+ return {
56
+ ok,
57
+ profile: (_a = xml.match(/profileName="([^"]*)"/)) === null || _a === void 0 ? void 0 : _a[1],
58
+ passedRules: det ? Number(det[1]) : undefined,
59
+ failedRules: det ? Number(det[2]) : undefined,
60
+ failures,
61
+ };
62
+ }
63
+ /** Run veraPDF on a PDF (auto-detecting the PDF/A flavour) and return the parsed report. */
64
+ export function runVeraPdf(file, bin = findVerapdf()) {
65
+ var _a;
66
+ if (!bin)
67
+ throw new Error("veraPDF is not installed - run `jasy verapdf --install`");
68
+ const r = spawnSync(bin, ["--format", "xml", file], {
69
+ encoding: "utf-8",
70
+ maxBuffer: 64 * 1024 * 1024,
71
+ });
72
+ if (r.error)
73
+ throw r.error; // e.g. Java missing - the verapdf launcher couldn't start
74
+ const xml = (_a = r.stdout) !== null && _a !== void 0 ? _a : "";
75
+ if (!xml.includes("<validationReport")) {
76
+ throw new Error("veraPDF returned no report" + (r.stderr ? `: ${r.stderr.split("\n")[0]}` : ""));
77
+ }
78
+ return parseVeraReport(xml);
79
+ }
80
+ // ── self-contained install into ~/.jasy/verapdf (no admin, no installer GUI) ────────────────────────
81
+ /** Extract one entry from a ZIP by name, via the central directory (robust vs data descriptors). */
82
+ function extractFromZip(zip, pattern) {
83
+ let eo = zip.length - 22;
84
+ while (eo >= 0 && zip.readUInt32LE(eo) !== 0x06054b50)
85
+ eo--; // end-of-central-directory record
86
+ if (eo < 0)
87
+ return undefined;
88
+ let cd = zip.readUInt32LE(eo + 16);
89
+ const count = zip.readUInt16LE(eo + 10);
90
+ for (let k = 0; k < count && zip.readUInt32LE(cd) === 0x02014b50; k++) {
91
+ const method = zip.readUInt16LE(cd + 10);
92
+ const comp = zip.readUInt32LE(cd + 20);
93
+ const nameLen = zip.readUInt16LE(cd + 28);
94
+ const extraLen = zip.readUInt16LE(cd + 30);
95
+ const commentLen = zip.readUInt16LE(cd + 32);
96
+ const lho = zip.readUInt32LE(cd + 42);
97
+ const name = zip.subarray(cd + 46, cd + 46 + nameLen).toString("utf-8");
98
+ if (pattern.test(name)) {
99
+ const dataStart = lho + 30 + zip.readUInt16LE(lho + 26) + zip.readUInt16LE(lho + 28);
100
+ const body = zip.subarray(dataStart, dataStart + comp);
101
+ return method === 8 ? inflateRawSync(body) : Buffer.from(body);
102
+ }
103
+ cd += 46 + nameLen + extraLen + commentLen;
104
+ }
105
+ return undefined;
106
+ }
107
+ /** veraPDF's IzPack auto-install descriptor - installs the CLI (no GUI prompts) into `home`. */
108
+ function autoInstallXml(home) {
109
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
110
+ <AutomatedInstallation langpack="eng">
111
+ <com.izforge.izpack.panels.htmlhello.HTMLHelloPanel id="welcome"/>
112
+ <com.izforge.izpack.panels.target.TargetPanel id="install_dir"><installpath>${home}</installpath></com.izforge.izpack.panels.target.TargetPanel>
113
+ <com.izforge.izpack.panels.packs.PacksPanel id="sdk_pack_select">
114
+ <pack index="0" name="veraPDF GUI" selected="true"/>
115
+ <pack index="1" name="veraPDF Mac and *nix Scripts" selected="true"/>
116
+ <pack index="2" name="veraPDF Batch files" selected="true"/>
117
+ <pack index="3" name="veraPDF Sample Files" selected="false"/>
118
+ <pack index="4" name="Documentation" selected="false"/>
119
+ </com.izforge.izpack.panels.packs.PacksPanel>
120
+ <com.izforge.izpack.panels.install.InstallPanel id="install"/>
121
+ <com.izforge.izpack.panels.finish.SimpleFinishPanel id="finish"/>
122
+ </AutomatedInstallation>`;
123
+ }
124
+ /** Download + headless-install veraPDF into ~/.jasy/verapdf. `log` streams progress to the UI. */
125
+ export function installVeraPdf(log) {
126
+ return __awaiter(this, void 0, void 0, function* () {
127
+ const tools = detectTools();
128
+ if (!tools.java) {
129
+ throw new Error("Java is required (veraPDF is a Java app). Install a JRE 11+ first, then re-run `jasy verapdf --install`.");
130
+ }
131
+ log(`Java ${tools.java} found.`);
132
+ const tmp = mkdtempSync(join(tmpdir(), "jasy-vera-"));
133
+ try {
134
+ log("Downloading veraPDF (~33 MB) ...");
135
+ const res = yield fetch(INSTALLER_URL);
136
+ if (!res.ok)
137
+ throw new Error(`download failed: HTTP ${res.status}`);
138
+ const zip = Buffer.from(yield res.arrayBuffer());
139
+ log("Extracting the installer ...");
140
+ const jar = extractFromZip(zip, /izpack-installer-[^/]*\.jar$/);
141
+ if (!jar)
142
+ throw new Error("could not find the veraPDF installer inside the download");
143
+ const jarPath = join(tmp, "installer.jar");
144
+ const xmlPath = join(tmp, "auto-install.xml");
145
+ writeFileSync(jarPath, jar);
146
+ writeFileSync(xmlPath, autoInstallXml(VERAPDF_HOME));
147
+ rmSync(VERAPDF_HOME, { recursive: true, force: true }); // clean (re)install
148
+ log(`Installing into ${VERAPDF_HOME} ...`);
149
+ const r = spawnSync("java", ["-Djava.awt.headless=true", "-jar", jarPath, xmlPath], {
150
+ encoding: "utf-8",
151
+ });
152
+ if (!existsSync(MANAGED_BIN)) {
153
+ throw new Error("install did not complete: " + (r.stderr || r.stdout || "").split("\n").slice(-3).join(" "));
154
+ }
155
+ return MANAGED_BIN;
156
+ }
157
+ finally {
158
+ rmSync(tmp, { recursive: true, force: true });
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { readCommand } from "./commands/read.js";
12
+ import { validateCommand } from "./commands/validate.js";
13
+ import { exportCommand } from "./commands/export.js";
14
+ import { verapdfCommand } from "./commands/verapdf.js";
15
+ import { launchTui } from "./tui/app.js";
16
+ // jasy CLI entry. With a command (e.g. `jasy read invoice.pdf`) it runs non-interactively and exits;
17
+ // with no command it opens the interactive JANO terminal.
18
+ const argv = process.argv.slice(2);
19
+ const cmd = argv[0];
20
+ // run a command and return true (then we exit), or open the interactive TUI and return false (it owns
21
+ // the process). Wrapped in an async fn so `verapdf` can await without needing top-level await.
22
+ function dispatch() {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ if (cmd === "read") {
25
+ readCommand(argv.slice(1));
26
+ return true;
27
+ }
28
+ if (cmd === "validate") {
29
+ validateCommand(argv.slice(1));
30
+ return true;
31
+ }
32
+ if (cmd === "export") {
33
+ exportCommand(argv.slice(1));
34
+ return true;
35
+ }
36
+ if (cmd === "verapdf") {
37
+ yield verapdfCommand(argv.slice(1));
38
+ return true;
39
+ }
40
+ // shorthand: `jasy some-invoice.pdf` == `jasy read some-invoice.pdf`
41
+ if (cmd && /\.(pdf|xml)$/i.test(cmd)) {
42
+ readCommand(argv);
43
+ return true;
44
+ }
45
+ if (cmd === "-h" || cmd === "--help") {
46
+ printHelp();
47
+ return true;
48
+ }
49
+ launchTui();
50
+ return false;
51
+ });
52
+ }
53
+ void dispatch().then((done) => {
54
+ var _a;
55
+ if (done)
56
+ process.exit((_a = process.exitCode) !== null && _a !== void 0 ? _a : 0);
57
+ });
58
+ function printHelp() {
59
+ console.log(`jasy - ZUGFeRD / XRechnung terminal
60
+
61
+ usage:
62
+ jasy open the interactive terminal
63
+ jasy read <file> read a PDF/XML invoice and show what it is
64
+ jasy read <file> --xml print the embedded XML to stdout
65
+ jasy read <file> -o x.xml save the embedded XML to a file
66
+ jasy validate <file> check EN 16931 rules + PDF/A-3 structure (exit 1 if invalid)
67
+ jasy validate <file> -v also list every passing check
68
+ jasy export <file> -f json write the invoice as JSON (or txt) to stdout
69
+ jasy export <file> -o x.xlsx export the invoice as a spreadsheet
70
+ jasy verapdf check the optional full-ISO PDF/A validator (veraPDF)
71
+ jasy verapdf --install install veraPDF locally for the full PDF/A check
72
+ `);
73
+ }
@@ -0,0 +1 @@
1
+ export declare function launchTui(): void;