@aderis-erp/cli 1.0.17 → 1.1.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/commands/access-groups.js +49 -17
- package/dist/commands/account-transaction-allocations.js +46 -14
- package/dist/commands/account-transactions.js +75 -35
- package/dist/commands/accounts.js +54 -22
- package/dist/commands/agent-platform.js +53 -21
- package/dist/commands/{nango-integrations.d.ts → agent-runs.d.ts} +1 -1
- package/dist/commands/agent-runs.js +1155 -0
- package/dist/commands/analytics.js +47 -15
- package/dist/commands/api-keys.js +49 -16
- package/dist/commands/{text-templates.d.ts → async-jobs.d.ts} +1 -1
- package/dist/commands/async-jobs.js +1158 -0
- package/dist/commands/audit-log.js +46 -14
- package/dist/commands/bank-accounts.js +63 -30
- package/dist/commands/bank-integrations.d.ts +1 -0
- package/dist/commands/bank-integrations.js +66 -31
- package/dist/commands/business-partners.js +77 -44
- package/dist/commands/chat.js +52 -19
- package/dist/commands/credit-card-accounts.d.ts +1 -0
- package/dist/commands/credit-card-accounts.js +67 -29
- package/dist/commands/delivery-notes.js +78 -44
- package/dist/commands/dunning-configs.js +49 -17
- package/dist/commands/dunning-reminders.js +52 -20
- package/dist/commands/dunning-runs.js +45 -13
- package/dist/commands/email-transactions.js +47 -15
- package/dist/commands/employees.js +76 -44
- package/dist/commands/entity-configurations.js +47 -15
- package/dist/commands/event-processor-mappings.js +49 -17
- package/dist/commands/files.js +56 -24
- package/dist/commands/health.js +49 -17
- package/dist/commands/imports.d.ts +39 -0
- package/dist/commands/{nango-integrations.js → imports.js} +61 -25
- package/dist/commands/inbox.js +52 -19
- package/dist/commands/integration-identities.d.ts +40 -0
- package/dist/commands/integration-identities.js +1151 -0
- package/dist/commands/integrations.js +47 -15
- package/dist/commands/item-categories.js +49 -17
- package/dist/commands/items.js +49 -17
- package/dist/commands/journal-lines.js +45 -13
- package/dist/commands/journals.js +68 -36
- package/dist/commands/ledgers.d.ts +3 -1
- package/dist/commands/ledgers.js +95 -34
- package/dist/commands/my-account.d.ts +39 -0
- package/dist/commands/my-account.js +1160 -0
- package/dist/commands/office-locations.js +49 -17
- package/dist/commands/payment-methods.js +54 -22
- package/dist/commands/payment-runs.js +61 -29
- package/dist/commands/payment-terms.js +50 -18
- package/dist/commands/payments.js +51 -19
- package/dist/commands/payroll-runs.d.ts +43 -0
- package/dist/commands/payroll-runs.js +1188 -0
- package/dist/commands/permissions.d.ts +39 -0
- package/dist/commands/{text-templates.js → permissions.js} +54 -30
- package/dist/commands/price-lists.js +54 -22
- package/dist/commands/processor-mappings.js +48 -16
- package/dist/commands/processors.js +55 -24
- package/dist/commands/purchase-order-types.js +49 -17
- package/dist/commands/purchase-orders.js +69 -36
- package/dist/commands/reports.js +49 -17
- package/dist/commands/roles.d.ts +39 -0
- package/dist/commands/roles.js +1152 -0
- package/dist/commands/salary-types.js +49 -17
- package/dist/commands/sales-invoices.js +84 -51
- package/dist/commands/sales-orders.js +78 -44
- package/dist/commands/saved-views.js +47 -15
- package/dist/commands/scheduled-processor-mappings.js +49 -17
- package/dist/commands/schedules.js +53 -21
- package/dist/commands/tag-configurations.js +50 -18
- package/dist/commands/tags.js +49 -17
- package/dist/commands/tax-codes.js +52 -20
- package/dist/commands/tax-matrix.js +49 -17
- package/dist/commands/templates.d.ts +39 -0
- package/dist/commands/templates.js +1157 -0
- package/dist/commands/tenants.js +52 -20
- package/dist/commands/tools.js +46 -14
- package/dist/commands/users.d.ts +42 -0
- package/dist/commands/users.js +1174 -0
- package/dist/commands/vat-declarations.d.ts +2 -0
- package/dist/commands/vat-declarations.js +72 -36
- package/dist/commands/vendor-invoices.js +81 -49
- package/dist/commands/webhooks.js +51 -19
- package/dist/commands/workflows.js +47 -15
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +13 -0
- package/dist/lib/auth/index.js +15 -4
- package/dist/lib/auth/jwks.d.ts +19 -0
- package/dist/lib/auth/jwks.js +112 -0
- package/dist/lib/auth/keycloak-provider.d.ts +23 -7
- package/dist/lib/auth/keycloak-provider.js +53 -24
- package/dist/lib/auth/tokens.d.ts +8 -3
- package/dist/lib/auth/tokens.js +8 -7
- package/dist/lib/auth/types.d.ts +5 -1
- package/oclif.manifest.json +2152 -715
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/office-locations', summary: 'Get all office locations with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/office-locations', summary: 'Create a new office location', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/office-locations/{id}', summary: 'Get an office location by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/office-locations/{id}', summary: 'Update an office location', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/office-locations/{id}', summary: 'Delete an office location', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/office-locations', summary: 'Get all office locations with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/office-locations', summary: 'Create a new office location', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/office-locations/{id}', summary: 'Get an office location by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/office-locations/{id}', summary: 'Update an office location', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/office-locations/{id}', summary: 'Delete an office location', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class OfficeLocations extends Command {
|
|
18
18
|
static description = 'Manage office locations';
|
|
@@ -264,10 +264,11 @@ export default class OfficeLocations extends Command {
|
|
|
264
264
|
process.stderr.write('\n');
|
|
265
265
|
}
|
|
266
266
|
try {
|
|
267
|
-
// Binary response:
|
|
267
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
268
268
|
if (endpoint.isBinaryResponse) {
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
const toStdout = !flags.file;
|
|
270
|
+
if (toStdout && process.stdout.isTTY) {
|
|
271
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
271
272
|
}
|
|
272
273
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
273
274
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -275,24 +276,55 @@ export default class OfficeLocations extends Command {
|
|
|
275
276
|
contextName: flags.context,
|
|
276
277
|
binary: true,
|
|
277
278
|
});
|
|
278
|
-
const fsModule = await import('node:fs');
|
|
279
279
|
const binData = binResponse.data;
|
|
280
280
|
const buffer = Buffer.isBuffer(binData)
|
|
281
281
|
? binData
|
|
282
282
|
: Buffer.from(binData);
|
|
283
|
+
if (toStdout) {
|
|
284
|
+
// Nothing else may reach stdout here — it is the file.
|
|
285
|
+
process.stdout.write(buffer);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const fsModule = await import('node:fs');
|
|
283
289
|
fsModule.writeFileSync(flags.file, buffer);
|
|
284
290
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
285
291
|
return;
|
|
286
292
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
let response;
|
|
294
|
+
if (endpoint.hasBinaryVariant) {
|
|
295
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
296
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
297
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
298
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
299
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
300
|
+
data,
|
|
301
|
+
contextName: flags.context,
|
|
302
|
+
binary: true,
|
|
303
|
+
});
|
|
304
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
305
|
+
const rawData = raw.data;
|
|
306
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
307
|
+
if (!isTextualMediaType(rawType)) {
|
|
308
|
+
if (process.stdout.isTTY) {
|
|
309
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
310
|
+
}
|
|
311
|
+
// Nothing else may reach stdout here — it is the file.
|
|
312
|
+
process.stdout.write(buffer);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
319
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
320
|
+
data,
|
|
321
|
+
contextName: flags.context,
|
|
322
|
+
acceptAny: true,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
293
325
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
294
326
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
295
|
-
if (contentType
|
|
327
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
296
328
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
297
329
|
this.log(String(response.data));
|
|
298
330
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,16 +8,16 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = ["ledger-mappings"];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/payment-methods', summary: 'Get all payment methods with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/payment-methods', summary: 'Create a new payment method', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['alternativeBankAccountIds', 'alternativeCreditCardAccountIds'] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/payment-methods/{id}', summary: 'Get a payment method by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/payment-methods/{id}', summary: 'Update a payment method', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['alternativeBankAccountIds', 'alternativeCreditCardAccountIds'] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/payment-methods/{id}', summary: 'Delete a payment method', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'list-ledger-mappings': { method: 'GET', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings', summary: 'List ledger mappings for a payment method', pathParams: ['paymentMethodId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'create-ledger-mappings': { method: 'POST', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings', summary: 'Create payment method ledger mapping', pathParams: ['paymentMethodId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
-
'get-ledger-mappings': { method: 'GET', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Get payment method ledger mapping by ID', pathParams: ['paymentMethodId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'update-ledger-mappings': { method: 'PATCH', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Update payment method ledger mapping', pathParams: ['paymentMethodId', 'id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
-
'delete-ledger-mappings': { method: 'DELETE', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Delete payment method ledger mapping', pathParams: ['paymentMethodId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/payment-methods', summary: 'Get all payment methods with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/payment-methods', summary: 'Create a new payment method', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['alternativeBankAccountIds', 'alternativeCreditCardAccountIds'] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/payment-methods/{id}', summary: 'Get a payment method by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/payment-methods/{id}', summary: 'Update a payment method', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['alternativeBankAccountIds', 'alternativeCreditCardAccountIds'] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/payment-methods/{id}', summary: 'Delete a payment method', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'list-ledger-mappings': { method: 'GET', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings', summary: 'List ledger mappings for a payment method', pathParams: ['paymentMethodId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'create-ledger-mappings': { method: 'POST', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings', summary: 'Create payment method ledger mapping', pathParams: ['paymentMethodId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
+
'get-ledger-mappings': { method: 'GET', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Get payment method ledger mapping by ID', pathParams: ['paymentMethodId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'update-ledger-mappings': { method: 'PATCH', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Update payment method ledger mapping', pathParams: ['paymentMethodId', 'id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'delete-ledger-mappings': { method: 'DELETE', path: '/v1/payment-methods/{paymentMethodId}/ledger-mappings/{id}', summary: 'Delete payment method ledger mapping', pathParams: ['paymentMethodId', 'id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
21
|
};
|
|
22
22
|
export default class PaymentMethods extends Command {
|
|
23
23
|
static description = 'Manage payment methods';
|
|
@@ -270,10 +270,11 @@ export default class PaymentMethods extends Command {
|
|
|
270
270
|
process.stderr.write('\n');
|
|
271
271
|
}
|
|
272
272
|
try {
|
|
273
|
-
// Binary response:
|
|
273
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
274
274
|
if (endpoint.isBinaryResponse) {
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
const toStdout = !flags.file;
|
|
276
|
+
if (toStdout && process.stdout.isTTY) {
|
|
277
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
277
278
|
}
|
|
278
279
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
279
280
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -281,24 +282,55 @@ export default class PaymentMethods extends Command {
|
|
|
281
282
|
contextName: flags.context,
|
|
282
283
|
binary: true,
|
|
283
284
|
});
|
|
284
|
-
const fsModule = await import('node:fs');
|
|
285
285
|
const binData = binResponse.data;
|
|
286
286
|
const buffer = Buffer.isBuffer(binData)
|
|
287
287
|
? binData
|
|
288
288
|
: Buffer.from(binData);
|
|
289
|
+
if (toStdout) {
|
|
290
|
+
// Nothing else may reach stdout here — it is the file.
|
|
291
|
+
process.stdout.write(buffer);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const fsModule = await import('node:fs');
|
|
289
295
|
fsModule.writeFileSync(flags.file, buffer);
|
|
290
296
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
291
297
|
return;
|
|
292
298
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
let response;
|
|
300
|
+
if (endpoint.hasBinaryVariant) {
|
|
301
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
302
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
303
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
304
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
305
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
306
|
+
data,
|
|
307
|
+
contextName: flags.context,
|
|
308
|
+
binary: true,
|
|
309
|
+
});
|
|
310
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
311
|
+
const rawData = raw.data;
|
|
312
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
313
|
+
if (!isTextualMediaType(rawType)) {
|
|
314
|
+
if (process.stdout.isTTY) {
|
|
315
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
316
|
+
}
|
|
317
|
+
// Nothing else may reach stdout here — it is the file.
|
|
318
|
+
process.stdout.write(buffer);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
325
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
326
|
+
data,
|
|
327
|
+
contextName: flags.context,
|
|
328
|
+
acceptAny: true,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
299
331
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
300
332
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
301
|
-
if (contentType
|
|
333
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
302
334
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
303
335
|
this.log(String(response.data));
|
|
304
336
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,23 +8,23 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["open-submissions", "eligible-payments", "list", "create", "preview", "schedule", "status", "get", "update", "delete", "create-payments", "delete-payments", "send", "painfile", "sync-status", "sync-status-xml", "sync-all"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'open-submissions': { method: 'GET', path: '/v1/payment-runs/bank-accounts/{bankAccountId}/open-submissions', summary: 'Get open submission IDs for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'eligible-payments': { method: 'GET', path: '/v1/payment-runs/eligible-payments/{bankAccountId}', summary: 'Show what a payment run from this bank account would pay', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'list': { method: 'GET', path: '/v1/payment-runs', summary: 'List all payment runs with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'create': { method: 'POST', path: '/v1/payment-runs', summary: 'Create a DRAFT payment run from payment IDs', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentIds'] },
|
|
15
|
-
'preview': { method: 'POST', path: '/v1/payment-runs/preview/{bankAccountId}', summary: 'Preview eligible and filtered invoices for a payment run', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['invoiceIds'] },
|
|
16
|
-
'schedule': { method: 'POST', path: '/v1/payment-runs/schedule/{bankAccountId}', summary: 'Auto-create DRAFT from eligible payments for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'status': { method: 'GET', path: '/v1/payment-runs/status/{providerId}/{submissionId}', summary: 'Fetch live payment status from provider (read-only)', pathParams: ['providerId', 'submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
-
'get': { method: 'GET', path: '/v1/payment-runs/{id}', summary: 'Get a payment run details by ID and show the currently persisted status.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'update': { method: 'PATCH', path: '/v1/payment-runs/{id}', summary: 'Update a DRAFT payment run (mode)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
-
'delete': { method: 'DELETE', path: '/v1/payment-runs/{id}', summary: 'Delete a DRAFT payment run (releases payments)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
21
|
-
'create-payments': { method: 'POST', path: '/v1/payment-runs/{id}/payments', summary: 'Add payments to a DRAFT payment run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentIds'] },
|
|
22
|
-
'delete-payments': { method: 'DELETE', path: '/v1/payment-runs/{id}/payments/{paymentId}', summary: 'Remove an item from a DRAFT payment run', pathParams: ['id', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
23
|
-
'send': { method: 'POST', path: '/v1/payment-runs/{id}/send', summary: 'Execute a DRAFT payment run (with dryRun support)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
24
|
-
'painfile': { method: 'GET', path: '/v1/payment-runs/{id}/painfile', summary: 'Download or preview PAIN XML for a payment run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: true, bodyArrayFields: [] },
|
|
25
|
-
'sync-status': { method: 'POST', path: '/v1/payment-runs/update/status/{submissionId}', summary: 'Sync payment status for a specific submission', pathParams: ['submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
26
|
-
'sync-status-xml': { method: 'POST', path: '/v1/payment-runs/update/status/{submissionId}/xml', summary: 'Sync payment status from pain.002 XML for a specific submission', pathParams: ['submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
27
|
-
'sync-all': { method: 'POST', path: '/v1/payment-runs/status', summary: 'Sync payment status for all pending submissions', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'open-submissions': { method: 'GET', path: '/v1/payment-runs/bank-accounts/{bankAccountId}/open-submissions', summary: 'Get open submission IDs for a bank account', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'eligible-payments': { method: 'GET', path: '/v1/payment-runs/eligible-payments/{bankAccountId}', summary: 'Show what a payment run from this bank account would pay', pathParams: ['bankAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'list': { method: 'GET', path: '/v1/payment-runs', summary: 'List all payment runs with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'create': { method: 'POST', path: '/v1/payment-runs', summary: 'Create a DRAFT payment run from payment IDs', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentIds'] },
|
|
15
|
+
'preview': { method: 'POST', path: '/v1/payment-runs/preview/{bankAccountId}', summary: 'Preview eligible and filtered invoices for a payment run', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['invoiceIds'] },
|
|
16
|
+
'schedule': { method: 'POST', path: '/v1/payment-runs/schedule/{bankAccountId}', summary: 'Auto-create DRAFT from eligible payments for a bank account', pathParams: ['bankAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'status': { method: 'GET', path: '/v1/payment-runs/status/{providerId}/{submissionId}', summary: 'Fetch live payment status from provider (read-only)', pathParams: ['providerId', 'submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
+
'get': { method: 'GET', path: '/v1/payment-runs/{id}', summary: 'Get a payment run details by ID and show the currently persisted status.', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'update': { method: 'PATCH', path: '/v1/payment-runs/{id}', summary: 'Update a DRAFT payment run (mode, execution date)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'delete': { method: 'DELETE', path: '/v1/payment-runs/{id}', summary: 'Delete a DRAFT payment run (releases payments)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
+
'create-payments': { method: 'POST', path: '/v1/payment-runs/{id}/payments', summary: 'Add payments to a DRAFT payment run', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentIds'] },
|
|
22
|
+
'delete-payments': { method: 'DELETE', path: '/v1/payment-runs/{id}/payments/{paymentId}', summary: 'Remove an item from a DRAFT payment run', pathParams: ['id', 'paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
+
'send': { method: 'POST', path: '/v1/payment-runs/{id}/send', summary: 'Execute a DRAFT payment run (with dryRun support)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
+
'painfile': { method: 'GET', path: '/v1/payment-runs/{id}/painfile', summary: 'Download or preview PAIN XML for a payment run', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
25
|
+
'sync-status': { method: 'POST', path: '/v1/payment-runs/update/status/{submissionId}', summary: 'Sync payment status for a specific submission', pathParams: ['submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
|
+
'sync-status-xml': { method: 'POST', path: '/v1/payment-runs/update/status/{submissionId}/xml', summary: 'Sync payment status from pain.002 XML for a specific submission', pathParams: ['submissionId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
27
|
+
'sync-all': { method: 'POST', path: '/v1/payment-runs/status', summary: 'Sync payment status for all pending submissions', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
28
28
|
};
|
|
29
29
|
export default class PaymentRuns extends Command {
|
|
30
30
|
static description = 'Manage payment runs';
|
|
@@ -278,10 +278,11 @@ export default class PaymentRuns extends Command {
|
|
|
278
278
|
process.stderr.write('\n');
|
|
279
279
|
}
|
|
280
280
|
try {
|
|
281
|
-
// Binary response:
|
|
281
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
282
282
|
if (endpoint.isBinaryResponse) {
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
const toStdout = !flags.file;
|
|
284
|
+
if (toStdout && process.stdout.isTTY) {
|
|
285
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
285
286
|
}
|
|
286
287
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
287
288
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -289,24 +290,55 @@ export default class PaymentRuns extends Command {
|
|
|
289
290
|
contextName: flags.context,
|
|
290
291
|
binary: true,
|
|
291
292
|
});
|
|
292
|
-
const fsModule = await import('node:fs');
|
|
293
293
|
const binData = binResponse.data;
|
|
294
294
|
const buffer = Buffer.isBuffer(binData)
|
|
295
295
|
? binData
|
|
296
296
|
: Buffer.from(binData);
|
|
297
|
+
if (toStdout) {
|
|
298
|
+
// Nothing else may reach stdout here — it is the file.
|
|
299
|
+
process.stdout.write(buffer);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const fsModule = await import('node:fs');
|
|
297
303
|
fsModule.writeFileSync(flags.file, buffer);
|
|
298
304
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
299
305
|
return;
|
|
300
306
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
let response;
|
|
308
|
+
if (endpoint.hasBinaryVariant) {
|
|
309
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
310
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
311
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
312
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
313
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
314
|
+
data,
|
|
315
|
+
contextName: flags.context,
|
|
316
|
+
binary: true,
|
|
317
|
+
});
|
|
318
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
319
|
+
const rawData = raw.data;
|
|
320
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
321
|
+
if (!isTextualMediaType(rawType)) {
|
|
322
|
+
if (process.stdout.isTTY) {
|
|
323
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
324
|
+
}
|
|
325
|
+
// Nothing else may reach stdout here — it is the file.
|
|
326
|
+
process.stdout.write(buffer);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
333
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
334
|
+
data,
|
|
335
|
+
contextName: flags.context,
|
|
336
|
+
acceptAny: true,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
307
339
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
308
340
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
309
|
-
if (contentType
|
|
341
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
310
342
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
311
343
|
this.log(String(response.data));
|
|
312
344
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,12 +8,12 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "default", "get", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/payment-terms', summary: 'Get all payment terms with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/payment-terms', summary: 'Create a new payment term', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts'] },
|
|
13
|
-
'default': { method: 'GET', path: '/v1/payment-terms/default', summary: 'Get the default payment term for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'get': { method: 'GET', path: '/v1/payment-terms/{id}', summary: 'Get a payment term by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'update': { method: 'PATCH', path: '/v1/payment-terms/{id}', summary: 'Update a payment term', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'delete': { method: 'DELETE', path: '/v1/payment-terms/{id}', summary: 'Delete a payment term', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/payment-terms', summary: 'Get all payment terms with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/payment-terms', summary: 'Create a new payment term', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['earlyPaymentDiscounts'] },
|
|
13
|
+
'default': { method: 'GET', path: '/v1/payment-terms/default', summary: 'Get the default payment term for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'get': { method: 'GET', path: '/v1/payment-terms/{id}', summary: 'Get a payment term by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'update': { method: 'PATCH', path: '/v1/payment-terms/{id}', summary: 'Update a payment term', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'delete': { method: 'DELETE', path: '/v1/payment-terms/{id}', summary: 'Delete a payment term', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
17
|
};
|
|
18
18
|
export default class PaymentTerms extends Command {
|
|
19
19
|
static description = 'Manage payment terms';
|
|
@@ -265,10 +265,11 @@ export default class PaymentTerms extends Command {
|
|
|
265
265
|
process.stderr.write('\n');
|
|
266
266
|
}
|
|
267
267
|
try {
|
|
268
|
-
// Binary response:
|
|
268
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
269
269
|
if (endpoint.isBinaryResponse) {
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
const toStdout = !flags.file;
|
|
271
|
+
if (toStdout && process.stdout.isTTY) {
|
|
272
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
272
273
|
}
|
|
273
274
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
274
275
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -276,24 +277,55 @@ export default class PaymentTerms extends Command {
|
|
|
276
277
|
contextName: flags.context,
|
|
277
278
|
binary: true,
|
|
278
279
|
});
|
|
279
|
-
const fsModule = await import('node:fs');
|
|
280
280
|
const binData = binResponse.data;
|
|
281
281
|
const buffer = Buffer.isBuffer(binData)
|
|
282
282
|
? binData
|
|
283
283
|
: Buffer.from(binData);
|
|
284
|
+
if (toStdout) {
|
|
285
|
+
// Nothing else may reach stdout here — it is the file.
|
|
286
|
+
process.stdout.write(buffer);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const fsModule = await import('node:fs');
|
|
284
290
|
fsModule.writeFileSync(flags.file, buffer);
|
|
285
291
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
286
292
|
return;
|
|
287
293
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
+
let response;
|
|
295
|
+
if (endpoint.hasBinaryVariant) {
|
|
296
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
297
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
298
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
299
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
300
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
301
|
+
data,
|
|
302
|
+
contextName: flags.context,
|
|
303
|
+
binary: true,
|
|
304
|
+
});
|
|
305
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
306
|
+
const rawData = raw.data;
|
|
307
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
308
|
+
if (!isTextualMediaType(rawType)) {
|
|
309
|
+
if (process.stdout.isTTY) {
|
|
310
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
311
|
+
}
|
|
312
|
+
// Nothing else may reach stdout here — it is the file.
|
|
313
|
+
process.stdout.write(buffer);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
320
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
321
|
+
data,
|
|
322
|
+
contextName: flags.context,
|
|
323
|
+
acceptAny: true,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
294
326
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
295
327
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
296
|
-
if (contentType
|
|
328
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
297
329
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
298
330
|
this.log(String(response.data));
|
|
299
331
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,13 +8,13 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "approve", "cancel"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/payments', summary: 'List payments', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/payments', summary: 'Create a standalone payment', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/payments/{paymentId}', summary: 'Get a payment by ID', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/payments/{paymentId}', summary: 'Update a payment', pathParams: ['paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/payments/{paymentId}', summary: 'Delete a payment', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'approve': { method: 'POST', path: '/v1/payments/{paymentId}/approve', summary: 'Approve a payment (OPEN → APPROVED)', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'cancel': { method: 'POST', path: '/v1/payments/{paymentId}/cancel', summary: 'Cancel a sent payment (SENT → ERROR)', pathParams: ['paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/payments', summary: 'List payments', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/payments', summary: 'Create a standalone payment', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/payments/{paymentId}', summary: 'Get a payment by ID', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/payments/{paymentId}', summary: 'Update a payment', pathParams: ['paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/payments/{paymentId}', summary: 'Delete a payment', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'approve': { method: 'POST', path: '/v1/payments/{paymentId}/approve', summary: 'Approve a payment (OPEN → APPROVED)', pathParams: ['paymentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'cancel': { method: 'POST', path: '/v1/payments/{paymentId}/cancel', summary: 'Cancel a sent payment (SENT → ERROR)', pathParams: ['paymentId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
18
|
};
|
|
19
19
|
export default class Payments extends Command {
|
|
20
20
|
static description = 'Manage payments';
|
|
@@ -266,10 +266,11 @@ export default class Payments extends Command {
|
|
|
266
266
|
process.stderr.write('\n');
|
|
267
267
|
}
|
|
268
268
|
try {
|
|
269
|
-
// Binary response:
|
|
269
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
270
270
|
if (endpoint.isBinaryResponse) {
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
const toStdout = !flags.file;
|
|
272
|
+
if (toStdout && process.stdout.isTTY) {
|
|
273
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
273
274
|
}
|
|
274
275
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
275
276
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -277,24 +278,55 @@ export default class Payments extends Command {
|
|
|
277
278
|
contextName: flags.context,
|
|
278
279
|
binary: true,
|
|
279
280
|
});
|
|
280
|
-
const fsModule = await import('node:fs');
|
|
281
281
|
const binData = binResponse.data;
|
|
282
282
|
const buffer = Buffer.isBuffer(binData)
|
|
283
283
|
? binData
|
|
284
284
|
: Buffer.from(binData);
|
|
285
|
+
if (toStdout) {
|
|
286
|
+
// Nothing else may reach stdout here — it is the file.
|
|
287
|
+
process.stdout.write(buffer);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const fsModule = await import('node:fs');
|
|
285
291
|
fsModule.writeFileSync(flags.file, buffer);
|
|
286
292
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
287
293
|
return;
|
|
288
294
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
let response;
|
|
296
|
+
if (endpoint.hasBinaryVariant) {
|
|
297
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
298
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
299
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
300
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
301
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
302
|
+
data,
|
|
303
|
+
contextName: flags.context,
|
|
304
|
+
binary: true,
|
|
305
|
+
});
|
|
306
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
307
|
+
const rawData = raw.data;
|
|
308
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
309
|
+
if (!isTextualMediaType(rawType)) {
|
|
310
|
+
if (process.stdout.isTTY) {
|
|
311
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
312
|
+
}
|
|
313
|
+
// Nothing else may reach stdout here — it is the file.
|
|
314
|
+
process.stdout.write(buffer);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
321
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
322
|
+
data,
|
|
323
|
+
contextName: flags.context,
|
|
324
|
+
acceptAny: true,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
295
327
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
296
328
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
297
|
-
if (contentType
|
|
329
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
298
330
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
299
331
|
this.log(String(response.data));
|
|
300
332
|
return;
|