@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,44 +1,46 @@
|
|
|
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 = ["line-items", "notes", "files"];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "create-from-orders", "create-from-invoice", "create-invoice", "get", "update", "delete", "pdf", "submit", "approve", "cancel", "replace", "deliver", "ship", "send", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "create-from-orders", "create-from-invoice", "create-invoice", "get", "update", "delete", "get-pdf", "create-pdf", "submit", "approve", "cancel", "replace", "copy", "deliver", "ship", "send", "create-line-items", "update-line-items-reorder", "update-line-items", "delete-line-items", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/delivery-notes', summary: 'Get all delivery notes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'lineItems'] },
|
|
13
|
-
'create-from-orders': { method: 'POST', path: '/v1/delivery-notes/create-from-orders', summary: 'Create a DRAFT delivery note from several sales orders', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderIds'] },
|
|
14
|
-
'create-from-invoice': { method: 'POST', path: '/v1/delivery-notes/create-from-invoice', summary: 'Create a DRAFT delivery note from a sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'create-invoice': { method: 'POST', path: '/v1/delivery-notes/create-invoice', summary: 'Create ONE draft sales invoice from several delivery notes (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['deliveryNoteIds'] },
|
|
16
|
-
'get': { method: 'GET', path: '/v1/delivery-notes/{id}', summary: 'Get a delivery note by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'removeFileIds'] },
|
|
18
|
-
'delete': { method: 'DELETE', path: '/v1/delivery-notes/{id}', summary: 'Delete a delivery note', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'pdf': { method: 'GET', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'files
|
|
11
|
+
'list': { method: 'GET', path: '/v1/delivery-notes', summary: 'Get all delivery notes with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/delivery-notes', summary: 'Create a new delivery note', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'lineItems'] },
|
|
13
|
+
'create-from-orders': { method: 'POST', path: '/v1/delivery-notes/create-from-orders', summary: 'Create a DRAFT delivery note from several sales orders', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderIds'] },
|
|
14
|
+
'create-from-invoice': { method: 'POST', path: '/v1/delivery-notes/create-from-invoice', summary: 'Create a DRAFT delivery note from a sales invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'create-invoice': { method: 'POST', path: '/v1/delivery-notes/create-invoice', summary: 'Create ONE draft sales invoice from several delivery notes (same customer)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['deliveryNoteIds'] },
|
|
16
|
+
'get': { method: 'GET', path: '/v1/delivery-notes/{id}', summary: 'Get a delivery note by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'update': { method: 'PATCH', path: '/v1/delivery-notes/{id}', summary: 'Update a delivery note', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['files', 'notes', 'tagIds', 'tagCodes', 'removeFileIds'] },
|
|
18
|
+
'delete': { method: 'DELETE', path: '/v1/delivery-notes/{id}', summary: 'Delete a delivery note', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'get-pdf': { method: 'GET', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF as the mapped event would, or preview a stored template', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'create-pdf': { method: 'POST', path: '/v1/delivery-notes/{id}/pdf', summary: 'Render a delivery note PDF from unsaved HTML posted in the body', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
+
'submit': { method: 'POST', path: '/v1/delivery-notes/{id}/submit', summary: 'Submit a delivery note for approval (DRAFT → PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
+
'approve': { method: 'POST', path: '/v1/delivery-notes/{id}/approve', summary: 'Approve a delivery note (DRAFT or PENDING_APPROVAL → APPROVED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
+
'cancel': { method: 'POST', path: '/v1/delivery-notes/{id}/cancel', summary: 'Cancel a delivery note (DRAFT / PENDING_APPROVAL / APPROVED / SHIPPED / DELIVERED → CANCELLED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
+
'replace': { method: 'POST', path: '/v1/delivery-notes/{id}/replace', summary: 'Replace a delivery note (APPROVED / SHIPPED / DELIVERED / CANCELLED → REPLACED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
25
|
+
'copy': { method: 'POST', path: '/v1/delivery-notes/{id}/copy', summary: 'Create a copy of a delivery note as a new draft', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
|
+
'deliver': { method: 'POST', path: '/v1/delivery-notes/{id}/deliver', summary: 'Mark a delivery note as delivered (SHIPPED → DELIVERED)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
27
|
+
'ship': { method: 'POST', path: '/v1/delivery-notes/{id}/ship', summary: 'Ship a delivery note (APPROVED → SHIPPED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['lineItems'] },
|
|
28
|
+
'send': { method: 'POST', path: '/v1/delivery-notes/{id}/send', summary: 'Send a delivery note to its recipient by email', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['emailCc', 'emailBcc'] },
|
|
29
|
+
'create-line-items': { method: 'POST', path: '/v1/delivery-notes/{noteId}/line-items', summary: 'Add a line item to a delivery note', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'] },
|
|
30
|
+
'update-line-items-reorder': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/reorder', summary: 'Bulk reorder line items (drag-and-drop)', pathParams: ['noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['items'] },
|
|
31
|
+
'update-line-items': { method: 'PATCH', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Update a line item on a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['salesOrderLineItems', 'tagIds', 'tagCodes'] },
|
|
32
|
+
'delete-line-items': { method: 'DELETE', path: '/v1/delivery-notes/{noteId}/line-items/{lineItemId}', summary: 'Remove a line item from a delivery note', pathParams: ['noteId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
33
|
+
'list-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Get all notes for a delivery note', pathParams: ['deliveryNoteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
34
|
+
'create-notes': { method: 'POST', path: '/v1/delivery-notes/{deliveryNoteId}/notes', summary: 'Create a note for a delivery note', pathParams: ['deliveryNoteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
35
|
+
'get-notes': { method: 'GET', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Get a specific note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
36
|
+
'update-notes': { method: 'PATCH', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Update a note for a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
37
|
+
'delete-notes': { method: 'DELETE', path: '/v1/delivery-notes/{deliveryNoteId}/notes/{noteId}', summary: 'Delete a note from a delivery note', pathParams: ['deliveryNoteId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
38
|
+
'list-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files', summary: 'List file attachments for a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
39
|
+
'create-files': { method: 'POST', path: '/v1/delivery-notes/{parentId}/files', summary: 'Upload a file attachment to a delivery note', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
40
|
+
'get-files': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Get a file attachment for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
41
|
+
'update-files': { method: 'PATCH', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a delivery note', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
42
|
+
'delete-files': { method: 'DELETE', path: '/v1/delivery-notes/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
43
|
+
'files-download': { method: 'GET', path: '/v1/delivery-notes/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a delivery note', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
42
44
|
};
|
|
43
45
|
export default class DeliveryNotes extends Command {
|
|
44
46
|
static description = 'Manage delivery notes';
|
|
@@ -72,7 +74,7 @@ export default class DeliveryNotes extends Command {
|
|
|
72
74
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
73
75
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
74
76
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
75
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
77
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
76
78
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
77
79
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
78
80
|
};
|
|
@@ -144,6 +146,8 @@ export default class DeliveryNotes extends Command {
|
|
|
144
146
|
queryParams.search = flags.search;
|
|
145
147
|
if (flags['sort-by'])
|
|
146
148
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
149
|
+
if (flags.fields)
|
|
150
|
+
queryParams.fields = flags.fields;
|
|
147
151
|
if (flags.query) {
|
|
148
152
|
try {
|
|
149
153
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -291,10 +295,11 @@ export default class DeliveryNotes extends Command {
|
|
|
291
295
|
process.stderr.write('\n');
|
|
292
296
|
}
|
|
293
297
|
try {
|
|
294
|
-
// Binary response:
|
|
298
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
295
299
|
if (endpoint.isBinaryResponse) {
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
const toStdout = !flags.file;
|
|
301
|
+
if (toStdout && process.stdout.isTTY) {
|
|
302
|
+
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.`);
|
|
298
303
|
}
|
|
299
304
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
300
305
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -302,24 +307,55 @@ export default class DeliveryNotes extends Command {
|
|
|
302
307
|
contextName: flags.context,
|
|
303
308
|
binary: true,
|
|
304
309
|
});
|
|
305
|
-
const fsModule = await import('node:fs');
|
|
306
310
|
const binData = binResponse.data;
|
|
307
311
|
const buffer = Buffer.isBuffer(binData)
|
|
308
312
|
? binData
|
|
309
313
|
: Buffer.from(binData);
|
|
314
|
+
if (toStdout) {
|
|
315
|
+
// Nothing else may reach stdout here — it is the file.
|
|
316
|
+
process.stdout.write(buffer);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const fsModule = await import('node:fs');
|
|
310
320
|
fsModule.writeFileSync(flags.file, buffer);
|
|
311
321
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
312
322
|
return;
|
|
313
323
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
324
|
+
let response;
|
|
325
|
+
if (endpoint.hasBinaryVariant) {
|
|
326
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
327
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
328
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
329
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
330
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
331
|
+
data,
|
|
332
|
+
contextName: flags.context,
|
|
333
|
+
binary: true,
|
|
334
|
+
});
|
|
335
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
336
|
+
const rawData = raw.data;
|
|
337
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
338
|
+
if (!isTextualMediaType(rawType)) {
|
|
339
|
+
if (process.stdout.isTTY) {
|
|
340
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
341
|
+
}
|
|
342
|
+
// Nothing else may reach stdout here — it is the file.
|
|
343
|
+
process.stdout.write(buffer);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
350
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
351
|
+
data,
|
|
352
|
+
contextName: flags.context,
|
|
353
|
+
acceptAny: true,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
320
356
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
321
357
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
322
|
-
if (contentType
|
|
358
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
323
359
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
324
360
|
this.log(String(response.data));
|
|
325
361
|
return;
|
|
@@ -346,11 +382,6 @@ export default class DeliveryNotes extends Command {
|
|
|
346
382
|
output = obj[arrayKeys[0]];
|
|
347
383
|
}
|
|
348
384
|
}
|
|
349
|
-
// Filter fields if --fields flag is provided
|
|
350
|
-
if (flags.fields) {
|
|
351
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
352
|
-
output = this.filterFields(output, fieldList);
|
|
353
|
-
}
|
|
354
385
|
// Sanitize output if requested
|
|
355
386
|
if (flags.sanitize) {
|
|
356
387
|
output = this.sanitizeOutput(output);
|
|
@@ -605,6 +636,8 @@ export default class DeliveryNotes extends Command {
|
|
|
605
636
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
606
637
|
if (flags.include)
|
|
607
638
|
baseParams.include = flags.include;
|
|
639
|
+
if (flags.fields)
|
|
640
|
+
baseParams.fields = flags.fields;
|
|
608
641
|
if (flags.query) {
|
|
609
642
|
try {
|
|
610
643
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -659,12 +692,7 @@ export default class DeliveryNotes extends Command {
|
|
|
659
692
|
}
|
|
660
693
|
}
|
|
661
694
|
process.stderr.write(' done\n');
|
|
662
|
-
// Filter fields if specified
|
|
663
695
|
let output = allItems;
|
|
664
|
-
if (flags.fields) {
|
|
665
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
666
|
-
output = this.filterFields(allItems, fieldList);
|
|
667
|
-
}
|
|
668
696
|
// Sanitize output if requested
|
|
669
697
|
if (flags.sanitize) {
|
|
670
698
|
output = this.sanitizeOutput(output);
|
|
@@ -993,56 +1021,6 @@ export default class DeliveryNotes extends Command {
|
|
|
993
1021
|
return `\$${op}`;
|
|
994
1022
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
995
1023
|
}
|
|
996
|
-
filterFields(data, fields) {
|
|
997
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
998
|
-
const pick = (obj, keys) => {
|
|
999
|
-
// Group keys by their first segment so sibling sub-paths
|
|
1000
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
1001
|
-
// and don't clobber each other on merge.
|
|
1002
|
-
const directKeys = new Set();
|
|
1003
|
-
const nestedKeys = new Map();
|
|
1004
|
-
for (const key of keys) {
|
|
1005
|
-
if (key.split('.').some(isUnsafeKey))
|
|
1006
|
-
continue;
|
|
1007
|
-
if (key.includes('.')) {
|
|
1008
|
-
const [first, ...rest] = key.split('.');
|
|
1009
|
-
if (isUnsafeKey(first))
|
|
1010
|
-
continue;
|
|
1011
|
-
const list = nestedKeys.get(first);
|
|
1012
|
-
if (list) {
|
|
1013
|
-
list.push(rest.join('.'));
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
directKeys.add(key);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
const result = {};
|
|
1024
|
-
for (const key of directKeys) {
|
|
1025
|
-
if (key in obj)
|
|
1026
|
-
result[key] = obj[key];
|
|
1027
|
-
}
|
|
1028
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1029
|
-
const val = obj[first];
|
|
1030
|
-
if (!val || typeof val !== 'object')
|
|
1031
|
-
continue;
|
|
1032
|
-
if (Array.isArray(val)) {
|
|
1033
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
result[first] = pick(val, restKeys);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return result;
|
|
1040
|
-
};
|
|
1041
|
-
if (Array.isArray(data)) {
|
|
1042
|
-
return data.map(item => pick(item, fields));
|
|
1043
|
-
}
|
|
1044
|
-
return pick(data, fields);
|
|
1045
|
-
}
|
|
1046
1024
|
sanitizeOutput(data) {
|
|
1047
1025
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1048
1026
|
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/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/dunning-configs', summary: 'List all dunning run configs', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/dunning-configs', summary: 'Create a new dunning run config', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/dunning-configs/{id}', summary: 'Get a dunning run config by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/dunning-configs/{id}', summary: 'Update a dunning run config', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['paymentMethodIds', 'accessGroupIds'] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/dunning-configs/{id}', summary: 'Delete a dunning run config', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class DunningConfigs extends Command {
|
|
18
18
|
static description = 'Manage dunning configs';
|
|
@@ -43,7 +43,7 @@ export default class DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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 DunningConfigs 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;
|