@getpeppr/cli 0.8.4 → 0.9.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/CHANGELOG.md +16 -4
- package/README.md +2 -2
- package/dist/index.js +105 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,24 @@ All notable changes to `@getpeppr/cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The CLI bundles `@getpeppr/sdk` into its published artifact (tsup `noExternal`), so `validate`, `convert` and `init` run the SDK's validators, country rules and UBL builder locally — an SDK release only reaches CLI users once the CLI is rebundled. Rebundle-only releases are listed with the SDK version they ship.
|
|
6
6
|
|
|
7
|
+
## [0.9.0] — 2026-08-23
|
|
8
|
+
|
|
9
|
+
Before every sandbox send, the CLI now reads the exact sender profile from
|
|
10
|
+
`GET /v1/identity`. Its own generated fixture is adapted to O/0 for a
|
|
11
|
+
routing-only sender or AE/0 for a sender carrying a Tax Identifier. A
|
|
12
|
+
user-authored file is never rewritten silently: an incompatible tax mode is
|
|
13
|
+
refused locally before the invoice API is called.
|
|
14
|
+
|
|
15
|
+
This replaces the incomplete static O/0 assumption in 0.8.4. It also bundles
|
|
16
|
+
SDK 4.8.0 and fails closed when the provider inventory or scheme role cannot be
|
|
17
|
+
verified (GPR-1149).
|
|
18
|
+
|
|
7
19
|
## [0.8.4] — 2026-08-22
|
|
8
20
|
|
|
9
|
-
`getpeppr init`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
`getpeppr init` scaffolded the SPF Economie test receiver plus out-of-scope
|
|
22
|
+
`O/0` lines. That fixed routing-only senders but incorrectly assumed every
|
|
23
|
+
sandbox sender lacked a Tax Identifier; 0.9.0 supersedes the static choice with
|
|
24
|
+
the exact sender profile.
|
|
13
25
|
|
|
14
26
|
Every generated `O` line includes the builder-only exemption reason required by
|
|
15
27
|
the SDK's local validator. The quick `getpeppr send --to …` payload remains on
|
package/README.md
CHANGED
|
@@ -237,11 +237,11 @@ The CLI runs three validation engines from the [@getpeppr/sdk](https://www.npmjs
|
|
|
237
237
|
2. **Business Rules** — Peppol BIS 3.0 / EN 16931 compliance (BR-xx, BR-CO-xx, PEPPOL-xx rules)
|
|
238
238
|
3. **Country Rules** — Belgium (BE), France (FR), Italy (IT), Netherlands (NL), Germany (DE)
|
|
239
239
|
|
|
240
|
-
Scaffolding (`init`), validation, and conversion run fully offline — no API key or network connection required. Only `lookup`, `send`, and `whoami` need a network connection.
|
|
240
|
+
Scaffolding (`init`), validation, and conversion run fully offline — no API key or network connection required. Only `lookup`, `send`, and `whoami` need a network connection. Before a sandbox `send`, the CLI reads `GET /v1/identity`: its own generated fixture is adapted to the returned O/0 or AE/0 profile, while a user-edited file is never rewritten silently and is refused locally if its tax mode conflicts with the sender.
|
|
241
241
|
|
|
242
242
|
## Invoice format
|
|
243
243
|
|
|
244
|
-
The input file must be a JSON object matching the getpeppr `InvoiceInput` type. `number`, `to`, and `lines` are required for API sends. The `from` party is included in the scaffold because offline validation and UBL conversion need seller metadata; API sends use the legal entity linked to your key. The generated file
|
|
244
|
+
The input file must be a JSON object matching the getpeppr `InvoiceInput` type. `number`, `to`, and `lines` are required for API sends. The `from` party is included in the scaffold because offline validation and UBL conversion need seller metadata; API sends use the legal entity linked to your key. The generated file starts as an O/0 integration fixture. At send time the CLI verifies it against the actual sender profile and never forwards an incompatible tax mode.
|
|
245
245
|
|
|
246
246
|
```json
|
|
247
247
|
{
|
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ Validating: ${pc.bold(filename)}
|
|
|
130
130
|
return lines.join("\n");
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// ../sdk/dist/core/canonical-schemes.js
|
|
134
134
|
var ALIAS_TO_EAS = /* @__PURE__ */ new Map([
|
|
135
135
|
["AD:VAT", "9922"],
|
|
136
136
|
["AE:TIN", "0235"],
|
|
@@ -356,7 +356,7 @@ function countryForScheme(scheme) {
|
|
|
356
356
|
var CANONICAL_SCHEME_COUNT = ALIAS_TO_EAS.size;
|
|
357
357
|
var SCHEME_COUNTRY_COUNT = EAS_TO_COUNTRY.size;
|
|
358
358
|
|
|
359
|
-
//
|
|
359
|
+
// ../sdk/dist/core/peppol-id.js
|
|
360
360
|
function isWellFormedPeppolId(peppolId) {
|
|
361
361
|
if (!peppolId.includes(":"))
|
|
362
362
|
return false;
|
|
@@ -382,7 +382,7 @@ function parsePeppolId(peppolId) {
|
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
//
|
|
385
|
+
// ../sdk/dist/core/iso6523-icd-codes.js
|
|
386
386
|
var ICD_CODES = /* @__PURE__ */ new Set([
|
|
387
387
|
"0002",
|
|
388
388
|
"0003",
|
|
@@ -642,7 +642,7 @@ function canCarryPartyIdentification(scheme, context) {
|
|
|
642
642
|
return scheme === "SEPA" && context !== "AccountingCustomerParty";
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
-
//
|
|
645
|
+
// ../sdk/dist/core/ubl-builder.js
|
|
646
646
|
var UBL_NS = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2";
|
|
647
647
|
var CAC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2";
|
|
648
648
|
var CBC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2";
|
|
@@ -1242,7 +1242,7 @@ function buildCreditNoteXml(input) {
|
|
|
1242
1242
|
</CreditNote>`;
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
|
-
//
|
|
1245
|
+
// ../sdk/dist/core/checksums/luhn.js
|
|
1246
1246
|
function isValidLuhn(input) {
|
|
1247
1247
|
if (typeof input !== "string")
|
|
1248
1248
|
return false;
|
|
@@ -1283,7 +1283,7 @@ function isValidLuhnSiret(input) {
|
|
|
1283
1283
|
return false;
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
-
//
|
|
1286
|
+
// ../sdk/dist/core/country-rules.js
|
|
1287
1287
|
function warn(field, message, ruleId) {
|
|
1288
1288
|
return { field, message, ruleId };
|
|
1289
1289
|
}
|
|
@@ -1424,7 +1424,7 @@ function validateCountryRules(input) {
|
|
|
1424
1424
|
return { errors, warnings };
|
|
1425
1425
|
}
|
|
1426
1426
|
|
|
1427
|
-
//
|
|
1427
|
+
// ../sdk/dist/core/code-lists.js
|
|
1428
1428
|
var CURRENCIES = /* @__PURE__ */ new Map([
|
|
1429
1429
|
["EUR", { code: "EUR", name: "Euro", minorUnits: 2 }],
|
|
1430
1430
|
["USD", { code: "USD", name: "US Dollar", minorUnits: 2 }],
|
|
@@ -1549,7 +1549,7 @@ function getAllUnits() {
|
|
|
1549
1549
|
return Array.from(UNIT_CODES.entries()).map(([code, name]) => ({ code, name })).sort((a, b) => a.code.localeCompare(b.code));
|
|
1550
1550
|
}
|
|
1551
1551
|
|
|
1552
|
-
//
|
|
1552
|
+
// ../sdk/dist/core/validator.js
|
|
1553
1553
|
function error(field, message, ruleId, suggestion) {
|
|
1554
1554
|
return { field, message, ruleId, suggestion };
|
|
1555
1555
|
}
|
|
@@ -1781,7 +1781,7 @@ function validateInvoice(input) {
|
|
|
1781
1781
|
};
|
|
1782
1782
|
}
|
|
1783
1783
|
|
|
1784
|
-
//
|
|
1784
|
+
// ../sdk/dist/core/schematron.js
|
|
1785
1785
|
function violation(ruleId, severity, message, field) {
|
|
1786
1786
|
return { ruleId, severity, message, field };
|
|
1787
1787
|
}
|
|
@@ -2306,7 +2306,7 @@ var SDK_SCHEMATRON_RULE_IDS = [
|
|
|
2306
2306
|
"PEPPOL-EN16931-R080"
|
|
2307
2307
|
];
|
|
2308
2308
|
|
|
2309
|
-
//
|
|
2309
|
+
// ../sdk/dist/core/status-precedence.js
|
|
2310
2310
|
var STATUS_PRECEDENCE = [
|
|
2311
2311
|
{ status: "failed", family: "terminal-failure" },
|
|
2312
2312
|
{ status: "rejected", family: "terminal-failure" },
|
|
@@ -2330,10 +2330,10 @@ function statusFamily(status) {
|
|
|
2330
2330
|
}
|
|
2331
2331
|
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2332
2332
|
|
|
2333
|
-
//
|
|
2334
|
-
var SDK_VERSION = "4.
|
|
2333
|
+
// ../sdk/dist/version.js
|
|
2334
|
+
var SDK_VERSION = "4.8.0";
|
|
2335
2335
|
|
|
2336
|
-
//
|
|
2336
|
+
// ../sdk/dist/core/client.js
|
|
2337
2337
|
function findHeaderCaseInsensitive(headers, name) {
|
|
2338
2338
|
if (!headers)
|
|
2339
2339
|
return void 0;
|
|
@@ -2604,6 +2604,8 @@ var GetpepprAdapter = class {
|
|
|
2604
2604
|
params.set("limit", String(options.limit));
|
|
2605
2605
|
if (options?.offset != null)
|
|
2606
2606
|
params.set("offset", String(options.offset));
|
|
2607
|
+
if (options?.number != null)
|
|
2608
|
+
params.set("number", options.number);
|
|
2607
2609
|
if (options?.includeLines)
|
|
2608
2610
|
params.set("include", "lines");
|
|
2609
2611
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
@@ -2737,7 +2739,9 @@ var GetpepprAdapter = class {
|
|
|
2737
2739
|
params.set("limit", String(options.limit));
|
|
2738
2740
|
if (options?.offset != null)
|
|
2739
2741
|
params.set("offset", String(options.offset));
|
|
2740
|
-
if (options?.
|
|
2742
|
+
if (options?.documentId != null)
|
|
2743
|
+
params.set("documentId", options.documentId);
|
|
2744
|
+
if (options?.invoiceId != null)
|
|
2741
2745
|
params.set("invoiceId", options.invoiceId);
|
|
2742
2746
|
if (options?.dateFrom)
|
|
2743
2747
|
params.set("dateFrom", options.dateFrom);
|
|
@@ -3244,9 +3248,49 @@ function parseAccountIdentity(raw) {
|
|
|
3244
3248
|
return {
|
|
3245
3249
|
environment,
|
|
3246
3250
|
legalEntity: parseAccountIdentityLegalEntity(readOwn(result, "legalEntity"), result),
|
|
3247
|
-
identifiers
|
|
3251
|
+
identifiers,
|
|
3252
|
+
sandboxFirstSend: parseSandboxFirstSendProfile(readOwn(result, "sandboxFirstSend"), result)
|
|
3248
3253
|
};
|
|
3249
3254
|
}
|
|
3255
|
+
function parseSandboxFirstSendProfile(raw, body) {
|
|
3256
|
+
if (raw === null)
|
|
3257
|
+
return null;
|
|
3258
|
+
if (!isRecord(raw)) {
|
|
3259
|
+
throw new PeppolProtocolError("The getpeppr API answered the identity request without a valid sandboxFirstSend field. Please report this response to support@getpeppr.dev.", "sandboxFirstSend", boundedBody(body));
|
|
3260
|
+
}
|
|
3261
|
+
const status = readOwn(raw, "status");
|
|
3262
|
+
if (status === "blocked") {
|
|
3263
|
+
const code = readOwn(raw, "code");
|
|
3264
|
+
const message = readOwn(raw, "message");
|
|
3265
|
+
if (typeof code === "string" && code.trim() !== "" && typeof message === "string" && message.trim() !== "") {
|
|
3266
|
+
return { status, code, message };
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
if (status === "ready") {
|
|
3270
|
+
const taxMode = readOwn(raw, "taxMode");
|
|
3271
|
+
const line = readOwn(raw, "line");
|
|
3272
|
+
if ((taxMode === "outside_scope" || taxMode === "reverse_charge") && isRecord(line)) {
|
|
3273
|
+
const vatRate = readOwn(line, "vatRate");
|
|
3274
|
+
const vatCategory = readOwn(line, "vatCategory");
|
|
3275
|
+
const taxExemptReason = readOwn(line, "taxExemptReason");
|
|
3276
|
+
if (vatRate === 0 && typeof taxExemptReason === "string" && taxExemptReason.trim() !== "" && taxMode === "outside_scope" && vatCategory === "O") {
|
|
3277
|
+
return {
|
|
3278
|
+
status,
|
|
3279
|
+
taxMode,
|
|
3280
|
+
line: { vatRate, vatCategory, taxExemptReason }
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
if (vatRate === 0 && typeof taxExemptReason === "string" && taxExemptReason.trim() !== "" && taxMode === "reverse_charge" && vatCategory === "AE") {
|
|
3284
|
+
return {
|
|
3285
|
+
status,
|
|
3286
|
+
taxMode,
|
|
3287
|
+
line: { vatRate, vatCategory, taxExemptReason }
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
throw new PeppolProtocolError("The getpeppr API answered the identity request with a malformed sandboxFirstSend profile. Please report this response to support@getpeppr.dev.", "sandboxFirstSend", boundedBody(body));
|
|
3293
|
+
}
|
|
3250
3294
|
function parseAccountIdentityLegalEntity(raw, body) {
|
|
3251
3295
|
if (raw === null)
|
|
3252
3296
|
return null;
|
|
@@ -3976,8 +4020,8 @@ var EventOperations = class {
|
|
|
3976
4020
|
* const result = await peppol.events.list({ limit: 10 });
|
|
3977
4021
|
* console.log(result.data, result.meta);
|
|
3978
4022
|
*
|
|
3979
|
-
* // Filter by
|
|
3980
|
-
* const invoiceEvents = await peppol.events.list({
|
|
4023
|
+
* // Filter by provider document ID or getpeppr submission ID
|
|
4024
|
+
* const invoiceEvents = await peppol.events.list({ documentId: "inv-123" });
|
|
3981
4025
|
* ```
|
|
3982
4026
|
*/
|
|
3983
4027
|
async list(options) {
|
|
@@ -3988,7 +4032,7 @@ var EventOperations = class {
|
|
|
3988
4032
|
*
|
|
3989
4033
|
* @example
|
|
3990
4034
|
* ```ts
|
|
3991
|
-
* for await (const event of peppol.events.listAll({
|
|
4035
|
+
* for await (const event of peppol.events.listAll({ documentId: "inv-123" })) {
|
|
3992
4036
|
* console.log(event.name, event.createdAt);
|
|
3993
4037
|
* }
|
|
3994
4038
|
* ```
|
|
@@ -4536,9 +4580,9 @@ function registerInitCommand(program2) {
|
|
|
4536
4580
|
3. Convert to XML: getpeppr convert ${filename}
|
|
4537
4581
|
4. Send: getpeppr send ${filename}
|
|
4538
4582
|
|
|
4539
|
-
${pc2.dim("Sandbox note:")}
|
|
4540
|
-
|
|
4541
|
-
|
|
4583
|
+
${pc2.dim("Sandbox note:")} this offline template starts with O/0 tax lines.
|
|
4584
|
+
On send, the CLI checks GET /v1/identity and refuses a conflicting sender
|
|
4585
|
+
profile before anything reaches the provider.
|
|
4542
4586
|
`);
|
|
4543
4587
|
process.exit(0);
|
|
4544
4588
|
}
|
|
@@ -5102,11 +5146,10 @@ function buildDefaultSendPayload(overrides = {}) {
|
|
|
5102
5146
|
unitPrice: amount,
|
|
5103
5147
|
vatRate: 0,
|
|
5104
5148
|
// vatCategory "O" = outside the scope of VAT (UBL 2.1 / EN 16931).
|
|
5105
|
-
//
|
|
5106
|
-
//
|
|
5107
|
-
//
|
|
5108
|
-
// derive its own provider
|
|
5109
|
-
// Do not fall back to VAT-bearing category "S": a fresh sandbox sender has no VAT number.
|
|
5149
|
+
// This offline fixture starts at O/0. Before sending, the CLI reads
|
|
5150
|
+
// GET /identity and replaces these tax fields with the sender-specific
|
|
5151
|
+
// O/0 or AE/0 first-send profile. The SDK transport strips the
|
|
5152
|
+
// builder-only reason so Storecove can derive its own provider text.
|
|
5110
5153
|
vatCategory: "O",
|
|
5111
5154
|
taxExemptReason: "Not subject to VAT"
|
|
5112
5155
|
}
|
|
@@ -5282,6 +5325,42 @@ function registerSendCommand(program2) {
|
|
|
5282
5325
|
}
|
|
5283
5326
|
throw e;
|
|
5284
5327
|
}
|
|
5328
|
+
const baseUrl = flags.local ? LOCAL_BASE : API_BASE;
|
|
5329
|
+
const client = new Peppol({ apiKey: auth.apiKey, baseUrl });
|
|
5330
|
+
if (auth.environment === "sandbox") {
|
|
5331
|
+
let profile;
|
|
5332
|
+
try {
|
|
5333
|
+
profile = (await client.identity.get()).sandboxFirstSend;
|
|
5334
|
+
} catch (e) {
|
|
5335
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5336
|
+
process.stderr.write(`${pc6.red("\u2722")} Could not verify the sandbox sender profile: ${msg}
|
|
5337
|
+
`);
|
|
5338
|
+
process.exit(1);
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
if (!profile || profile.status !== "ready") {
|
|
5342
|
+
const message = profile?.status === "blocked" ? profile.message : "The sandbox first-send profile is unavailable.";
|
|
5343
|
+
process.stderr.write(`${pc6.red("\u2722")} ${message}
|
|
5344
|
+
`);
|
|
5345
|
+
process.exit(1);
|
|
5346
|
+
return;
|
|
5347
|
+
}
|
|
5348
|
+
const taxEntries = [payload.lines, payload.allowances, payload.charges].filter(Array.isArray).flat();
|
|
5349
|
+
const payloadOutsideScope = taxEntries.length > 0 && taxEntries.every((entry) => entry.vatCategory === "O");
|
|
5350
|
+
const profileOutsideScope = profile.taxMode === "outside_scope";
|
|
5351
|
+
if (file) {
|
|
5352
|
+
if (payloadOutsideScope !== profileOutsideScope) {
|
|
5353
|
+
process.stderr.write(
|
|
5354
|
+
`${pc6.red("\u2722")} This file's tax mode does not match the sandbox sender. GET /v1/identity recommends ${profile.line.vatCategory}/0. Nothing was sent.
|
|
5355
|
+
`
|
|
5356
|
+
);
|
|
5357
|
+
process.exit(1);
|
|
5358
|
+
return;
|
|
5359
|
+
}
|
|
5360
|
+
} else {
|
|
5361
|
+
payload.lines = payload.lines.map((line) => ({ ...line, ...profile.line }));
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5285
5364
|
if (flags.validate !== false) {
|
|
5286
5365
|
const result2 = runValidation(payload);
|
|
5287
5366
|
if (!result2.valid) {
|
|
@@ -5302,8 +5381,6 @@ function registerSendCommand(program2) {
|
|
|
5302
5381
|
process.exit(0);
|
|
5303
5382
|
}
|
|
5304
5383
|
}
|
|
5305
|
-
const baseUrl = flags.local ? LOCAL_BASE : API_BASE;
|
|
5306
|
-
const client = new Peppol({ apiKey: auth.apiKey, baseUrl });
|
|
5307
5384
|
let result;
|
|
5308
5385
|
try {
|
|
5309
5386
|
result = await client.invoices.send(payload);
|