@aderis-erp/cli 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/access-groups.js +17 -17
- package/dist/commands/account-transaction-allocations.js +17 -17
- package/dist/commands/account-transactions.js +19 -19
- package/dist/commands/accounts.js +19 -19
- package/dist/commands/agent-platform.js +19 -18
- package/dist/commands/analytics.d.ts +40 -0
- package/dist/commands/{documents.js → analytics.js} +29 -39
- package/dist/commands/audit-log.js +17 -17
- package/dist/commands/auth/login.js +11 -2
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.js +19 -6
- package/dist/commands/auth/status.js +26 -12
- package/dist/commands/auth/tenant.js +12 -3
- package/dist/commands/auth.js +32 -17
- package/dist/commands/bank-accounts.js +17 -17
- package/dist/commands/bank-integrations.js +17 -17
- package/dist/commands/business-partners.js +25 -22
- package/dist/commands/chat.js +17 -17
- package/dist/commands/config/add.d.ts +9 -0
- package/dist/commands/config/add.js +64 -2
- package/dist/commands/config/current.js +9 -4
- package/dist/commands/config/list.js +9 -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.js +17 -17
- package/dist/commands/delivery-notes.js +22 -21
- package/dist/commands/dunning-configs.js +17 -17
- package/dist/commands/dunning-reminders.js +17 -17
- package/dist/commands/dunning-runs.js +17 -17
- package/dist/commands/email-transactions.js +17 -17
- package/dist/commands/employees.js +19 -19
- package/dist/commands/entity-configurations.js +17 -17
- package/dist/commands/event-processor-mappings.js +17 -17
- package/dist/commands/{documents.d.ts → files.d.ts} +1 -1
- package/dist/commands/{files-internal.js → files.js} +35 -29
- package/dist/commands/health.js +17 -17
- package/dist/commands/inbox.js +17 -17
- package/dist/commands/integrations.js +17 -17
- package/dist/commands/item-categories.js +17 -17
- package/dist/commands/items.js +19 -19
- package/dist/commands/journal-lines.js +17 -17
- package/dist/commands/journals.js +21 -20
- package/dist/commands/ledgers.js +21 -18
- package/dist/commands/office-locations.js +17 -17
- package/dist/commands/payment-methods.js +17 -17
- package/dist/commands/payment-runs.js +19 -18
- package/dist/commands/payment-terms.js +17 -17
- package/dist/commands/payments.js +17 -17
- package/dist/commands/price-lists.js +17 -17
- package/dist/commands/processor-mappings.js +17 -17
- package/dist/commands/processors.js +17 -17
- package/dist/commands/purchase-order-types.js +17 -17
- package/dist/commands/purchase-orders.d.ts +1 -0
- package/dist/commands/purchase-orders.js +28 -21
- package/dist/commands/reports.js +17 -17
- package/dist/commands/salary-types.js +17 -17
- package/dist/commands/sales-invoices.js +27 -25
- package/dist/commands/sales-orders.js +19 -19
- package/dist/commands/saved-views.js +17 -17
- package/dist/commands/scheduled-processor-mappings.js +17 -17
- package/dist/commands/schedules.js +17 -17
- package/dist/commands/switch.js +10 -3
- package/dist/commands/{files-internal.d.ts → tag-configurations.d.ts} +1 -1
- package/dist/commands/tag-configurations.js +1178 -0
- package/dist/commands/tags.js +17 -17
- package/dist/commands/tax-codes.js +17 -17
- package/dist/commands/tax-matrix.js +17 -17
- package/dist/commands/tenants.js +17 -17
- package/dist/commands/text-templates.js +17 -17
- package/dist/commands/tools.js +17 -17
- package/dist/commands/vat-declarations.js +17 -17
- package/dist/commands/vendor-invoices.js +21 -21
- package/dist/commands/webhooks.js +17 -17
- package/dist/commands/workflows.js +17 -17
- package/dist/lib/api-client.js +11 -6
- package/dist/lib/auth.d.ts +8 -0
- package/dist/lib/auth.js +51 -7
- package/dist/lib/base-api-command.js +6 -4
- package/dist/lib/config.d.ts +31 -1
- package/dist/lib/config.js +31 -0
- package/oclif.manifest.json +729 -247
- package/package.json +2 -2
|
@@ -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 = true;
|
|
@@ -9,12 +9,12 @@ const NESTED_IMPORTS = ["line-items", "notes", "files", "payments"];
|
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "submit", "approve", "decline", "book", "rebuild-journal", "cancel", "confirm-cancellation", "change-vendor", "recalculate-payment-status", "validate", "create-line-items", "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", "list-payments", "create-payments", "get-payments", "update-payments", "delete-payments", "payments-approve", "payments-cancel"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
11
|
'list': { method: 'GET', path: '/v1/vendor-invoices', summary: 'Get all vendor invoices with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/vendor-invoices', summary: 'Create a new vendor invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'payments'] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/vendor-invoices', summary: 'Create a new vendor invoice', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'payments'] },
|
|
13
13
|
'get': { method: 'GET', path: '/v1/vendor-invoices/{id}', summary: 'Get a vendor invoice by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/vendor-invoices/{id}', summary: 'Update a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'removeFileIds'] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/vendor-invoices/{id}', summary: 'Update a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
|
|
15
15
|
'delete': { method: 'DELETE', path: '/v1/vendor-invoices/{id}', summary: 'Delete a vendor invoice', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
16
|
'submit': { method: 'POST', path: '/v1/vendor-invoices/{id}/submit', summary: 'Submit a vendor invoice for approval (transitions DRAFT to PENDING_APPROVAL)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'approve': { method: 'POST', path: '/v1/vendor-invoices/{id}/approve', summary: 'Approve a vendor invoice (transitions DRAFT/PENDING_APPROVAL to APPROVED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'removeFileIds'] },
|
|
17
|
+
'approve': { method: 'POST', path: '/v1/vendor-invoices/{id}/approve', summary: 'Approve a vendor invoice (transitions DRAFT/PENDING_APPROVAL to APPROVED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
|
|
18
18
|
'decline': { method: 'POST', path: '/v1/vendor-invoices/{id}/decline', summary: 'Decline a vendor invoice (transitions PENDING_APPROVAL to DECLINED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
19
|
'book': { method: 'POST', path: '/v1/vendor-invoices/{id}/book', summary: 'Manually book a vendor invoice (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
20
|
'rebuild-journal': { method: 'POST', path: '/v1/vendor-invoices/{id}/rebuild-journal', summary: 'Rebuild a vendor invoice booking journal (dispatch to ledger-booking-agent)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
@@ -22,7 +22,7 @@ const ENDPOINTS = {
|
|
|
22
22
|
'confirm-cancellation': { method: 'POST', path: '/v1/vendor-invoices/{id}/confirm-cancellation', summary: 'Confirm bank cancellation for a vendor invoice (PENDING_CANCELLATION to CANCELLED)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
23
23
|
'change-vendor': { method: 'POST', path: '/v1/vendor-invoices/{id}/change-vendor', summary: 'Change the vendor on a vendor invoice', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
24
24
|
'recalculate-payment-status': { method: 'POST', path: '/v1/vendor-invoices/{id}/recalculate-payment-status', summary: 'Recalculate a vendor invoice\'s open amount and payment status', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
25
|
-
'validate': { method: 'POST', path: '/v1/vendor-invoices/{id}/validate', summary: 'Validate a vendor invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'removeFileIds'] },
|
|
25
|
+
'validate': { method: 'POST', path: '/v1/vendor-invoices/{id}/validate', summary: 'Validate a vendor invoice update without persisting changes', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['earlyPaymentDiscounts', 'totalAmountByTaxRate', 'files', 'notes', 'lineItems', 'tagIds', 'tagCodes', 'removeFileIds'] },
|
|
26
26
|
'create-line-items': { method: 'POST', path: '/v1/vendor-invoices/{invoiceId}/line-items', summary: 'Add a line item to a vendor invoice', pathParams: ['invoiceId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
27
27
|
'update-line-items': { method: 'PATCH', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Update a line item on a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
28
28
|
'delete-line-items': { method: 'DELETE', path: '/v1/vendor-invoices/{invoiceId}/line-items/{lineItemId}', summary: 'Remove a line item from a vendor invoice', pathParams: ['invoiceId', 'lineItemId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
@@ -60,7 +60,7 @@ export default class VendorInvoices extends Command {
|
|
|
60
60
|
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
61
61
|
};
|
|
62
62
|
static flags = {
|
|
63
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
63
|
+
context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
|
|
64
64
|
output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
|
|
65
65
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
66
66
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
@@ -136,9 +136,9 @@ export default class VendorInvoices extends Command {
|
|
|
136
136
|
}
|
|
137
137
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
138
138
|
if (!context)
|
|
139
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
140
|
-
if (!context
|
|
141
|
-
this.error(
|
|
139
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
140
|
+
if (!hasCredentials(context))
|
|
141
|
+
this.error(missingCredentialsMessage(context));
|
|
142
142
|
const queryParams = {};
|
|
143
143
|
if (flags.page)
|
|
144
144
|
queryParams.page = flags.page;
|
|
@@ -381,9 +381,9 @@ export default class VendorInvoices extends Command {
|
|
|
381
381
|
async runImport(flags) {
|
|
382
382
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
383
383
|
if (!context)
|
|
384
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
385
|
-
if (!context
|
|
386
|
-
this.error(
|
|
384
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
385
|
+
if (!hasCredentials(context))
|
|
386
|
+
this.error(missingCredentialsMessage(context));
|
|
387
387
|
// Get data from file, --data, or --set flags
|
|
388
388
|
let items = [];
|
|
389
389
|
if (flags.file) {
|
|
@@ -593,9 +593,9 @@ export default class VendorInvoices extends Command {
|
|
|
593
593
|
async runExport(flags) {
|
|
594
594
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
595
595
|
if (!context)
|
|
596
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
597
|
-
if (!context
|
|
598
|
-
this.error(
|
|
596
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
597
|
+
if (!hasCredentials(context))
|
|
598
|
+
this.error(missingCredentialsMessage(context));
|
|
599
599
|
const listEndpoint = ENDPOINTS['list'];
|
|
600
600
|
if (!listEndpoint)
|
|
601
601
|
this.error('Export requires a list endpoint');
|
|
@@ -687,9 +687,9 @@ export default class VendorInvoices extends Command {
|
|
|
687
687
|
async runCamt053Import(flags) {
|
|
688
688
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
689
689
|
if (!context)
|
|
690
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
691
|
-
if (!context
|
|
692
|
-
this.error(
|
|
690
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
691
|
+
if (!hasCredentials(context))
|
|
692
|
+
this.error(missingCredentialsMessage(context));
|
|
693
693
|
if (!flags.file) {
|
|
694
694
|
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
695
695
|
}
|
|
@@ -777,9 +777,9 @@ export default class VendorInvoices extends Command {
|
|
|
777
777
|
}
|
|
778
778
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
779
779
|
if (!context)
|
|
780
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
781
|
-
if (!context
|
|
782
|
-
this.error(
|
|
780
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
781
|
+
if (!hasCredentials(context))
|
|
782
|
+
this.error(missingCredentialsMessage(context));
|
|
783
783
|
// Get data from file, --data, or --set flags
|
|
784
784
|
let items = [];
|
|
785
785
|
if (flags.file) {
|
|
@@ -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 = true;
|
|
@@ -31,7 +31,7 @@ export default class Webhooks extends Command {
|
|
|
31
31
|
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
32
32
|
};
|
|
33
33
|
static flags = {
|
|
34
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
34
|
+
context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
|
|
35
35
|
output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
|
|
36
36
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
37
37
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
@@ -103,9 +103,9 @@ export default class Webhooks extends Command {
|
|
|
103
103
|
}
|
|
104
104
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
105
105
|
if (!context)
|
|
106
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
107
|
-
if (!context
|
|
108
|
-
this.error(
|
|
106
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
107
|
+
if (!hasCredentials(context))
|
|
108
|
+
this.error(missingCredentialsMessage(context));
|
|
109
109
|
const queryParams = {};
|
|
110
110
|
if (flags.page)
|
|
111
111
|
queryParams.page = flags.page;
|
|
@@ -348,9 +348,9 @@ export default class Webhooks extends Command {
|
|
|
348
348
|
async runImport(flags) {
|
|
349
349
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
350
350
|
if (!context)
|
|
351
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
352
|
-
if (!context
|
|
353
|
-
this.error(
|
|
351
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
352
|
+
if (!hasCredentials(context))
|
|
353
|
+
this.error(missingCredentialsMessage(context));
|
|
354
354
|
// Get data from file, --data, or --set flags
|
|
355
355
|
let items = [];
|
|
356
356
|
if (flags.file) {
|
|
@@ -560,9 +560,9 @@ export default class Webhooks extends Command {
|
|
|
560
560
|
async runExport(flags) {
|
|
561
561
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
562
562
|
if (!context)
|
|
563
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
564
|
-
if (!context
|
|
565
|
-
this.error(
|
|
563
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
564
|
+
if (!hasCredentials(context))
|
|
565
|
+
this.error(missingCredentialsMessage(context));
|
|
566
566
|
const listEndpoint = ENDPOINTS['list'];
|
|
567
567
|
if (!listEndpoint)
|
|
568
568
|
this.error('Export requires a list endpoint');
|
|
@@ -654,9 +654,9 @@ export default class Webhooks extends Command {
|
|
|
654
654
|
async runCamt053Import(flags) {
|
|
655
655
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
656
656
|
if (!context)
|
|
657
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
658
|
-
if (!context
|
|
659
|
-
this.error(
|
|
657
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
658
|
+
if (!hasCredentials(context))
|
|
659
|
+
this.error(missingCredentialsMessage(context));
|
|
660
660
|
if (!flags.file) {
|
|
661
661
|
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
662
662
|
}
|
|
@@ -744,9 +744,9 @@ export default class Webhooks extends Command {
|
|
|
744
744
|
}
|
|
745
745
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
746
746
|
if (!context)
|
|
747
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
748
|
-
if (!context
|
|
749
|
-
this.error(
|
|
747
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
748
|
+
if (!hasCredentials(context))
|
|
749
|
+
this.error(missingCredentialsMessage(context));
|
|
750
750
|
// Get data from file, --data, or --set flags
|
|
751
751
|
let items = [];
|
|
752
752
|
if (flags.file) {
|
|
@@ -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 = false;
|
|
6
6
|
const HAS_EXPORT = true;
|
|
@@ -23,7 +23,7 @@ export default class Workflows extends Command {
|
|
|
23
23
|
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
24
24
|
};
|
|
25
25
|
static flags = {
|
|
26
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
26
|
+
context: Flags.string({ char: 'c', aliases: ['config'], env: 'ADERIS_CONTEXT', description: 'Context to use for this command, overriding the current context (alias: --config)' }),
|
|
27
27
|
output: Flags.string({ char: 'o', description: 'Output format (default: table for import, json for other commands)', options: ['json', 'table', 'csv'] }),
|
|
28
28
|
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
29
29
|
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
@@ -95,9 +95,9 @@ export default class Workflows extends Command {
|
|
|
95
95
|
}
|
|
96
96
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
97
97
|
if (!context)
|
|
98
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
99
|
-
if (!context
|
|
100
|
-
this.error(
|
|
98
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
99
|
+
if (!hasCredentials(context))
|
|
100
|
+
this.error(missingCredentialsMessage(context));
|
|
101
101
|
const queryParams = {};
|
|
102
102
|
if (flags.page)
|
|
103
103
|
queryParams.page = flags.page;
|
|
@@ -340,9 +340,9 @@ export default class Workflows extends Command {
|
|
|
340
340
|
async runImport(flags) {
|
|
341
341
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
342
342
|
if (!context)
|
|
343
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
344
|
-
if (!context
|
|
345
|
-
this.error(
|
|
343
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
344
|
+
if (!hasCredentials(context))
|
|
345
|
+
this.error(missingCredentialsMessage(context));
|
|
346
346
|
// Get data from file, --data, or --set flags
|
|
347
347
|
let items = [];
|
|
348
348
|
if (flags.file) {
|
|
@@ -552,9 +552,9 @@ export default class Workflows extends Command {
|
|
|
552
552
|
async runExport(flags) {
|
|
553
553
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
554
554
|
if (!context)
|
|
555
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
556
|
-
if (!context
|
|
557
|
-
this.error(
|
|
555
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
556
|
+
if (!hasCredentials(context))
|
|
557
|
+
this.error(missingCredentialsMessage(context));
|
|
558
558
|
const listEndpoint = ENDPOINTS['list'];
|
|
559
559
|
if (!listEndpoint)
|
|
560
560
|
this.error('Export requires a list endpoint');
|
|
@@ -646,9 +646,9 @@ export default class Workflows extends Command {
|
|
|
646
646
|
async runCamt053Import(flags) {
|
|
647
647
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
648
648
|
if (!context)
|
|
649
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
650
|
-
if (!context
|
|
651
|
-
this.error(
|
|
649
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
650
|
+
if (!hasCredentials(context))
|
|
651
|
+
this.error(missingCredentialsMessage(context));
|
|
652
652
|
if (!flags.file) {
|
|
653
653
|
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
654
654
|
}
|
|
@@ -736,9 +736,9 @@ export default class Workflows extends Command {
|
|
|
736
736
|
}
|
|
737
737
|
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
738
738
|
if (!context)
|
|
739
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
740
|
-
if (!context
|
|
741
|
-
this.error(
|
|
739
|
+
this.error(flags.context ? `Context "${flags.context}" not found. List contexts with: aderis config list` : 'No context configured. Run: aderis auth login --new');
|
|
740
|
+
if (!hasCredentials(context))
|
|
741
|
+
this.error(missingCredentialsMessage(context));
|
|
742
742
|
// Get data from file, --data, or --set flags
|
|
743
743
|
let items = [];
|
|
744
744
|
if (flags.file) {
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import {
|
|
3
|
-
import { getBaseUrls, getCurrentContext } from './config.js';
|
|
2
|
+
import { getAuthHeaders } from './auth.js';
|
|
3
|
+
import { getAuthType, getBaseUrls, getCurrentContext } from './config.js';
|
|
4
4
|
/**
|
|
5
5
|
* Normalize a --sort-by value: comma-separated fields, each optionally suffixed
|
|
6
6
|
* with `:ASC` or `:DESC`. Fields without an explicit direction default to DESC
|
|
@@ -39,14 +39,15 @@ export async function createApiClient(contextName, options = false) {
|
|
|
39
39
|
if (!context) {
|
|
40
40
|
throw new Error('No context configured. Run: aderis auth login --new');
|
|
41
41
|
}
|
|
42
|
-
const
|
|
42
|
+
const authHeaders = await getAuthHeaders(context.name);
|
|
43
43
|
const { api: baseURL } = getBaseUrls(context.environment);
|
|
44
44
|
const client = axios.create({
|
|
45
45
|
baseURL,
|
|
46
46
|
headers: {
|
|
47
47
|
Accept: binary ? '*/*' : (acceptAny ? '*/*' : 'application/json'),
|
|
48
|
-
Authorization: `Bearer ${token}`,
|
|
49
48
|
'Content-Type': 'application/json',
|
|
49
|
+
// Either `Authorization: Bearer <token>` or `x-api-key: <key>`.
|
|
50
|
+
...authHeaders,
|
|
50
51
|
},
|
|
51
52
|
// Serialize arrays as repeated params (key=v1&key=v2) instead of bracket format (key[]=v1)
|
|
52
53
|
paramsSerializer: { indexes: null },
|
|
@@ -56,9 +57,13 @@ export async function createApiClient(contextName, options = false) {
|
|
|
56
57
|
// Response interceptor for error handling
|
|
57
58
|
client.interceptors.response.use((response) => response, (error) => {
|
|
58
59
|
if (error.response) {
|
|
59
|
-
// Handle 401 with a clear message
|
|
60
|
+
// Handle 401 with a clear message, pointing at the right remedy for the
|
|
61
|
+
// context's auth type — re-login only helps OAuth contexts.
|
|
60
62
|
if (error.response.status === 401) {
|
|
61
|
-
|
|
63
|
+
const message = getAuthType(context) === 'api_key'
|
|
64
|
+
? `API key for context "${context.name}" was rejected. Check the key with: aderis config set-api-key --context ${context.name}`
|
|
65
|
+
: 'Session expired. Please run: aderis auth login';
|
|
66
|
+
throw new ApiError(message, 401, error.response.data);
|
|
62
67
|
}
|
|
63
68
|
const message = extractErrorMessage(error.response.data);
|
|
64
69
|
throw new ApiError(message, error.response.status, error.response.data);
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export declare function login(context: Context, options?: {
|
|
|
4
4
|
}): Promise<TokenData>;
|
|
5
5
|
export declare function refreshTokens(contextName: string): Promise<TokenData>;
|
|
6
6
|
export declare function getValidToken(contextName: string): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Build the auth headers for a context: a bearer token for `oauth2` contexts
|
|
9
|
+
* (refreshing it first if needed) or `x-api-key` for `api_key` contexts.
|
|
10
|
+
*
|
|
11
|
+
* This is the single place that decides how credentials reach the API — callers
|
|
12
|
+
* should never read `tokens` or `api_key` off a context to build headers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAuthHeaders(contextName: string): Promise<Record<string, string>>;
|
|
7
15
|
export declare function testAuth(contextName: string): Promise<{
|
|
8
16
|
email: string;
|
|
9
17
|
name?: string;
|
package/dist/lib/auth.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as crypto from 'node:crypto';
|
|
|
4
4
|
import * as http from 'node:http';
|
|
5
5
|
import { URL, URLSearchParams } from 'node:url';
|
|
6
6
|
import open from 'open';
|
|
7
|
-
import { getBaseUrls, getContext, saveCachedTenants, saveTokens, } from './config.js';
|
|
7
|
+
import { getAuthType, getBaseUrls, getContext, missingCredentialsMessage, saveCachedTenants, saveTokens, } from './config.js';
|
|
8
8
|
function buildValidatedUrl(baseUrl, path) {
|
|
9
9
|
try {
|
|
10
10
|
// Minimal path validation
|
|
@@ -132,6 +132,23 @@ function renderAuthPage(type, title, message) {
|
|
|
132
132
|
</body>
|
|
133
133
|
</html>`;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Guard for flows that only exist for OAuth contexts (browser login, token
|
|
137
|
+
* refresh, Frontegg tenant switching). API keys are issued per tenant, so there
|
|
138
|
+
* is nothing to log into or switch.
|
|
139
|
+
*/
|
|
140
|
+
function requireOAuthContext(context, action) {
|
|
141
|
+
if (getAuthType(context) === 'api_key') {
|
|
142
|
+
throw new Error(`${action} requires OAuth. Context "${context.name}" authenticates with an API key, which is already scoped to one tenant.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** `client_id` is optional on Context (API-key contexts have none), so OAuth flows must assert it. */
|
|
146
|
+
function requireClientId(context) {
|
|
147
|
+
if (!context.client_id) {
|
|
148
|
+
throw new Error(`Context "${context.name}" has no OAuth client ID. Recreate it with: aderis config add`);
|
|
149
|
+
}
|
|
150
|
+
return context.client_id;
|
|
151
|
+
}
|
|
135
152
|
function generatePKCE() {
|
|
136
153
|
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
137
154
|
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
@@ -143,7 +160,7 @@ function generateState() {
|
|
|
143
160
|
function buildAuthorizeUrl(context, pkce, state, options = {}) {
|
|
144
161
|
const { auth: authUrl } = getBaseUrls(context.environment);
|
|
145
162
|
const url = new URL(`${authUrl}/oauth/authorize`);
|
|
146
|
-
url.searchParams.set('client_id', context
|
|
163
|
+
url.searchParams.set('client_id', requireClientId(context));
|
|
147
164
|
url.searchParams.set('redirect_uri', REDIRECT_URI);
|
|
148
165
|
url.searchParams.set('response_type', 'code');
|
|
149
166
|
url.searchParams.set('state', state);
|
|
@@ -155,6 +172,7 @@ function buildAuthorizeUrl(context, pkce, state, options = {}) {
|
|
|
155
172
|
return url.toString();
|
|
156
173
|
}
|
|
157
174
|
export async function login(context, options = {}) {
|
|
175
|
+
requireOAuthContext(context, 'Browser login');
|
|
158
176
|
const pkce = generatePKCE();
|
|
159
177
|
const state = generateState();
|
|
160
178
|
const authorizationUrl = buildAuthorizeUrl(context, pkce, state, options);
|
|
@@ -252,7 +270,7 @@ async function exchangeCodeForTokens(context, code, codeVerifier) {
|
|
|
252
270
|
const { auth: authUrl } = getBaseUrls(context.environment);
|
|
253
271
|
const params = new URLSearchParams();
|
|
254
272
|
params.set('grant_type', 'authorization_code');
|
|
255
|
-
params.set('client_id', context
|
|
273
|
+
params.set('client_id', requireClientId(context));
|
|
256
274
|
params.set('code', code);
|
|
257
275
|
params.set('redirect_uri', REDIRECT_URI);
|
|
258
276
|
params.set('code_verifier', codeVerifier);
|
|
@@ -279,13 +297,14 @@ export async function refreshTokens(contextName) {
|
|
|
279
297
|
if (!context) {
|
|
280
298
|
throw new Error(`Context "${contextName}" not found`);
|
|
281
299
|
}
|
|
300
|
+
requireOAuthContext(context, 'Token refresh');
|
|
282
301
|
if (!context.tokens?.refresh_token) {
|
|
283
302
|
throw new Error('No refresh token available. Please login again.');
|
|
284
303
|
}
|
|
285
304
|
const { auth: authUrl } = getBaseUrls(context.environment);
|
|
286
305
|
const params = new URLSearchParams();
|
|
287
306
|
params.set('grant_type', 'refresh_token');
|
|
288
|
-
params.set('client_id', context
|
|
307
|
+
params.set('client_id', requireClientId(context));
|
|
289
308
|
params.set('refresh_token', context.tokens.refresh_token);
|
|
290
309
|
if (context.client_secret) {
|
|
291
310
|
params.set('client_secret', context.client_secret);
|
|
@@ -321,6 +340,9 @@ export async function getValidToken(contextName) {
|
|
|
321
340
|
if (!context) {
|
|
322
341
|
throw new Error(`Context "${contextName}" not found`);
|
|
323
342
|
}
|
|
343
|
+
if (getAuthType(context) === 'api_key') {
|
|
344
|
+
throw new Error(`Context "${contextName}" authenticates with an API key and has no access token. Use getAuthHeaders() instead.`);
|
|
345
|
+
}
|
|
324
346
|
if (!context.tokens?.access_token) {
|
|
325
347
|
throw new Error('Not logged in. Please run: aderis auth login');
|
|
326
348
|
}
|
|
@@ -334,17 +356,36 @@ export async function getValidToken(contextName) {
|
|
|
334
356
|
}
|
|
335
357
|
return context.tokens.access_token;
|
|
336
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Build the auth headers for a context: a bearer token for `oauth2` contexts
|
|
361
|
+
* (refreshing it first if needed) or `x-api-key` for `api_key` contexts.
|
|
362
|
+
*
|
|
363
|
+
* This is the single place that decides how credentials reach the API — callers
|
|
364
|
+
* should never read `tokens` or `api_key` off a context to build headers.
|
|
365
|
+
*/
|
|
366
|
+
export async function getAuthHeaders(contextName) {
|
|
367
|
+
const context = getContext(contextName);
|
|
368
|
+
if (!context) {
|
|
369
|
+
throw new Error(`Context "${contextName}" not found`);
|
|
370
|
+
}
|
|
371
|
+
if (getAuthType(context) === 'api_key') {
|
|
372
|
+
if (!context.api_key) {
|
|
373
|
+
throw new Error(missingCredentialsMessage(context));
|
|
374
|
+
}
|
|
375
|
+
return { 'x-api-key': context.api_key };
|
|
376
|
+
}
|
|
377
|
+
return { Authorization: `Bearer ${await getValidToken(contextName)}` };
|
|
378
|
+
}
|
|
337
379
|
export async function testAuth(contextName) {
|
|
338
380
|
const context = getContext(contextName);
|
|
339
381
|
if (!context) {
|
|
340
382
|
throw new Error(`Context "${contextName}" not found`);
|
|
341
383
|
}
|
|
342
|
-
const token = await getValidToken(contextName);
|
|
343
384
|
const { api: apiUrl } = getBaseUrls(context.environment);
|
|
344
385
|
const response = await axios.get(buildValidatedUrl(apiUrl, '/v1/auth/me'), {
|
|
345
386
|
headers: {
|
|
346
387
|
Accept: 'application/json',
|
|
347
|
-
|
|
388
|
+
...(await getAuthHeaders(contextName)),
|
|
348
389
|
},
|
|
349
390
|
});
|
|
350
391
|
return response.data;
|
|
@@ -354,6 +395,7 @@ export async function listTenants(contextName) {
|
|
|
354
395
|
if (!context) {
|
|
355
396
|
throw new Error(`Context "${contextName}" not found`);
|
|
356
397
|
}
|
|
398
|
+
requireOAuthContext(context, 'Listing tenants');
|
|
357
399
|
const token = await getValidToken(contextName);
|
|
358
400
|
const { auth: authUrl } = getBaseUrls(context.environment);
|
|
359
401
|
let response;
|
|
@@ -389,7 +431,8 @@ export async function getCurrentTenant(contextName) {
|
|
|
389
431
|
if (!context) {
|
|
390
432
|
throw new Error(`Context "${contextName}" not found`);
|
|
391
433
|
}
|
|
392
|
-
|
|
434
|
+
// API-key contexts carry no JWT, so there is no tenant to decode locally.
|
|
435
|
+
if (getAuthType(context) === 'api_key' || !context.tokens?.access_token) {
|
|
393
436
|
return null;
|
|
394
437
|
}
|
|
395
438
|
const decoded = jwt.decode(context.tokens.access_token);
|
|
@@ -415,6 +458,7 @@ export async function switchTenant(contextName, tenantId) {
|
|
|
415
458
|
if (!context) {
|
|
416
459
|
throw new Error(`Context "${contextName}" not found`);
|
|
417
460
|
}
|
|
461
|
+
requireOAuthContext(context, 'Switching tenants');
|
|
418
462
|
if (!context.tokens?.refresh_token) {
|
|
419
463
|
throw new Error('No refresh token available. Please login again.');
|
|
420
464
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { ApiError, apiRequest } from './api-client.js';
|
|
3
|
-
import { getCurrentContext } from './config.js';
|
|
3
|
+
import { getCurrentContext, hasCredentials, missingCredentialsMessage } from './config.js';
|
|
4
4
|
export class BaseApiCommand extends Command {
|
|
5
5
|
static globalFlags = {
|
|
6
6
|
context: Flags.string({
|
|
7
|
+
aliases: ['config'],
|
|
7
8
|
char: 'c',
|
|
8
|
-
description: 'Context to use
|
|
9
|
+
description: 'Context to use, overriding the current context (alias: --config)',
|
|
10
|
+
env: 'ADERIS_CONTEXT',
|
|
9
11
|
}),
|
|
10
12
|
output: Flags.string({
|
|
11
13
|
char: 'o',
|
|
@@ -66,8 +68,8 @@ export class BaseApiCommand extends Command {
|
|
|
66
68
|
if (!context) {
|
|
67
69
|
this.error('No context configured. Run: aderis auth login --new');
|
|
68
70
|
}
|
|
69
|
-
if (!context
|
|
70
|
-
this.error(
|
|
71
|
+
if (!hasCredentials(context)) {
|
|
72
|
+
this.error(missingCredentialsMessage(context));
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
printTable(data) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
export type Environment = 'dev' | 'live' | 'staging';
|
|
3
|
+
/**
|
|
4
|
+
* How a context authenticates against the API.
|
|
5
|
+
*
|
|
6
|
+
* - `oauth2`: browser PKCE login, sends `Authorization: Bearer <access_token>`
|
|
7
|
+
* - `api_key`: a long-lived key, sends `x-api-key: <key>`
|
|
8
|
+
*
|
|
9
|
+
* Contexts created before API-key support have no `auth_type` field; they are
|
|
10
|
+
* treated as `oauth2`, so always read it through {@link getAuthType}.
|
|
11
|
+
*/
|
|
12
|
+
export type AuthType = 'api_key' | 'oauth2';
|
|
3
13
|
export interface BaseUrls {
|
|
4
14
|
api: string;
|
|
5
15
|
auth: string;
|
|
@@ -17,8 +27,11 @@ export interface CachedTenant {
|
|
|
17
27
|
tenantId: string;
|
|
18
28
|
}
|
|
19
29
|
export interface Context {
|
|
30
|
+
api_key?: string;
|
|
31
|
+
auth_type?: AuthType;
|
|
20
32
|
cachedTenants?: CachedTenant[];
|
|
21
|
-
|
|
33
|
+
/** Only required for `oauth2` contexts; API-key contexts have no OAuth client. */
|
|
34
|
+
client_id?: string;
|
|
22
35
|
client_secret?: string;
|
|
23
36
|
environment: Environment;
|
|
24
37
|
name: string;
|
|
@@ -44,5 +57,22 @@ export declare function saveCachedTenants(contextName: string, tenants: CachedTe
|
|
|
44
57
|
export declare function getCachedTenants(contextName: string): CachedTenant[] | undefined;
|
|
45
58
|
export declare function getTokens(contextName: string): null | TokenData;
|
|
46
59
|
export declare function clearTokens(contextName: string): void;
|
|
60
|
+
export declare function saveApiKey(contextName: string, apiKey: string): void;
|
|
61
|
+
export declare function clearApiKey(contextName: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Auth type of a context, defaulting to `oauth2` for contexts stored before
|
|
64
|
+
* API-key support existed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getAuthType(context: Context): AuthType;
|
|
67
|
+
/** Whether a context holds the credential its auth type needs to call the API. */
|
|
68
|
+
export declare function hasCredentials(context: Context): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The error to show when a context can't authenticate. Kept here so every
|
|
71
|
+
* command — including the ~60 generated ones — points at the right remedy for
|
|
72
|
+
* the context's auth type instead of always suggesting `auth login`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function missingCredentialsMessage(context: Context): string;
|
|
75
|
+
/** Redact a key for display: first and last 4 characters only. */
|
|
76
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
47
77
|
export declare function getBaseUrls(environment: Environment): BaseUrls;
|
|
48
78
|
export { config };
|
package/dist/lib/config.js
CHANGED
|
@@ -133,6 +133,37 @@ export function getTokens(contextName) {
|
|
|
133
133
|
export function clearTokens(contextName) {
|
|
134
134
|
updateContext(contextName, { tokens: undefined });
|
|
135
135
|
}
|
|
136
|
+
export function saveApiKey(contextName, apiKey) {
|
|
137
|
+
updateContext(contextName, { api_key: apiKey, auth_type: 'api_key' });
|
|
138
|
+
}
|
|
139
|
+
export function clearApiKey(contextName) {
|
|
140
|
+
updateContext(contextName, { api_key: undefined });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Auth type of a context, defaulting to `oauth2` for contexts stored before
|
|
144
|
+
* API-key support existed.
|
|
145
|
+
*/
|
|
146
|
+
export function getAuthType(context) {
|
|
147
|
+
return context.auth_type ?? 'oauth2';
|
|
148
|
+
}
|
|
149
|
+
/** Whether a context holds the credential its auth type needs to call the API. */
|
|
150
|
+
export function hasCredentials(context) {
|
|
151
|
+
return getAuthType(context) === 'api_key' ? Boolean(context.api_key) : Boolean(context.tokens);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The error to show when a context can't authenticate. Kept here so every
|
|
155
|
+
* command — including the ~60 generated ones — points at the right remedy for
|
|
156
|
+
* the context's auth type instead of always suggesting `auth login`.
|
|
157
|
+
*/
|
|
158
|
+
export function missingCredentialsMessage(context) {
|
|
159
|
+
return getAuthType(context) === 'api_key'
|
|
160
|
+
? `No API key set for context "${context.name}". Run: aderis config set-api-key --context ${context.name}`
|
|
161
|
+
: `Not logged in to context "${context.name}". Run: aderis auth login --context ${context.name}`;
|
|
162
|
+
}
|
|
163
|
+
/** Redact a key for display: first and last 4 characters only. */
|
|
164
|
+
export function maskApiKey(apiKey) {
|
|
165
|
+
return apiKey.length <= 8 ? '*'.repeat(apiKey.length) : `${apiKey.slice(0, 4)}${'*'.repeat(8)}${apiKey.slice(-4)}`;
|
|
166
|
+
}
|
|
136
167
|
export function getBaseUrls(environment) {
|
|
137
168
|
return BASE_URLS[environment];
|
|
138
169
|
}
|