@aderis-erp/cli 1.0.15 → 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/README.md +30 -8
- package/dist/commands/access-groups.d.ts +0 -1
- package/dist/commands/access-groups.js +54 -78
- package/dist/commands/account-transaction-allocations.d.ts +0 -1
- package/dist/commands/account-transaction-allocations.js +51 -75
- package/dist/commands/account-transactions.d.ts +0 -1
- package/dist/commands/account-transactions.js +80 -94
- package/dist/commands/accounts.d.ts +0 -1
- package/dist/commands/accounts.js +59 -83
- package/dist/commands/agent-platform.d.ts +0 -1
- package/dist/commands/agent-platform.js +58 -82
- package/dist/commands/agent-runs.d.ts +39 -0
- package/dist/commands/agent-runs.js +1155 -0
- package/dist/commands/analytics.d.ts +0 -1
- package/dist/commands/analytics.js +52 -76
- package/dist/commands/{text-templates.d.ts → api-keys.d.ts} +1 -2
- package/dist/commands/api-keys.js +1150 -0
- package/dist/commands/async-jobs.d.ts +39 -0
- package/dist/commands/async-jobs.js +1158 -0
- package/dist/commands/audit-log.d.ts +0 -1
- package/dist/commands/audit-log.js +51 -75
- package/dist/commands/auth/login.d.ts +9 -0
- package/dist/commands/auth/login.js +73 -20
- package/dist/commands/auth/logout.d.ts +4 -0
- package/dist/commands/auth/logout.js +4 -0
- package/dist/commands/auth/status.js +26 -17
- package/dist/commands/auth/tenant.js +3 -6
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +5 -34
- package/dist/commands/bank-accounts.d.ts +0 -1
- package/dist/commands/bank-accounts.js +68 -91
- package/dist/commands/bank-integrations.d.ts +1 -1
- package/dist/commands/bank-integrations.js +71 -92
- package/dist/commands/business-partners.d.ts +1 -1
- package/dist/commands/business-partners.js +84 -100
- package/dist/commands/chat.d.ts +0 -1
- package/dist/commands/chat.js +57 -80
- package/dist/commands/config/add.d.ts +3 -0
- package/dist/commands/config/add.js +34 -16
- package/dist/commands/config/current.js +3 -0
- package/dist/commands/config/list.js +3 -0
- package/dist/commands/credit-card-accounts.d.ts +1 -1
- package/dist/commands/credit-card-accounts.js +72 -90
- package/dist/commands/delivery-notes.d.ts +0 -1
- package/dist/commands/delivery-notes.js +83 -105
- package/dist/commands/dunning-configs.d.ts +0 -1
- package/dist/commands/dunning-configs.js +54 -78
- package/dist/commands/dunning-reminders.d.ts +0 -1
- package/dist/commands/dunning-reminders.js +57 -81
- package/dist/commands/dunning-runs.d.ts +0 -1
- package/dist/commands/dunning-runs.js +50 -74
- package/dist/commands/email-transactions.d.ts +0 -1
- package/dist/commands/email-transactions.js +52 -76
- package/dist/commands/employees.d.ts +0 -1
- package/dist/commands/employees.js +81 -105
- package/dist/commands/entity-configurations.d.ts +0 -1
- package/dist/commands/entity-configurations.js +52 -76
- package/dist/commands/event-processor-mappings.d.ts +0 -1
- package/dist/commands/event-processor-mappings.js +54 -78
- package/dist/commands/files.d.ts +0 -1
- package/dist/commands/files.js +61 -85
- package/dist/commands/health.d.ts +0 -1
- package/dist/commands/health.js +54 -78
- package/dist/commands/imports.d.ts +39 -0
- package/dist/commands/imports.js +1151 -0
- package/dist/commands/inbox.d.ts +0 -1
- package/dist/commands/inbox.js +57 -80
- package/dist/commands/integration-identities.d.ts +40 -0
- package/dist/commands/integration-identities.js +1151 -0
- package/dist/commands/integrations.d.ts +0 -1
- package/dist/commands/integrations.js +52 -76
- package/dist/commands/item-categories.d.ts +0 -1
- package/dist/commands/item-categories.js +54 -78
- package/dist/commands/items.d.ts +0 -1
- package/dist/commands/items.js +54 -78
- package/dist/commands/journal-lines.d.ts +0 -1
- package/dist/commands/journal-lines.js +50 -74
- package/dist/commands/journals.d.ts +1 -1
- package/dist/commands/journals.js +75 -96
- package/dist/commands/ledgers.d.ts +3 -2
- package/dist/commands/ledgers.js +100 -95
- package/dist/commands/my-account.d.ts +39 -0
- package/dist/commands/my-account.js +1160 -0
- package/dist/commands/office-locations.d.ts +0 -1
- package/dist/commands/office-locations.js +54 -78
- package/dist/commands/payment-methods.d.ts +0 -1
- package/dist/commands/payment-methods.js +59 -83
- package/dist/commands/payment-runs.d.ts +0 -1
- package/dist/commands/payment-runs.js +66 -90
- package/dist/commands/payment-terms.d.ts +0 -1
- package/dist/commands/payment-terms.js +55 -79
- package/dist/commands/payments.d.ts +0 -1
- package/dist/commands/payments.js +56 -80
- 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/permissions.js +1146 -0
- package/dist/commands/price-lists.d.ts +0 -1
- package/dist/commands/price-lists.js +59 -83
- package/dist/commands/processor-mappings.d.ts +0 -1
- package/dist/commands/processor-mappings.js +53 -77
- package/dist/commands/processors.d.ts +0 -1
- package/dist/commands/processors.js +60 -85
- package/dist/commands/purchase-order-types.d.ts +0 -1
- package/dist/commands/purchase-order-types.js +54 -78
- package/dist/commands/purchase-orders.d.ts +0 -1
- package/dist/commands/purchase-orders.js +74 -97
- package/dist/commands/reports.d.ts +0 -1
- package/dist/commands/reports.js +54 -78
- package/dist/commands/roles.d.ts +39 -0
- package/dist/commands/{text-templates.js → roles.js} +64 -90
- package/dist/commands/salary-types.d.ts +0 -1
- package/dist/commands/salary-types.js +54 -78
- package/dist/commands/sales-invoices.d.ts +0 -1
- package/dist/commands/sales-invoices.js +90 -110
- package/dist/commands/sales-orders.d.ts +0 -1
- package/dist/commands/sales-orders.js +83 -105
- package/dist/commands/saved-views.d.ts +0 -1
- package/dist/commands/saved-views.js +52 -76
- package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
- package/dist/commands/scheduled-processor-mappings.js +54 -78
- package/dist/commands/schedules.d.ts +0 -1
- package/dist/commands/schedules.js +58 -82
- package/dist/commands/switch.js +4 -4
- package/dist/commands/tag-configurations.d.ts +0 -1
- package/dist/commands/tag-configurations.js +56 -79
- package/dist/commands/tags.d.ts +0 -1
- package/dist/commands/tags.js +54 -78
- package/dist/commands/tax-codes.d.ts +0 -1
- package/dist/commands/tax-codes.js +57 -81
- package/dist/commands/tax-matrix.d.ts +0 -1
- package/dist/commands/tax-matrix.js +54 -78
- package/dist/commands/templates.d.ts +39 -0
- package/dist/commands/templates.js +1157 -0
- package/dist/commands/tenants.d.ts +0 -1
- package/dist/commands/tenants.js +58 -81
- package/dist/commands/tools.d.ts +0 -1
- package/dist/commands/tools.js +51 -75
- package/dist/commands/users.d.ts +42 -0
- package/dist/commands/users.js +1174 -0
- package/dist/commands/vat-declarations.d.ts +3 -1
- package/dist/commands/vat-declarations.js +78 -93
- package/dist/commands/vendor-invoices.d.ts +0 -1
- package/dist/commands/vendor-invoices.js +87 -110
- package/dist/commands/webhooks.d.ts +0 -1
- package/dist/commands/webhooks.js +56 -80
- package/dist/commands/workflows.d.ts +0 -1
- package/dist/commands/workflows.js +52 -76
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +16 -6
- package/dist/lib/auth/constants.d.ts +11 -0
- package/dist/lib/auth/constants.js +11 -0
- package/dist/lib/auth/errors.d.ts +17 -0
- package/dist/lib/auth/errors.js +22 -0
- package/dist/lib/auth/flow.d.ts +27 -0
- package/dist/lib/auth/flow.js +163 -0
- package/dist/lib/auth/guards.d.ts +5 -0
- package/dist/lib/auth/guards.js +14 -0
- package/dist/lib/auth/http.d.ts +16 -0
- package/dist/lib/auth/http.js +49 -0
- package/dist/lib/auth/index.d.ts +49 -0
- package/dist/lib/auth/index.js +228 -0
- 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 +91 -0
- package/dist/lib/auth/keycloak-provider.js +244 -0
- package/dist/lib/auth/pages.d.ts +25 -0
- package/dist/lib/auth/pages.js +123 -0
- package/dist/lib/auth/pkce.d.ts +11 -0
- package/dist/lib/auth/pkce.js +17 -0
- package/dist/lib/auth/tokens.d.ts +59 -0
- package/dist/lib/auth/tokens.js +105 -0
- package/dist/lib/auth/types.d.ts +61 -0
- package/dist/lib/auth/types.js +1 -0
- package/dist/lib/auth/validated-url.d.ts +6 -0
- package/dist/lib/auth/validated-url.js +53 -0
- package/dist/lib/config.d.ts +13 -3
- package/dist/lib/config.js +24 -19
- package/oclif.manifest.json +2447 -641
- package/package.json +5 -4
- package/dist/lib/auth.d.ts +0 -43
- package/dist/lib/auth.js +0 -610
|
@@ -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 = ["prices"];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-prices", "create-prices", "get-prices", "update-prices", "delete-prices"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/price-lists', summary: 'Get all price lists with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/price-lists', summary: 'Create a new price list', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/price-lists/{id}', summary: 'Get a price list by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/price-lists/{id}', summary: 'Update a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/price-lists/{id}', summary: 'Delete a price list', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'list-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices', summary: 'Get all prices in a price list with pagination', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'create-prices': { method: 'POST', path: '/v1/price-lists/{id}/prices', summary: 'Create a new price in a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
-
'get-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Get a price by ID', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'update-prices': { method: 'PATCH', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Update a price', pathParams: ['id', 'priceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
-
'delete-prices': { method: 'DELETE', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Delete a price', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/price-lists', summary: 'Get all price lists with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/price-lists', summary: 'Create a new price list', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/price-lists/{id}', summary: 'Get a price list by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/price-lists/{id}', summary: 'Update a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/price-lists/{id}', summary: 'Delete a price list', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'list-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices', summary: 'Get all prices in a price list with pagination', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'create-prices': { method: 'POST', path: '/v1/price-lists/{id}/prices', summary: 'Create a new price in a price list', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
+
'get-prices': { method: 'GET', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Get a price by ID', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'update-prices': { method: 'PATCH', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Update a price', pathParams: ['id', 'priceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'delete-prices': { method: 'DELETE', path: '/v1/price-lists/{id}/prices/{priceId}', summary: 'Delete a price', pathParams: ['id', 'priceId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
21
|
};
|
|
22
22
|
export default class PriceLists extends Command {
|
|
23
23
|
static description = 'Manage price lists';
|
|
@@ -49,7 +49,7 @@ export default class PriceLists extends Command {
|
|
|
49
49
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
50
50
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
51
51
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
52
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
52
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
53
53
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
54
54
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
55
55
|
};
|
|
@@ -121,6 +121,8 @@ export default class PriceLists extends Command {
|
|
|
121
121
|
queryParams.search = flags.search;
|
|
122
122
|
if (flags['sort-by'])
|
|
123
123
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
124
|
+
if (flags.fields)
|
|
125
|
+
queryParams.fields = flags.fields;
|
|
124
126
|
if (flags.query) {
|
|
125
127
|
try {
|
|
126
128
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -268,10 +270,11 @@ export default class PriceLists extends Command {
|
|
|
268
270
|
process.stderr.write('\n');
|
|
269
271
|
}
|
|
270
272
|
try {
|
|
271
|
-
// Binary response:
|
|
273
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
272
274
|
if (endpoint.isBinaryResponse) {
|
|
273
|
-
|
|
274
|
-
|
|
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.`);
|
|
275
278
|
}
|
|
276
279
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
277
280
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -279,24 +282,55 @@ export default class PriceLists extends Command {
|
|
|
279
282
|
contextName: flags.context,
|
|
280
283
|
binary: true,
|
|
281
284
|
});
|
|
282
|
-
const fsModule = await import('node:fs');
|
|
283
285
|
const binData = binResponse.data;
|
|
284
286
|
const buffer = Buffer.isBuffer(binData)
|
|
285
287
|
? binData
|
|
286
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');
|
|
287
295
|
fsModule.writeFileSync(flags.file, buffer);
|
|
288
296
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
289
297
|
return;
|
|
290
298
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
}
|
|
297
331
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
298
332
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
299
|
-
if (contentType
|
|
333
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
300
334
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
301
335
|
this.log(String(response.data));
|
|
302
336
|
return;
|
|
@@ -323,11 +357,6 @@ export default class PriceLists extends Command {
|
|
|
323
357
|
output = obj[arrayKeys[0]];
|
|
324
358
|
}
|
|
325
359
|
}
|
|
326
|
-
// Filter fields if --fields flag is provided
|
|
327
|
-
if (flags.fields) {
|
|
328
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
329
|
-
output = this.filterFields(output, fieldList);
|
|
330
|
-
}
|
|
331
360
|
// Sanitize output if requested
|
|
332
361
|
if (flags.sanitize) {
|
|
333
362
|
output = this.sanitizeOutput(output);
|
|
@@ -582,6 +611,8 @@ export default class PriceLists extends Command {
|
|
|
582
611
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
583
612
|
if (flags.include)
|
|
584
613
|
baseParams.include = flags.include;
|
|
614
|
+
if (flags.fields)
|
|
615
|
+
baseParams.fields = flags.fields;
|
|
585
616
|
if (flags.query) {
|
|
586
617
|
try {
|
|
587
618
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -636,12 +667,7 @@ export default class PriceLists extends Command {
|
|
|
636
667
|
}
|
|
637
668
|
}
|
|
638
669
|
process.stderr.write(' done\n');
|
|
639
|
-
// Filter fields if specified
|
|
640
670
|
let output = allItems;
|
|
641
|
-
if (flags.fields) {
|
|
642
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
643
|
-
output = this.filterFields(allItems, fieldList);
|
|
644
|
-
}
|
|
645
671
|
// Sanitize output if requested
|
|
646
672
|
if (flags.sanitize) {
|
|
647
673
|
output = this.sanitizeOutput(output);
|
|
@@ -970,56 +996,6 @@ export default class PriceLists extends Command {
|
|
|
970
996
|
return `\$${op}`;
|
|
971
997
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
972
998
|
}
|
|
973
|
-
filterFields(data, fields) {
|
|
974
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
975
|
-
const pick = (obj, keys) => {
|
|
976
|
-
// Group keys by their first segment so sibling sub-paths
|
|
977
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
978
|
-
// and don't clobber each other on merge.
|
|
979
|
-
const directKeys = new Set();
|
|
980
|
-
const nestedKeys = new Map();
|
|
981
|
-
for (const key of keys) {
|
|
982
|
-
if (key.split('.').some(isUnsafeKey))
|
|
983
|
-
continue;
|
|
984
|
-
if (key.includes('.')) {
|
|
985
|
-
const [first, ...rest] = key.split('.');
|
|
986
|
-
if (isUnsafeKey(first))
|
|
987
|
-
continue;
|
|
988
|
-
const list = nestedKeys.get(first);
|
|
989
|
-
if (list) {
|
|
990
|
-
list.push(rest.join('.'));
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
else {
|
|
997
|
-
directKeys.add(key);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const result = {};
|
|
1001
|
-
for (const key of directKeys) {
|
|
1002
|
-
if (key in obj)
|
|
1003
|
-
result[key] = obj[key];
|
|
1004
|
-
}
|
|
1005
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1006
|
-
const val = obj[first];
|
|
1007
|
-
if (!val || typeof val !== 'object')
|
|
1008
|
-
continue;
|
|
1009
|
-
if (Array.isArray(val)) {
|
|
1010
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1011
|
-
}
|
|
1012
|
-
else {
|
|
1013
|
-
result[first] = pick(val, restKeys);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
return result;
|
|
1017
|
-
};
|
|
1018
|
-
if (Array.isArray(data)) {
|
|
1019
|
-
return data.map(item => pick(item, fields));
|
|
1020
|
-
}
|
|
1021
|
-
return pick(data, fields);
|
|
1022
|
-
}
|
|
1023
999
|
sanitizeOutput(data) {
|
|
1024
1000
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1025
1001
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -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,10 +8,10 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['documentTypes'] },
|
|
13
|
-
'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['documentTypes'] },
|
|
14
|
-
'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/processor-mappings', summary: 'List all document type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/processor-mappings', summary: 'Create a new document type processor mapping', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
|
|
13
|
+
'update': { method: 'PATCH', path: '/v1/processor-mappings/{id}', summary: 'Update a document type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['documentTypes'] },
|
|
14
|
+
'delete': { method: 'DELETE', path: '/v1/processor-mappings/{id}', summary: 'Delete a document type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
15
|
};
|
|
16
16
|
export default class ProcessorMappings extends Command {
|
|
17
17
|
static description = 'Manage processor mappings';
|
|
@@ -41,7 +41,7 @@ export default class ProcessorMappings extends Command {
|
|
|
41
41
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
42
42
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
43
43
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
44
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
44
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
45
45
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
46
46
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
47
47
|
};
|
|
@@ -113,6 +113,8 @@ export default class ProcessorMappings extends Command {
|
|
|
113
113
|
queryParams.search = flags.search;
|
|
114
114
|
if (flags['sort-by'])
|
|
115
115
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
116
|
+
if (flags.fields)
|
|
117
|
+
queryParams.fields = flags.fields;
|
|
116
118
|
if (flags.query) {
|
|
117
119
|
try {
|
|
118
120
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -260,10 +262,11 @@ export default class ProcessorMappings extends Command {
|
|
|
260
262
|
process.stderr.write('\n');
|
|
261
263
|
}
|
|
262
264
|
try {
|
|
263
|
-
// Binary response:
|
|
265
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
264
266
|
if (endpoint.isBinaryResponse) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
const toStdout = !flags.file;
|
|
268
|
+
if (toStdout && process.stdout.isTTY) {
|
|
269
|
+
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.`);
|
|
267
270
|
}
|
|
268
271
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
269
272
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -271,24 +274,55 @@ export default class ProcessorMappings extends Command {
|
|
|
271
274
|
contextName: flags.context,
|
|
272
275
|
binary: true,
|
|
273
276
|
});
|
|
274
|
-
const fsModule = await import('node:fs');
|
|
275
277
|
const binData = binResponse.data;
|
|
276
278
|
const buffer = Buffer.isBuffer(binData)
|
|
277
279
|
? binData
|
|
278
280
|
: Buffer.from(binData);
|
|
281
|
+
if (toStdout) {
|
|
282
|
+
// Nothing else may reach stdout here — it is the file.
|
|
283
|
+
process.stdout.write(buffer);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const fsModule = await import('node:fs');
|
|
279
287
|
fsModule.writeFileSync(flags.file, buffer);
|
|
280
288
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
281
289
|
return;
|
|
282
290
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
let response;
|
|
292
|
+
if (endpoint.hasBinaryVariant) {
|
|
293
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
294
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
295
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
296
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
297
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
298
|
+
data,
|
|
299
|
+
contextName: flags.context,
|
|
300
|
+
binary: true,
|
|
301
|
+
});
|
|
302
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
303
|
+
const rawData = raw.data;
|
|
304
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
305
|
+
if (!isTextualMediaType(rawType)) {
|
|
306
|
+
if (process.stdout.isTTY) {
|
|
307
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
308
|
+
}
|
|
309
|
+
// Nothing else may reach stdout here — it is the file.
|
|
310
|
+
process.stdout.write(buffer);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
317
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
318
|
+
data,
|
|
319
|
+
contextName: flags.context,
|
|
320
|
+
acceptAny: true,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
289
323
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
290
324
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
291
|
-
if (contentType
|
|
325
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
292
326
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
293
327
|
this.log(String(response.data));
|
|
294
328
|
return;
|
|
@@ -315,11 +349,6 @@ export default class ProcessorMappings extends Command {
|
|
|
315
349
|
output = obj[arrayKeys[0]];
|
|
316
350
|
}
|
|
317
351
|
}
|
|
318
|
-
// Filter fields if --fields flag is provided
|
|
319
|
-
if (flags.fields) {
|
|
320
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
321
|
-
output = this.filterFields(output, fieldList);
|
|
322
|
-
}
|
|
323
352
|
// Sanitize output if requested
|
|
324
353
|
if (flags.sanitize) {
|
|
325
354
|
output = this.sanitizeOutput(output);
|
|
@@ -574,6 +603,8 @@ export default class ProcessorMappings extends Command {
|
|
|
574
603
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
575
604
|
if (flags.include)
|
|
576
605
|
baseParams.include = flags.include;
|
|
606
|
+
if (flags.fields)
|
|
607
|
+
baseParams.fields = flags.fields;
|
|
577
608
|
if (flags.query) {
|
|
578
609
|
try {
|
|
579
610
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -628,12 +659,7 @@ export default class ProcessorMappings extends Command {
|
|
|
628
659
|
}
|
|
629
660
|
}
|
|
630
661
|
process.stderr.write(' done\n');
|
|
631
|
-
// Filter fields if specified
|
|
632
662
|
let output = allItems;
|
|
633
|
-
if (flags.fields) {
|
|
634
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
635
|
-
output = this.filterFields(allItems, fieldList);
|
|
636
|
-
}
|
|
637
663
|
// Sanitize output if requested
|
|
638
664
|
if (flags.sanitize) {
|
|
639
665
|
output = this.sanitizeOutput(output);
|
|
@@ -962,56 +988,6 @@ export default class ProcessorMappings extends Command {
|
|
|
962
988
|
return `\$${op}`;
|
|
963
989
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
964
990
|
}
|
|
965
|
-
filterFields(data, fields) {
|
|
966
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
967
|
-
const pick = (obj, keys) => {
|
|
968
|
-
// Group keys by their first segment so sibling sub-paths
|
|
969
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
970
|
-
// and don't clobber each other on merge.
|
|
971
|
-
const directKeys = new Set();
|
|
972
|
-
const nestedKeys = new Map();
|
|
973
|
-
for (const key of keys) {
|
|
974
|
-
if (key.split('.').some(isUnsafeKey))
|
|
975
|
-
continue;
|
|
976
|
-
if (key.includes('.')) {
|
|
977
|
-
const [first, ...rest] = key.split('.');
|
|
978
|
-
if (isUnsafeKey(first))
|
|
979
|
-
continue;
|
|
980
|
-
const list = nestedKeys.get(first);
|
|
981
|
-
if (list) {
|
|
982
|
-
list.push(rest.join('.'));
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
985
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
directKeys.add(key);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
const result = {};
|
|
993
|
-
for (const key of directKeys) {
|
|
994
|
-
if (key in obj)
|
|
995
|
-
result[key] = obj[key];
|
|
996
|
-
}
|
|
997
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
998
|
-
const val = obj[first];
|
|
999
|
-
if (!val || typeof val !== 'object')
|
|
1000
|
-
continue;
|
|
1001
|
-
if (Array.isArray(val)) {
|
|
1002
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
result[first] = pick(val, restKeys);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
return result;
|
|
1009
|
-
};
|
|
1010
|
-
if (Array.isArray(data)) {
|
|
1011
|
-
return data.map(item => pick(item, fields));
|
|
1012
|
-
}
|
|
1013
|
-
return pick(data, fields);
|
|
1014
|
-
}
|
|
1015
991
|
sanitizeOutput(data) {
|
|
1016
992
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1017
993
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|