@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,24 +1,23 @@
|
|
|
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;
|
|
6
6
|
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [...["list-tools", "list", "create", "resolve", "resolve-event", "get", "update", "delete", "
|
|
9
|
+
const SUBCOMMANDS = [...["list-tools", "list", "create", "resolve", "resolve-event", "get", "update", "delete", "update-config", "delete-config"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
14
|
-
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'delete-config': { method: 'DELETE', path: '/v1/processors/{id}/config', summary: 'Remove the tenant prompt overlay on a system processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list-tools': { method: 'GET', path: '/v1/processors/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'list': { method: 'GET', path: '/v1/processors', summary: 'List processors available to the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'create': { method: 'POST', path: '/v1/processors', summary: 'Create a new tenant-scoped processor', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
14
|
+
'resolve': { method: 'GET', path: '/v1/processors/resolve/{documentType}', summary: 'Resolve processor for a document type', pathParams: ['documentType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'resolve-event': { method: 'GET', path: '/v1/processors/resolve/event/{eventType}', summary: 'Resolve processors for an event type', pathParams: ['eventType'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'get': { method: 'GET', path: '/v1/processors/{id}', summary: 'Get a single processor by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'update': { method: 'PATCH', path: '/v1/processors/{id}', summary: 'Update a tenant-scoped processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['processorTools', 'options'] },
|
|
18
|
+
'delete': { method: 'DELETE', path: '/v1/processors/{id}', summary: 'Delete a tenant-scoped processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'update-config': { method: 'PUT', path: '/v1/processors/{id}/config', summary: 'Set or replace the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'delete-config': { method: 'DELETE', path: '/v1/processors/{id}/config', summary: 'Remove the tenant\'s overwrite on a system processor', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
21
|
};
|
|
23
22
|
export default class Processors extends Command {
|
|
24
23
|
static description = 'Manage processors';
|
|
@@ -49,7 +48,7 @@ export default class Processors extends Command {
|
|
|
49
48
|
'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
49
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
51
50
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
52
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
51
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
53
52
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
54
53
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
55
54
|
};
|
|
@@ -121,6 +120,8 @@ export default class Processors extends Command {
|
|
|
121
120
|
queryParams.search = flags.search;
|
|
122
121
|
if (flags['sort-by'])
|
|
123
122
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
123
|
+
if (flags.fields)
|
|
124
|
+
queryParams.fields = flags.fields;
|
|
124
125
|
if (flags.query) {
|
|
125
126
|
try {
|
|
126
127
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -268,10 +269,11 @@ export default class Processors extends Command {
|
|
|
268
269
|
process.stderr.write('\n');
|
|
269
270
|
}
|
|
270
271
|
try {
|
|
271
|
-
// Binary response:
|
|
272
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
272
273
|
if (endpoint.isBinaryResponse) {
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
const toStdout = !flags.file;
|
|
275
|
+
if (toStdout && process.stdout.isTTY) {
|
|
276
|
+
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
277
|
}
|
|
276
278
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
277
279
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -279,24 +281,55 @@ export default class Processors extends Command {
|
|
|
279
281
|
contextName: flags.context,
|
|
280
282
|
binary: true,
|
|
281
283
|
});
|
|
282
|
-
const fsModule = await import('node:fs');
|
|
283
284
|
const binData = binResponse.data;
|
|
284
285
|
const buffer = Buffer.isBuffer(binData)
|
|
285
286
|
? binData
|
|
286
287
|
: Buffer.from(binData);
|
|
288
|
+
if (toStdout) {
|
|
289
|
+
// Nothing else may reach stdout here — it is the file.
|
|
290
|
+
process.stdout.write(buffer);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const fsModule = await import('node:fs');
|
|
287
294
|
fsModule.writeFileSync(flags.file, buffer);
|
|
288
295
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
289
296
|
return;
|
|
290
297
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
let response;
|
|
299
|
+
if (endpoint.hasBinaryVariant) {
|
|
300
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
301
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
302
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
303
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
304
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
305
|
+
data,
|
|
306
|
+
contextName: flags.context,
|
|
307
|
+
binary: true,
|
|
308
|
+
});
|
|
309
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
310
|
+
const rawData = raw.data;
|
|
311
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
312
|
+
if (!isTextualMediaType(rawType)) {
|
|
313
|
+
if (process.stdout.isTTY) {
|
|
314
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
315
|
+
}
|
|
316
|
+
// Nothing else may reach stdout here — it is the file.
|
|
317
|
+
process.stdout.write(buffer);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
324
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
325
|
+
data,
|
|
326
|
+
contextName: flags.context,
|
|
327
|
+
acceptAny: true,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
297
330
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
298
331
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
299
|
-
if (contentType
|
|
332
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
300
333
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
301
334
|
this.log(String(response.data));
|
|
302
335
|
return;
|
|
@@ -323,11 +356,6 @@ export default class Processors extends Command {
|
|
|
323
356
|
output = obj[arrayKeys[0]];
|
|
324
357
|
}
|
|
325
358
|
}
|
|
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
359
|
// Sanitize output if requested
|
|
332
360
|
if (flags.sanitize) {
|
|
333
361
|
output = this.sanitizeOutput(output);
|
|
@@ -582,6 +610,8 @@ export default class Processors extends Command {
|
|
|
582
610
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
583
611
|
if (flags.include)
|
|
584
612
|
baseParams.include = flags.include;
|
|
613
|
+
if (flags.fields)
|
|
614
|
+
baseParams.fields = flags.fields;
|
|
585
615
|
if (flags.query) {
|
|
586
616
|
try {
|
|
587
617
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -636,12 +666,7 @@ export default class Processors extends Command {
|
|
|
636
666
|
}
|
|
637
667
|
}
|
|
638
668
|
process.stderr.write(' done\n');
|
|
639
|
-
// Filter fields if specified
|
|
640
669
|
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
670
|
// Sanitize output if requested
|
|
646
671
|
if (flags.sanitize) {
|
|
647
672
|
output = this.sanitizeOutput(output);
|
|
@@ -970,56 +995,6 @@ export default class Processors extends Command {
|
|
|
970
995
|
return `\$${op}`;
|
|
971
996
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
972
997
|
}
|
|
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
998
|
sanitizeOutput(data) {
|
|
1024
999
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1025
1000
|
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,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/purchase-order-types', summary: 'Get all purchase order types with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/purchase-order-types', summary: 'Create a new purchase order type', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/purchase-order-types/{id}', summary: 'Get a purchase order type by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/purchase-order-types/{id}', summary: 'Update a purchase order type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/purchase-order-types/{id}', summary: 'Delete a purchase order type', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/purchase-order-types', summary: 'Get all purchase order types with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/purchase-order-types', summary: 'Create a new purchase order type', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/purchase-order-types/{id}', summary: 'Get a purchase order type by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/purchase-order-types/{id}', summary: 'Update a purchase order type', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/purchase-order-types/{id}', summary: 'Delete a purchase order type', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class PurchaseOrderTypes extends Command {
|
|
18
18
|
static description = 'Manage purchase order types';
|
|
@@ -43,7 +43,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
43
43
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
44
44
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
45
45
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
46
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
46
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
47
47
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
48
48
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
49
49
|
};
|
|
@@ -115,6 +115,8 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
115
115
|
queryParams.search = flags.search;
|
|
116
116
|
if (flags['sort-by'])
|
|
117
117
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
118
|
+
if (flags.fields)
|
|
119
|
+
queryParams.fields = flags.fields;
|
|
118
120
|
if (flags.query) {
|
|
119
121
|
try {
|
|
120
122
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -262,10 +264,11 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
262
264
|
process.stderr.write('\n');
|
|
263
265
|
}
|
|
264
266
|
try {
|
|
265
|
-
// Binary response:
|
|
267
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
266
268
|
if (endpoint.isBinaryResponse) {
|
|
267
|
-
|
|
268
|
-
|
|
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.`);
|
|
269
272
|
}
|
|
270
273
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
271
274
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -273,24 +276,55 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
273
276
|
contextName: flags.context,
|
|
274
277
|
binary: true,
|
|
275
278
|
});
|
|
276
|
-
const fsModule = await import('node:fs');
|
|
277
279
|
const binData = binResponse.data;
|
|
278
280
|
const buffer = Buffer.isBuffer(binData)
|
|
279
281
|
? binData
|
|
280
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');
|
|
281
289
|
fsModule.writeFileSync(flags.file, buffer);
|
|
282
290
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
283
291
|
return;
|
|
284
292
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
}
|
|
291
325
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
292
326
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
293
|
-
if (contentType
|
|
327
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
294
328
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
295
329
|
this.log(String(response.data));
|
|
296
330
|
return;
|
|
@@ -317,11 +351,6 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
317
351
|
output = obj[arrayKeys[0]];
|
|
318
352
|
}
|
|
319
353
|
}
|
|
320
|
-
// Filter fields if --fields flag is provided
|
|
321
|
-
if (flags.fields) {
|
|
322
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
323
|
-
output = this.filterFields(output, fieldList);
|
|
324
|
-
}
|
|
325
354
|
// Sanitize output if requested
|
|
326
355
|
if (flags.sanitize) {
|
|
327
356
|
output = this.sanitizeOutput(output);
|
|
@@ -576,6 +605,8 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
576
605
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
577
606
|
if (flags.include)
|
|
578
607
|
baseParams.include = flags.include;
|
|
608
|
+
if (flags.fields)
|
|
609
|
+
baseParams.fields = flags.fields;
|
|
579
610
|
if (flags.query) {
|
|
580
611
|
try {
|
|
581
612
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -630,12 +661,7 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
630
661
|
}
|
|
631
662
|
}
|
|
632
663
|
process.stderr.write(' done\n');
|
|
633
|
-
// Filter fields if specified
|
|
634
664
|
let output = allItems;
|
|
635
|
-
if (flags.fields) {
|
|
636
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
637
|
-
output = this.filterFields(allItems, fieldList);
|
|
638
|
-
}
|
|
639
665
|
// Sanitize output if requested
|
|
640
666
|
if (flags.sanitize) {
|
|
641
667
|
output = this.sanitizeOutput(output);
|
|
@@ -964,56 +990,6 @@ export default class PurchaseOrderTypes extends Command {
|
|
|
964
990
|
return `\$${op}`;
|
|
965
991
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
966
992
|
}
|
|
967
|
-
filterFields(data, fields) {
|
|
968
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
969
|
-
const pick = (obj, keys) => {
|
|
970
|
-
// Group keys by their first segment so sibling sub-paths
|
|
971
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
972
|
-
// and don't clobber each other on merge.
|
|
973
|
-
const directKeys = new Set();
|
|
974
|
-
const nestedKeys = new Map();
|
|
975
|
-
for (const key of keys) {
|
|
976
|
-
if (key.split('.').some(isUnsafeKey))
|
|
977
|
-
continue;
|
|
978
|
-
if (key.includes('.')) {
|
|
979
|
-
const [first, ...rest] = key.split('.');
|
|
980
|
-
if (isUnsafeKey(first))
|
|
981
|
-
continue;
|
|
982
|
-
const list = nestedKeys.get(first);
|
|
983
|
-
if (list) {
|
|
984
|
-
list.push(rest.join('.'));
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
directKeys.add(key);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
const result = {};
|
|
995
|
-
for (const key of directKeys) {
|
|
996
|
-
if (key in obj)
|
|
997
|
-
result[key] = obj[key];
|
|
998
|
-
}
|
|
999
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1000
|
-
const val = obj[first];
|
|
1001
|
-
if (!val || typeof val !== 'object')
|
|
1002
|
-
continue;
|
|
1003
|
-
if (Array.isArray(val)) {
|
|
1004
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
result[first] = pick(val, restKeys);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
return result;
|
|
1011
|
-
};
|
|
1012
|
-
if (Array.isArray(data)) {
|
|
1013
|
-
return data.map(item => pick(item, fields));
|
|
1014
|
-
}
|
|
1015
|
-
return pick(data, fields);
|
|
1016
|
-
}
|
|
1017
993
|
sanitizeOutput(data) {
|
|
1018
994
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1019
995
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|