@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,5 +1,5 @@
|
|
|
1
1
|
import { Command, Args, Flags } from '@oclif/core';
|
|
2
|
-
import { apiRequest, ApiError, normalizeSortBy } from '../lib/api-client.js';
|
|
2
|
+
import { apiRequest, ApiError, isTextualMediaType, normalizeSortBy } from '../lib/api-client.js';
|
|
3
3
|
import { getCurrentContext, getContext, getBaseUrls, hasCredentials, missingCredentialsMessage } from '../lib/config.js';
|
|
4
4
|
// Add 'import' action if both create and update exist
|
|
5
5
|
const HAS_IMPORT = true;
|
|
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["events", "list", "create", "update", "delete"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'events': { method: 'GET', path: '/v1/event-processor-mappings/events', summary: 'List all valid event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'list': { method: 'GET', path: '/v1/event-processor-mappings', summary: 'List all event type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'create': { method: 'POST', path: '/v1/event-processor-mappings', summary: 'Create event type processor mappings', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['eventTypes', 'includes'] },
|
|
14
|
-
'update': { method: 'PATCH', path: '/v1/event-processor-mappings/{id}', summary: 'Update an event type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['includes'] },
|
|
15
|
-
'delete': { method: 'DELETE', path: '/v1/event-processor-mappings/{id}', summary: 'Delete an event type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'events': { method: 'GET', path: '/v1/event-processor-mappings/events', summary: 'List all valid event types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'list': { method: 'GET', path: '/v1/event-processor-mappings', summary: 'List all event type processor mappings for the tenant', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'create': { method: 'POST', path: '/v1/event-processor-mappings', summary: 'Create event type processor mappings', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['eventTypes', 'includes'] },
|
|
14
|
+
'update': { method: 'PATCH', path: '/v1/event-processor-mappings/{id}', summary: 'Update an event type processor mapping', pathParams: ['id'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['includes'] },
|
|
15
|
+
'delete': { method: 'DELETE', path: '/v1/event-processor-mappings/{id}', summary: 'Delete an event type processor mapping', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class EventProcessorMappings extends Command {
|
|
18
18
|
static description = 'Manage event processor mappings';
|
|
@@ -42,7 +42,7 @@ export default class EventProcessorMappings extends Command {
|
|
|
42
42
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
43
43
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
44
44
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
45
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
45
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
46
46
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
47
47
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
48
48
|
};
|
|
@@ -114,6 +114,8 @@ export default class EventProcessorMappings extends Command {
|
|
|
114
114
|
queryParams.search = flags.search;
|
|
115
115
|
if (flags['sort-by'])
|
|
116
116
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
117
|
+
if (flags.fields)
|
|
118
|
+
queryParams.fields = flags.fields;
|
|
117
119
|
if (flags.query) {
|
|
118
120
|
try {
|
|
119
121
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -261,10 +263,11 @@ export default class EventProcessorMappings extends Command {
|
|
|
261
263
|
process.stderr.write('\n');
|
|
262
264
|
}
|
|
263
265
|
try {
|
|
264
|
-
// Binary response:
|
|
266
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
265
267
|
if (endpoint.isBinaryResponse) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
+
const toStdout = !flags.file;
|
|
269
|
+
if (toStdout && process.stdout.isTTY) {
|
|
270
|
+
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.`);
|
|
268
271
|
}
|
|
269
272
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
270
273
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -272,24 +275,55 @@ export default class EventProcessorMappings extends Command {
|
|
|
272
275
|
contextName: flags.context,
|
|
273
276
|
binary: true,
|
|
274
277
|
});
|
|
275
|
-
const fsModule = await import('node:fs');
|
|
276
278
|
const binData = binResponse.data;
|
|
277
279
|
const buffer = Buffer.isBuffer(binData)
|
|
278
280
|
? binData
|
|
279
281
|
: Buffer.from(binData);
|
|
282
|
+
if (toStdout) {
|
|
283
|
+
// Nothing else may reach stdout here — it is the file.
|
|
284
|
+
process.stdout.write(buffer);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const fsModule = await import('node:fs');
|
|
280
288
|
fsModule.writeFileSync(flags.file, buffer);
|
|
281
289
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
282
290
|
return;
|
|
283
291
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
let response;
|
|
293
|
+
if (endpoint.hasBinaryVariant) {
|
|
294
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
295
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
296
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
297
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
298
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
299
|
+
data,
|
|
300
|
+
contextName: flags.context,
|
|
301
|
+
binary: true,
|
|
302
|
+
});
|
|
303
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
304
|
+
const rawData = raw.data;
|
|
305
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
306
|
+
if (!isTextualMediaType(rawType)) {
|
|
307
|
+
if (process.stdout.isTTY) {
|
|
308
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
309
|
+
}
|
|
310
|
+
// Nothing else may reach stdout here — it is the file.
|
|
311
|
+
process.stdout.write(buffer);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
318
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
319
|
+
data,
|
|
320
|
+
contextName: flags.context,
|
|
321
|
+
acceptAny: true,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
290
324
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
291
325
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
292
|
-
if (contentType
|
|
326
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
293
327
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
294
328
|
this.log(String(response.data));
|
|
295
329
|
return;
|
|
@@ -316,11 +350,6 @@ export default class EventProcessorMappings extends Command {
|
|
|
316
350
|
output = obj[arrayKeys[0]];
|
|
317
351
|
}
|
|
318
352
|
}
|
|
319
|
-
// Filter fields if --fields flag is provided
|
|
320
|
-
if (flags.fields) {
|
|
321
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
322
|
-
output = this.filterFields(output, fieldList);
|
|
323
|
-
}
|
|
324
353
|
// Sanitize output if requested
|
|
325
354
|
if (flags.sanitize) {
|
|
326
355
|
output = this.sanitizeOutput(output);
|
|
@@ -575,6 +604,8 @@ export default class EventProcessorMappings extends Command {
|
|
|
575
604
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
576
605
|
if (flags.include)
|
|
577
606
|
baseParams.include = flags.include;
|
|
607
|
+
if (flags.fields)
|
|
608
|
+
baseParams.fields = flags.fields;
|
|
578
609
|
if (flags.query) {
|
|
579
610
|
try {
|
|
580
611
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -629,12 +660,7 @@ export default class EventProcessorMappings extends Command {
|
|
|
629
660
|
}
|
|
630
661
|
}
|
|
631
662
|
process.stderr.write(' done\n');
|
|
632
|
-
// Filter fields if specified
|
|
633
663
|
let output = allItems;
|
|
634
|
-
if (flags.fields) {
|
|
635
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
636
|
-
output = this.filterFields(allItems, fieldList);
|
|
637
|
-
}
|
|
638
664
|
// Sanitize output if requested
|
|
639
665
|
if (flags.sanitize) {
|
|
640
666
|
output = this.sanitizeOutput(output);
|
|
@@ -963,56 +989,6 @@ export default class EventProcessorMappings extends Command {
|
|
|
963
989
|
return `\$${op}`;
|
|
964
990
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
965
991
|
}
|
|
966
|
-
filterFields(data, fields) {
|
|
967
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
968
|
-
const pick = (obj, keys) => {
|
|
969
|
-
// Group keys by their first segment so sibling sub-paths
|
|
970
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
971
|
-
// and don't clobber each other on merge.
|
|
972
|
-
const directKeys = new Set();
|
|
973
|
-
const nestedKeys = new Map();
|
|
974
|
-
for (const key of keys) {
|
|
975
|
-
if (key.split('.').some(isUnsafeKey))
|
|
976
|
-
continue;
|
|
977
|
-
if (key.includes('.')) {
|
|
978
|
-
const [first, ...rest] = key.split('.');
|
|
979
|
-
if (isUnsafeKey(first))
|
|
980
|
-
continue;
|
|
981
|
-
const list = nestedKeys.get(first);
|
|
982
|
-
if (list) {
|
|
983
|
-
list.push(rest.join('.'));
|
|
984
|
-
}
|
|
985
|
-
else {
|
|
986
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
directKeys.add(key);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const result = {};
|
|
994
|
-
for (const key of directKeys) {
|
|
995
|
-
if (key in obj)
|
|
996
|
-
result[key] = obj[key];
|
|
997
|
-
}
|
|
998
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
999
|
-
const val = obj[first];
|
|
1000
|
-
if (!val || typeof val !== 'object')
|
|
1001
|
-
continue;
|
|
1002
|
-
if (Array.isArray(val)) {
|
|
1003
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
result[first] = pick(val, restKeys);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return result;
|
|
1010
|
-
};
|
|
1011
|
-
if (Array.isArray(data)) {
|
|
1012
|
-
return data.map(item => pick(item, fields));
|
|
1013
|
-
}
|
|
1014
|
-
return pick(data, fields);
|
|
1015
|
-
}
|
|
1016
992
|
sanitizeOutput(data) {
|
|
1017
993
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1018
994
|
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/files.d.ts
CHANGED
package/dist/commands/files.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 = true;
|
|
@@ -8,18 +8,18 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["list", "create", "download-all", "download", "get", "references", "update", "discard", "move", "retention", "hold", "reclassify"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
-
'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
-
'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
-
'download': { method: 'GET', path: '/v1/files/{id}/download', summary: 'Get a signed download URL for a file', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
-
'get': { method: 'GET', path: '/v1/files/{id}', summary: 'Get a file by id (permission-scoped)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
-
'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
-
'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: ['tags', 'references'] },
|
|
18
|
-
'discard': { method: 'POST', path: '/v1/files/{fileId}/discard', summary: 'Discard a file (sets status DISCARDED; stays readable by id)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
19
|
-
'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
20
|
-
'retention': { method: 'POST', path: '/v1/files/{fileId}/retention', summary: 'Set or override retention period on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
21
|
-
'hold': { method: 'POST', path: '/v1/files/{fileId}/hold', summary: 'Set or release legal hold on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
22
|
-
'reclassify': { method: 'POST', path: '/v1/files/{fileId}/reclassify', summary: 'Reclassify a file and reprocess it through the inbox pipeline', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
11
|
+
'list': { method: 'GET', path: '/v1/files', summary: 'List files (paginated, permission-scoped)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
12
|
+
'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
13
|
+
'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: true, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
14
|
+
'download': { method: 'GET', path: '/v1/files/{id}/download', summary: 'Get a signed download URL for a file', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
15
|
+
'get': { method: 'GET', path: '/v1/files/{id}', summary: 'Get a file by id (permission-scoped)', pathParams: ['id'], hasBody: false, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
16
|
+
'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
17
|
+
'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: ['tags', 'references'] },
|
|
18
|
+
'discard': { method: 'POST', path: '/v1/files/{fileId}/discard', summary: 'Discard a file (sets status DISCARDED; stays readable by id)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
19
|
+
'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
20
|
+
'retention': { method: 'POST', path: '/v1/files/{fileId}/retention', summary: 'Set or override retention period on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
21
|
+
'hold': { method: 'POST', path: '/v1/files/{fileId}/hold', summary: 'Set or release legal hold on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
22
|
+
'reclassify': { method: 'POST', path: '/v1/files/{fileId}/reclassify', summary: 'Reclassify a file and reprocess it through the inbox pipeline', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, hasBinaryVariant: false, bodyArrayFields: [] },
|
|
23
23
|
};
|
|
24
24
|
export default class Files extends Command {
|
|
25
25
|
static description = 'Manage files';
|
|
@@ -50,7 +50,7 @@ export default class Files extends Command {
|
|
|
50
50
|
'sort-by': Flags.string({ description: 'Sort field(s), comma-separated. Direction (:ASC/:DESC) is optional; date-like fields default to DESC, others to ASC.' }),
|
|
51
51
|
filter: Flags.string({ description: 'Filter (e.g. --filter status=ACTIVE). Operators: in:A,B, like:%, gt:, lt:, null:', multiple: true }),
|
|
52
52
|
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
53
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to
|
|
53
|
+
fields: Flags.string({ description: 'Comma-separated list of fields to return, applied by the API (e.g. --fields=id,name,status)' }),
|
|
54
54
|
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
55
55
|
'show-http-request': Flags.boolean({ description: 'Show HTTP request details (method, URL, body)' }),
|
|
56
56
|
};
|
|
@@ -122,6 +122,8 @@ export default class Files extends Command {
|
|
|
122
122
|
queryParams.search = flags.search;
|
|
123
123
|
if (flags['sort-by'])
|
|
124
124
|
queryParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
125
|
+
if (flags.fields)
|
|
126
|
+
queryParams.fields = flags.fields;
|
|
125
127
|
if (flags.query) {
|
|
126
128
|
try {
|
|
127
129
|
Object.assign(queryParams, JSON.parse(flags.query));
|
|
@@ -269,10 +271,11 @@ export default class Files extends Command {
|
|
|
269
271
|
process.stderr.write('\n');
|
|
270
272
|
}
|
|
271
273
|
try {
|
|
272
|
-
// Binary response:
|
|
274
|
+
// Binary response: to -f / --file, or to stdout when it is redirected.
|
|
273
275
|
if (endpoint.isBinaryResponse) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
+
const toStdout = !flags.file;
|
|
277
|
+
if (toStdout && process.stdout.isTTY) {
|
|
278
|
+
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.`);
|
|
276
279
|
}
|
|
277
280
|
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
278
281
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
@@ -280,24 +283,55 @@ export default class Files extends Command {
|
|
|
280
283
|
contextName: flags.context,
|
|
281
284
|
binary: true,
|
|
282
285
|
});
|
|
283
|
-
const fsModule = await import('node:fs');
|
|
284
286
|
const binData = binResponse.data;
|
|
285
287
|
const buffer = Buffer.isBuffer(binData)
|
|
286
288
|
? binData
|
|
287
289
|
: Buffer.from(binData);
|
|
290
|
+
if (toStdout) {
|
|
291
|
+
// Nothing else may reach stdout here — it is the file.
|
|
292
|
+
process.stdout.write(buffer);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const fsModule = await import('node:fs');
|
|
288
296
|
fsModule.writeFileSync(flags.file, buffer);
|
|
289
297
|
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
290
298
|
return;
|
|
291
299
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
let response;
|
|
301
|
+
if (endpoint.hasBinaryVariant) {
|
|
302
|
+
// This route answers JSON or a file depending on the request body, so the
|
|
303
|
+
// bytes are read first and the response's own content-type decides. Asking
|
|
304
|
+
// for text here would corrupt an xlsx/pdf before anything could inspect it.
|
|
305
|
+
const raw = await apiRequest(endpoint.method, apiPath, {
|
|
306
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
307
|
+
data,
|
|
308
|
+
contextName: flags.context,
|
|
309
|
+
binary: true,
|
|
310
|
+
});
|
|
311
|
+
const rawType = String(raw.headers['content-type'] || '').toLowerCase();
|
|
312
|
+
const rawData = raw.data;
|
|
313
|
+
const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);
|
|
314
|
+
if (!isTextualMediaType(rawType)) {
|
|
315
|
+
if (process.stdout.isTTY) {
|
|
316
|
+
this.error(`The API returned ${rawType || 'binary data'}. Redirect stdout to a file, e.g. ... > report.xlsx`);
|
|
317
|
+
}
|
|
318
|
+
// Nothing else may reach stdout here — it is the file.
|
|
319
|
+
process.stdout.write(buffer);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
response = { data: buffer.toString('utf8'), headers: raw.headers, status: raw.status };
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
response = await apiRequest(endpoint.method, apiPath, {
|
|
326
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
327
|
+
data,
|
|
328
|
+
contextName: flags.context,
|
|
329
|
+
acceptAny: true,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
298
332
|
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
299
333
|
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
300
|
-
if (contentType
|
|
334
|
+
if (isTextualMediaType(contentType) && !contentType.startsWith('application/json')) {
|
|
301
335
|
// Output raw content for XML/text responses (CSV, plain, HTML, etc.)
|
|
302
336
|
this.log(String(response.data));
|
|
303
337
|
return;
|
|
@@ -324,11 +358,6 @@ export default class Files extends Command {
|
|
|
324
358
|
output = obj[arrayKeys[0]];
|
|
325
359
|
}
|
|
326
360
|
}
|
|
327
|
-
// Filter fields if --fields flag is provided
|
|
328
|
-
if (flags.fields) {
|
|
329
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
330
|
-
output = this.filterFields(output, fieldList);
|
|
331
|
-
}
|
|
332
361
|
// Sanitize output if requested
|
|
333
362
|
if (flags.sanitize) {
|
|
334
363
|
output = this.sanitizeOutput(output);
|
|
@@ -583,6 +612,8 @@ export default class Files extends Command {
|
|
|
583
612
|
baseParams.sortBy = normalizeSortBy(flags['sort-by']);
|
|
584
613
|
if (flags.include)
|
|
585
614
|
baseParams.include = flags.include;
|
|
615
|
+
if (flags.fields)
|
|
616
|
+
baseParams.fields = flags.fields;
|
|
586
617
|
if (flags.query) {
|
|
587
618
|
try {
|
|
588
619
|
Object.assign(baseParams, JSON.parse(flags.query));
|
|
@@ -637,12 +668,7 @@ export default class Files extends Command {
|
|
|
637
668
|
}
|
|
638
669
|
}
|
|
639
670
|
process.stderr.write(' done\n');
|
|
640
|
-
// Filter fields if specified
|
|
641
671
|
let output = allItems;
|
|
642
|
-
if (flags.fields) {
|
|
643
|
-
const fieldList = flags.fields.split(',').map(f => f.trim());
|
|
644
|
-
output = this.filterFields(allItems, fieldList);
|
|
645
|
-
}
|
|
646
672
|
// Sanitize output if requested
|
|
647
673
|
if (flags.sanitize) {
|
|
648
674
|
output = this.sanitizeOutput(output);
|
|
@@ -971,56 +997,6 @@ export default class Files extends Command {
|
|
|
971
997
|
return `\$${op}`;
|
|
972
998
|
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
973
999
|
}
|
|
974
|
-
filterFields(data, fields) {
|
|
975
|
-
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
976
|
-
const pick = (obj, keys) => {
|
|
977
|
-
// Group keys by their first segment so sibling sub-paths
|
|
978
|
-
// (e.g. 'items.amount' + 'items._type') resolve in one recursive pass
|
|
979
|
-
// and don't clobber each other on merge.
|
|
980
|
-
const directKeys = new Set();
|
|
981
|
-
const nestedKeys = new Map();
|
|
982
|
-
for (const key of keys) {
|
|
983
|
-
if (key.split('.').some(isUnsafeKey))
|
|
984
|
-
continue;
|
|
985
|
-
if (key.includes('.')) {
|
|
986
|
-
const [first, ...rest] = key.split('.');
|
|
987
|
-
if (isUnsafeKey(first))
|
|
988
|
-
continue;
|
|
989
|
-
const list = nestedKeys.get(first);
|
|
990
|
-
if (list) {
|
|
991
|
-
list.push(rest.join('.'));
|
|
992
|
-
}
|
|
993
|
-
else {
|
|
994
|
-
nestedKeys.set(first, [rest.join('.')]);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
else {
|
|
998
|
-
directKeys.add(key);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
const result = {};
|
|
1002
|
-
for (const key of directKeys) {
|
|
1003
|
-
if (key in obj)
|
|
1004
|
-
result[key] = obj[key];
|
|
1005
|
-
}
|
|
1006
|
-
for (const [first, restKeys] of nestedKeys) {
|
|
1007
|
-
const val = obj[first];
|
|
1008
|
-
if (!val || typeof val !== 'object')
|
|
1009
|
-
continue;
|
|
1010
|
-
if (Array.isArray(val)) {
|
|
1011
|
-
result[first] = val.map(item => pick(item, restKeys));
|
|
1012
|
-
}
|
|
1013
|
-
else {
|
|
1014
|
-
result[first] = pick(val, restKeys);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return result;
|
|
1018
|
-
};
|
|
1019
|
-
if (Array.isArray(data)) {
|
|
1020
|
-
return data.map(item => pick(item, fields));
|
|
1021
|
-
}
|
|
1022
|
-
return pick(data, fields);
|
|
1023
|
-
}
|
|
1024
1000
|
sanitizeOutput(data) {
|
|
1025
1001
|
const auditFields = ['_type', 'tenantId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
|
|
1026
1002
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|