@aderis-erp/cli 1.0.3 → 1.0.7
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 +6 -0
- package/dist/commands/access-groups.d.ts +1 -0
- package/dist/commands/access-groups.js +84 -28
- package/dist/commands/{async-endpoints-internal.d.ts → account-transaction-allocations.d.ts} +3 -2
- package/dist/commands/{async-endpoints-internal.js → account-transaction-allocations.js} +93 -33
- package/dist/commands/account-transactions.d.ts +1 -0
- package/dist/commands/account-transactions.js +96 -40
- package/dist/commands/accounts.d.ts +1 -0
- package/dist/commands/accounts.js +89 -33
- package/dist/commands/agent-runs.d.ts +1 -0
- package/dist/commands/agent-runs.js +85 -27
- package/dist/commands/audit-log.d.ts +1 -0
- package/dist/commands/audit-log.js +82 -26
- package/dist/commands/auth/login.js +1 -2
- package/dist/commands/auth/tenant.d.ts +1 -1
- package/dist/commands/auth/tenant.js +11 -11
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bank-accounts.d.ts +1 -0
- package/dist/commands/bank-accounts.js +96 -40
- package/dist/commands/bank-integrations.d.ts +1 -0
- package/dist/commands/bank-integrations.js +97 -41
- package/dist/commands/business-partners---contacts.d.ts +1 -0
- package/dist/commands/business-partners---contacts.js +89 -33
- package/dist/commands/business-partners.d.ts +1 -0
- package/dist/commands/business-partners.js +89 -33
- package/dist/commands/credit-card-accounts.d.ts +1 -0
- package/dist/commands/credit-card-accounts.js +94 -38
- package/dist/commands/documents.d.ts +1 -0
- package/dist/commands/documents.js +85 -29
- package/dist/commands/dunning-configs.d.ts +1 -0
- package/dist/commands/dunning-configs.js +84 -28
- package/dist/commands/dunning-reminders.d.ts +1 -0
- package/dist/commands/dunning-reminders.js +87 -31
- package/dist/commands/dunning-runs.d.ts +1 -0
- package/dist/commands/dunning-runs.js +80 -24
- package/dist/commands/employees.d.ts +1 -0
- package/dist/commands/employees.js +111 -55
- package/dist/commands/event-processor-mappings.d.ts +1 -0
- package/dist/commands/event-processor-mappings.js +84 -28
- package/dist/commands/files-internal.d.ts +1 -0
- package/dist/commands/files-internal.js +88 -32
- package/dist/commands/health.d.ts +1 -0
- package/dist/commands/health.js +84 -28
- package/dist/commands/inbox.d.ts +1 -0
- package/dist/commands/inbox.js +84 -28
- package/dist/commands/integrations.d.ts +1 -0
- package/dist/commands/integrations.js +82 -26
- package/dist/commands/item-categories.d.ts +1 -0
- package/dist/commands/item-categories.js +84 -28
- package/dist/commands/items.d.ts +1 -0
- package/dist/commands/items.js +84 -28
- package/dist/commands/journal-lines.d.ts +1 -0
- package/dist/commands/journal-lines.js +80 -24
- package/dist/commands/journals.d.ts +1 -0
- package/dist/commands/journals.js +98 -42
- package/dist/commands/ledgers.d.ts +1 -0
- package/dist/commands/ledgers.js +95 -39
- package/dist/commands/office-locations.d.ts +1 -0
- package/dist/commands/office-locations.js +84 -28
- package/dist/commands/payment-method-ledger-mappings.d.ts +1 -0
- package/dist/commands/payment-method-ledger-mappings.js +84 -28
- package/dist/commands/payment-methods.d.ts +1 -0
- package/dist/commands/payment-methods.js +84 -28
- package/dist/commands/payment-runs.d.ts +1 -0
- package/dist/commands/payment-runs.js +97 -41
- package/dist/commands/payment-terms.d.ts +1 -0
- package/dist/commands/payment-terms.js +85 -29
- package/dist/commands/price-lists.d.ts +1 -0
- package/dist/commands/price-lists.js +89 -33
- package/dist/commands/processor-mappings.d.ts +1 -0
- package/dist/commands/processor-mappings.js +83 -27
- package/dist/commands/processors.d.ts +1 -0
- package/dist/commands/processors.js +88 -32
- package/dist/commands/reconciliation.d.ts +1 -0
- package/dist/commands/reconciliation.js +85 -29
- package/dist/commands/reports.d.ts +1 -0
- package/dist/commands/reports.js +85 -26
- package/dist/commands/salary-types.d.ts +1 -0
- package/dist/commands/salary-types.js +84 -28
- package/dist/commands/sales-invoices.d.ts +1 -0
- package/dist/commands/sales-invoices.js +109 -53
- package/dist/commands/sales-orders.d.ts +1 -0
- package/dist/commands/sales-orders.js +107 -51
- package/dist/commands/tags.d.ts +1 -0
- package/dist/commands/tags.js +84 -28
- package/dist/commands/tax-codes.d.ts +1 -0
- package/dist/commands/tax-codes.js +87 -31
- package/dist/commands/tax-matrix.d.ts +1 -0
- package/dist/commands/tax-matrix.js +84 -28
- package/dist/commands/tenants.d.ts +1 -0
- package/dist/commands/tenants.js +84 -28
- package/dist/commands/text-templates.d.ts +1 -0
- package/dist/commands/text-templates.js +84 -28
- package/dist/commands/tools.d.ts +1 -0
- package/dist/commands/tools.js +81 -25
- package/dist/commands/vat-declaration.d.ts +41 -0
- package/dist/commands/vat-declaration.js +1079 -0
- package/dist/commands/vendor-invoices.d.ts +1 -0
- package/dist/commands/vendor-invoices.js +113 -57
- package/dist/commands/webhooks.d.ts +1 -0
- package/dist/commands/webhooks.js +86 -30
- package/dist/commands/workflows.d.ts +1 -0
- package/dist/commands/workflows.js +82 -26
- package/dist/lib/api-client.d.ts +6 -1
- package/dist/lib/api-client.js +23 -5
- package/dist/lib/auth.js +18 -0
- package/dist/lib/config.js +1 -1
- package/oclif.manifest.json +229 -52
- package/package.json +15 -13
- package/dist/index 2.js +0 -1
- package/dist/index.d 2.ts +0 -1
|
@@ -8,15 +8,15 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["create", "download-all", "download", "references", "update", "move", "renditions", "retention", "hold"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true },
|
|
12
|
-
'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false },
|
|
13
|
-
'download': { method: 'GET', path: '/v1/files/{id}/download', summary: 'Get a signed download URL for a file', pathParams: ['id'], hasBody: false, isMultipart: false },
|
|
14
|
-
'references': { method: 'POST', path: '/v1/files/references', summary: 'Register a file reference to an existing storage object', pathParams: [], hasBody: true, isMultipart: false },
|
|
15
|
-
'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false },
|
|
16
|
-
'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false },
|
|
17
|
-
'renditions': { method: 'POST', path: '/v1/files/{fileId}/renditions', summary: 'Register a business-significant rendition', pathParams: ['fileId'], hasBody: true, isMultipart: false },
|
|
18
|
-
'retention': { method: 'POST', path: '/v1/files/{fileId}/retention', summary: 'Set or override retention period on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false },
|
|
19
|
-
'hold': { method: 'POST', path: '/v1/files/{fileId}/hold', summary: 'Set or release legal hold on a file', pathParams: ['fileId'], hasBody: true, isMultipart: false },
|
|
11
|
+
'create': { method: 'POST', path: '/v1/files', summary: 'Upload a file (streaming, memory-efficient)', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'download-all': { method: 'GET', path: '/v1/files/download/*', summary: 'Download file directly by storage key', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'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: [] },
|
|
14
|
+
'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: [] },
|
|
15
|
+
'update': { method: 'PATCH', path: '/v1/files/{fileId}', summary: 'Update file metadata (type, parent, sortOrder)', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
|
+
'move': { method: 'POST', path: '/v1/files/{fileId}/move', summary: 'Move file between storage zones', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
17
|
+
'renditions': { method: 'POST', path: '/v1/files/{fileId}/renditions', summary: 'Register a business-significant rendition', pathParams: ['fileId'], hasBody: true, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
18
|
+
'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: [] },
|
|
19
|
+
'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: [] },
|
|
20
20
|
};
|
|
21
21
|
export default class FilesInternal extends Command {
|
|
22
22
|
static description = 'Manage files internal';
|
|
@@ -82,11 +82,6 @@ export default class FilesInternal extends Command {
|
|
|
82
82
|
const endpoint = ENDPOINTS[args.action];
|
|
83
83
|
if (!endpoint)
|
|
84
84
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
85
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
86
|
-
if (!context)
|
|
87
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
88
|
-
if (!context.tokens)
|
|
89
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
90
85
|
let apiPath = endpoint.path;
|
|
91
86
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
92
87
|
const param = endpoint.pathParams[i];
|
|
@@ -105,6 +100,11 @@ export default class FilesInternal extends Command {
|
|
|
105
100
|
}
|
|
106
101
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
107
102
|
}
|
|
103
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
104
|
+
if (!context)
|
|
105
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
106
|
+
if (!context.tokens)
|
|
107
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
108
108
|
const queryParams = {};
|
|
109
109
|
if (flags.page)
|
|
110
110
|
queryParams.page = flags.page;
|
|
@@ -131,15 +131,7 @@ export default class FilesInternal extends Command {
|
|
|
131
131
|
if (eqIndex === -1)
|
|
132
132
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
133
133
|
const key = f.slice(0, eqIndex);
|
|
134
|
-
|
|
135
|
-
// Check for operator prefix (with or without $)
|
|
136
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
137
|
-
if (opMatch) {
|
|
138
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
value = `\$eq:${value}`;
|
|
142
|
-
}
|
|
134
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
143
135
|
const filterKey = `filter.${key}`;
|
|
144
136
|
if (filterKey in queryParams) {
|
|
145
137
|
// Multiple filters on same field: collect as array
|
|
@@ -172,7 +164,9 @@ export default class FilesInternal extends Command {
|
|
|
172
164
|
data = form;
|
|
173
165
|
}
|
|
174
166
|
else if (endpoint.hasBody) {
|
|
175
|
-
|
|
167
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
168
|
+
// input file — skip the read-as-body branch.
|
|
169
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
176
170
|
const fsModule = await import('node:fs');
|
|
177
171
|
try {
|
|
178
172
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -194,6 +188,7 @@ export default class FilesInternal extends Command {
|
|
|
194
188
|
if (!data)
|
|
195
189
|
data = {};
|
|
196
190
|
const body = data;
|
|
191
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
197
192
|
for (const pair of flags.set) {
|
|
198
193
|
const eqIndex = pair.indexOf('=');
|
|
199
194
|
if (eqIndex === -1)
|
|
@@ -205,6 +200,19 @@ export default class FilesInternal extends Command {
|
|
|
205
200
|
value = JSON.parse(value);
|
|
206
201
|
}
|
|
207
202
|
catch { /* keep as string */ }
|
|
203
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
204
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
205
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
206
|
+
// already provided a JSON array.
|
|
207
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
208
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
209
|
+
if (typeof value === 'string') {
|
|
210
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
211
|
+
}
|
|
212
|
+
else if (value !== undefined && value !== null) {
|
|
213
|
+
value = [value];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
208
216
|
// Support nested keys like "address.city"
|
|
209
217
|
const keys = key.split('.');
|
|
210
218
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -255,6 +263,26 @@ export default class FilesInternal extends Command {
|
|
|
255
263
|
process.stderr.write('\n');
|
|
256
264
|
}
|
|
257
265
|
try {
|
|
266
|
+
// Binary response: download to file
|
|
267
|
+
if (endpoint.isBinaryResponse) {
|
|
268
|
+
if (!flags.file) {
|
|
269
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
270
|
+
}
|
|
271
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
272
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
273
|
+
data,
|
|
274
|
+
contextName: flags.context,
|
|
275
|
+
binary: true,
|
|
276
|
+
});
|
|
277
|
+
const fsModule = await import('node:fs');
|
|
278
|
+
const binData = binResponse.data;
|
|
279
|
+
const buffer = Buffer.isBuffer(binData)
|
|
280
|
+
? binData
|
|
281
|
+
: Buffer.from(binData);
|
|
282
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
283
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
258
286
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
259
287
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
260
288
|
data,
|
|
@@ -345,6 +373,7 @@ export default class FilesInternal extends Command {
|
|
|
345
373
|
}
|
|
346
374
|
else if (flags.set && flags.set.length > 0) {
|
|
347
375
|
const item = {};
|
|
376
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
348
377
|
for (const pair of flags.set) {
|
|
349
378
|
const eqIndex = pair.indexOf('=');
|
|
350
379
|
if (eqIndex === -1)
|
|
@@ -355,6 +384,15 @@ export default class FilesInternal extends Command {
|
|
|
355
384
|
value = JSON.parse(value);
|
|
356
385
|
}
|
|
357
386
|
catch { /* keep as string */ }
|
|
387
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
388
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
389
|
+
if (typeof value === 'string') {
|
|
390
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
391
|
+
}
|
|
392
|
+
else if (value !== undefined && value !== null) {
|
|
393
|
+
value = [value];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
358
396
|
const keys = key.split('.');
|
|
359
397
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
360
398
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -504,14 +542,7 @@ export default class FilesInternal extends Command {
|
|
|
504
542
|
if (eqIndex === -1)
|
|
505
543
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
506
544
|
const key = f.slice(0, eqIndex);
|
|
507
|
-
|
|
508
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
509
|
-
if (opMatch) {
|
|
510
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
value = `\$eq:${value}`;
|
|
514
|
-
}
|
|
545
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
515
546
|
const filterKey = `filter.${key}`;
|
|
516
547
|
if (filterKey in baseParams) {
|
|
517
548
|
const existing = baseParams[filterKey];
|
|
@@ -688,6 +719,7 @@ export default class FilesInternal extends Command {
|
|
|
688
719
|
}
|
|
689
720
|
else if (flags.set && flags.set.length > 0) {
|
|
690
721
|
const item = {};
|
|
722
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
691
723
|
for (const pair of flags.set) {
|
|
692
724
|
const eqIndex = pair.indexOf('=');
|
|
693
725
|
if (eqIndex === -1)
|
|
@@ -698,6 +730,15 @@ export default class FilesInternal extends Command {
|
|
|
698
730
|
value = JSON.parse(value);
|
|
699
731
|
}
|
|
700
732
|
catch { /* keep as string */ }
|
|
733
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
734
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
735
|
+
if (typeof value === 'string') {
|
|
736
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
737
|
+
}
|
|
738
|
+
else if (value !== undefined && value !== null) {
|
|
739
|
+
value = [value];
|
|
740
|
+
}
|
|
741
|
+
}
|
|
701
742
|
const keys = key.split('.');
|
|
702
743
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
703
744
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -817,6 +858,21 @@ export default class FilesInternal extends Command {
|
|
|
817
858
|
this.log('');
|
|
818
859
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
819
860
|
}
|
|
861
|
+
parseFilterValue(value) {
|
|
862
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
863
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
864
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
865
|
+
// no operator prefix is present.
|
|
866
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
867
|
+
const m = value.match(opRe);
|
|
868
|
+
if (!m)
|
|
869
|
+
return `\$eq:${value}`;
|
|
870
|
+
const op = m[1];
|
|
871
|
+
const rest = value.slice(m[0].length);
|
|
872
|
+
if (rest === '')
|
|
873
|
+
return `\$${op}`;
|
|
874
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
875
|
+
}
|
|
820
876
|
filterFields(data, fields) {
|
|
821
877
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
822
878
|
const pick = (obj, keys) => {
|
package/dist/commands/health.js
CHANGED
|
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["get", "get-all", "ready", "get-3", "v1"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'get': { method: 'GET', path: '/', summary: 'GET /', pathParams: [], hasBody: false, isMultipart: false },
|
|
12
|
-
'get-all': { method: 'GET', path: '/health', summary: 'GET /health', pathParams: [], hasBody: false, isMultipart: false },
|
|
13
|
-
'ready': { method: 'GET', path: '/ready', summary: 'GET /ready', pathParams: [], hasBody: false, isMultipart: false },
|
|
14
|
-
'get-3': { method: 'GET', path: '/v1/status', summary: 'GET /v1/status', pathParams: [], hasBody: false, isMultipart: false },
|
|
15
|
-
'v1': { method: 'GET', path: '/v1', summary: 'GET /v1', pathParams: [], hasBody: false, isMultipart: false },
|
|
11
|
+
'get': { method: 'GET', path: '/', summary: 'GET /', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'get-all': { method: 'GET', path: '/health', summary: 'GET /health', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'ready': { method: 'GET', path: '/ready', summary: 'GET /ready', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
+
'get-3': { method: 'GET', path: '/v1/status', summary: 'GET /v1/status', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
+
'v1': { method: 'GET', path: '/v1', summary: 'GET /v1', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class Health extends Command {
|
|
18
18
|
static description = 'Manage health';
|
|
@@ -76,11 +76,6 @@ export default class Health extends Command {
|
|
|
76
76
|
const endpoint = ENDPOINTS[args.action];
|
|
77
77
|
if (!endpoint)
|
|
78
78
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
79
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
80
|
-
if (!context)
|
|
81
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
82
|
-
if (!context.tokens)
|
|
83
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
84
79
|
let apiPath = endpoint.path;
|
|
85
80
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
86
81
|
const param = endpoint.pathParams[i];
|
|
@@ -99,6 +94,11 @@ export default class Health extends Command {
|
|
|
99
94
|
}
|
|
100
95
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
101
96
|
}
|
|
97
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
98
|
+
if (!context)
|
|
99
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
100
|
+
if (!context.tokens)
|
|
101
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
102
102
|
const queryParams = {};
|
|
103
103
|
if (flags.page)
|
|
104
104
|
queryParams.page = flags.page;
|
|
@@ -125,15 +125,7 @@ export default class Health extends Command {
|
|
|
125
125
|
if (eqIndex === -1)
|
|
126
126
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
127
127
|
const key = f.slice(0, eqIndex);
|
|
128
|
-
|
|
129
|
-
// Check for operator prefix (with or without $)
|
|
130
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
131
|
-
if (opMatch) {
|
|
132
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
value = `\$eq:${value}`;
|
|
136
|
-
}
|
|
128
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
137
129
|
const filterKey = `filter.${key}`;
|
|
138
130
|
if (filterKey in queryParams) {
|
|
139
131
|
// Multiple filters on same field: collect as array
|
|
@@ -166,7 +158,9 @@ export default class Health extends Command {
|
|
|
166
158
|
data = form;
|
|
167
159
|
}
|
|
168
160
|
else if (endpoint.hasBody) {
|
|
169
|
-
|
|
161
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
162
|
+
// input file — skip the read-as-body branch.
|
|
163
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
170
164
|
const fsModule = await import('node:fs');
|
|
171
165
|
try {
|
|
172
166
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -188,6 +182,7 @@ export default class Health extends Command {
|
|
|
188
182
|
if (!data)
|
|
189
183
|
data = {};
|
|
190
184
|
const body = data;
|
|
185
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
191
186
|
for (const pair of flags.set) {
|
|
192
187
|
const eqIndex = pair.indexOf('=');
|
|
193
188
|
if (eqIndex === -1)
|
|
@@ -199,6 +194,19 @@ export default class Health extends Command {
|
|
|
199
194
|
value = JSON.parse(value);
|
|
200
195
|
}
|
|
201
196
|
catch { /* keep as string */ }
|
|
197
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
198
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
199
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
200
|
+
// already provided a JSON array.
|
|
201
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
202
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
203
|
+
if (typeof value === 'string') {
|
|
204
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
205
|
+
}
|
|
206
|
+
else if (value !== undefined && value !== null) {
|
|
207
|
+
value = [value];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
202
210
|
// Support nested keys like "address.city"
|
|
203
211
|
const keys = key.split('.');
|
|
204
212
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -249,6 +257,26 @@ export default class Health extends Command {
|
|
|
249
257
|
process.stderr.write('\n');
|
|
250
258
|
}
|
|
251
259
|
try {
|
|
260
|
+
// Binary response: download to file
|
|
261
|
+
if (endpoint.isBinaryResponse) {
|
|
262
|
+
if (!flags.file) {
|
|
263
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
264
|
+
}
|
|
265
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
266
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
267
|
+
data,
|
|
268
|
+
contextName: flags.context,
|
|
269
|
+
binary: true,
|
|
270
|
+
});
|
|
271
|
+
const fsModule = await import('node:fs');
|
|
272
|
+
const binData = binResponse.data;
|
|
273
|
+
const buffer = Buffer.isBuffer(binData)
|
|
274
|
+
? binData
|
|
275
|
+
: Buffer.from(binData);
|
|
276
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
277
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
252
280
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
253
281
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
254
282
|
data,
|
|
@@ -339,6 +367,7 @@ export default class Health extends Command {
|
|
|
339
367
|
}
|
|
340
368
|
else if (flags.set && flags.set.length > 0) {
|
|
341
369
|
const item = {};
|
|
370
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
342
371
|
for (const pair of flags.set) {
|
|
343
372
|
const eqIndex = pair.indexOf('=');
|
|
344
373
|
if (eqIndex === -1)
|
|
@@ -349,6 +378,15 @@ export default class Health extends Command {
|
|
|
349
378
|
value = JSON.parse(value);
|
|
350
379
|
}
|
|
351
380
|
catch { /* keep as string */ }
|
|
381
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
382
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
383
|
+
if (typeof value === 'string') {
|
|
384
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
385
|
+
}
|
|
386
|
+
else if (value !== undefined && value !== null) {
|
|
387
|
+
value = [value];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
352
390
|
const keys = key.split('.');
|
|
353
391
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
354
392
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -498,14 +536,7 @@ export default class Health extends Command {
|
|
|
498
536
|
if (eqIndex === -1)
|
|
499
537
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
500
538
|
const key = f.slice(0, eqIndex);
|
|
501
|
-
|
|
502
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
503
|
-
if (opMatch) {
|
|
504
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
value = `\$eq:${value}`;
|
|
508
|
-
}
|
|
539
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
509
540
|
const filterKey = `filter.${key}`;
|
|
510
541
|
if (filterKey in baseParams) {
|
|
511
542
|
const existing = baseParams[filterKey];
|
|
@@ -682,6 +713,7 @@ export default class Health extends Command {
|
|
|
682
713
|
}
|
|
683
714
|
else if (flags.set && flags.set.length > 0) {
|
|
684
715
|
const item = {};
|
|
716
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
685
717
|
for (const pair of flags.set) {
|
|
686
718
|
const eqIndex = pair.indexOf('=');
|
|
687
719
|
if (eqIndex === -1)
|
|
@@ -692,6 +724,15 @@ export default class Health extends Command {
|
|
|
692
724
|
value = JSON.parse(value);
|
|
693
725
|
}
|
|
694
726
|
catch { /* keep as string */ }
|
|
727
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
728
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
729
|
+
if (typeof value === 'string') {
|
|
730
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
731
|
+
}
|
|
732
|
+
else if (value !== undefined && value !== null) {
|
|
733
|
+
value = [value];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
695
736
|
const keys = key.split('.');
|
|
696
737
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
697
738
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -811,6 +852,21 @@ export default class Health extends Command {
|
|
|
811
852
|
this.log('');
|
|
812
853
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
813
854
|
}
|
|
855
|
+
parseFilterValue(value) {
|
|
856
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
857
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
858
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
859
|
+
// no operator prefix is present.
|
|
860
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
861
|
+
const m = value.match(opRe);
|
|
862
|
+
if (!m)
|
|
863
|
+
return `\$eq:${value}`;
|
|
864
|
+
const op = m[1];
|
|
865
|
+
const rest = value.slice(m[0].length);
|
|
866
|
+
if (rest === '')
|
|
867
|
+
return `\$${op}`;
|
|
868
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
869
|
+
}
|
|
814
870
|
filterFields(data, fields) {
|
|
815
871
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
816
872
|
const pick = (obj, keys) => {
|
package/dist/commands/inbox.d.ts
CHANGED
package/dist/commands/inbox.js
CHANGED
|
@@ -8,11 +8,11 @@ const HAS_CAMT053 = false;
|
|
|
8
8
|
const NESTED_IMPORTS = [];
|
|
9
9
|
const SUBCOMMANDS = [...["upload", "email-webhook", "document-types", "list-processing-logs", "processing-logs"], ...(HAS_IMPORT ? ['import'] : []), ...(HAS_EXPORT ? ['export'] : []), ...NESTED_IMPORTS.map(n => `import-${n}`)];
|
|
10
10
|
const ENDPOINTS = {
|
|
11
|
-
'upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: true },
|
|
12
|
-
'email-webhook': { method: 'POST', path: '/v1/inbox/email-webhook/{secret}', summary: 'Email webhook for document ingestion (SendGrid inbound-parse)', pathParams: ['secret'], hasBody: false, isMultipart: false },
|
|
13
|
-
'document-types': { method: 'GET', path: '/v1/inbox/document-types', summary: 'Get available document types and per-parent allowed types', pathParams: [], hasBody: false, isMultipart: false },
|
|
14
|
-
'list-processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs', summary: 'List recent inbox processing logs (newest first)', pathParams: [], hasBody: false, isMultipart: false },
|
|
15
|
-
'processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false },
|
|
11
|
+
'upload': { method: 'POST', path: '/v1/inbox/upload', summary: 'Upload document(s) to inbox for processing', pathParams: [], hasBody: false, isMultipart: true, isBinaryResponse: false, bodyArrayFields: [] },
|
|
12
|
+
'email-webhook': { method: 'POST', path: '/v1/inbox/email-webhook/{secret}', summary: 'Email webhook for document ingestion (SendGrid inbound-parse)', pathParams: ['secret'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
13
|
+
'document-types': { method: 'GET', path: '/v1/inbox/document-types', summary: 'Get available document types and per-parent allowed types', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
14
|
+
'list-processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs', summary: 'List recent inbox processing logs (newest first)', pathParams: [], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
15
|
+
'processing-logs': { method: 'GET', path: '/v1/inbox/processing-logs/{processingId}', summary: 'Get a processing run by its processing identifier', pathParams: ['processingId'], hasBody: false, isMultipart: false, isBinaryResponse: false, bodyArrayFields: [] },
|
|
16
16
|
};
|
|
17
17
|
export default class Inbox extends Command {
|
|
18
18
|
static description = 'Manage inbox';
|
|
@@ -74,11 +74,6 @@ export default class Inbox extends Command {
|
|
|
74
74
|
const endpoint = ENDPOINTS[args.action];
|
|
75
75
|
if (!endpoint)
|
|
76
76
|
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
77
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
78
|
-
if (!context)
|
|
79
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
80
|
-
if (!context.tokens)
|
|
81
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
82
77
|
let apiPath = endpoint.path;
|
|
83
78
|
for (let i = 0; i < endpoint.pathParams.length; i++) {
|
|
84
79
|
const param = endpoint.pathParams[i];
|
|
@@ -97,6 +92,11 @@ export default class Inbox extends Command {
|
|
|
97
92
|
}
|
|
98
93
|
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
99
94
|
}
|
|
95
|
+
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
96
|
+
if (!context)
|
|
97
|
+
this.error('No context configured. Run: aderis auth login --new');
|
|
98
|
+
if (!context.tokens)
|
|
99
|
+
this.error('Not logged in. Run: aderis auth login');
|
|
100
100
|
const queryParams = {};
|
|
101
101
|
if (flags.page)
|
|
102
102
|
queryParams.page = flags.page;
|
|
@@ -123,15 +123,7 @@ export default class Inbox extends Command {
|
|
|
123
123
|
if (eqIndex === -1)
|
|
124
124
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
125
125
|
const key = f.slice(0, eqIndex);
|
|
126
|
-
|
|
127
|
-
// Check for operator prefix (with or without $)
|
|
128
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
129
|
-
if (opMatch) {
|
|
130
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
value = `\$eq:${value}`;
|
|
134
|
-
}
|
|
126
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
135
127
|
const filterKey = `filter.${key}`;
|
|
136
128
|
if (filterKey in queryParams) {
|
|
137
129
|
// Multiple filters on same field: collect as array
|
|
@@ -164,7 +156,9 @@ export default class Inbox extends Command {
|
|
|
164
156
|
data = form;
|
|
165
157
|
}
|
|
166
158
|
else if (endpoint.hasBody) {
|
|
167
|
-
|
|
159
|
+
// For binary-response endpoints, --file is the output path, not a JSON
|
|
160
|
+
// input file — skip the read-as-body branch.
|
|
161
|
+
if (flags.file && !endpoint.isBinaryResponse) {
|
|
168
162
|
const fsModule = await import('node:fs');
|
|
169
163
|
try {
|
|
170
164
|
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf-8'));
|
|
@@ -186,6 +180,7 @@ export default class Inbox extends Command {
|
|
|
186
180
|
if (!data)
|
|
187
181
|
data = {};
|
|
188
182
|
const body = data;
|
|
183
|
+
const arrayFields = new Set(endpoint.bodyArrayFields);
|
|
189
184
|
for (const pair of flags.set) {
|
|
190
185
|
const eqIndex = pair.indexOf('=');
|
|
191
186
|
if (eqIndex === -1)
|
|
@@ -197,6 +192,19 @@ export default class Inbox extends Command {
|
|
|
197
192
|
value = JSON.parse(value);
|
|
198
193
|
}
|
|
199
194
|
catch { /* keep as string */ }
|
|
195
|
+
// For top-level keys typed as array in the API schema, split bare
|
|
196
|
+
// comma-separated input (e.g. "1171,1170" -> ["1171","1170"]) and
|
|
197
|
+
// wrap a lone scalar in a single-element array. Skips when --set
|
|
198
|
+
// already provided a JSON array.
|
|
199
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
200
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
201
|
+
if (typeof value === 'string') {
|
|
202
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
203
|
+
}
|
|
204
|
+
else if (value !== undefined && value !== null) {
|
|
205
|
+
value = [value];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
200
208
|
// Support nested keys like "address.city"
|
|
201
209
|
const keys = key.split('.');
|
|
202
210
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
@@ -247,6 +255,26 @@ export default class Inbox extends Command {
|
|
|
247
255
|
process.stderr.write('\n');
|
|
248
256
|
}
|
|
249
257
|
try {
|
|
258
|
+
// Binary response: download to file
|
|
259
|
+
if (endpoint.isBinaryResponse) {
|
|
260
|
+
if (!flags.file) {
|
|
261
|
+
this.error(`Endpoint returns a binary file. Specify the output path with -f / --file (e.g. -f report.xlsx).`);
|
|
262
|
+
}
|
|
263
|
+
const binResponse = await apiRequest(endpoint.method, apiPath, {
|
|
264
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
265
|
+
data,
|
|
266
|
+
contextName: flags.context,
|
|
267
|
+
binary: true,
|
|
268
|
+
});
|
|
269
|
+
const fsModule = await import('node:fs');
|
|
270
|
+
const binData = binResponse.data;
|
|
271
|
+
const buffer = Buffer.isBuffer(binData)
|
|
272
|
+
? binData
|
|
273
|
+
: Buffer.from(binData);
|
|
274
|
+
fsModule.writeFileSync(flags.file, buffer);
|
|
275
|
+
this.log(`Saved ${buffer.length} bytes to ${flags.file}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
250
278
|
const response = await apiRequest(endpoint.method, apiPath, {
|
|
251
279
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
252
280
|
data,
|
|
@@ -337,6 +365,7 @@ export default class Inbox extends Command {
|
|
|
337
365
|
}
|
|
338
366
|
else if (flags.set && flags.set.length > 0) {
|
|
339
367
|
const item = {};
|
|
368
|
+
const arrayFields = new Set(ENDPOINTS['create']?.bodyArrayFields ?? []);
|
|
340
369
|
for (const pair of flags.set) {
|
|
341
370
|
const eqIndex = pair.indexOf('=');
|
|
342
371
|
if (eqIndex === -1)
|
|
@@ -347,6 +376,15 @@ export default class Inbox extends Command {
|
|
|
347
376
|
value = JSON.parse(value);
|
|
348
377
|
}
|
|
349
378
|
catch { /* keep as string */ }
|
|
379
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
380
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
381
|
+
if (typeof value === 'string') {
|
|
382
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
383
|
+
}
|
|
384
|
+
else if (value !== undefined && value !== null) {
|
|
385
|
+
value = [value];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
350
388
|
const keys = key.split('.');
|
|
351
389
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
352
390
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -496,14 +534,7 @@ export default class Inbox extends Command {
|
|
|
496
534
|
if (eqIndex === -1)
|
|
497
535
|
this.error(`Invalid --filter format: ${f}. Use: --filter key=value`);
|
|
498
536
|
const key = f.slice(0, eqIndex);
|
|
499
|
-
|
|
500
|
-
const opMatch = value.match(/^\$?(eq|ne|in|like|gt|gte|lt|lte|null):/);
|
|
501
|
-
if (opMatch) {
|
|
502
|
-
value = `\$${opMatch[1]}:${value.slice(opMatch[0].length)}`;
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
value = `\$eq:${value}`;
|
|
506
|
-
}
|
|
537
|
+
const value = this.parseFilterValue(f.slice(eqIndex + 1));
|
|
507
538
|
const filterKey = `filter.${key}`;
|
|
508
539
|
if (filterKey in baseParams) {
|
|
509
540
|
const existing = baseParams[filterKey];
|
|
@@ -680,6 +711,7 @@ export default class Inbox extends Command {
|
|
|
680
711
|
}
|
|
681
712
|
else if (flags.set && flags.set.length > 0) {
|
|
682
713
|
const item = {};
|
|
714
|
+
const arrayFields = new Set(ENDPOINTS[`create-${suffix}`]?.bodyArrayFields ?? []);
|
|
683
715
|
for (const pair of flags.set) {
|
|
684
716
|
const eqIndex = pair.indexOf('=');
|
|
685
717
|
if (eqIndex === -1)
|
|
@@ -690,6 +722,15 @@ export default class Inbox extends Command {
|
|
|
690
722
|
value = JSON.parse(value);
|
|
691
723
|
}
|
|
692
724
|
catch { /* keep as string */ }
|
|
725
|
+
const isArrayField = !key.includes('.') && arrayFields.has(key);
|
|
726
|
+
if (isArrayField && !Array.isArray(value)) {
|
|
727
|
+
if (typeof value === 'string') {
|
|
728
|
+
value = value.split(',').map(v => v.trim()).filter(v => v.length > 0);
|
|
729
|
+
}
|
|
730
|
+
else if (value !== undefined && value !== null) {
|
|
731
|
+
value = [value];
|
|
732
|
+
}
|
|
733
|
+
}
|
|
693
734
|
const keys = key.split('.');
|
|
694
735
|
if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) {
|
|
695
736
|
this.error(`Invalid --set key: ${key}`);
|
|
@@ -809,6 +850,21 @@ export default class Inbox extends Command {
|
|
|
809
850
|
this.log('');
|
|
810
851
|
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
811
852
|
}
|
|
853
|
+
parseFilterValue(value) {
|
|
854
|
+
// Recognises the nestjs-paginate operator set (with or without the leading $)
|
|
855
|
+
// and recursively unwraps chained operators like 'not:null' -> '$not:$null'
|
|
856
|
+
// or 'not:like:John' -> '$not:$like:John'. Falls back to '$eq:<value>' when
|
|
857
|
+
// no operator prefix is present.
|
|
858
|
+
const opRe = /^\$?(and|or|not|eq|gt|gte|in|null|lt|lte|btw|ne|ilike|like|sw|contains)(?::|$)/;
|
|
859
|
+
const m = value.match(opRe);
|
|
860
|
+
if (!m)
|
|
861
|
+
return `\$eq:${value}`;
|
|
862
|
+
const op = m[1];
|
|
863
|
+
const rest = value.slice(m[0].length);
|
|
864
|
+
if (rest === '')
|
|
865
|
+
return `\$${op}`;
|
|
866
|
+
return opRe.test(rest) ? `\$${op}:${this.parseFilterValue(rest)}` : `\$${op}:${rest}`;
|
|
867
|
+
}
|
|
812
868
|
filterFields(data, fields) {
|
|
813
869
|
const isUnsafeKey = (k) => k === '__proto__' || k === 'constructor' || k === 'prototype';
|
|
814
870
|
const pick = (obj, keys) => {
|