@aderis-erp/cli 1.0.1 → 1.0.2
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 +1 -1
- package/dist/commands/{access-groups.js → access-groups 2.js } +6 -4
- package/dist/commands/bank-integrations.d.ts +2 -0
- package/dist/commands/bank-integrations.js +11 -1
- package/dist/commands/documents.js +6 -4
- package/dist/commands/inbox.js +1 -1
- package/dist/commands/processing-logs.js +3 -2
- package/dist/commands/{audit-log.d.ts → pubsub.d.ts} +2 -2
- package/dist/commands/{audit-log.js → pubsub.js} +10 -14
- package/dist/commands/sales-invoices.js +2 -1
- package/dist/commands/sales-orders.js +2 -1
- package/dist/lib/auth.js +16 -7
- package/oclif.manifest.json +660 -2403
- package/package.json +1 -1
- package/dist/commands/access-groups.d.ts +0 -39
- package/dist/commands/account-statement-runs.d.ts +0 -40
- package/dist/commands/account-statement-runs.js +0 -1004
- package/dist/commands/account-transactions.d.ts +0 -41
- package/dist/commands/account-transactions.js +0 -1020
- package/dist/commands/accounts.d.ts +0 -40
- package/dist/commands/accounts.js +0 -1012
- package/dist/commands/agent-runs.d.ts +0 -39
- package/dist/commands/agent-runs.js +0 -1001
- package/dist/commands/auth/login.d.ts +0 -14
- package/dist/commands/auth/login.js +0 -116
- package/dist/commands/auth/logout.d.ts +0 -10
- package/dist/commands/auth/logout.js +0 -58
- package/dist/commands/auth/status.d.ts +0 -12
- package/dist/commands/auth/status.js +0 -101
- package/dist/commands/auth/tenant.d.ts +0 -18
- package/dist/commands/auth/tenant.js +0 -168
- package/dist/commands/auth.d.ts +0 -37
- package/dist/commands/auth.js +0 -871
- package/dist/commands/bank-accounts.d.ts +0 -41
- package/dist/commands/bank-accounts.js +0 -1020
- package/dist/commands/bank-statements.d.ts +0 -40
- package/dist/commands/bank-statements.js +0 -998
- package/dist/commands/config/add.d.ts +0 -13
- package/dist/commands/config/add.js +0 -120
- package/dist/commands/config/current.d.ts +0 -9
- package/dist/commands/config/current.js +0 -43
- package/dist/commands/config/delete.d.ts +0 -12
- package/dist/commands/config/delete.js +0 -67
- package/dist/commands/config/list.d.ts +0 -9
- package/dist/commands/config/list.js +0 -50
- package/dist/commands/config/use.d.ts +0 -9
- package/dist/commands/config/use.js +0 -40
- package/dist/commands/salary-types.js +0 -1006
package/dist/commands/auth.js
DELETED
|
@@ -1,871 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import { ApiError, apiRequest } from '../lib/api-client.js';
|
|
3
|
-
import { getContext, getCurrentContext } from '../lib/config.js';
|
|
4
|
-
// Add 'import' action if both create and update exist
|
|
5
|
-
const HAS_IMPORT = false;
|
|
6
|
-
const HAS_EXPORT = false;
|
|
7
|
-
const HAS_CAMT053 = false;
|
|
8
|
-
const NESTED_IMPORTS = [];
|
|
9
|
-
const SUBCOMMANDS = [
|
|
10
|
-
'login', 'logout', 'callback', 'success', 'me', 'create-agent-tenant-token',
|
|
11
|
-
...(HAS_IMPORT ? ['import'] : []),
|
|
12
|
-
...(HAS_EXPORT ? ['export'] : []),
|
|
13
|
-
...NESTED_IMPORTS.map((n) => `import-${n}`),
|
|
14
|
-
];
|
|
15
|
-
const ENDPOINTS = {
|
|
16
|
-
callback: {
|
|
17
|
-
hasBody: false,
|
|
18
|
-
method: 'GET',
|
|
19
|
-
path: '/v1/auth/callback',
|
|
20
|
-
pathParams: [],
|
|
21
|
-
summary: 'OAuth callback handler',
|
|
22
|
-
},
|
|
23
|
-
'create-agent-tenant-token': {
|
|
24
|
-
hasBody: true,
|
|
25
|
-
method: 'POST',
|
|
26
|
-
path: '/v1/auth/agent/tenant-token',
|
|
27
|
-
pathParams: [],
|
|
28
|
-
summary: 'Mint a tenant-scoped token for agent workflows',
|
|
29
|
-
},
|
|
30
|
-
login: { hasBody: false, method: 'GET', path: '/v1/auth/login', pathParams: [], summary: 'Initiate OAuth login flow' },
|
|
31
|
-
logout: { hasBody: false, method: 'GET', path: '/v1/auth/logout', pathParams: [], summary: 'Logout and clear session' },
|
|
32
|
-
me: { hasBody: false, method: 'GET', path: '/v1/auth/me', pathParams: [], summary: 'Get current authenticated user' },
|
|
33
|
-
success: { hasBody: false, method: 'GET', path: '/v1/auth/success', pathParams: [], summary: 'Login success page' },
|
|
34
|
-
};
|
|
35
|
-
export default class Auth extends Command {
|
|
36
|
-
static args = {
|
|
37
|
-
action: Args.string({ description: 'Action: ' + SUBCOMMANDS.join(', '), required: true }),
|
|
38
|
-
id: Args.string({ description: 'Resource ID (for get, update, delete operations)', required: false }),
|
|
39
|
-
};
|
|
40
|
-
static description = 'Manage auth';
|
|
41
|
-
static examples = [];
|
|
42
|
-
static flags = {
|
|
43
|
-
context: Flags.string({ char: 'c', description: 'Context to use' }),
|
|
44
|
-
csv: Flags.boolean({ description: 'Output as CSV (shortcut for --output csv)' }),
|
|
45
|
-
data: Flags.string({ char: 'd', description: 'JSON data for request body' }),
|
|
46
|
-
fields: Flags.string({ description: 'Comma-separated list of fields to show (e.g. --fields=id,name,status)' }),
|
|
47
|
-
file: Flags.string({ char: 'f', description: 'Read request body from file' }),
|
|
48
|
-
include: Flags.string({ description: 'Relations to include' }),
|
|
49
|
-
limit: Flags.string({ description: 'Items per page' }),
|
|
50
|
-
output: Flags.string({
|
|
51
|
-
char: 'o',
|
|
52
|
-
description: 'Output format (default: table for import, json for other commands)',
|
|
53
|
-
options: ['json', 'table', 'csv'],
|
|
54
|
-
}),
|
|
55
|
-
page: Flags.string({ description: 'Page number' }),
|
|
56
|
-
query: Flags.string({ description: 'Additional query params as JSON' }),
|
|
57
|
-
sanitize: Flags.boolean({ description: 'Remove null values, audit fields, and duplicate UUIDs from output' }),
|
|
58
|
-
search: Flags.string({ description: 'Search term' }),
|
|
59
|
-
set: Flags.string({ char: 's', description: 'Set individual field (e.g. --set firstName=John)', multiple: true }),
|
|
60
|
-
'sort-by': Flags.string({ description: 'Sort field' }),
|
|
61
|
-
table: Flags.boolean({ char: 't', description: 'Output as table (shortcut for --output table)' }),
|
|
62
|
-
};
|
|
63
|
-
async run() {
|
|
64
|
-
const { args, flags } = await this.parse(Auth);
|
|
65
|
-
// Handle import action specially
|
|
66
|
-
if (args.action === 'import') {
|
|
67
|
-
if (!HAS_IMPORT)
|
|
68
|
-
this.error('Import not available for this resource');
|
|
69
|
-
await this.runImport(flags);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
// Handle export action specially
|
|
73
|
-
if (args.action === 'export') {
|
|
74
|
-
if (!HAS_EXPORT)
|
|
75
|
-
this.error('Export not available for this resource');
|
|
76
|
-
await this.runExport(flags);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
// Handle CAMT.053 import specially - sends raw XML
|
|
80
|
-
if (args.action === 'import-camt053') {
|
|
81
|
-
if (!HAS_CAMT053)
|
|
82
|
-
this.error('CAMT.053 import not available for this resource');
|
|
83
|
-
await this.runCamt053Import(flags);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// Handle nested resource imports (e.g., import-prices)
|
|
87
|
-
if (args.action.startsWith('import-')) {
|
|
88
|
-
const suffix = args.action.replace('import-', '');
|
|
89
|
-
if (NESTED_IMPORTS.includes(suffix)) {
|
|
90
|
-
await this.runNestedImport(suffix, args.id, flags);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const endpoint = ENDPOINTS[args.action];
|
|
95
|
-
if (!endpoint)
|
|
96
|
-
this.error(`Unknown action: ${args.action}. Available: ${SUBCOMMANDS.join(', ')}`);
|
|
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
|
-
let apiPath = endpoint.path;
|
|
103
|
-
for (const param of endpoint.pathParams) {
|
|
104
|
-
let value;
|
|
105
|
-
if (param === 'id') {
|
|
106
|
-
// Use positional argument for 'id' parameter
|
|
107
|
-
value = args.id;
|
|
108
|
-
if (!value)
|
|
109
|
-
this.error('Missing required argument: id. Usage: aderis auth <action> <id>');
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const flagName = param
|
|
113
|
-
.replaceAll('.', '-')
|
|
114
|
-
.replaceAll(/([A-Z])/g, '-$1')
|
|
115
|
-
.toLowerCase()
|
|
116
|
-
.replace(/^-/, '');
|
|
117
|
-
value = flags[flagName];
|
|
118
|
-
if (!value)
|
|
119
|
-
this.error(`Missing required parameter: --${flagName}`);
|
|
120
|
-
}
|
|
121
|
-
apiPath = apiPath.replace(`{${param}}`, String(value));
|
|
122
|
-
}
|
|
123
|
-
const queryParams = {};
|
|
124
|
-
if (flags.page)
|
|
125
|
-
queryParams.page = flags.page;
|
|
126
|
-
if (flags.limit)
|
|
127
|
-
queryParams.limit = flags.limit;
|
|
128
|
-
if (flags.include)
|
|
129
|
-
queryParams.include = flags.include;
|
|
130
|
-
if (flags.search)
|
|
131
|
-
queryParams.search = flags.search;
|
|
132
|
-
if (flags['sort-by'])
|
|
133
|
-
queryParams.sortBy = flags['sort-by'];
|
|
134
|
-
if (flags.query) {
|
|
135
|
-
try {
|
|
136
|
-
Object.assign(queryParams, JSON.parse(flags.query));
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
this.error('Invalid JSON in --query');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
let data;
|
|
143
|
-
if (endpoint.hasBody) {
|
|
144
|
-
if (flags.file) {
|
|
145
|
-
const fsModule = await import('node:fs');
|
|
146
|
-
try {
|
|
147
|
-
data = JSON.parse(fsModule.readFileSync(flags.file, 'utf8'));
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
this.error(`Failed to read file: ${flags.file}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
else if (flags.data) {
|
|
154
|
-
try {
|
|
155
|
-
data = JSON.parse(flags.data);
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
this.error('Invalid JSON in --data');
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Apply --set flags (can be combined with --data or --file)
|
|
162
|
-
if (flags.set && flags.set.length > 0) {
|
|
163
|
-
data = data || {};
|
|
164
|
-
for (const pair of flags.set) {
|
|
165
|
-
const eqIndex = pair.indexOf('=');
|
|
166
|
-
if (eqIndex === -1)
|
|
167
|
-
this.error(`Invalid --set format: ${pair}. Use: --set key=value`);
|
|
168
|
-
const key = pair.slice(0, eqIndex);
|
|
169
|
-
let value = pair.slice(eqIndex + 1);
|
|
170
|
-
// Try to parse as JSON for numbers, booleans, arrays, objects
|
|
171
|
-
try {
|
|
172
|
-
value = JSON.parse(value);
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
/* keep as string */
|
|
176
|
-
}
|
|
177
|
-
// Support nested keys like "address.city"
|
|
178
|
-
const keys = key.split('.');
|
|
179
|
-
let obj = data;
|
|
180
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
181
|
-
if (!(keys[i] in obj) || typeof obj[keys[i]] !== 'object') {
|
|
182
|
-
obj[keys[i]] = {};
|
|
183
|
-
}
|
|
184
|
-
obj = obj[keys[i]];
|
|
185
|
-
}
|
|
186
|
-
obj[keys.at(-1)] = value;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
const response = await apiRequest(endpoint.method, apiPath, {
|
|
192
|
-
acceptAny: true,
|
|
193
|
-
contextName: flags.context,
|
|
194
|
-
data,
|
|
195
|
-
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
196
|
-
});
|
|
197
|
-
// Check content-type for non-JSON responses (XML, text, etc.)
|
|
198
|
-
const contentType = (response.headers['content-type'] || '').toLowerCase();
|
|
199
|
-
if (contentType.includes('xml') || contentType.includes('text/plain') || contentType.includes('text/html')) {
|
|
200
|
-
// Output raw content for XML/text responses
|
|
201
|
-
this.log(String(response.data));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
// Parse JSON if response came as string (due to acceptAny)
|
|
205
|
-
let output = response.data;
|
|
206
|
-
if (typeof output === 'string') {
|
|
207
|
-
try {
|
|
208
|
-
output = JSON.parse(output);
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
/* keep as string */
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// For list operations, extract the data array if present
|
|
215
|
-
if (args.action === 'list' && output && typeof output === 'object' && 'data' in output) {
|
|
216
|
-
output = output.data;
|
|
217
|
-
}
|
|
218
|
-
// Filter fields if --fields flag is provided
|
|
219
|
-
if (flags.fields) {
|
|
220
|
-
const fieldList = flags.fields.split(',').map((f) => f.trim());
|
|
221
|
-
output = this.filterFields(output, fieldList);
|
|
222
|
-
}
|
|
223
|
-
// Sanitize output if requested
|
|
224
|
-
if (flags.sanitize) {
|
|
225
|
-
output = this.sanitizeOutput(output);
|
|
226
|
-
}
|
|
227
|
-
const fieldList = flags.fields?.split(',').map((f) => f.trim());
|
|
228
|
-
if (flags.csv || flags.output === 'csv')
|
|
229
|
-
this.printCsv(output, fieldList);
|
|
230
|
-
else if (flags.table || flags.output === 'table')
|
|
231
|
-
this.printTable(output, fieldList);
|
|
232
|
-
else
|
|
233
|
-
this.log(JSON.stringify(output, null, 2));
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
if (error instanceof ApiError) {
|
|
237
|
-
if (error.status === 404)
|
|
238
|
-
this.error(error.message);
|
|
239
|
-
this.error(`API Error (${error.status}): ${error.message}`);
|
|
240
|
-
}
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
filterFields(data, fields) {
|
|
245
|
-
const pick = (obj, keys) => {
|
|
246
|
-
const result = {};
|
|
247
|
-
for (const key of keys) {
|
|
248
|
-
if (key.includes('.')) {
|
|
249
|
-
// Handle nested fields like 'customer.name'
|
|
250
|
-
const [first, ...rest] = key.split('.');
|
|
251
|
-
if (obj[first] && typeof obj[first] === 'object') {
|
|
252
|
-
const nested = pick(obj[first], [rest.join('.')]);
|
|
253
|
-
result[first] = { ...result[first], ...nested };
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else if (key in obj) {
|
|
257
|
-
result[key] = obj[key];
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return result;
|
|
261
|
-
};
|
|
262
|
-
if (Array.isArray(data)) {
|
|
263
|
-
return data.map((item) => pick(item, fields));
|
|
264
|
-
}
|
|
265
|
-
return pick(data, fields);
|
|
266
|
-
}
|
|
267
|
-
getColumns(data, fields, flatten = false) {
|
|
268
|
-
const items = Array.isArray(data) ? data : [data];
|
|
269
|
-
if (items.length === 0)
|
|
270
|
-
return { cols: [], items: [] };
|
|
271
|
-
const first = items[0];
|
|
272
|
-
const hiddenFields = new Set(['_type', 'tenantId']);
|
|
273
|
-
if (fields) {
|
|
274
|
-
return { cols: fields, items: items };
|
|
275
|
-
}
|
|
276
|
-
// Recursively collect all column paths, flattening objects (but not arrays)
|
|
277
|
-
const collectPaths = (obj, prefix = '') => {
|
|
278
|
-
const paths = [];
|
|
279
|
-
for (const key of Object.keys(obj)) {
|
|
280
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
281
|
-
if (hiddenFields.has(key))
|
|
282
|
-
continue;
|
|
283
|
-
const val = obj[key];
|
|
284
|
-
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
285
|
-
// Flatten nested objects (but not arrays)
|
|
286
|
-
if (flatten) {
|
|
287
|
-
paths.push(...collectPaths(val, fullKey));
|
|
288
|
-
}
|
|
289
|
-
// Skip objects when not flattening
|
|
290
|
-
}
|
|
291
|
-
else if (!Array.isArray(val)) {
|
|
292
|
-
// Include primitives (and null)
|
|
293
|
-
paths.push(fullKey);
|
|
294
|
-
}
|
|
295
|
-
// Skip arrays entirely
|
|
296
|
-
}
|
|
297
|
-
return paths;
|
|
298
|
-
};
|
|
299
|
-
const cols = collectPaths(first);
|
|
300
|
-
return { cols: flatten ? cols : cols.slice(0, 8), items: items };
|
|
301
|
-
}
|
|
302
|
-
getValue(row, col) {
|
|
303
|
-
let val = row[col];
|
|
304
|
-
if (col.includes('.')) {
|
|
305
|
-
const parts = col.split('.');
|
|
306
|
-
val = parts.reduce((o, k) => (o && typeof o === 'object' ? o[k] : undefined), row);
|
|
307
|
-
}
|
|
308
|
-
return String(val ?? '');
|
|
309
|
-
}
|
|
310
|
-
printCsv(data, fields) {
|
|
311
|
-
const { cols, items } = this.getColumns(data, fields, true); // flatten=true for CSV
|
|
312
|
-
if (items.length === 0) {
|
|
313
|
-
this.log('No results');
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const escape = (val) => {
|
|
317
|
-
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
|
318
|
-
return '"' + val.replaceAll('"', '""') + '"';
|
|
319
|
-
}
|
|
320
|
-
return val;
|
|
321
|
-
};
|
|
322
|
-
// Print header
|
|
323
|
-
this.log(cols.map(escape).join(','));
|
|
324
|
-
// Print rows
|
|
325
|
-
for (const item of items) {
|
|
326
|
-
const row = cols.map((col) => escape(this.getValue(item, col)));
|
|
327
|
-
this.log(row.join(','));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
printTable(data, fields) {
|
|
331
|
-
const { cols, items } = this.getColumns(data, fields);
|
|
332
|
-
if (items.length === 0) {
|
|
333
|
-
this.log('No results');
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
// Calculate column widths
|
|
337
|
-
const widths = cols.map((c) => c.length);
|
|
338
|
-
for (const item of items) {
|
|
339
|
-
for (const [i, col] of cols.entries()) {
|
|
340
|
-
const val = this.getValue(item, col).slice(0, 40);
|
|
341
|
-
widths[i] = Math.max(widths[i], val.length);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// Print header
|
|
345
|
-
const header = cols.map((c, i) => c.padEnd(widths[i])).join(' ');
|
|
346
|
-
this.log(header);
|
|
347
|
-
this.log(widths.map((w) => '-'.repeat(w)).join(' '));
|
|
348
|
-
// Print rows
|
|
349
|
-
for (const item of items) {
|
|
350
|
-
const row = cols
|
|
351
|
-
.map((col, i) => {
|
|
352
|
-
const val = this.getValue(item, col).slice(0, 40);
|
|
353
|
-
return val.padEnd(widths[i]);
|
|
354
|
-
})
|
|
355
|
-
.join(' ');
|
|
356
|
-
this.log(row);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async runCamt053Import(flags) {
|
|
360
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
361
|
-
if (!context)
|
|
362
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
363
|
-
if (!context.tokens)
|
|
364
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
365
|
-
if (!flags.file) {
|
|
366
|
-
this.error('CAMT.053 import requires --file (-f) flag with path to XML file');
|
|
367
|
-
}
|
|
368
|
-
const fsModule = await import('node:fs');
|
|
369
|
-
let xmlContent;
|
|
370
|
-
try {
|
|
371
|
-
xmlContent = fsModule.readFileSync(flags.file, 'utf8');
|
|
372
|
-
}
|
|
373
|
-
catch {
|
|
374
|
-
this.error(`Failed to read file: ${flags.file}`);
|
|
375
|
-
}
|
|
376
|
-
const endpoint = ENDPOINTS['import-camt053'];
|
|
377
|
-
try {
|
|
378
|
-
const response = await apiRequest(endpoint.method, endpoint.path, {
|
|
379
|
-
contentType: 'application/xml',
|
|
380
|
-
contextName: flags.context,
|
|
381
|
-
data: xmlContent,
|
|
382
|
-
});
|
|
383
|
-
// Extract transactions array and counts from response
|
|
384
|
-
let transactions = [];
|
|
385
|
-
let imported = 0;
|
|
386
|
-
let skipped = 0;
|
|
387
|
-
if (response.data && typeof response.data === 'object') {
|
|
388
|
-
const data = response.data;
|
|
389
|
-
if ('data' in data && Array.isArray(data.data)) {
|
|
390
|
-
transactions = data.data;
|
|
391
|
-
}
|
|
392
|
-
else if ('transactions' in data && Array.isArray(data.transactions)) {
|
|
393
|
-
transactions = data.transactions;
|
|
394
|
-
}
|
|
395
|
-
else if (Array.isArray(response.data)) {
|
|
396
|
-
transactions = response.data;
|
|
397
|
-
}
|
|
398
|
-
if (typeof data.imported === 'number')
|
|
399
|
-
imported = data.imported;
|
|
400
|
-
if (typeof data.skipped === 'number')
|
|
401
|
-
skipped = data.skipped;
|
|
402
|
-
}
|
|
403
|
-
else if (Array.isArray(response.data)) {
|
|
404
|
-
transactions = response.data;
|
|
405
|
-
}
|
|
406
|
-
if (imported === 0 && skipped === 0 && transactions.length > 0) {
|
|
407
|
-
for (const tx of transactions) {
|
|
408
|
-
const t = tx;
|
|
409
|
-
if (t.status === 'skipped' || t.skipped === true) {
|
|
410
|
-
skipped++;
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
imported++;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (imported === 0 && skipped === 0) {
|
|
417
|
-
imported = transactions.length;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
let output = transactions;
|
|
421
|
-
if (flags.sanitize) {
|
|
422
|
-
output = this.sanitizeOutput(transactions);
|
|
423
|
-
}
|
|
424
|
-
const fieldList = flags.fields ? flags.fields.split(',').map((f) => f.trim()) : undefined;
|
|
425
|
-
if (flags.csv || flags.output === 'csv') {
|
|
426
|
-
this.printCsv(output, fieldList);
|
|
427
|
-
}
|
|
428
|
-
else if (flags.table || flags.output === 'table') {
|
|
429
|
-
this.printTable(output, fieldList);
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
this.log(JSON.stringify(output, null, 2));
|
|
433
|
-
}
|
|
434
|
-
this.log('');
|
|
435
|
-
this.log(`Summary: ${imported} imported, ${skipped} skipped`);
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
if (error instanceof ApiError) {
|
|
439
|
-
if (error.status === 404)
|
|
440
|
-
this.error(error.message);
|
|
441
|
-
this.error(`API Error (${error.status}): ${error.message}`);
|
|
442
|
-
}
|
|
443
|
-
throw error;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
async runExport(flags) {
|
|
447
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
448
|
-
if (!context)
|
|
449
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
450
|
-
if (!context.tokens)
|
|
451
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
452
|
-
const listEndpoint = ENDPOINTS.list;
|
|
453
|
-
if (!listEndpoint)
|
|
454
|
-
this.error('Export requires a list endpoint');
|
|
455
|
-
const allItems = [];
|
|
456
|
-
let page = 1;
|
|
457
|
-
const limit = 100;
|
|
458
|
-
let hasMore = true;
|
|
459
|
-
// Build base query params
|
|
460
|
-
const baseParams = {};
|
|
461
|
-
if (flags.search)
|
|
462
|
-
baseParams.search = flags.search;
|
|
463
|
-
if (flags['sort-by'])
|
|
464
|
-
baseParams.sortBy = flags['sort-by'];
|
|
465
|
-
if (flags.include)
|
|
466
|
-
baseParams.include = flags.include;
|
|
467
|
-
if (flags.query) {
|
|
468
|
-
try {
|
|
469
|
-
Object.assign(baseParams, JSON.parse(flags.query));
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
this.error('Invalid JSON in --query');
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// Fetch all pages
|
|
476
|
-
process.stderr.write('Exporting...');
|
|
477
|
-
while (hasMore) {
|
|
478
|
-
const params = { ...baseParams, limit, page };
|
|
479
|
-
const response = await apiRequest('GET', listEndpoint.path, {
|
|
480
|
-
contextName: flags.context,
|
|
481
|
-
params,
|
|
482
|
-
});
|
|
483
|
-
let items = [];
|
|
484
|
-
const data = response.data;
|
|
485
|
-
// Handle paginated response format
|
|
486
|
-
if (data && typeof data === 'object' && 'data' in data && Array.isArray(data.data)) {
|
|
487
|
-
items = data.data;
|
|
488
|
-
}
|
|
489
|
-
else if (Array.isArray(response.data)) {
|
|
490
|
-
items = response.data;
|
|
491
|
-
}
|
|
492
|
-
allItems.push(...items);
|
|
493
|
-
process.stderr.write(` ${allItems.length}`);
|
|
494
|
-
// Check if there are more pages
|
|
495
|
-
if (items.length < limit) {
|
|
496
|
-
hasMore = false;
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
page++;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
process.stderr.write(' done\n');
|
|
503
|
-
// Filter fields if specified
|
|
504
|
-
let output = allItems;
|
|
505
|
-
if (flags.fields) {
|
|
506
|
-
const fieldList = flags.fields.split(',').map((f) => f.trim());
|
|
507
|
-
output = this.filterFields(allItems, fieldList);
|
|
508
|
-
}
|
|
509
|
-
// Sanitize output if requested
|
|
510
|
-
if (flags.sanitize) {
|
|
511
|
-
output = this.sanitizeOutput(output);
|
|
512
|
-
}
|
|
513
|
-
// Output in requested format
|
|
514
|
-
const fieldList = flags.fields ? flags.fields.split(',').map((f) => f.trim()) : undefined;
|
|
515
|
-
if (flags.csv || flags.output === 'csv') {
|
|
516
|
-
this.printCsv(output, fieldList);
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
this.log(JSON.stringify(output, null, 2));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async runImport(flags) {
|
|
523
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
524
|
-
if (!context)
|
|
525
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
526
|
-
if (!context.tokens)
|
|
527
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
528
|
-
// Get data from file, --data, or --set flags
|
|
529
|
-
let items = [];
|
|
530
|
-
if (flags.file) {
|
|
531
|
-
const fsModule = await import('node:fs');
|
|
532
|
-
try {
|
|
533
|
-
const content = JSON.parse(fsModule.readFileSync(flags.file, 'utf8'));
|
|
534
|
-
items = Array.isArray(content) ? content : [content];
|
|
535
|
-
}
|
|
536
|
-
catch {
|
|
537
|
-
this.error(`Failed to read file: ${flags.file}`);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
else if (flags.data) {
|
|
541
|
-
try {
|
|
542
|
-
const content = JSON.parse(flags.data);
|
|
543
|
-
items = Array.isArray(content) ? content : [content];
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
this.error('Invalid JSON in --data');
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
else if (flags.set && flags.set.length > 0) {
|
|
550
|
-
const item = {};
|
|
551
|
-
for (const pair of flags.set) {
|
|
552
|
-
const eqIndex = pair.indexOf('=');
|
|
553
|
-
if (eqIndex === -1)
|
|
554
|
-
this.error(`Invalid --set format: ${pair}. Use: --set key=value`);
|
|
555
|
-
const key = pair.slice(0, eqIndex);
|
|
556
|
-
let value = pair.slice(eqIndex + 1);
|
|
557
|
-
try {
|
|
558
|
-
value = JSON.parse(value);
|
|
559
|
-
}
|
|
560
|
-
catch {
|
|
561
|
-
/* keep as string */
|
|
562
|
-
}
|
|
563
|
-
const keys = key.split('.');
|
|
564
|
-
let obj = item;
|
|
565
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
566
|
-
if (!(keys[i] in obj))
|
|
567
|
-
obj[keys[i]] = {};
|
|
568
|
-
obj = obj[keys[i]];
|
|
569
|
-
}
|
|
570
|
-
obj[keys.at(-1)] = value;
|
|
571
|
-
}
|
|
572
|
-
items = [item];
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
this.error('Import requires --file, --data, or --set flags');
|
|
576
|
-
}
|
|
577
|
-
const createEndpoint = ENDPOINTS.create;
|
|
578
|
-
const updateEndpoint = ENDPOINTS.update;
|
|
579
|
-
if (!createEndpoint || !updateEndpoint) {
|
|
580
|
-
this.error('Import requires both create and update endpoints');
|
|
581
|
-
}
|
|
582
|
-
// Helper to extract identifier from item (id, code, or xxxCode)
|
|
583
|
-
const getItemIdentifier = (item) => {
|
|
584
|
-
if (item.id)
|
|
585
|
-
return String(item.id);
|
|
586
|
-
if (item.code)
|
|
587
|
-
return String(item.code);
|
|
588
|
-
// Look for xxxCode fields (e.g., ledgerCode, accountCode)
|
|
589
|
-
for (const key of Object.keys(item)) {
|
|
590
|
-
if (key.endsWith('Code') && item[key])
|
|
591
|
-
return String(item[key]);
|
|
592
|
-
}
|
|
593
|
-
// Look for name as fallback
|
|
594
|
-
if (item.name)
|
|
595
|
-
return String(item.name).slice(0, 30);
|
|
596
|
-
return '';
|
|
597
|
-
};
|
|
598
|
-
const results = [];
|
|
599
|
-
for (const item of items) {
|
|
600
|
-
const identifier = getItemIdentifier(item);
|
|
601
|
-
try {
|
|
602
|
-
// Try to create
|
|
603
|
-
const response = await apiRequest('POST', createEndpoint.path, {
|
|
604
|
-
contextName: flags.context,
|
|
605
|
-
data: item,
|
|
606
|
-
});
|
|
607
|
-
const id = response.data?.id;
|
|
608
|
-
results.push({ id: id ? String(id) : undefined, identifier, status: 'created' });
|
|
609
|
-
}
|
|
610
|
-
catch (error) {
|
|
611
|
-
if (error instanceof ApiError && error.status === 409) {
|
|
612
|
-
// Conflict - try to update existing
|
|
613
|
-
const errorData = error.data;
|
|
614
|
-
const existingId = errorData?.existingId;
|
|
615
|
-
if (existingId) {
|
|
616
|
-
try {
|
|
617
|
-
const updatePath = updateEndpoint.path.replace('{id}', existingId);
|
|
618
|
-
await apiRequest('PATCH', updatePath, {
|
|
619
|
-
contextName: flags.context,
|
|
620
|
-
data: item,
|
|
621
|
-
});
|
|
622
|
-
results.push({ id: existingId, identifier, status: 'updated' });
|
|
623
|
-
}
|
|
624
|
-
catch (updateError) {
|
|
625
|
-
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
626
|
-
results.push({ error: `Update failed: ${msg}`, id: existingId, identifier, status: 'error' });
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
results.push({ error: 'Conflict but no existingId returned', identifier, status: 'error' });
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
else {
|
|
634
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
635
|
-
results.push({ error: msg, identifier, status: 'error' });
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
// Summary counts
|
|
640
|
-
const created = results.filter((r) => r.status === 'created').length;
|
|
641
|
-
const updated = results.filter((r) => r.status === 'updated').length;
|
|
642
|
-
const errors = results.filter((r) => r.status === 'error');
|
|
643
|
-
// Output results based on format (default: table)
|
|
644
|
-
const outputFormat = flags.output || 'table';
|
|
645
|
-
if (outputFormat === 'json') {
|
|
646
|
-
this.log(JSON.stringify(results, null, 2));
|
|
647
|
-
}
|
|
648
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
649
|
-
// CSV output
|
|
650
|
-
this.log('status,identifier,id,error');
|
|
651
|
-
for (const r of results) {
|
|
652
|
-
const err = (r.error || '').replaceAll('"', '""');
|
|
653
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
// Table output (default)
|
|
658
|
-
if (results.length > 0) {
|
|
659
|
-
const statusWidth = 8;
|
|
660
|
-
const identifierWidth = Math.max(10, ...results.map((r) => (r.identifier || '').length));
|
|
661
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
662
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
663
|
-
for (const r of results) {
|
|
664
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
665
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
// Always show summary at the end
|
|
670
|
-
this.log('');
|
|
671
|
-
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
672
|
-
}
|
|
673
|
-
async runNestedImport(suffix, parentId, flags) {
|
|
674
|
-
if (!parentId) {
|
|
675
|
-
this.error(`Missing required argument: id. Usage: aderis auth import-${suffix} <parent-id> --file data.json`);
|
|
676
|
-
}
|
|
677
|
-
const context = flags.context ? getContext(flags.context) : getCurrentContext();
|
|
678
|
-
if (!context)
|
|
679
|
-
this.error('No context configured. Run: aderis auth login --new');
|
|
680
|
-
if (!context.tokens)
|
|
681
|
-
this.error('Not logged in. Run: aderis auth login');
|
|
682
|
-
// Get data from file, --data, or --set flags
|
|
683
|
-
let items = [];
|
|
684
|
-
if (flags.file) {
|
|
685
|
-
const fsModule = await import('node:fs');
|
|
686
|
-
try {
|
|
687
|
-
const content = JSON.parse(fsModule.readFileSync(flags.file, 'utf8'));
|
|
688
|
-
items = Array.isArray(content) ? content : [content];
|
|
689
|
-
}
|
|
690
|
-
catch {
|
|
691
|
-
this.error(`Failed to read file: ${flags.file}`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
else if (flags.data) {
|
|
695
|
-
try {
|
|
696
|
-
const content = JSON.parse(flags.data);
|
|
697
|
-
items = Array.isArray(content) ? content : [content];
|
|
698
|
-
}
|
|
699
|
-
catch {
|
|
700
|
-
this.error('Invalid JSON in --data');
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
else if (flags.set && flags.set.length > 0) {
|
|
704
|
-
const item = {};
|
|
705
|
-
for (const pair of flags.set) {
|
|
706
|
-
const eqIndex = pair.indexOf('=');
|
|
707
|
-
if (eqIndex === -1)
|
|
708
|
-
this.error(`Invalid --set format: ${pair}. Use: --set key=value`);
|
|
709
|
-
const key = pair.slice(0, eqIndex);
|
|
710
|
-
let value = pair.slice(eqIndex + 1);
|
|
711
|
-
try {
|
|
712
|
-
value = JSON.parse(value);
|
|
713
|
-
}
|
|
714
|
-
catch {
|
|
715
|
-
/* keep as string */
|
|
716
|
-
}
|
|
717
|
-
const keys = key.split('.');
|
|
718
|
-
let obj = item;
|
|
719
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
720
|
-
if (!(keys[i] in obj))
|
|
721
|
-
obj[keys[i]] = {};
|
|
722
|
-
obj = obj[keys[i]];
|
|
723
|
-
}
|
|
724
|
-
obj[keys.at(-1)] = value;
|
|
725
|
-
}
|
|
726
|
-
items = [item];
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
this.error(`Import requires --file, --data, or --set flags`);
|
|
730
|
-
}
|
|
731
|
-
const createEndpoint = ENDPOINTS[`create-${suffix}`];
|
|
732
|
-
const updateEndpoint = ENDPOINTS[`update-${suffix}`];
|
|
733
|
-
if (!createEndpoint || !updateEndpoint) {
|
|
734
|
-
this.error(`Import requires both create-${suffix} and update-${suffix} endpoints`);
|
|
735
|
-
}
|
|
736
|
-
// Build the base path with parent ID
|
|
737
|
-
const basePath = createEndpoint.path.replace('{id}', parentId);
|
|
738
|
-
// Helper to extract identifier from item
|
|
739
|
-
const getItemIdentifier = (item) => {
|
|
740
|
-
if (item.id)
|
|
741
|
-
return String(item.id);
|
|
742
|
-
if (item.itemId)
|
|
743
|
-
return String(item.itemId);
|
|
744
|
-
if (item.code)
|
|
745
|
-
return String(item.code);
|
|
746
|
-
for (const key of Object.keys(item)) {
|
|
747
|
-
if (key.endsWith('Id') && item[key])
|
|
748
|
-
return String(item[key]);
|
|
749
|
-
if (key.endsWith('Code') && item[key])
|
|
750
|
-
return String(item[key]);
|
|
751
|
-
}
|
|
752
|
-
return '';
|
|
753
|
-
};
|
|
754
|
-
const results = [];
|
|
755
|
-
for (const item of items) {
|
|
756
|
-
const identifier = getItemIdentifier(item);
|
|
757
|
-
try {
|
|
758
|
-
// Try to create
|
|
759
|
-
const response = await apiRequest('POST', basePath, {
|
|
760
|
-
contextName: flags.context,
|
|
761
|
-
data: item,
|
|
762
|
-
});
|
|
763
|
-
const id = response.data?.id;
|
|
764
|
-
results.push({ id: id ? String(id) : undefined, identifier, status: 'created' });
|
|
765
|
-
}
|
|
766
|
-
catch (error) {
|
|
767
|
-
if (error instanceof ApiError && error.status === 409) {
|
|
768
|
-
// Conflict - try to update existing
|
|
769
|
-
const errorData = error.data;
|
|
770
|
-
const existingId = errorData?.existingId || item.id;
|
|
771
|
-
if (existingId) {
|
|
772
|
-
try {
|
|
773
|
-
// Find the nested ID param name (e.g., 'priceId' for prices)
|
|
774
|
-
const nestedIdParam = updateEndpoint.pathParams.find((p) => p !== 'id');
|
|
775
|
-
let updatePath = updateEndpoint.path.replace('{id}', parentId);
|
|
776
|
-
if (nestedIdParam) {
|
|
777
|
-
updatePath = updatePath.replace(`{${nestedIdParam}}`, existingId);
|
|
778
|
-
}
|
|
779
|
-
await apiRequest('PATCH', updatePath, {
|
|
780
|
-
contextName: flags.context,
|
|
781
|
-
data: item,
|
|
782
|
-
});
|
|
783
|
-
results.push({ id: existingId, identifier, status: 'updated' });
|
|
784
|
-
}
|
|
785
|
-
catch (updateError) {
|
|
786
|
-
const msg = updateError instanceof ApiError ? updateError.message : String(updateError);
|
|
787
|
-
results.push({ error: `Update failed: ${msg}`, id: existingId, identifier, status: 'error' });
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
results.push({ error: 'Conflict but no existingId returned', identifier, status: 'error' });
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
const msg = error instanceof ApiError ? `(${error.status}) ${error.message}` : String(error);
|
|
796
|
-
results.push({ error: msg, identifier, status: 'error' });
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
// Summary counts
|
|
801
|
-
const created = results.filter((r) => r.status === 'created').length;
|
|
802
|
-
const updated = results.filter((r) => r.status === 'updated').length;
|
|
803
|
-
const errors = results.filter((r) => r.status === 'error');
|
|
804
|
-
// Output results based on format (default: table)
|
|
805
|
-
const outputFormat = flags.output || 'table';
|
|
806
|
-
if (outputFormat === 'json') {
|
|
807
|
-
this.log(JSON.stringify(results, null, 2));
|
|
808
|
-
}
|
|
809
|
-
else if (outputFormat === 'csv' || flags.csv) {
|
|
810
|
-
this.log('status,identifier,id,error');
|
|
811
|
-
for (const r of results) {
|
|
812
|
-
const err = (r.error || '').replaceAll('"', '""');
|
|
813
|
-
this.log(`${r.status},${r.identifier || ''},${r.id || ''},"${err}"`);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
else if (results.length > 0) {
|
|
817
|
-
const statusWidth = 8;
|
|
818
|
-
const identifierWidth = Math.max(10, ...results.map((r) => (r.identifier || '').length));
|
|
819
|
-
this.log(`${'status'.padEnd(statusWidth)} ${'identifier'.padEnd(identifierWidth)} error`);
|
|
820
|
-
this.log(`${'-'.repeat(statusWidth)} ${'-'.repeat(identifierWidth)} ${'-'.repeat(50)}`);
|
|
821
|
-
for (const r of results) {
|
|
822
|
-
const status = r.status === 'created' ? 'created' : r.status === 'updated' ? 'updated' : 'error';
|
|
823
|
-
this.log(`${status.padEnd(statusWidth)} ${(r.identifier || '').padEnd(identifierWidth)} ${r.error || ''}`);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
this.log('');
|
|
827
|
-
this.log(`Summary: ${created} created, ${updated} updated, ${errors.length} failed`);
|
|
828
|
-
}
|
|
829
|
-
sanitizeOutput(data) {
|
|
830
|
-
const auditFields = new Set(['_type', 'createdAt', 'createdBy', 'tenantId', 'updatedAt', 'updatedBy']);
|
|
831
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
832
|
-
const sanitize = (obj, parentUuids = new Set()) => {
|
|
833
|
-
if (obj === null || obj === undefined)
|
|
834
|
-
return undefined;
|
|
835
|
-
if (Array.isArray(obj)) {
|
|
836
|
-
const result = obj.map((item) => sanitize(item, new Set(parentUuids))).filter((item) => item !== undefined);
|
|
837
|
-
return result.length > 0 ? result : undefined;
|
|
838
|
-
}
|
|
839
|
-
if (typeof obj === 'object') {
|
|
840
|
-
const input = obj;
|
|
841
|
-
const result = {};
|
|
842
|
-
// Collect UUIDs from this level to pass to children
|
|
843
|
-
const currentUuids = new Set(parentUuids);
|
|
844
|
-
for (const [key, value] of Object.entries(input)) {
|
|
845
|
-
if (typeof value === 'string' && uuidRegex.test(value)) {
|
|
846
|
-
currentUuids.add(value);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
for (const [key, value] of Object.entries(input)) {
|
|
850
|
-
// Skip audit fields
|
|
851
|
-
if (auditFields.has(key))
|
|
852
|
-
continue;
|
|
853
|
-
// Skip null/undefined values
|
|
854
|
-
if (value === null || value === undefined)
|
|
855
|
-
continue;
|
|
856
|
-
// Skip UUID values that exist in parent
|
|
857
|
-
if (typeof value === 'string' && uuidRegex.test(value) && parentUuids.has(value))
|
|
858
|
-
continue;
|
|
859
|
-
// Recursively sanitize nested objects/arrays
|
|
860
|
-
const sanitized = sanitize(value, currentUuids);
|
|
861
|
-
if (sanitized !== undefined) {
|
|
862
|
-
result[key] = sanitized;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
return Object.keys(result).length > 0 ? result : undefined;
|
|
866
|
-
}
|
|
867
|
-
return obj;
|
|
868
|
-
};
|
|
869
|
-
return sanitize(data) ?? (Array.isArray(data) ? [] : {});
|
|
870
|
-
}
|
|
871
|
-
}
|