@aderis-erp/cli 1.0.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -8
- package/dist/commands/access-groups.d.ts +0 -1
- package/dist/commands/access-groups.js +54 -78
- package/dist/commands/account-transaction-allocations.d.ts +0 -1
- package/dist/commands/account-transaction-allocations.js +51 -75
- package/dist/commands/account-transactions.d.ts +0 -1
- package/dist/commands/account-transactions.js +80 -94
- package/dist/commands/accounts.d.ts +0 -1
- package/dist/commands/accounts.js +59 -83
- package/dist/commands/agent-platform.d.ts +0 -1
- package/dist/commands/agent-platform.js +58 -82
- package/dist/commands/agent-runs.d.ts +39 -0
- package/dist/commands/agent-runs.js +1155 -0
- package/dist/commands/analytics.d.ts +0 -1
- package/dist/commands/analytics.js +52 -76
- package/dist/commands/{text-templates.d.ts → api-keys.d.ts} +1 -2
- package/dist/commands/api-keys.js +1150 -0
- package/dist/commands/async-jobs.d.ts +39 -0
- package/dist/commands/async-jobs.js +1158 -0
- package/dist/commands/audit-log.d.ts +0 -1
- package/dist/commands/audit-log.js +51 -75
- package/dist/commands/auth/login.d.ts +9 -0
- package/dist/commands/auth/login.js +73 -20
- package/dist/commands/auth/logout.d.ts +4 -0
- package/dist/commands/auth/logout.js +4 -0
- package/dist/commands/auth/status.js +26 -17
- package/dist/commands/auth/tenant.js +3 -6
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +5 -34
- package/dist/commands/bank-accounts.d.ts +0 -1
- package/dist/commands/bank-accounts.js +68 -91
- package/dist/commands/bank-integrations.d.ts +1 -1
- package/dist/commands/bank-integrations.js +71 -92
- package/dist/commands/business-partners.d.ts +1 -1
- package/dist/commands/business-partners.js +84 -100
- package/dist/commands/chat.d.ts +0 -1
- package/dist/commands/chat.js +57 -80
- package/dist/commands/config/add.d.ts +3 -0
- package/dist/commands/config/add.js +34 -16
- package/dist/commands/config/current.js +3 -0
- package/dist/commands/config/list.js +3 -0
- package/dist/commands/credit-card-accounts.d.ts +1 -1
- package/dist/commands/credit-card-accounts.js +72 -90
- package/dist/commands/delivery-notes.d.ts +0 -1
- package/dist/commands/delivery-notes.js +83 -105
- package/dist/commands/dunning-configs.d.ts +0 -1
- package/dist/commands/dunning-configs.js +54 -78
- package/dist/commands/dunning-reminders.d.ts +0 -1
- package/dist/commands/dunning-reminders.js +57 -81
- package/dist/commands/dunning-runs.d.ts +0 -1
- package/dist/commands/dunning-runs.js +50 -74
- package/dist/commands/email-transactions.d.ts +0 -1
- package/dist/commands/email-transactions.js +52 -76
- package/dist/commands/employees.d.ts +0 -1
- package/dist/commands/employees.js +81 -105
- package/dist/commands/entity-configurations.d.ts +0 -1
- package/dist/commands/entity-configurations.js +52 -76
- package/dist/commands/event-processor-mappings.d.ts +0 -1
- package/dist/commands/event-processor-mappings.js +54 -78
- package/dist/commands/files.d.ts +0 -1
- package/dist/commands/files.js +61 -85
- package/dist/commands/health.d.ts +0 -1
- package/dist/commands/health.js +54 -78
- package/dist/commands/imports.d.ts +39 -0
- package/dist/commands/imports.js +1151 -0
- package/dist/commands/inbox.d.ts +0 -1
- package/dist/commands/inbox.js +57 -80
- package/dist/commands/integration-identities.d.ts +40 -0
- package/dist/commands/integration-identities.js +1151 -0
- package/dist/commands/integrations.d.ts +0 -1
- package/dist/commands/integrations.js +52 -76
- package/dist/commands/item-categories.d.ts +0 -1
- package/dist/commands/item-categories.js +54 -78
- package/dist/commands/items.d.ts +0 -1
- package/dist/commands/items.js +54 -78
- package/dist/commands/journal-lines.d.ts +0 -1
- package/dist/commands/journal-lines.js +50 -74
- package/dist/commands/journals.d.ts +1 -1
- package/dist/commands/journals.js +75 -96
- package/dist/commands/ledgers.d.ts +3 -2
- package/dist/commands/ledgers.js +100 -95
- package/dist/commands/my-account.d.ts +39 -0
- package/dist/commands/my-account.js +1160 -0
- package/dist/commands/office-locations.d.ts +0 -1
- package/dist/commands/office-locations.js +54 -78
- package/dist/commands/payment-methods.d.ts +0 -1
- package/dist/commands/payment-methods.js +59 -83
- package/dist/commands/payment-runs.d.ts +0 -1
- package/dist/commands/payment-runs.js +66 -90
- package/dist/commands/payment-terms.d.ts +0 -1
- package/dist/commands/payment-terms.js +55 -79
- package/dist/commands/payments.d.ts +0 -1
- package/dist/commands/payments.js +56 -80
- package/dist/commands/payroll-runs.d.ts +43 -0
- package/dist/commands/payroll-runs.js +1188 -0
- package/dist/commands/permissions.d.ts +39 -0
- package/dist/commands/permissions.js +1146 -0
- package/dist/commands/price-lists.d.ts +0 -1
- package/dist/commands/price-lists.js +59 -83
- package/dist/commands/processor-mappings.d.ts +0 -1
- package/dist/commands/processor-mappings.js +53 -77
- package/dist/commands/processors.d.ts +0 -1
- package/dist/commands/processors.js +60 -85
- package/dist/commands/purchase-order-types.d.ts +0 -1
- package/dist/commands/purchase-order-types.js +54 -78
- package/dist/commands/purchase-orders.d.ts +0 -1
- package/dist/commands/purchase-orders.js +74 -97
- package/dist/commands/reports.d.ts +0 -1
- package/dist/commands/reports.js +54 -78
- package/dist/commands/roles.d.ts +39 -0
- package/dist/commands/{text-templates.js → roles.js} +64 -90
- package/dist/commands/salary-types.d.ts +0 -1
- package/dist/commands/salary-types.js +54 -78
- package/dist/commands/sales-invoices.d.ts +0 -1
- package/dist/commands/sales-invoices.js +90 -110
- package/dist/commands/sales-orders.d.ts +0 -1
- package/dist/commands/sales-orders.js +83 -105
- package/dist/commands/saved-views.d.ts +0 -1
- package/dist/commands/saved-views.js +52 -76
- package/dist/commands/scheduled-processor-mappings.d.ts +0 -1
- package/dist/commands/scheduled-processor-mappings.js +54 -78
- package/dist/commands/schedules.d.ts +0 -1
- package/dist/commands/schedules.js +58 -82
- package/dist/commands/switch.js +4 -4
- package/dist/commands/tag-configurations.d.ts +0 -1
- package/dist/commands/tag-configurations.js +56 -79
- package/dist/commands/tags.d.ts +0 -1
- package/dist/commands/tags.js +54 -78
- package/dist/commands/tax-codes.d.ts +0 -1
- package/dist/commands/tax-codes.js +57 -81
- package/dist/commands/tax-matrix.d.ts +0 -1
- package/dist/commands/tax-matrix.js +54 -78
- package/dist/commands/templates.d.ts +39 -0
- package/dist/commands/templates.js +1157 -0
- package/dist/commands/tenants.d.ts +0 -1
- package/dist/commands/tenants.js +58 -81
- package/dist/commands/tools.d.ts +0 -1
- package/dist/commands/tools.js +51 -75
- package/dist/commands/users.d.ts +42 -0
- package/dist/commands/users.js +1174 -0
- package/dist/commands/vat-declarations.d.ts +3 -1
- package/dist/commands/vat-declarations.js +78 -93
- package/dist/commands/vendor-invoices.d.ts +0 -1
- package/dist/commands/vendor-invoices.js +87 -110
- package/dist/commands/webhooks.d.ts +0 -1
- package/dist/commands/webhooks.js +56 -80
- package/dist/commands/workflows.d.ts +0 -1
- package/dist/commands/workflows.js +52 -76
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +16 -6
- package/dist/lib/auth/constants.d.ts +11 -0
- package/dist/lib/auth/constants.js +11 -0
- package/dist/lib/auth/errors.d.ts +17 -0
- package/dist/lib/auth/errors.js +22 -0
- package/dist/lib/auth/flow.d.ts +27 -0
- package/dist/lib/auth/flow.js +163 -0
- package/dist/lib/auth/guards.d.ts +5 -0
- package/dist/lib/auth/guards.js +14 -0
- package/dist/lib/auth/http.d.ts +16 -0
- package/dist/lib/auth/http.js +49 -0
- package/dist/lib/auth/index.d.ts +49 -0
- package/dist/lib/auth/index.js +228 -0
- package/dist/lib/auth/jwks.d.ts +19 -0
- package/dist/lib/auth/jwks.js +112 -0
- package/dist/lib/auth/keycloak-provider.d.ts +91 -0
- package/dist/lib/auth/keycloak-provider.js +244 -0
- package/dist/lib/auth/pages.d.ts +25 -0
- package/dist/lib/auth/pages.js +123 -0
- package/dist/lib/auth/pkce.d.ts +11 -0
- package/dist/lib/auth/pkce.js +17 -0
- package/dist/lib/auth/tokens.d.ts +59 -0
- package/dist/lib/auth/tokens.js +105 -0
- package/dist/lib/auth/types.d.ts +61 -0
- package/dist/lib/auth/types.js +1 -0
- package/dist/lib/auth/validated-url.d.ts +6 -0
- package/dist/lib/auth/validated-url.js +53 -0
- package/dist/lib/config.d.ts +13 -3
- package/dist/lib/config.js +24 -19
- package/oclif.manifest.json +2447 -641
- package/package.json +5 -4
- package/dist/lib/auth.d.ts +0 -43
- package/dist/lib/auth.js +0 -610
|
@@ -1,6 +1,7 @@
|
|
|
1
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 = [
|
|
@@ -8,6 +9,7 @@ export default class ConfigAdd extends Command {
|
|
|
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',
|
|
10
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',
|
|
11
13
|
];
|
|
12
14
|
static flags = {
|
|
13
15
|
'api-key': Flags.string({
|
|
@@ -25,14 +27,17 @@ export default class ConfigAdd extends Command {
|
|
|
25
27
|
}),
|
|
26
28
|
environment: Flags.string({
|
|
27
29
|
char: 'e',
|
|
28
|
-
description: 'Environment (dev, staging, live)',
|
|
29
|
-
options: ['dev', 'staging', 'live'],
|
|
30
|
+
description: 'Environment (dev, test, staging, live)',
|
|
31
|
+
options: ['dev', 'test', 'staging', 'live'],
|
|
30
32
|
}),
|
|
31
33
|
force: Flags.boolean({
|
|
32
34
|
char: 'f',
|
|
33
35
|
default: false,
|
|
34
36
|
description: 'Overwrite existing context without prompting',
|
|
35
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
|
+
}),
|
|
36
41
|
name: Flags.string({
|
|
37
42
|
char: 'n',
|
|
38
43
|
description: 'Context name',
|
|
@@ -74,6 +79,7 @@ export default class ConfigAdd extends Command {
|
|
|
74
79
|
choices: [
|
|
75
80
|
{ name: 'live (production)', value: 'live' },
|
|
76
81
|
{ name: 'staging', value: 'staging' },
|
|
82
|
+
{ name: 'test (QA)', value: 'test' },
|
|
77
83
|
{ name: 'dev (development)', value: 'dev' },
|
|
78
84
|
],
|
|
79
85
|
message: 'Environment:',
|
|
@@ -87,6 +93,9 @@ export default class ConfigAdd extends Command {
|
|
|
87
93
|
if (flags['client-id'] || flags['client-secret']) {
|
|
88
94
|
this.error('--client-id and --client-secret only apply to OAuth contexts; omit them with --auth-type api-key.');
|
|
89
95
|
}
|
|
96
|
+
if (flags['keycloak-url']) {
|
|
97
|
+
this.error('--keycloak-url only applies to OAuth contexts; omit it with --auth-type api-key.');
|
|
98
|
+
}
|
|
90
99
|
const apiKey = flags['api-key'] ??
|
|
91
100
|
(await password({
|
|
92
101
|
mask: true,
|
|
@@ -108,25 +117,21 @@ export default class ConfigAdd extends Command {
|
|
|
108
117
|
this.log(`\nNo login needed — the CLI is ready to use.`);
|
|
109
118
|
return;
|
|
110
119
|
}
|
|
111
|
-
|
|
120
|
+
const keycloakBaseUrl = this.resolveKeycloakBaseUrl(flags['keycloak-url']);
|
|
121
|
+
// Without a terminal the default client id is taken without confirmation.
|
|
112
122
|
let clientId = flags['client-id'];
|
|
113
123
|
if (!clientId) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const useDefault = await confirm({
|
|
124
|
+
const useDefault = !process.stdin.isTTY ||
|
|
125
|
+
(await confirm({
|
|
117
126
|
default: true,
|
|
118
|
-
message: `Use default client ID
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (!clientId) {
|
|
125
|
-
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({
|
|
126
132
|
message: 'OAuth client ID:',
|
|
127
133
|
validate: (value) => (value.trim() ? true : 'Client ID is required'),
|
|
128
134
|
});
|
|
129
|
-
}
|
|
130
135
|
}
|
|
131
136
|
// Get client secret (optional)
|
|
132
137
|
let clientSecret = flags['client-secret'];
|
|
@@ -148,10 +153,12 @@ export default class ConfigAdd extends Command {
|
|
|
148
153
|
client_id: clientId,
|
|
149
154
|
client_secret: clientSecret,
|
|
150
155
|
environment,
|
|
156
|
+
keycloak_base_url: keycloakBaseUrl,
|
|
151
157
|
name,
|
|
152
158
|
});
|
|
153
159
|
this.log(`\nContext "${name}" created successfully.`);
|
|
154
160
|
this.log(`Environment: ${environment}`);
|
|
161
|
+
this.log(`Keycloak URL: ${keycloakBaseUrlOf({ environment, keycloak_base_url: keycloakBaseUrl })}`);
|
|
155
162
|
this.log(`Client ID: ${clientId}`);
|
|
156
163
|
this.log(`\nTo authenticate, run: aderis auth login`);
|
|
157
164
|
}
|
|
@@ -161,6 +168,17 @@ export default class ConfigAdd extends Command {
|
|
|
161
168
|
* OAuth, and a non-TTY stdin means we must not block on a prompt — both keep
|
|
162
169
|
* scripted `config add` calls working exactly as before API-key support.
|
|
163
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
|
+
}
|
|
164
182
|
async resolveAuthType(flags) {
|
|
165
183
|
if (flags['auth-type']) {
|
|
166
184
|
return flags['auth-type'] === 'api-key' ? 'api_key' : 'oauth2';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { keycloakBaseUrlOf } from '../../lib/auth/index.js';
|
|
2
3
|
import { getAuthType, getCurrentContext, hasCredentials } from '../../lib/config.js';
|
|
3
4
|
export default class ConfigCurrent extends Command {
|
|
4
5
|
static description = 'Show the current context';
|
|
@@ -28,6 +29,7 @@ export default class ConfigCurrent extends Command {
|
|
|
28
29
|
authenticated: hasCredentials(context),
|
|
29
30
|
client_id: context.client_id,
|
|
30
31
|
environment: context.environment,
|
|
32
|
+
keycloak_base_url: getAuthType(context) === 'oauth2' ? keycloakBaseUrlOf(context) : undefined,
|
|
31
33
|
name: context.name,
|
|
32
34
|
tenant_id: context.tenant_id,
|
|
33
35
|
}, null, 2));
|
|
@@ -38,6 +40,7 @@ export default class ConfigCurrent extends Command {
|
|
|
38
40
|
this.log(`Environment: ${context.environment}`);
|
|
39
41
|
this.log(`Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
|
|
40
42
|
if (authType === 'oauth2') {
|
|
43
|
+
this.log(`Keycloak URL: ${keycloakBaseUrlOf(context)}`);
|
|
41
44
|
this.log(`Client ID: ${context.client_id ?? 'N/A'}`);
|
|
42
45
|
}
|
|
43
46
|
if (context.tenant_id) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { keycloakBaseUrlOf } from '../../lib/auth/index.js';
|
|
2
3
|
import { getAuthType, getCurrentContext, hasCredentials, listContexts } from '../../lib/config.js';
|
|
3
4
|
export default class ConfigList extends Command {
|
|
4
5
|
static description = 'List all configured contexts';
|
|
@@ -20,6 +21,7 @@ export default class ConfigList extends Command {
|
|
|
20
21
|
authenticated: hasCredentials(c),
|
|
21
22
|
client_id: c.client_id,
|
|
22
23
|
environment: c.environment,
|
|
24
|
+
keycloak_base_url: getAuthType(c) === 'oauth2' ? keycloakBaseUrlOf(c) : undefined,
|
|
23
25
|
name: c.name,
|
|
24
26
|
tenant_id: c.tenant_id,
|
|
25
27
|
})),
|
|
@@ -42,6 +44,7 @@ export default class ConfigList extends Command {
|
|
|
42
44
|
this.log(` Environment: ${ctx.environment}`);
|
|
43
45
|
this.log(` Auth: ${authType === 'api_key' ? 'API key (x-api-key)' : 'oauth2'}`);
|
|
44
46
|
if (authType === 'oauth2') {
|
|
47
|
+
this.log(` Keycloak URL: ${keycloakBaseUrlOf(ctx)}`);
|
|
45
48
|
this.log(` Client ID: ${ctx.client_id ?? 'N/A'}`);
|
|
46
49
|
}
|
|
47
50
|
this.log(` Authenticated: ${auth}`);
|
|
@@ -16,6 +16,7 @@ export default class CreditCardAccounts extends Command {
|
|
|
16
16
|
set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
17
|
'mapping-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
18
|
'note-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
'card-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
20
|
page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
21
|
limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
22
|
include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -33,7 +34,6 @@ export default class CreditCardAccounts extends Command {
|
|
|
33
34
|
private runCamt053Import;
|
|
34
35
|
private runNestedImport;
|
|
35
36
|
private parseFilterValue;
|
|
36
|
-
private filterFields;
|
|
37
37
|
private sanitizeOutput;
|
|
38
38
|
private getColumns;
|
|
39
39
|
private getValue;
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
6
6
|
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
|
-
const NESTED_IMPORTS = ["ledger-mappings", "notes"];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
8
|
+
const NESTED_IMPORTS = ["ledger-mappings", "notes", "cards"];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "list-ledger-mappings", "create-ledger-mappings", "get-ledger-mappings", "update-ledger-mappings", "delete-ledger-mappings", "list-notes", "create-notes", "get-notes", "update-notes", "delete-notes", "list-cards", "create-cards", "get-cards", "update-cards", "delete-cards"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['
|
|
13
|
-
'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
-
'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
-
'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
21
|
-
'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
22
|
-
'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
23
|
-
'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
24
|
-
'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
25
|
-
'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/credit-card-accounts', summary: 'Get all credit card accounts with pagination', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/credit-card-accounts', summary: 'Create a new credit card account (with optional notes)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/credit-card-accounts/{id}', summary: 'Get a credit card account by ID', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/credit-card-accounts/{id}', summary: 'Update a credit card account', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['notes'] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/credit-card-accounts/{id}', summary: 'Delete a credit card account', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'list-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Get all ledger mappings for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'create-ledger-mappings': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings', summary: 'Create a ledger mapping for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
+
'get-ledger-mappings': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Get a ledger mapping by ID', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'update-ledger-mappings': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Update a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'delete-ledger-mappings': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/ledger-mappings/{mappingId}', summary: 'Delete a ledger mapping', pathParams: ['creditCardAccountId', 'mappingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
+
'list-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Get all notes for a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
+
'create-notes': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes', summary: 'Create a note for a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
|
+
'get-notes': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Get a specific note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
24
|
+
'update-notes': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Update a note for a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
25
|
+
'delete-notes': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/notes/{noteId}', summary: 'Delete a note from a credit card account', pathParams: ['creditCardAccountId', 'noteId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
|
+
'list-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Get all cards on a credit card account', pathParams: ['creditCardAccountId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
27
|
+
'create-cards': { method: 'POST', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards', summary: 'Create a card on a credit card account', pathParams: ['creditCardAccountId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
28
|
+
'get-cards': { method: 'GET', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Get a card by ID', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
29
|
+
'update-cards': { method: 'PATCH', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Update a card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
30
|
+
'delete-cards': { method: 'DELETE', path: '/v1/credit-card-accounts/{creditCardAccountId}/cards/{cardId}', summary: 'Delete a manual card', pathParams: ['creditCardAccountId', 'cardId'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
26
31
|
};
|
|
27
32
|
export default class CreditCardAccounts extends Command {
|
|
28
33
|
static description = 'Manage credit card accounts';
|
|
@@ -48,6 +53,7 @@ export default class CreditCardAccounts extends Command {
|
|
|
48
53
|
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
49
54
|
'mapping-id': Flags.string({ description: 'Path param: mappingId' }),
|
|
50
55
|
'note-id': Flags.string({ description: 'Path param: noteId' }),
|
|
56
|
+
'card-id': Flags.string({ description: 'Path param: cardId' }),
|
|
51
57
|
page: Flags.string({ description: 'Page number' }),
|
|
52
58
|
limit: Flags.string({ description: 'Items per page' }),
|
|
53
59
|
include: Flags.string({ description: 'Relations to include' }),
|
|
@@ -55,7 +61,7 @@ export default class CreditCardAccounts extends Command {
|
|
|
55
61
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
56
62
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
57
63
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
58
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
64
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
59
65
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
60
66
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
61
67
|
};
|
|
@@ -127,6 +133,8 @@ export default class CreditCardAccounts extends Command {
|
|
|
127
133
|
queryParams.search = flags.search;
|
|
128
134
|
if (flags['sort-by'])
|
|
129
135
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
136
|
+
if (flags.fields)
|
|
137
|
+
queryParams.fields = flags.fields;
|
|
130
138
|
if (flags.query) {
|
|
131
139
|
try {
|
|
132
140
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -274,10 +282,11 @@ export default class CreditCardAccounts extends Command {
|
|
|
274
282
|
process.stderr.write('\n');
|
|
275
283
|
}
|
|
276
284
|
try {
|
|
277
|
-
// Binary response:
|
|
285
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
278
286
|
if (endpoint.isBinaryResponse) {
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
const toStdout = !flags.file;
|
|
288
|
+
if (toStdout && process.stdout.isTTY) {
|
|
289
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx), or redirect stdout to a file.`);
|
|
281
290
|
}
|
|
282
291
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
283
292
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -285,24 +294,55 @@ export default class CreditCardAccounts extends Command {
|
|
|
285
294
|
contextName: flags.context,
|
|
286
295
|
binary: true,
|
|
287
296
|
});
|
|
288
|
-
const fsModule = await import('node:fs');
|
|
289
297
|
const binData = binResponse.data;
|
|
290
298
|
const buffer = Buffer.isBuffer(binData)
|
|
291
299
|
? binData
|
|
292
300
|
: Buffer.from(binData);
|
|
301
|
+
if (toStdout) {
|
|
302
|
+
// Nothing else may reach stdout here — it is the file.
|
|
303
|
+
process.stdout.write(buffer);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const fsModule = await import('node:fs');
|
|
293
307
|
fsModule.writeFileSync(flags.file, buffer);
|
|
294
308
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
295
309
|
return;
|
|
296
310
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
311
|
+
let response;
|
|
312
|
+
if (endpoint.hasBinaryVariant) {
|
|
313
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
314
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
315
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
316
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
317
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
318
|
+
data,
|
|
319
|
+
contextName: flags.context,
|
|
320
|
+
binary: true,
|
|
321
|
+
});
|
|
322
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
323
|
+
const rawData = raw.data;
|
|
324
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
325
|
+
if (!isTextualMediaType(rawType)) {
|
|
326
|
+
if (process.stdout.isTTY) {
|
|
327
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
328
|
+
}
|
|
329
|
+
// Nothing else may reach stdout here — it is the file.
|
|
330
|
+
process.stdout.write(buffer);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
337
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
338
|
+
data,
|
|
339
|
+
contextName: flags.context,
|
|
340
|
+
acceptAny: true,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
303
343
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
304
344
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
305
|
-
if (contentType
|
|
345
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
306
346
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
307
347
|
this.log(String(response.data));
|
|
308
348
|
return;
|
|
@@ -329,11 +369,6 @@ export default class CreditCardAccounts extends Command {
|
|
|
329
369
|
output = obj[arrayKeys[0]];
|
|
330
370
|
}
|
|
331
371
|
}
|
|
332
|
-
// Filter fields if --fields flag is provided
|
|
333
|
-
if (flags.fields) {
|
|
334
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
335
|
-
output = this.filterFields(output, fieldList);
|
|
336
|
-
}
|
|
337
372
|
// Sanitize output if requested
|
|
338
373
|
if (flags.sanitize) {
|
|
339
374
|
output = this.sanitizeOutput(output);
|
|
@@ -588,6 +623,8 @@ export default class CreditCardAccounts extends Command {
|
|
|
588
623
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
589
624
|
if (flags.include)
|
|
590
625
|
baseParams.include = flags.include;
|
|
626
|
+
if (flags.fields)
|
|
627
|
+
baseParams.fields = flags.fields;
|
|
591
628
|
if (flags.query) {
|
|
592
629
|
try {
|
|
593
630
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -642,12 +679,7 @@ export default class CreditCardAccounts extends Command {
|
|
|
642
679
|
}
|
|
643
680
|
}
|
|
644
681
|
process.stderr.write(' done\n');
|
|
645
|
-
// Filter fields if specified
|
|
646
682
|
let output = allItems;
|
|
647
|
-
if (flags.fields) {
|
|
648
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
649
|
-
output = this.filterFields(allItems, fieldList);
|
|
650
|
-
}
|
|
651
683
|
// Sanitize output if requested
|
|
652
684
|
if (flags.sanitize) {
|
|
653
685
|
output = this.sanitizeOutput(output);
|
|
@@ -976,56 +1008,6 @@ export default class CreditCardAccounts extends Command {
|
|
|
976
1008
|
return `\$${op}`;
|
|
977
1009
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
978
1010
|
}
|
|
979
|
-
filterFields(data, fields) {
|
|
980
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
981
|
-
const pick = (obj, keys) => {
|
|
982
|
-
// Group keys by their first segment so sibling sub-paths
|
|
983
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
984
|
-
// and don't clobber each other on merge.
|
|
985
|
-
const directKeys = new Set();
|
|
986
|
-
const nestedKeys = new Map();
|
|
987
|
-
for (const key of keys) {
|
|
988
|
-
if (key.split('.').some(isUnsafeKey))
|
|
989
|
-
continue;
|
|
990
|
-
if (key.includes('.')) {
|
|
991
|
-
const [first, ...rest] = key.split('.');
|
|
992
|
-
if (isUnsafeKey(first))
|
|
993
|
-
continue;
|
|
994
|
-
const list = nestedKeys.get(first);
|
|
995
|
-
if (list) {
|
|
996
|
-
list.push(rest.join('.'));
|
|
997
|
-
}
|
|
998
|
-
else {
|
|
999
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
directKeys.add(key);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
const result = {};
|
|
1007
|
-
for (const key of directKeys) {
|
|
1008
|
-
if (key in obj)
|
|
1009
|
-
result[key] = obj[key];
|
|
1010
|
-
}
|
|
1011
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1012
|
-
const val = obj[first];
|
|
1013
|
-
if (!val || typeof val !== 'object')
|
|
1014
|
-
continue;
|
|
1015
|
-
if (Array.isArray(val)) {
|
|
1016
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1017
|
-
}
|
|
1018
|
-
else {
|
|
1019
|
-
result[first] = pick(val, restKeys);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
return result;
|
|
1023
|
-
};
|
|
1024
|
-
if (Array.isArray(data)) {
|
|
1025
|
-
return data.map(item => pick(item, fields));
|
|
1026
|
-
}
|
|
1027
|
-
return pick(data, fields);
|
|
1028
|
-
}
|
|
1029
1011
|
sanitizeOutput(data) {
|
|
1030
1012
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1031
1013
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|