@aderis-erp/cli 1.0.14 → 1.0.17
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 +22 -78
- package/dist/commands/account-transaction-allocations.d.ts +0 -1
- package/dist/commands/account-transaction-allocations.js +22 -78
- package/dist/commands/account-transactions.d.ts +0 -1
- package/dist/commands/account-transactions.js +28 -82
- package/dist/commands/accounts.d.ts +0 -1
- package/dist/commands/accounts.js +24 -80
- package/dist/commands/agent-platform.d.ts +0 -1
- package/dist/commands/agent-platform.js +24 -79
- package/dist/commands/analytics.d.ts +39 -0
- package/dist/commands/analytics.js +1113 -0
- package/dist/commands/{documents.d.ts → api-keys.d.ts} +1 -2
- package/dist/commands/api-keys.js +1117 -0
- package/dist/commands/audit-log.d.ts +0 -1
- package/dist/commands/audit-log.js +22 -78
- package/dist/commands/auth/login.d.ts +9 -0
- package/dist/commands/auth/login.js +83 -21
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +23 -6
- package/dist/commands/auth/status.js +46 -23
- package/dist/commands/auth/tenant.js +15 -9
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +37 -51
- package/dist/commands/bank-accounts.d.ts +0 -1
- package/dist/commands/bank-accounts.js +22 -78
- package/dist/commands/bank-integrations.d.ts +0 -1
- package/dist/commands/bank-integrations.js +22 -78
- package/dist/commands/business-partners.d.ts +1 -1
- package/dist/commands/business-partners.js +38 -84
- package/dist/commands/chat.d.ts +0 -1
- package/dist/commands/chat.js +22 -78
- package/dist/commands/config/add.d.ts +12 -0
- package/dist/commands/config/add.js +97 -17
- package/dist/commands/config/current.js +12 -4
- package/dist/commands/config/list.js +12 -4
- package/dist/commands/config/set-api-key.d.ts +10 -0
- package/dist/commands/config/set-api-key.js +49 -0
- package/dist/commands/config/use.js +3 -4
- package/dist/commands/credit-card-accounts.d.ts +0 -1
- package/dist/commands/credit-card-accounts.js +22 -78
- package/dist/commands/delivery-notes.d.ts +0 -1
- package/dist/commands/delivery-notes.js +29 -84
- package/dist/commands/dunning-configs.d.ts +0 -1
- package/dist/commands/dunning-configs.js +22 -78
- package/dist/commands/dunning-reminders.d.ts +0 -1
- package/dist/commands/dunning-reminders.js +22 -78
- package/dist/commands/dunning-runs.d.ts +0 -1
- package/dist/commands/dunning-runs.js +22 -78
- package/dist/commands/email-transactions.d.ts +0 -1
- package/dist/commands/email-transactions.js +22 -78
- package/dist/commands/employees.d.ts +0 -1
- package/dist/commands/employees.js +24 -80
- package/dist/commands/entity-configurations.d.ts +0 -1
- package/dist/commands/entity-configurations.js +22 -78
- package/dist/commands/event-processor-mappings.d.ts +0 -1
- package/dist/commands/event-processor-mappings.js +22 -78
- package/dist/commands/{files-internal.d.ts → files.d.ts} +1 -2
- package/dist/commands/{files-internal.js → files.js} +40 -90
- package/dist/commands/health.d.ts +0 -1
- package/dist/commands/health.js +22 -78
- package/dist/commands/inbox.d.ts +0 -1
- package/dist/commands/inbox.js +22 -78
- package/dist/commands/integrations.d.ts +0 -1
- package/dist/commands/integrations.js +22 -78
- package/dist/commands/item-categories.d.ts +0 -1
- package/dist/commands/item-categories.js +22 -78
- package/dist/commands/items.d.ts +0 -1
- package/dist/commands/items.js +24 -80
- package/dist/commands/journal-lines.d.ts +0 -1
- package/dist/commands/journal-lines.js +22 -78
- package/dist/commands/journals.d.ts +1 -1
- package/dist/commands/journals.js +29 -81
- package/dist/commands/ledgers.d.ts +0 -1
- package/dist/commands/ledgers.js +26 -79
- package/dist/commands/nango-integrations.d.ts +39 -0
- package/dist/commands/nango-integrations.js +1115 -0
- package/dist/commands/office-locations.d.ts +0 -1
- package/dist/commands/office-locations.js +22 -78
- package/dist/commands/payment-methods.d.ts +0 -1
- package/dist/commands/payment-methods.js +22 -78
- package/dist/commands/payment-runs.d.ts +0 -1
- package/dist/commands/payment-runs.js +24 -79
- package/dist/commands/payment-terms.d.ts +0 -1
- package/dist/commands/payment-terms.js +22 -78
- package/dist/commands/payments.d.ts +0 -1
- package/dist/commands/payments.js +22 -78
- package/dist/commands/price-lists.d.ts +0 -1
- package/dist/commands/price-lists.js +22 -78
- package/dist/commands/processor-mappings.d.ts +0 -1
- package/dist/commands/processor-mappings.js +22 -78
- package/dist/commands/processors.d.ts +0 -1
- package/dist/commands/processors.js +22 -78
- package/dist/commands/purchase-order-types.d.ts +0 -1
- package/dist/commands/purchase-order-types.js +22 -78
- package/dist/commands/purchase-orders.d.ts +1 -1
- package/dist/commands/purchase-orders.js +35 -84
- package/dist/commands/reports.d.ts +0 -1
- package/dist/commands/reports.js +23 -79
- package/dist/commands/salary-types.d.ts +0 -1
- package/dist/commands/salary-types.js +22 -78
- package/dist/commands/sales-invoices.d.ts +0 -1
- package/dist/commands/sales-invoices.js +38 -89
- package/dist/commands/sales-orders.d.ts +0 -1
- package/dist/commands/sales-orders.js +26 -82
- package/dist/commands/saved-views.d.ts +0 -1
- package/dist/commands/saved-views.js +22 -78
- package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
- package/dist/commands/scheduled-processor-mappings.js +22 -78
- package/dist/commands/schedules.d.ts +0 -1
- package/dist/commands/schedules.js +22 -78
- package/dist/commands/switch.js +14 -7
- package/dist/commands/tag-configurations.d.ts +39 -0
- package/dist/commands/{documents.js → tag-configurations.js} +40 -96
- package/dist/commands/tags.d.ts +0 -1
- package/dist/commands/tags.js +22 -78
- package/dist/commands/tax-codes.d.ts +0 -1
- package/dist/commands/tax-codes.js +22 -78
- package/dist/commands/tax-matrix.d.ts +0 -1
- package/dist/commands/tax-matrix.js +22 -78
- package/dist/commands/tenants.d.ts +0 -1
- package/dist/commands/tenants.js +24 -79
- package/dist/commands/text-templates.d.ts +0 -1
- package/dist/commands/text-templates.js +22 -78
- package/dist/commands/tools.d.ts +0 -1
- package/dist/commands/tools.js +22 -78
- package/dist/commands/vat-declarations.d.ts +1 -1
- package/dist/commands/vat-declarations.js +28 -79
- package/dist/commands/vendor-invoices.d.ts +0 -1
- package/dist/commands/vendor-invoices.js +31 -86
- package/dist/commands/webhooks.d.ts +0 -1
- package/dist/commands/webhooks.js +22 -78
- package/dist/commands/workflows.d.ts +0 -1
- package/dist/commands/workflows.js +22 -78
- package/dist/lib/api-client.js +8 -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 +217 -0
- package/dist/lib/auth/keycloak-provider.d.ts +75 -0
- package/dist/lib/auth/keycloak-provider.js +215 -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 +54 -0
- package/dist/lib/auth/tokens.js +104 -0
- package/dist/lib/auth/types.d.ts +57 -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/base-api-command.js +6 -4
- package/dist/lib/config.d.ts +44 -4
- package/dist/lib/config.js +55 -19
- package/oclif.manifest.json +1181 -330
- package/package.json +3 -2
- package/dist/lib/auth.d.ts +0 -35
- package/dist/lib/auth.js +0 -566
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
3
|
-
import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
|
|
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
|
-
const NESTED_IMPORTS = ["notes", "contacts", "contacts-notes"];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "merge", "activate", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
8
|
+
const NESTED_IMPORTS = ["notes", "files", "contacts", "contacts-notes"];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "merge", "activate", "get-favicon", "update-favicon", "trigger-favicon-backfill", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-files", "create-files", "get-files", "update-files", "delete-files", "files-download", "list-contacts", "create-contacts", "get-contacts", "update-contacts", "delete-contacts", "list-contacts-notes", "create-contacts-notes", "get-contacts-notes", "update-contacts-notes", "delete-contacts-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
11
|
'list': { method: 'GET', path: '/v1/business-partners', summary: 'Get all business partners with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/business-partners', summary: 'Create a new business partner', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
|
|
13
13
|
'get': { method: 'GET', path: '/v1/business-partners/{id}', summary: 'Get a business partner by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/business-partners/{id}', summary: 'Update a business partner', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['salesOrderRecipientCc', 'salesOrderRecipientBcc', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc', 'contacts', 'tagIds', 'tagCodes', 'salesEarlyPaymentDiscounts', 'purchaseEarlyPaymentDiscounts', 'notes'] },
|
|
15
15
|
'delete': { method: 'DELETE', path: '/v1/business-partners/{id}', summary: 'Delete a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
16
|
'merge': { method: 'POST', path: '/v1/business-partners/{id}/merge', summary: 'Merge a business partner into another', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
17
|
'activate': { method: 'POST', path: '/v1/business-partners/{id}/activate', summary: 'Activate a business partner', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
+
'get-favicon': { method: 'GET', path: '/v1/business-partners/{id}/favicon', summary: 'Download a business partner\'s stored favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
+
'update-favicon': { method: 'PUT', path: '/v1/business-partners/{id}/favicon', summary: 'Manually upload a business partner\'s favicon', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
+
'trigger-favicon-backfill': { method: 'POST', path: '/v1/business-partners/favicon-backfill', summary: 'Fetch favicons for every business partner in this tenant that has none yet', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
21
|
'list-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Get all notes for a business partner', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
22
|
'create-notes': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/notes', summary: 'Create a note for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
23
|
'get-notes': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Get a specific note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
21
24
|
'update-notes': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Update a note for a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
22
25
|
'delete-notes': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/notes/{noteId}', summary: 'Delete a note from a business partner', pathParams: ['businessPartnerId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
26
|
+
'list-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files', summary: 'List file attachments for a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
27
|
+
'create-files': { method: 'POST', path: '/v1/business-partners/{parentId}/files', summary: 'Upload a file attachment to a business partner', pathParams: ['parentId'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
28
|
+
'get-files': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Get a file attachment for a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
29
|
+
'update-files': { method: 'PATCH', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Update file attachment metadata for a business partner', pathParams: ['parentId', 'fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
30
|
+
'delete-files': { method: 'DELETE', path: '/v1/business-partners/{parentId}/files/{fileId}', summary: 'Delete a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
31
|
+
'files-download': { method: 'GET', path: '/v1/business-partners/{parentId}/files/{fileId}/download', summary: 'Download a file attachment from a business partner', pathParams: ['parentId', 'fileId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
23
32
|
'list-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Get all contacts for a business partner with pagination', pathParams: ['businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
24
|
-
'create-contacts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Create a new contact for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['notes'] },
|
|
33
|
+
'create-contacts': { method: 'POST', path: '/v1/business-partners/{businessPartnerId}/contacts', summary: 'Create a new contact for a business partner', pathParams: ['businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
|
|
25
34
|
'get-contacts': { method: 'GET', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Get a contact by ID', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
26
|
-
'update-contacts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Update a contact', pathParams: ['id', 'businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['notes'] },
|
|
35
|
+
'update-contacts': { method: 'PATCH', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Update a contact', pathParams: ['id', 'businessPartnerId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['tagIds', 'tagCodes', 'notes'] },
|
|
27
36
|
'delete-contacts': { method: 'DELETE', path: '/v1/business-partners/{businessPartnerId}/contacts/{id}', summary: 'Delete a contact', pathParams: ['id', 'businessPartnerId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
28
37
|
'list-contacts-notes': { method: 'GET', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Get all notes for a contact', pathParams: ['contactId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
29
38
|
'create-contacts-notes': { method: 'POST', path: '/v1/business-partners/contacts/{contactId}/notes', summary: 'Create a note for a contact', pathParams: ['contactId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
@@ -46,7 +55,7 @@ export default class BusinessPartners extends Command {
|
|
|
46
55
|
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
47
56
|
};
|
|
48
57
|
static flags = {
|
|
49
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
58
|
+
context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
|
|
50
59
|
output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
|
|
51
60
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
52
61
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
@@ -54,6 +63,7 @@ export default class BusinessPartners extends Command {
|
|
|
54
63
|
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
55
64
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
56
65
|
'note-id': Flags.string({ description: 'Path param: noteId' }),
|
|
66
|
+
'file-id': Flags.string({ description: 'Path param: fileId' }),
|
|
57
67
|
'business-partner-id': Flags.string({ description: 'Path param: businessPartnerId' }),
|
|
58
68
|
page: Flags.string({ description: 'Page number' }),
|
|
59
69
|
limit: Flags.string({ description: 'Items per page' }),
|
|
@@ -62,7 +72,7 @@ export default class BusinessPartners extends Command {
|
|
|
62
72
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
63
73
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
64
74
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
65
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
75
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
66
76
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
67
77
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
68
78
|
};
|
|
@@ -120,9 +130,9 @@ export default class BusinessPartners extends Command {
|
|
|
120
130
|
}
|
|
121
131
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
122
132
|
if (!context)
|
|
123
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
124
|
-
if (!context
|
|
125
|
-
this.error(
|
|
133
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
134
|
+
if (!hasCredentials(context))
|
|
135
|
+
this.error(missingCredentialsMessage(context));
|
|
126
136
|
const queryParams = {};
|
|
127
137
|
if (flags.page)
|
|
128
138
|
queryParams.page = flags.page;
|
|
@@ -134,6 +144,8 @@ export default class BusinessPartners extends Command {
|
|
|
134
144
|
queryParams.search = flags.search;
|
|
135
145
|
if (flags['sort-by'])
|
|
136
146
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
147
|
+
if (flags.fields)
|
|
148
|
+
queryParams.fields = flags.fields;
|
|
137
149
|
if (flags.query) {
|
|
138
150
|
try {
|
|
139
151
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -336,11 +348,6 @@ export default class BusinessPartners extends Command {
|
|
|
336
348
|
output = obj[arrayKeys[0]];
|
|
337
349
|
}
|
|
338
350
|
}
|
|
339
|
-
// Filter fields if --fields flag is provided
|
|
340
|
-
if (flags.fields) {
|
|
341
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
342
|
-
output = this.filterFields(output, fieldList);
|
|
343
|
-
}
|
|
344
351
|
// Sanitize output if requested
|
|
345
352
|
if (flags.sanitize) {
|
|
346
353
|
output = this.sanitizeOutput(output);
|
|
@@ -365,9 +372,9 @@ export default class BusinessPartners extends Command {
|
|
|
365
372
|
async runImport(flags) {
|
|
366
373
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
367
374
|
if (!context)
|
|
368
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
369
|
-
if (!context
|
|
370
|
-
this.error(
|
|
375
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
376
|
+
if (!hasCredentials(context))
|
|
377
|
+
this.error(missingCredentialsMessage(context));
|
|
371
378
|
// Get data from file, --data, or --set flags
|
|
372
379
|
let items = [];
|
|
373
380
|
if (flags.file) {
|
|
@@ -577,9 +584,9 @@ export default class BusinessPartners extends Command {
|
|
|
577
584
|
async runExport(flags) {
|
|
578
585
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
579
586
|
if (!context)
|
|
580
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
581
|
-
if (!context
|
|
582
|
-
this.error(
|
|
587
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
588
|
+
if (!hasCredentials(context))
|
|
589
|
+
this.error(missingCredentialsMessage(context));
|
|
583
590
|
const listEndpoint = ENDPOINTS['list'];
|
|
584
591
|
if (!listEndpoint)
|
|
585
592
|
this.error('Export requires a list endpoint');
|
|
@@ -595,6 +602,8 @@ export default class BusinessPartners extends Command {
|
|
|
595
602
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
596
603
|
if (flags.include)
|
|
597
604
|
baseParams.include = flags.include;
|
|
605
|
+
if (flags.fields)
|
|
606
|
+
baseParams.fields = flags.fields;
|
|
598
607
|
if (flags.query) {
|
|
599
608
|
try {
|
|
600
609
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -649,12 +658,7 @@ export default class BusinessPartners extends Command {
|
|
|
649
658
|
}
|
|
650
659
|
}
|
|
651
660
|
process.stderr.write(' done\n');
|
|
652
|
-
// Filter fields if specified
|
|
653
661
|
let output = allItems;
|
|
654
|
-
if (flags.fields) {
|
|
655
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
656
|
-
output = this.filterFields(allItems, fieldList);
|
|
657
|
-
}
|
|
658
662
|
// Sanitize output if requested
|
|
659
663
|
if (flags.sanitize) {
|
|
660
664
|
output = this.sanitizeOutput(output);
|
|
@@ -671,9 +675,9 @@ export default class BusinessPartners extends Command {
|
|
|
671
675
|
async runCamt053Import(flags) {
|
|
672
676
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
673
677
|
if (!context)
|
|
674
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
675
|
-
if (!context
|
|
676
|
-
this.error(
|
|
678
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
679
|
+
if (!hasCredentials(context))
|
|
680
|
+
this.error(missingCredentialsMessage(context));
|
|
677
681
|
if (!flags.file) {
|
|
678
682
|
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
679
683
|
}
|
|
@@ -761,9 +765,9 @@ export default class BusinessPartners extends Command {
|
|
|
761
765
|
}
|
|
762
766
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
763
767
|
if (!context)
|
|
764
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
765
|
-
if (!context
|
|
766
|
-
this.error(
|
|
768
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
769
|
+
if (!hasCredentials(context))
|
|
770
|
+
this.error(missingCredentialsMessage(context));
|
|
767
771
|
// Get data from file, --data, or --set flags
|
|
768
772
|
let items = [];
|
|
769
773
|
if (flags.file) {
|
|
@@ -983,56 +987,6 @@ export default class BusinessPartners extends Command {
|
|
|
983
987
|
return `\$${op}`;
|
|
984
988
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
985
989
|
}
|
|
986
|
-
filterFields(data, fields) {
|
|
987
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
988
|
-
const pick = (obj, keys) => {
|
|
989
|
-
// Group keys by their first segment so sibling sub-paths
|
|
990
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
991
|
-
// and don't clobber each other on merge.
|
|
992
|
-
const directKeys = new Set();
|
|
993
|
-
const nestedKeys = new Map();
|
|
994
|
-
for (const key of keys) {
|
|
995
|
-
if (key.split('.').some(isUnsafeKey))
|
|
996
|
-
continue;
|
|
997
|
-
if (key.includes('.')) {
|
|
998
|
-
const [first, ...rest] = key.split('.');
|
|
999
|
-
if (isUnsafeKey(first))
|
|
1000
|
-
continue;
|
|
1001
|
-
const list = nestedKeys.get(first);
|
|
1002
|
-
if (list) {
|
|
1003
|
-
list.push(rest.join('.'));
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
else {
|
|
1010
|
-
directKeys.add(key);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
const result = {};
|
|
1014
|
-
for (const key of directKeys) {
|
|
1015
|
-
if (key in obj)
|
|
1016
|
-
result[key] = obj[key];
|
|
1017
|
-
}
|
|
1018
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1019
|
-
const val = obj[first];
|
|
1020
|
-
if (!val || typeof val !== 'object')
|
|
1021
|
-
continue;
|
|
1022
|
-
if (Array.isArray(val)) {
|
|
1023
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1024
|
-
}
|
|
1025
|
-
else {
|
|
1026
|
-
result[first] = pick(val, restKeys);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
return result;
|
|
1030
|
-
};
|
|
1031
|
-
if (Array.isArray(data)) {
|
|
1032
|
-
return data.map(item => pick(item, fields));
|
|
1033
|
-
}
|
|
1034
|
-
return pick(data, fields);
|
|
1035
|
-
}
|
|
1036
990
|
sanitizeOutput(data) {
|
|
1037
991
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1038
992
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
package/dist/commands/chat.d.ts
CHANGED
package/dist/commands/chat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
2
|
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
3
|
-
import { getCurrentContext, getContext, getBaseUrls } from '../lib/config.js';
|
|
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 = false;
|
|
@@ -28,7 +28,7 @@ export default class Chat extends Command {
|
|
|
28
28
|
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
29
29
|
};
|
|
30
30
|
static flags = {
|
|
31
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
31
|
+
context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
|
|
32
32
|
output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
|
|
33
33
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
34
34
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
@@ -42,7 +42,7 @@ export default class Chat extends Command {
|
|
|
42
42
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
43
43
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
44
44
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
45
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
45
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
46
46
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
47
47
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
48
48
|
};
|
|
@@ -100,9 +100,9 @@ export default class Chat extends Command {
|
|
|
100
100
|
}
|
|
101
101
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
102
102
|
if (!context)
|
|
103
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
104
|
-
if (!context
|
|
105
|
-
this.error(
|
|
103
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
104
|
+
if (!hasCredentials(context))
|
|
105
|
+
this.error(missingCredentialsMessage(context));
|
|
106
106
|
const queryParams = {};
|
|
107
107
|
if (flags.page)
|
|
108
108
|
queryParams.page = flags.page;
|
|
@@ -114,6 +114,8 @@ export default class Chat extends Command {
|
|
|
114
114
|
queryParams.search = flags.search;
|
|
115
115
|
if (flags['sort-by'])
|
|
116
116
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
117
|
+
if (flags.fields)
|
|
118
|
+
queryParams.fields = flags.fields;
|
|
117
119
|
if (flags.query) {
|
|
118
120
|
try {
|
|
119
121
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -316,11 +318,6 @@ export default class Chat extends Command {
|
|
|
316
318
|
output = obj[arrayKeys[0]];
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
|
-
// Filter fields if --fields flag is provided
|
|
320
|
-
if (flags.fields) {
|
|
321
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
322
|
-
output = this.filterFields(output, fieldList);
|
|
323
|
-
}
|
|
324
321
|
// Sanitize output if requested
|
|
325
322
|
if (flags.sanitize) {
|
|
326
323
|
output = this.sanitizeOutput(output);
|
|
@@ -345,9 +342,9 @@ export default class Chat extends Command {
|
|
|
345
342
|
async runImport(flags) {
|
|
346
343
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
347
344
|
if (!context)
|
|
348
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
349
|
-
if (!context
|
|
350
|
-
this.error(
|
|
345
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
346
|
+
if (!hasCredentials(context))
|
|
347
|
+
this.error(missingCredentialsMessage(context));
|
|
351
348
|
// Get data from file, --data, or --set flags
|
|
352
349
|
let items = [];
|
|
353
350
|
if (flags.file) {
|
|
@@ -557,9 +554,9 @@ export default class Chat extends Command {
|
|
|
557
554
|
async runExport(flags) {
|
|
558
555
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
559
556
|
if (!context)
|
|
560
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
561
|
-
if (!context
|
|
562
|
-
this.error(
|
|
557
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
558
|
+
if (!hasCredentials(context))
|
|
559
|
+
this.error(missingCredentialsMessage(context));
|
|
563
560
|
const listEndpoint = ENDPOINTS['list'];
|
|
564
561
|
if (!listEndpoint)
|
|
565
562
|
this.error('Export requires a list endpoint');
|
|
@@ -575,6 +572,8 @@ export default class Chat extends Command {
|
|
|
575
572
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
576
573
|
if (flags.include)
|
|
577
574
|
baseParams.include = flags.include;
|
|
575
|
+
if (flags.fields)
|
|
576
|
+
baseParams.fields = flags.fields;
|
|
578
577
|
if (flags.query) {
|
|
579
578
|
try {
|
|
580
579
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -629,12 +628,7 @@ export default class Chat extends Command {
|
|
|
629
628
|
}
|
|
630
629
|
}
|
|
631
630
|
process.stderr.write(' done\n');
|
|
632
|
-
// Filter fields if specified
|
|
633
631
|
let output = allItems;
|
|
634
|
-
if (flags.fields) {
|
|
635
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
636
|
-
output = this.filterFields(allItems, fieldList);
|
|
637
|
-
}
|
|
638
632
|
// Sanitize output if requested
|
|
639
633
|
if (flags.sanitize) {
|
|
640
634
|
output = this.sanitizeOutput(output);
|
|
@@ -651,9 +645,9 @@ export default class Chat extends Command {
|
|
|
651
645
|
async runCamt053Import(flags) {
|
|
652
646
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
653
647
|
if (!context)
|
|
654
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
655
|
-
if (!context
|
|
656
|
-
this.error(
|
|
648
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
649
|
+
if (!hasCredentials(context))
|
|
650
|
+
this.error(missingCredentialsMessage(context));
|
|
657
651
|
if (!flags.file) {
|
|
658
652
|
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
659
653
|
}
|
|
@@ -741,9 +735,9 @@ export default class Chat extends Command {
|
|
|
741
735
|
}
|
|
742
736
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
743
737
|
if (!context)
|
|
744
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
745
|
-
if (!context
|
|
746
|
-
this.error(
|
|
738
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
739
|
+
if (!hasCredentials(context))
|
|
740
|
+
this.error(missingCredentialsMessage(context));
|
|
747
741
|
// Get data from file, --data, or --set flags
|
|
748
742
|
let items = [];
|
|
749
743
|
if (flags.file) {
|
|
@@ -963,56 +957,6 @@ export default class Chat extends Command {
|
|
|
963
957
|
return `\$${op}`;
|
|
964
958
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
965
959
|
}
|
|
966
|
-
filterFields(data, fields) {
|
|
967
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
968
|
-
const pick = (obj, keys) => {
|
|
969
|
-
// Group keys by their first segment so sibling sub-paths
|
|
970
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
971
|
-
// and don't clobber each other on merge.
|
|
972
|
-
const directKeys = new Set();
|
|
973
|
-
const nestedKeys = new Map();
|
|
974
|
-
for (const key of keys) {
|
|
975
|
-
if (key.split('.').some(isUnsafeKey))
|
|
976
|
-
continue;
|
|
977
|
-
if (key.includes('.')) {
|
|
978
|
-
const [first, ...rest] = key.split('.');
|
|
979
|
-
if (isUnsafeKey(first))
|
|
980
|
-
continue;
|
|
981
|
-
const list = nestedKeys.get(first);
|
|
982
|
-
if (list) {
|
|
983
|
-
list.push(rest.join('.'));
|
|
984
|
-
}
|
|
985
|
-
else {
|
|
986
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
directKeys.add(key);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const result = {};
|
|
994
|
-
for (const key of directKeys) {
|
|
995
|
-
if (key in obj)
|
|
996
|
-
result[key] = obj[key];
|
|
997
|
-
}
|
|
998
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
999
|
-
const val = obj[first];
|
|
1000
|
-
if (!val || typeof val !== 'object')
|
|
1001
|
-
continue;
|
|
1002
|
-
if (Array.isArray(val)) {
|
|
1003
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
result[first] = pick(val, restKeys);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return result;
|
|
1010
|
-
};
|
|
1011
|
-
if (Array.isArray(data)) {
|
|
1012
|
-
return data.map(item => pick(item, fields));
|
|
1013
|
-
}
|
|
1014
|
-
return pick(data, fields);
|
|
1015
|
-
}
|
|
1016
960
|
sanitizeOutput(data) {
|
|
1017
961
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1018
962
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -3,11 +3,23 @@ export default class ConfigAdd extends Command {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'auth-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
8
|
'client-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
9
|
'client-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
11
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'keycloak-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
13
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
14
|
};
|
|
12
15
|
run(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Decide the auth type from flags, falling back to a prompt only when the
|
|
18
|
+
* invocation leaves it genuinely open. `--client-id`/`--client-secret` imply
|
|
19
|
+
* OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
|
|
20
|
+
* scripted `config add` calls working exactly as before API-key support.
|
|
21
|
+
*/
|
|
22
|
+
/** Validates `--keycloak-url` before the context is stored. */
|
|
23
|
+
private resolveKeycloakBaseUrl;
|
|
24
|
+
private resolveAuthType;
|
|
13
25
|
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import { confirm, input, select } from '@inquirer/prompts';
|
|
1
|
+
import { confirm, input, password, select } from '@inquirer/prompts';
|
|
2
2
|
import { Command, Flags } from '@oclif/core';
|
|
3
|
-
import {
|
|
3
|
+
import { keycloakBaseUrlOf, parseKeycloakBaseUrl } from '../../lib/auth/index.js';
|
|
4
|
+
import { addContext, DEFAULT_KEYCLOAK_CLIENT_ID, getContext, } from '../../lib/config.js';
|
|
4
5
|
export default class ConfigAdd extends Command {
|
|
5
6
|
static description = 'Add a new context configuration';
|
|
6
7
|
static examples = [
|
|
7
8
|
'<%= config.bin %> config add',
|
|
8
9
|
'<%= config.bin %> config add --name my-client --environment live',
|
|
9
10
|
'<%= config.bin %> config add --name dev-context --environment dev --client-id abc123',
|
|
11
|
+
'<%= config.bin %> config add --name ci --environment live --auth-type api-key --api-key <key>',
|
|
12
|
+
'<%= config.bin %> config add --name test-kc --environment test --keycloak-url https://iam-test.aderis.com',
|
|
10
13
|
];
|
|
11
14
|
static flags = {
|
|
15
|
+
'api-key': Flags.string({
|
|
16
|
+
description: 'API key to store (only with --auth-type api-key). Prompted for if omitted.',
|
|
17
|
+
}),
|
|
18
|
+
'auth-type': Flags.string({
|
|
19
|
+
description: 'How to authenticate: oauth2 (browser login) or api-key (x-api-key header)',
|
|
20
|
+
options: ['oauth2', 'api-key'],
|
|
21
|
+
}),
|
|
12
22
|
'client-id': Flags.string({
|
|
13
23
|
description: 'OAuth client ID (uses default if not specified)',
|
|
14
24
|
}),
|
|
@@ -17,14 +27,17 @@ export default class ConfigAdd extends Command {
|
|
|
17
27
|
}),
|
|
18
28
|
environment: Flags.string({
|
|
19
29
|
char: 'e',
|
|
20
|
-
description: 'Environment (dev, staging, live)',
|
|
21
|
-
options: ['dev', 'staging', 'live'],
|
|
30
|
+
description: 'Environment (dev, test, staging, live)',
|
|
31
|
+
options: ['dev', 'test', 'staging', 'live'],
|
|
22
32
|
}),
|
|
23
33
|
force: Flags.boolean({
|
|
24
34
|
char: 'f',
|
|
25
35
|
default: false,
|
|
26
36
|
description: 'Overwrite existing context without prompting',
|
|
27
37
|
}),
|
|
38
|
+
'keycloak-url': Flags.string({
|
|
39
|
+
description: 'Keycloak origin to use instead of the default host for the environment (https, allowlisted). The realm stays that of the environment',
|
|
40
|
+
}),
|
|
28
41
|
name: Flags.string({
|
|
29
42
|
char: 'n',
|
|
30
43
|
description: 'Context name',
|
|
@@ -66,30 +79,59 @@ export default class ConfigAdd extends Command {
|
|
|
66
79
|
choices: [
|
|
67
80
|
{ name: 'live (production)', value: 'live' },
|
|
68
81
|
{ name: 'staging', value: 'staging' },
|
|
82
|
+
{ name: 'test (QA)', value: 'test' },
|
|
69
83
|
{ name: 'dev (development)', value: 'dev' },
|
|
70
84
|
],
|
|
71
85
|
message: 'Environment:',
|
|
72
86
|
}));
|
|
73
87
|
}
|
|
74
|
-
// Get
|
|
88
|
+
// Get auth type. The flag uses the hyphenated `api-key` spelling; the stored
|
|
89
|
+
// value is the `api_key` AuthType. Only prompt when nothing in the invocation
|
|
90
|
+
// implies an answer, so existing non-interactive usage keeps working.
|
|
91
|
+
const authType = await this.resolveAuthType(flags);
|
|
92
|
+
if (authType === 'api_key') {
|
|
93
|
+
if (flags['client-id'] || flags['client-secret']) {
|
|
94
|
+
this.error('--client-id and --client-secret only apply to OAuth contexts; omit them with --auth-type api-key.');
|
|
95
|
+
}
|
|
96
|
+
if (flags['keycloak-url']) {
|
|
97
|
+
this.error('--keycloak-url only applies to OAuth contexts; omit it with --auth-type api-key.');
|
|
98
|
+
}
|
|
99
|
+
const apiKey = flags['api-key'] ??
|
|
100
|
+
(await password({
|
|
101
|
+
mask: true,
|
|
102
|
+
message: 'API key:',
|
|
103
|
+
validate: (value) => (value.trim() ? true : 'API key is required'),
|
|
104
|
+
}));
|
|
105
|
+
if (!apiKey.trim()) {
|
|
106
|
+
this.error('API key is required');
|
|
107
|
+
}
|
|
108
|
+
addContext({
|
|
109
|
+
api_key: apiKey.trim(),
|
|
110
|
+
auth_type: 'api_key',
|
|
111
|
+
environment,
|
|
112
|
+
name,
|
|
113
|
+
});
|
|
114
|
+
this.log(`\nContext "${name}" created successfully.`);
|
|
115
|
+
this.log(`Environment: ${environment}`);
|
|
116
|
+
this.log(`Auth: API key (sent as x-api-key)`);
|
|
117
|
+
this.log(`\nNo login needed — the CLI is ready to use.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const keycloakBaseUrl = this.resolveKeycloakBaseUrl(flags['keycloak-url']);
|
|
121
|
+
// Without a terminal the default client id is taken without confirmation.
|
|
75
122
|
let clientId = flags['client-id'];
|
|
76
123
|
if (!clientId) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const useDefault = await confirm({
|
|
124
|
+
const useDefault = !process.stdin.isTTY ||
|
|
125
|
+
(await confirm({
|
|
80
126
|
default: true,
|
|
81
|
-
message: `Use default client ID
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
if (!clientId) {
|
|
88
|
-
clientId = await input({
|
|
127
|
+
message: `Use default client ID (${DEFAULT_KEYCLOAK_CLIENT_ID})?`,
|
|
128
|
+
}));
|
|
129
|
+
clientId = useDefault
|
|
130
|
+
? DEFAULT_KEYCLOAK_CLIENT_ID
|
|
131
|
+
: await input({
|
|
89
132
|
message: 'OAuth client ID:',
|
|
90
133
|
validate: (value) => (value.trim() ? true : 'Client ID is required'),
|
|
91
134
|
});
|
|
92
|
-
}
|
|
93
135
|
}
|
|
94
136
|
// Get client secret (optional)
|
|
95
137
|
let clientSecret = flags['client-secret'];
|
|
@@ -107,14 +149,52 @@ export default class ConfigAdd extends Command {
|
|
|
107
149
|
}
|
|
108
150
|
// Create the context
|
|
109
151
|
addContext({
|
|
152
|
+
auth_type: 'oauth2',
|
|
110
153
|
client_id: clientId,
|
|
111
154
|
client_secret: clientSecret,
|
|
112
155
|
environment,
|
|
156
|
+
keycloak_base_url: keycloakBaseUrl,
|
|
113
157
|
name,
|
|
114
158
|
});
|
|
115
159
|
this.log(`\nContext "${name}" created successfully.`);
|
|
116
160
|
this.log(`Environment: ${environment}`);
|
|
161
|
+
this.log(`Keycloak URL: ${keycloakBaseUrlOf({ environment, keycloak_base_url: keycloakBaseUrl })}`);
|
|
117
162
|
this.log(`Client ID: ${clientId}`);
|
|
118
163
|
this.log(`\nTo authenticate, run: aderis auth login`);
|
|
119
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Decide the auth type from flags, falling back to a prompt only when the
|
|
167
|
+
* invocation leaves it genuinely open. `--client-id`/`--client-secret` imply
|
|
168
|
+
* OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
|
|
169
|
+
* scripted `config add` calls working exactly as before API-key support.
|
|
170
|
+
*/
|
|
171
|
+
/** Validates `--keycloak-url` before the context is stored. */
|
|
172
|
+
resolveKeycloakBaseUrl(flag) {
|
|
173
|
+
if (flag === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
try {
|
|
176
|
+
return parseKeycloakBaseUrl(flag);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.error(`Invalid --keycloak-url: ${error instanceof Error ? error.message : String(error)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async resolveAuthType(flags) {
|
|
183
|
+
if (flags['auth-type']) {
|
|
184
|
+
return flags['auth-type'] === 'api-key' ? 'api_key' : 'oauth2';
|
|
185
|
+
}
|
|
186
|
+
// A key without --auth-type unambiguously means API-key auth.
|
|
187
|
+
if (flags['api-key'])
|
|
188
|
+
return 'api_key';
|
|
189
|
+
// OAuth-only flags, or a non-interactive shell: keep the historical default.
|
|
190
|
+
if (flags['client-id'] || flags['client-secret'] || !process.stdin.isTTY)
|
|
191
|
+
return 'oauth2';
|
|
192
|
+
return select({
|
|
193
|
+
choices: [
|
|
194
|
+
{ description: 'Browser login via OAuth2 (PKCE)', name: 'oauth2', value: 'oauth2' },
|
|
195
|
+
{ description: 'Long-lived API key, sent as the x-api-key header', name: 'api-key', value: 'api_key' },
|
|
196
|
+
],
|
|
197
|
+
message: 'Authentication method:',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
120
200
|
}
|