@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
package/dist/commands/tenants.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
6
6
|
const HAS_EXPORT = true;
|
|
7
7
|
const HAS_CAMT053 = false;
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "logo", "icon"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
9
|
+
const SUBCOMMANDS = [...["list", "create", "get", "update", "delete", "logo", "icon", "update-identity-provider"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/tenants', summary: 'Get all tenants with pagination (admin only)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/tenants', summary: 'Create a new tenant (admin only)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['supportedLocales', 'allowedSenders', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'salesOrderRecipientCc', 'salesOrderRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc'] },
|
|
13
|
-
'get': { method: 'GET', path: '/v1/tenants/{id}', summary: 'Get a tenant by ID (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/tenants/{id}', summary: 'Update a tenant (own tenant or admin)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['supportedLocales', 'allowedSenders', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'salesOrderRecipientCc', 'salesOrderRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc'] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/tenants/{id}', summary: 'Delete a tenant (admin only)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'logo': { method: 'POST', path: '/v1/tenants/{id}/logo', summary: 'Upload the tenant logo to the public bucket and set logoUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'icon': { method: 'POST', path: '/v1/tenants/{id}/icon', summary: 'Upload the tenant icon to the public bucket and set iconUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/tenants', summary: 'Get all tenants with pagination (admin only)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/tenants', summary: 'Create a new tenant (admin only)', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['supportedLocales', 'allowedSenders', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'salesOrderRecipientCc', 'salesOrderRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc'] },
|
|
13
|
+
'get': { method: 'GET', path: '/v1/tenants/{id}', summary: 'Get a tenant by ID (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/tenants/{id}', summary: 'Update a tenant (own tenant or admin)', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['supportedLocales', 'allowedSenders', 'salesInvoiceRecipientCc', 'salesInvoiceRecipientBcc', 'salesOrderRecipientCc', 'salesOrderRecipientBcc', 'deliveryNoteRecipientCc', 'deliveryNoteRecipientBcc'] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/tenants/{id}', summary: 'Delete a tenant (admin only)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'logo': { method: 'POST', path: '/v1/tenants/{id}/logo', summary: 'Upload the tenant logo to the public bucket and set logoUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'icon': { method: 'POST', path: '/v1/tenants/{id}/icon', summary: 'Upload the tenant icon to the public bucket and set iconUrl (own tenant or admin)', pathParams: ['id'], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
|
+
'update-identity-provider': { method: 'PUT', path: '/v1/tenants/{id}/identity-provider', summary: 'Synchronize a tenant with the identity provider (admin only)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
18
19
|
};
|
|
19
20
|
export default class Tenants extends Command {
|
|
20
21
|
static description = 'Manage tenants';
|
|
@@ -45,7 +46,7 @@ export default class Tenants extends Command {
|
|
|
45
46
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
46
47
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
47
48
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
48
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
49
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
49
50
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
50
51
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
51
52
|
};
|
|
@@ -117,6 +118,8 @@ export default class Tenants extends Command {
|
|
|
117
118
|
queryParams.search = flags.search;
|
|
118
119
|
if (flags['sort-by'])
|
|
119
120
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
121
|
+
if (flags.fields)
|
|
122
|
+
queryParams.fields = flags.fields;
|
|
120
123
|
if (flags.query) {
|
|
121
124
|
try {
|
|
122
125
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -264,10 +267,11 @@ export default class Tenants extends Command {
|
|
|
264
267
|
process.stderr.write('\n');
|
|
265
268
|
}
|
|
266
269
|
try {
|
|
267
|
-
// Binary response:
|
|
270
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
268
271
|
if (endpoint.isBinaryResponse) {
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
const toStdout = !flags.file;
|
|
273
|
+
if (toStdout && process.stdout.isTTY) {
|
|
274
|
+
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.`);
|
|
271
275
|
}
|
|
272
276
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
273
277
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -275,24 +279,55 @@ export default class Tenants extends Command {
|
|
|
275
279
|
contextName: flags.context,
|
|
276
280
|
binary: true,
|
|
277
281
|
});
|
|
278
|
-
const fsModule = await import('node:fs');
|
|
279
282
|
const binData = binResponse.data;
|
|
280
283
|
const buffer = Buffer.isBuffer(binData)
|
|
281
284
|
? binData
|
|
282
285
|
: Buffer.from(binData);
|
|
286
|
+
if (toStdout) {
|
|
287
|
+
// Nothing else may reach stdout here — it is the file.
|
|
288
|
+
process.stdout.write(buffer);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const fsModule = await import('node:fs');
|
|
283
292
|
fsModule.writeFileSync(flags.file, buffer);
|
|
284
293
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
285
294
|
return;
|
|
286
295
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
let response;
|
|
297
|
+
if (endpoint.hasBinaryVariant) {
|
|
298
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
299
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
300
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
301
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
302
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
303
|
+
data,
|
|
304
|
+
contextName: flags.context,
|
|
305
|
+
binary: true,
|
|
306
|
+
});
|
|
307
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
308
|
+
const rawData = raw.data;
|
|
309
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
310
|
+
if (!isTextualMediaType(rawType)) {
|
|
311
|
+
if (process.stdout.isTTY) {
|
|
312
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
313
|
+
}
|
|
314
|
+
// Nothing else may reach stdout here — it is the file.
|
|
315
|
+
process.stdout.write(buffer);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
322
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
323
|
+
data,
|
|
324
|
+
contextName: flags.context,
|
|
325
|
+
acceptAny: true,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
293
328
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
294
329
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
295
|
-
if (contentType
|
|
330
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
296
331
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
297
332
|
this.log(String(response.data));
|
|
298
333
|
return;
|
|
@@ -319,11 +354,6 @@ export default class Tenants extends Command {
|
|
|
319
354
|
output = obj[arrayKeys[0]];
|
|
320
355
|
}
|
|
321
356
|
}
|
|
322
|
-
// Filter fields if --fields flag is provided
|
|
323
|
-
if (flags.fields) {
|
|
324
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
325
|
-
output = this.filterFields(output, fieldList);
|
|
326
|
-
}
|
|
327
357
|
// Sanitize output if requested
|
|
328
358
|
if (flags.sanitize) {
|
|
329
359
|
output = this.sanitizeOutput(output);
|
|
@@ -578,6 +608,8 @@ export default class Tenants extends Command {
|
|
|
578
608
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
579
609
|
if (flags.include)
|
|
580
610
|
baseParams.include = flags.include;
|
|
611
|
+
if (flags.fields)
|
|
612
|
+
baseParams.fields = flags.fields;
|
|
581
613
|
if (flags.query) {
|
|
582
614
|
try {
|
|
583
615
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -632,12 +664,7 @@ export default class Tenants extends Command {
|
|
|
632
664
|
}
|
|
633
665
|
}
|
|
634
666
|
process.stderr.write(' done\n');
|
|
635
|
-
// Filter fields if specified
|
|
636
667
|
let output = allItems;
|
|
637
|
-
if (flags.fields) {
|
|
638
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
639
|
-
output = this.filterFields(allItems, fieldList);
|
|
640
|
-
}
|
|
641
668
|
// Sanitize output if requested
|
|
642
669
|
if (flags.sanitize) {
|
|
643
670
|
output = this.sanitizeOutput(output);
|
|
@@ -966,56 +993,6 @@ export default class Tenants extends Command {
|
|
|
966
993
|
return `\$${op}`;
|
|
967
994
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
968
995
|
}
|
|
969
|
-
filterFields(data, fields) {
|
|
970
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
971
|
-
const pick = (obj, keys) => {
|
|
972
|
-
// Group keys by their first segment so sibling sub-paths
|
|
973
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
974
|
-
// and don't clobber each other on merge.
|
|
975
|
-
const directKeys = new Set();
|
|
976
|
-
const nestedKeys = new Map();
|
|
977
|
-
for (const key of keys) {
|
|
978
|
-
if (key.split('.').some(isUnsafeKey))
|
|
979
|
-
continue;
|
|
980
|
-
if (key.includes('.')) {
|
|
981
|
-
const [first, ...rest] = key.split('.');
|
|
982
|
-
if (isUnsafeKey(first))
|
|
983
|
-
continue;
|
|
984
|
-
const list = nestedKeys.get(first);
|
|
985
|
-
if (list) {
|
|
986
|
-
list.push(rest.join('.'));
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
directKeys.add(key);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
const result = {};
|
|
997
|
-
for (const key of directKeys) {
|
|
998
|
-
if (key in obj)
|
|
999
|
-
result[key] = obj[key];
|
|
1000
|
-
}
|
|
1001
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1002
|
-
const val = obj[first];
|
|
1003
|
-
if (!val || typeof val !== 'object')
|
|
1004
|
-
continue;
|
|
1005
|
-
if (Array.isArray(val)) {
|
|
1006
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1007
|
-
}
|
|
1008
|
-
else {
|
|
1009
|
-
result[first] = pick(val, restKeys);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return result;
|
|
1013
|
-
};
|
|
1014
|
-
if (Array.isArray(data)) {
|
|
1015
|
-
return data.map(item => pick(item, fields));
|
|
1016
|
-
}
|
|
1017
|
-
return pick(data, fields);
|
|
1018
|
-
}
|
|
1019
996
|
sanitizeOutput(data) {
|
|
1020
997
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1021
998
|
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/tools.d.ts
CHANGED
package/dist/commands/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = false;
|
|
@@ -8,8 +8,8 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "find"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'find': { method: 'GET', path: '/v1/tools/{name}', summary: 'Get a single tool by canonical name', pathParams: ['name'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/tools', summary: 'List all available tools', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'find': { method: 'GET', path: '/v1/tools/{name}', summary: 'Get a single tool by canonical name', pathParams: ['name'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
13
|
};
|
|
14
14
|
export default class Tools extends Command {
|
|
15
15
|
static description = 'Manage tools';
|
|
@@ -36,7 +36,7 @@ export default class Tools extends Command {
|
|
|
36
36
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
37
37
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
38
38
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
39
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
39
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
40
40
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
41
41
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
42
42
|
};
|
|
@@ -108,6 +108,8 @@ export default class Tools extends Command {
|
|
|
108
108
|
queryParams.search = flags.search;
|
|
109
109
|
if (flags['sort-by'])
|
|
110
110
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
111
|
+
if (flags.fields)
|
|
112
|
+
queryParams.fields = flags.fields;
|
|
111
113
|
if (flags.query) {
|
|
112
114
|
try {
|
|
113
115
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -255,10 +257,11 @@ export default class Tools extends Command {
|
|
|
255
257
|
process.stderr.write('\n');
|
|
256
258
|
}
|
|
257
259
|
try {
|
|
258
|
-
// Binary response:
|
|
260
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
259
261
|
if (endpoint.isBinaryResponse) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
const toStdout = !flags.file;
|
|
263
|
+
if (toStdout && process.stdout.isTTY) {
|
|
264
|
+
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.`);
|
|
262
265
|
}
|
|
263
266
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
264
267
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -266,24 +269,55 @@ export default class Tools extends Command {
|
|
|
266
269
|
contextName: flags.context,
|
|
267
270
|
binary: true,
|
|
268
271
|
});
|
|
269
|
-
const fsModule = await import('node:fs');
|
|
270
272
|
const binData = binResponse.data;
|
|
271
273
|
const buffer = Buffer.isBuffer(binData)
|
|
272
274
|
? binData
|
|
273
275
|
: Buffer.from(binData);
|
|
276
|
+
if (toStdout) {
|
|
277
|
+
// Nothing else may reach stdout here — it is the file.
|
|
278
|
+
process.stdout.write(buffer);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const fsModule = await import('node:fs');
|
|
274
282
|
fsModule.writeFileSync(flags.file, buffer);
|
|
275
283
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
276
284
|
return;
|
|
277
285
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
let response;
|
|
287
|
+
if (endpoint.hasBinaryVariant) {
|
|
288
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
289
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
290
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
291
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
292
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
293
|
+
data,
|
|
294
|
+
contextName: flags.context,
|
|
295
|
+
binary: true,
|
|
296
|
+
});
|
|
297
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
298
|
+
const rawData = raw.data;
|
|
299
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
300
|
+
if (!isTextualMediaType(rawType)) {
|
|
301
|
+
if (process.stdout.isTTY) {
|
|
302
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
303
|
+
}
|
|
304
|
+
// Nothing else may reach stdout here — it is the file.
|
|
305
|
+
process.stdout.write(buffer);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
312
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
313
|
+
data,
|
|
314
|
+
contextName: flags.context,
|
|
315
|
+
acceptAny: true,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
284
318
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
285
319
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
286
|
-
if (contentType
|
|
320
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
287
321
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
288
322
|
this.log(String(response.data));
|
|
289
323
|
return;
|
|
@@ -310,11 +344,6 @@ export default class Tools extends Command {
|
|
|
310
344
|
output = obj[arrayKeys[0]];
|
|
311
345
|
}
|
|
312
346
|
}
|
|
313
|
-
// Filter fields if --fields flag is provided
|
|
314
|
-
if (flags.fields) {
|
|
315
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
316
|
-
output = this.filterFields(output, fieldList);
|
|
317
|
-
}
|
|
318
347
|
// Sanitize output if requested
|
|
319
348
|
if (flags.sanitize) {
|
|
320
349
|
output = this.sanitizeOutput(output);
|
|
@@ -569,6 +598,8 @@ export default class Tools extends Command {
|
|
|
569
598
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
570
599
|
if (flags.include)
|
|
571
600
|
baseParams.include = flags.include;
|
|
601
|
+
if (flags.fields)
|
|
602
|
+
baseParams.fields = flags.fields;
|
|
572
603
|
if (flags.query) {
|
|
573
604
|
try {
|
|
574
605
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -623,12 +654,7 @@ export default class Tools extends Command {
|
|
|
623
654
|
}
|
|
624
655
|
}
|
|
625
656
|
process.stderr.write(' done\n');
|
|
626
|
-
// Filter fields if specified
|
|
627
657
|
let output = allItems;
|
|
628
|
-
if (flags.fields) {
|
|
629
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
630
|
-
output = this.filterFields(allItems, fieldList);
|
|
631
|
-
}
|
|
632
658
|
// Sanitize output if requested
|
|
633
659
|
if (flags.sanitize) {
|
|
634
660
|
output = this.sanitizeOutput(output);
|
|
@@ -957,56 +983,6 @@ export default class Tools extends Command {
|
|
|
957
983
|
return `\$${op}`;
|
|
958
984
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
959
985
|
}
|
|
960
|
-
filterFields(data, fields) {
|
|
961
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
962
|
-
const pick = (obj, keys) => {
|
|
963
|
-
// Group keys by their first segment so sibling sub-paths
|
|
964
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
965
|
-
// and don't clobber each other on merge.
|
|
966
|
-
const directKeys = new Set();
|
|
967
|
-
const nestedKeys = new Map();
|
|
968
|
-
for (const key of keys) {
|
|
969
|
-
if (key.split('.').some(isUnsafeKey))
|
|
970
|
-
continue;
|
|
971
|
-
if (key.includes('.')) {
|
|
972
|
-
const [first, ...rest] = key.split('.');
|
|
973
|
-
if (isUnsafeKey(first))
|
|
974
|
-
continue;
|
|
975
|
-
const list = nestedKeys.get(first);
|
|
976
|
-
if (list) {
|
|
977
|
-
list.push(rest.join('.'));
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
else {
|
|
984
|
-
directKeys.add(key);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
const result = {};
|
|
988
|
-
for (const key of directKeys) {
|
|
989
|
-
if (key in obj)
|
|
990
|
-
result[key] = obj[key];
|
|
991
|
-
}
|
|
992
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
993
|
-
const val = obj[first];
|
|
994
|
-
if (!val || typeof val !== 'object')
|
|
995
|
-
continue;
|
|
996
|
-
if (Array.isArray(val)) {
|
|
997
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
998
|
-
}
|
|
999
|
-
else {
|
|
1000
|
-
result[first] = pick(val, restKeys);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return result;
|
|
1004
|
-
};
|
|
1005
|
-
if (Array.isArray(data)) {
|
|
1006
|
-
return data.map(item => pick(item, fields));
|
|
1007
|
-
}
|
|
1008
|
-
return pick(data, fields);
|
|
1009
|
-
}
|
|
1010
986
|
sanitizeOutput(data) {
|
|
1011
987
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1012
988
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Users extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
context: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
table: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
set: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
'credential-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
'role-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
page: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
|
+
limit: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
|
+
include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
|
+
search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
+
'sort-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
|
+
filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
fields: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
sanitize: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
29
|
+
'show-http-request': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
30
|
+
};
|
|
31
|
+
run(): Promise<void>;
|
|
32
|
+
private runImport;
|
|
33
|
+
private runExport;
|
|
34
|
+
private runCamt053Import;
|
|
35
|
+
private runNestedImport;
|
|
36
|
+
private parseFilterValue;
|
|
37
|
+
private sanitizeOutput;
|
|
38
|
+
private getColumns;
|
|
39
|
+
private getValue;
|
|
40
|
+
private printTable;
|
|
41
|
+
private printCsv;
|
|
42
|
+
}
|