codex-configurator 0.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/LICENSE +21 -0
- package/README.md +97 -0
- package/index.js +759 -0
- package/package.json +42 -0
- package/src/components/ConfigNavigator.js +387 -0
- package/src/components/Header.js +33 -0
- package/src/configFeatures.js +233 -0
- package/src/configHelp.js +252 -0
- package/src/configParser.js +564 -0
- package/src/configReference.js +217 -0
- package/src/constants.js +4 -0
- package/src/interaction.js +25 -0
- package/src/layout.js +20 -0
- package/src/reference/config-reference.json +5837 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import * as toml from 'toml';
|
|
5
|
+
import { stringify } from '@iarna/toml';
|
|
6
|
+
import {
|
|
7
|
+
getConfigFeatureDefinition,
|
|
8
|
+
getConfigFeatureKeys,
|
|
9
|
+
} from './configFeatures.js';
|
|
10
|
+
import {
|
|
11
|
+
getReferenceOptionForPath,
|
|
12
|
+
getReferenceCustomIdPlaceholder,
|
|
13
|
+
getReferenceRootDefinitions,
|
|
14
|
+
getReferenceTableDefinitions,
|
|
15
|
+
} from './configReference.js';
|
|
16
|
+
|
|
17
|
+
export const CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
|
|
18
|
+
export const MAX_DETAIL_CHARS = 2200;
|
|
19
|
+
const MAX_ARRAY_PREVIEW_ITEMS = 3;
|
|
20
|
+
const MAX_ARRAY_PREVIEW_CHARS = 52;
|
|
21
|
+
|
|
22
|
+
export const isPlainObject = (value) =>
|
|
23
|
+
Object.prototype.toString.call(value) === '[object Object]';
|
|
24
|
+
|
|
25
|
+
const truncateText = (text, maxLength) =>
|
|
26
|
+
text.length <= maxLength ? text : `${text.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
27
|
+
|
|
28
|
+
const resolveSegment = (segment) => {
|
|
29
|
+
if (typeof segment === 'number') {
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof segment === 'string' && /^\d+$/.test(segment)) {
|
|
34
|
+
const parsed = Number(segment);
|
|
35
|
+
return Number.isInteger(parsed) ? parsed : segment;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return segment;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const formatArrayItemSummary = (value) => {
|
|
42
|
+
if (typeof value === 'string') {
|
|
43
|
+
return JSON.stringify(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
return `[${value.length} item(s)]`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isPlainObject(value)) {
|
|
55
|
+
const keys = Object.keys(value);
|
|
56
|
+
if (keys.length === 0) {
|
|
57
|
+
return '{}';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const preview = keys.slice(0, 2).join(', ');
|
|
61
|
+
const suffix = keys.length > 2 ? ', …' : '';
|
|
62
|
+
return `{${preview}${suffix}}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return String(value);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const formatArrayPreview = (value) => {
|
|
69
|
+
if (value.length === 0) {
|
|
70
|
+
return '[]';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const items = value.slice(0, MAX_ARRAY_PREVIEW_ITEMS).map(formatArrayItemSummary);
|
|
74
|
+
const remaining = value.length - items.length;
|
|
75
|
+
const joined = `[${items.join(', ')}${remaining > 0 ? `, +${remaining}` : ''}]`;
|
|
76
|
+
|
|
77
|
+
return truncateText(joined, MAX_ARRAY_PREVIEW_CHARS);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const readConfig = () => {
|
|
81
|
+
try {
|
|
82
|
+
const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
83
|
+
const data = toml.parse(fileContents);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
path: CONFIG_PATH,
|
|
88
|
+
data,
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
path: CONFIG_PATH,
|
|
94
|
+
error: error?.message || 'Unable to read or parse configuration file.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const normalizeFilePath = (outputPath) => outputPath || CONFIG_PATH;
|
|
100
|
+
|
|
101
|
+
export const writeConfig = (data, outputPath = CONFIG_PATH) => {
|
|
102
|
+
try {
|
|
103
|
+
const payload = stringify(data);
|
|
104
|
+
fs.writeFileSync(normalizeFilePath(outputPath), `${payload}\n`);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: error?.message || 'Unable to write configuration file.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getNodeKind = (value) => {
|
|
118
|
+
if (isPlainObject(value)) {
|
|
119
|
+
return 'table';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
if (value.length > 0 && value.every(isPlainObject)) {
|
|
124
|
+
return 'tableArray';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return 'array';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return 'value';
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const previewValue = (value) => {
|
|
134
|
+
if (typeof value === 'string') {
|
|
135
|
+
return JSON.stringify(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
|
|
139
|
+
return String(value);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
return formatArrayPreview(value);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isPlainObject(value)) {
|
|
147
|
+
return '{}';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return String(value);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const isFeaturesTable = (segments) =>
|
|
154
|
+
Array.isArray(segments) && segments[segments.length - 1] === 'features';
|
|
155
|
+
|
|
156
|
+
const buildFeatureRows = (node) => {
|
|
157
|
+
const rows = [];
|
|
158
|
+
const featureKeys = [...getConfigFeatureKeys()].sort((left, right) => left.localeCompare(right));
|
|
159
|
+
const configuredKeys = Object.keys(node);
|
|
160
|
+
const configuredSet = new Set(configuredKeys);
|
|
161
|
+
const seenKeys = new Set();
|
|
162
|
+
|
|
163
|
+
featureKeys.forEach((key) => {
|
|
164
|
+
const isConfigured = configuredSet.has(key);
|
|
165
|
+
const definition = getConfigFeatureDefinition(key);
|
|
166
|
+
const defaultValue = typeof definition?.defaultValue === 'boolean'
|
|
167
|
+
? definition.defaultValue
|
|
168
|
+
: false;
|
|
169
|
+
const value = isConfigured ? node[key] : defaultValue;
|
|
170
|
+
const preview = isConfigured ? previewValue(value) : `${String(defaultValue)} [default]`;
|
|
171
|
+
const isDeprecated = Boolean(definition?.deprecation);
|
|
172
|
+
|
|
173
|
+
seenKeys.add(key);
|
|
174
|
+
rows.push({
|
|
175
|
+
key,
|
|
176
|
+
kind: 'value',
|
|
177
|
+
value,
|
|
178
|
+
pathSegment: key,
|
|
179
|
+
label: `${key} = ${preview}`,
|
|
180
|
+
preview,
|
|
181
|
+
isConfigured,
|
|
182
|
+
isDeprecated,
|
|
183
|
+
isDocumented: true,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return sortRowsAlphabetically([
|
|
188
|
+
...rows,
|
|
189
|
+
...configuredKeys
|
|
190
|
+
.filter((key) => !seenKeys.has(key))
|
|
191
|
+
.sort((left, right) => left.localeCompare(right))
|
|
192
|
+
.map((key) => {
|
|
193
|
+
const value = node[key];
|
|
194
|
+
const preview = previewValue(value);
|
|
195
|
+
const definition = getConfigFeatureDefinition(key);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
key,
|
|
199
|
+
kind: getNodeKind(value),
|
|
200
|
+
value,
|
|
201
|
+
pathSegment: key,
|
|
202
|
+
label: `${key} = ${preview} [not in official list]`,
|
|
203
|
+
preview,
|
|
204
|
+
isConfigured: true,
|
|
205
|
+
isDeprecated: Boolean(definition?.deprecation),
|
|
206
|
+
isDocumented: false,
|
|
207
|
+
};
|
|
208
|
+
}),
|
|
209
|
+
]);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const isStringReferenceType = (type) => /^string(?:\s|$)/.test(String(type || '').trim());
|
|
213
|
+
const isBooleanReferenceType = (type) => String(type || '').trim() === 'boolean';
|
|
214
|
+
|
|
215
|
+
const inferBooleanDefaultFromDescription = (description) => {
|
|
216
|
+
const text = String(description || '');
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
/\bdefaults?\s+to\s+true\b/i.test(text) ||
|
|
220
|
+
/\bdefault:\s*true\b/i.test(text) ||
|
|
221
|
+
/\bdefault\s*=\s*true\b/i.test(text)
|
|
222
|
+
) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
/\bdefaults?\s+to\s+false\b/i.test(text) ||
|
|
228
|
+
/\bdefault:\s*false\b/i.test(text) ||
|
|
229
|
+
/\bdefault\s*=\s*false\b/i.test(text)
|
|
230
|
+
) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return false;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const getBooleanReferenceDefault = (pathSegments, key) => {
|
|
238
|
+
const referenceOption = getReferenceOptionForPath([...pathSegments, String(key)]);
|
|
239
|
+
if (!isBooleanReferenceType(referenceOption?.type)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return inferBooleanDefaultFromDescription(referenceOption?.description);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const formatMissingDefinitionLabel = (definition, pathSegments) => {
|
|
247
|
+
if (definition.kind === 'table') {
|
|
248
|
+
return `${definition.key} /`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const booleanDefault = getBooleanReferenceDefault(pathSegments, definition.key);
|
|
252
|
+
if (booleanDefault !== null) {
|
|
253
|
+
return `${definition.key} = ${String(booleanDefault)} [default]`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const referenceOption = getReferenceOptionForPath([...pathSegments, String(definition.key)]);
|
|
257
|
+
if (isStringReferenceType(referenceOption?.type)) {
|
|
258
|
+
return `${definition.key} = ""`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return `${definition.key} = default`;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const formatRowLabel = (key, kind, value) =>
|
|
265
|
+
kind === 'table'
|
|
266
|
+
? `${key} /`
|
|
267
|
+
: kind === 'tableArray'
|
|
268
|
+
? `${key} / [array:${value.length}]`
|
|
269
|
+
: `${key} = ${previewValue(value)}`;
|
|
270
|
+
|
|
271
|
+
const isPathDeprecated = (pathSegments, key) =>
|
|
272
|
+
Boolean(getReferenceOptionForPath([...pathSegments, String(key)])?.deprecated) ||
|
|
273
|
+
isToolsWebSearchDeprecated(pathSegments, key);
|
|
274
|
+
|
|
275
|
+
const sortRowsAlphabetically = (rows) =>
|
|
276
|
+
[...rows].sort((left, right) => String(left.key).localeCompare(String(right.key)));
|
|
277
|
+
|
|
278
|
+
const formatPlaceholderLabel = (placeholder) => {
|
|
279
|
+
const cleaned = String(placeholder || '')
|
|
280
|
+
.replace(/^</, '')
|
|
281
|
+
.replace(/>$/, '');
|
|
282
|
+
|
|
283
|
+
return cleaned || 'id';
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const appendCustomIdActionRow = (rows, pathSegments) => {
|
|
287
|
+
const placeholder = getReferenceCustomIdPlaceholder(pathSegments);
|
|
288
|
+
if (!placeholder) {
|
|
289
|
+
return rows;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const labelName = formatPlaceholderLabel(placeholder);
|
|
293
|
+
|
|
294
|
+
return [
|
|
295
|
+
...rows,
|
|
296
|
+
{
|
|
297
|
+
key: `add:${placeholder}`,
|
|
298
|
+
kind: 'action',
|
|
299
|
+
action: 'add-custom-id',
|
|
300
|
+
placeholder: labelName,
|
|
301
|
+
pathSegment: null,
|
|
302
|
+
label: `+ add ${labelName}`,
|
|
303
|
+
preview: '',
|
|
304
|
+
isConfigured: true,
|
|
305
|
+
isDeprecated: false,
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const buildDefinedRows = (node, definitions, pathSegments) => {
|
|
311
|
+
const rows = [];
|
|
312
|
+
const configuredKeys = Object.keys(node);
|
|
313
|
+
const configuredSet = new Set(configuredKeys);
|
|
314
|
+
const seenKeys = new Set();
|
|
315
|
+
|
|
316
|
+
definitions.forEach((definition) => {
|
|
317
|
+
const isConfigured = configuredSet.has(definition.key);
|
|
318
|
+
seenKeys.add(definition.key);
|
|
319
|
+
|
|
320
|
+
if (!isConfigured) {
|
|
321
|
+
const booleanDefault = getBooleanReferenceDefault(pathSegments, definition.key);
|
|
322
|
+
const value =
|
|
323
|
+
definition.kind === 'table'
|
|
324
|
+
? {}
|
|
325
|
+
: definition.kind === 'array'
|
|
326
|
+
? []
|
|
327
|
+
: booleanDefault !== null
|
|
328
|
+
? booleanDefault
|
|
329
|
+
: undefined;
|
|
330
|
+
|
|
331
|
+
rows.push({
|
|
332
|
+
key: definition.key,
|
|
333
|
+
kind: definition.kind,
|
|
334
|
+
value,
|
|
335
|
+
pathSegment: definition.key,
|
|
336
|
+
label: formatMissingDefinitionLabel(definition, pathSegments),
|
|
337
|
+
preview: booleanDefault !== null ? `${String(booleanDefault)} [default]` : 'default',
|
|
338
|
+
isConfigured: false,
|
|
339
|
+
isDeprecated: Boolean(definition.isDeprecated) || isPathDeprecated(pathSegments, definition.key),
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const value = node[definition.key];
|
|
345
|
+
const kind = getNodeKind(value);
|
|
346
|
+
rows.push({
|
|
347
|
+
key: definition.key,
|
|
348
|
+
kind,
|
|
349
|
+
value,
|
|
350
|
+
pathSegment: definition.key,
|
|
351
|
+
label: formatRowLabel(definition.key, kind, value),
|
|
352
|
+
preview: previewValue(value),
|
|
353
|
+
isConfigured: true,
|
|
354
|
+
isDeprecated: Boolean(definition.isDeprecated) || isPathDeprecated(pathSegments, definition.key),
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return sortRowsAlphabetically([
|
|
359
|
+
...rows,
|
|
360
|
+
...configuredKeys
|
|
361
|
+
.filter((key) => !seenKeys.has(key))
|
|
362
|
+
.sort((left, right) => left.localeCompare(right))
|
|
363
|
+
.map((key) => {
|
|
364
|
+
const value = node[key];
|
|
365
|
+
const kind = getNodeKind(value);
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
key,
|
|
369
|
+
kind,
|
|
370
|
+
value,
|
|
371
|
+
pathSegment: key,
|
|
372
|
+
label: formatRowLabel(key, kind, value),
|
|
373
|
+
preview: previewValue(value),
|
|
374
|
+
isConfigured: true,
|
|
375
|
+
isDeprecated: isPathDeprecated(pathSegments, key),
|
|
376
|
+
};
|
|
377
|
+
}),
|
|
378
|
+
]);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const buildRootRows = (node) => buildDefinedRows(node, getReferenceRootDefinitions(), []);
|
|
382
|
+
|
|
383
|
+
const getTableDefinitions = (pathSegments) =>
|
|
384
|
+
Array.isArray(pathSegments) ? getReferenceTableDefinitions(pathSegments) : [];
|
|
385
|
+
|
|
386
|
+
const isToolsWebSearchDeprecated = (pathSegments, key) =>
|
|
387
|
+
pathSegments[pathSegments.length - 1] === 'tools' && key === 'web_search';
|
|
388
|
+
|
|
389
|
+
export const getNodeAtPath = (root, segments) => {
|
|
390
|
+
let current = root;
|
|
391
|
+
|
|
392
|
+
for (const segment of segments) {
|
|
393
|
+
if (current == null) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const normalizedSegment = resolveSegment(segment);
|
|
398
|
+
|
|
399
|
+
if (Array.isArray(current) && Number.isInteger(normalizedSegment)) {
|
|
400
|
+
current = current[normalizedSegment];
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!isPlainObject(current) && !Array.isArray(current)) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (typeof current === 'object' && normalizedSegment in current) {
|
|
409
|
+
current = current[normalizedSegment];
|
|
410
|
+
} else {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return current;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
export const buildRows = (node, pathSegments = []) => {
|
|
419
|
+
if (node == null) {
|
|
420
|
+
if (pathSegments.length === 1 && pathSegments[0] === 'features') {
|
|
421
|
+
return buildFeatureRows({});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const tableDefinitions = getTableDefinitions(pathSegments);
|
|
425
|
+
if (tableDefinitions.length > 0) {
|
|
426
|
+
return appendCustomIdActionRow(
|
|
427
|
+
buildDefinedRows({}, tableDefinitions, pathSegments),
|
|
428
|
+
pathSegments
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return appendCustomIdActionRow([], pathSegments);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (isPlainObject(node)) {
|
|
436
|
+
if (pathSegments.length === 0) {
|
|
437
|
+
return buildRootRows(node);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (isFeaturesTable(pathSegments)) {
|
|
441
|
+
return buildFeatureRows(node);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const tableDefinitions = getTableDefinitions(pathSegments);
|
|
445
|
+
if (tableDefinitions.length > 0) {
|
|
446
|
+
return appendCustomIdActionRow(
|
|
447
|
+
buildDefinedRows(node, tableDefinitions, pathSegments),
|
|
448
|
+
pathSegments
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return appendCustomIdActionRow(
|
|
453
|
+
sortRowsAlphabetically(
|
|
454
|
+
Object.entries(node).map(([key, value]) => {
|
|
455
|
+
const kind = getNodeKind(value);
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
key,
|
|
459
|
+
kind,
|
|
460
|
+
value,
|
|
461
|
+
pathSegment: key,
|
|
462
|
+
label: formatRowLabel(key, kind, value),
|
|
463
|
+
preview: previewValue(value),
|
|
464
|
+
isDeprecated: isPathDeprecated(pathSegments, key),
|
|
465
|
+
};
|
|
466
|
+
})
|
|
467
|
+
),
|
|
468
|
+
pathSegments
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (Array.isArray(node)) {
|
|
473
|
+
if (node.length === 0) {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return node.map((value, index) => {
|
|
478
|
+
const kind = getNodeKind(value);
|
|
479
|
+
const label = kind === 'table' ? `[${index}] /` : `[${index}] = ${previewValue(value)}`;
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
key: String(index),
|
|
483
|
+
kind,
|
|
484
|
+
value,
|
|
485
|
+
pathSegment: index,
|
|
486
|
+
label,
|
|
487
|
+
preview: previewValue(value),
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return [];
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
export const formatDetails = (value) => {
|
|
496
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
497
|
+
const text = JSON.stringify(value, null, 2);
|
|
498
|
+
return text.length > MAX_DETAIL_CHARS ? `${text.slice(0, MAX_DETAIL_CHARS)}…` : text;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return String(value);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
export const setValueAtPath = (root, segments, nextValue) => {
|
|
505
|
+
if (!root || segments.length === 0) {
|
|
506
|
+
return root;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const copy = isPlainObject(root) ? { ...root } : Array.isArray(root) ? [...root] : root;
|
|
510
|
+
let current = copy;
|
|
511
|
+
|
|
512
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
513
|
+
const segment = resolveSegment(segments[index]);
|
|
514
|
+
const next = current?.[segment];
|
|
515
|
+
const nextContainer = isPlainObject(next)
|
|
516
|
+
? { ...next }
|
|
517
|
+
: Array.isArray(next)
|
|
518
|
+
? [...next]
|
|
519
|
+
: {};
|
|
520
|
+
current[segment] = nextContainer;
|
|
521
|
+
current = nextContainer;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const lastSegment = resolveSegment(segments[segments.length - 1]);
|
|
525
|
+
current[lastSegment] = nextValue;
|
|
526
|
+
|
|
527
|
+
return copy;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
export const deleteValueAtPath = (root, segments) => {
|
|
531
|
+
if (!root || segments.length === 0) {
|
|
532
|
+
return root;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const copy = isPlainObject(root) ? { ...root } : Array.isArray(root) ? [...root] : root;
|
|
536
|
+
let current = copy;
|
|
537
|
+
|
|
538
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
539
|
+
const segment = resolveSegment(segments[index]);
|
|
540
|
+
const next = current?.[segment];
|
|
541
|
+
const nextContainer = isPlainObject(next)
|
|
542
|
+
? { ...next }
|
|
543
|
+
: Array.isArray(next)
|
|
544
|
+
? [...next]
|
|
545
|
+
: {};
|
|
546
|
+
current[segment] = nextContainer;
|
|
547
|
+
current = nextContainer;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const lastSegment = resolveSegment(segments[segments.length - 1]);
|
|
551
|
+
|
|
552
|
+
if (Array.isArray(current) && Number.isInteger(lastSegment)) {
|
|
553
|
+
current.splice(lastSegment, 1);
|
|
554
|
+
return copy;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (current && typeof current === 'object') {
|
|
558
|
+
delete current[lastSegment];
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return copy;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
export const getTableKind = (node) => getNodeKind(node);
|