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,217 @@
|
|
|
1
|
+
import CONFIG_REFERENCE_DATA from './reference/config-reference.json' with { type: 'json' };
|
|
2
|
+
|
|
3
|
+
const DOCUMENT_ID = 'config.toml';
|
|
4
|
+
const PLACEHOLDER_SEGMENT = /^<[^>]+>$/;
|
|
5
|
+
const NON_DEPRECATED_KEYS = new Set(['approval_policy']);
|
|
6
|
+
const KIND_PRIORITY = {
|
|
7
|
+
value: 1,
|
|
8
|
+
array: 2,
|
|
9
|
+
table: 3,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const normalizeSegments = (segments) =>
|
|
13
|
+
Array.isArray(segments) ? segments.map((segment) => String(segment)) : [];
|
|
14
|
+
|
|
15
|
+
const isPlaceholderSegment = (segment) =>
|
|
16
|
+
PLACEHOLDER_SEGMENT.test(segment) || String(segment).endsWith('[]');
|
|
17
|
+
|
|
18
|
+
const isCustomIdPlaceholder = (segment) =>
|
|
19
|
+
isPlaceholderSegment(segment) &&
|
|
20
|
+
segment !== '<index>' &&
|
|
21
|
+
!String(segment).endsWith('[]');
|
|
22
|
+
|
|
23
|
+
const normalizeType = (type) => String(type || '').replace(/\s+/g, ' ').trim();
|
|
24
|
+
|
|
25
|
+
const mapTypeToKind = (type) => {
|
|
26
|
+
const normalizedType = normalizeType(type);
|
|
27
|
+
|
|
28
|
+
if (normalizedType === 'table') {
|
|
29
|
+
return 'table';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (normalizedType.startsWith('array<')) {
|
|
33
|
+
return 'array';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 'value';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getContainerKind = (path, depth) => {
|
|
40
|
+
const nextSegment = path[depth + 1];
|
|
41
|
+
|
|
42
|
+
if (nextSegment === '<index>' || String(nextSegment).endsWith('[]')) {
|
|
43
|
+
return 'array';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 'table';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const pathPrefixMatches = (referencePath, actualPrefix) => {
|
|
50
|
+
if (referencePath.length < actualPrefix.length) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < actualPrefix.length; index += 1) {
|
|
55
|
+
const referenceSegment = referencePath[index];
|
|
56
|
+
const actualSegment = actualPrefix[index];
|
|
57
|
+
|
|
58
|
+
if (isPlaceholderSegment(referenceSegment)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (referenceSegment !== actualSegment) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const fullPathMatches = (referencePath, actualPath) =>
|
|
71
|
+
referencePath.length === actualPath.length &&
|
|
72
|
+
pathPrefixMatches(referencePath, actualPath);
|
|
73
|
+
|
|
74
|
+
const countConcreteSegments = (segments) =>
|
|
75
|
+
segments.reduce((count, segment) => count + (isPlaceholderSegment(segment) ? 0 : 1), 0);
|
|
76
|
+
|
|
77
|
+
const configDocument =
|
|
78
|
+
CONFIG_REFERENCE_DATA?.documents?.find((document) => document?.id === DOCUMENT_ID) || null;
|
|
79
|
+
|
|
80
|
+
const referenceOptions = Array.isArray(configDocument?.options)
|
|
81
|
+
? configDocument.options.map((option) => {
|
|
82
|
+
const keyPath = normalizeSegments(option?.key_path);
|
|
83
|
+
const key = String(option?.key || keyPath.join('.'));
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
key,
|
|
87
|
+
keyPath,
|
|
88
|
+
type: normalizeType(option?.type),
|
|
89
|
+
enumValues: Array.isArray(option?.enum_values)
|
|
90
|
+
? option.enum_values.map((value) => String(value))
|
|
91
|
+
: [],
|
|
92
|
+
description: String(option?.description || ''),
|
|
93
|
+
deprecated: option?.deprecated === true && !NON_DEPRECATED_KEYS.has(key),
|
|
94
|
+
};
|
|
95
|
+
})
|
|
96
|
+
: [];
|
|
97
|
+
|
|
98
|
+
const optionsByKey = new Map(referenceOptions.map((option) => [option.key, option]));
|
|
99
|
+
|
|
100
|
+
const mergeDefinition = (map, definition) => {
|
|
101
|
+
const existing = map.get(definition.key);
|
|
102
|
+
if (!existing) {
|
|
103
|
+
map.set(definition.key, definition);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const existingPriority = KIND_PRIORITY[existing.kind] || 0;
|
|
108
|
+
const nextPriority = KIND_PRIORITY[definition.kind] || 0;
|
|
109
|
+
|
|
110
|
+
map.set(definition.key, {
|
|
111
|
+
...existing,
|
|
112
|
+
kind: nextPriority > existingPriority ? definition.kind : existing.kind,
|
|
113
|
+
isDeprecated: Boolean(existing.isDeprecated || definition.isDeprecated),
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const getReferenceOptionForPath = (pathSegments) => {
|
|
118
|
+
const normalizedPath = normalizeSegments(pathSegments);
|
|
119
|
+
if (normalizedPath.length === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const exactKey = normalizedPath.join('.');
|
|
124
|
+
const exactMatch = optionsByKey.get(exactKey);
|
|
125
|
+
if (exactMatch) {
|
|
126
|
+
return exactMatch;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const candidates = referenceOptions.filter((option) => fullPathMatches(option.keyPath, normalizedPath));
|
|
130
|
+
if (candidates.length === 0) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
candidates.sort((left, right) => {
|
|
135
|
+
const concreteDelta = countConcreteSegments(right.keyPath) - countConcreteSegments(left.keyPath);
|
|
136
|
+
if (concreteDelta !== 0) {
|
|
137
|
+
return concreteDelta;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return left.key.localeCompare(right.key);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return candidates[0];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const getReferenceTableDefinitions = (pathSegments = []) => {
|
|
147
|
+
const normalizedPath = normalizeSegments(pathSegments);
|
|
148
|
+
const childDefinitions = new Map();
|
|
149
|
+
const depth = normalizedPath.length;
|
|
150
|
+
|
|
151
|
+
referenceOptions.forEach((option) => {
|
|
152
|
+
if (!pathPrefixMatches(option.keyPath, normalizedPath)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (option.keyPath.length <= depth) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const childKey = option.keyPath[depth];
|
|
161
|
+
if (isPlaceholderSegment(childKey)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isLeaf = option.keyPath.length === depth + 1;
|
|
166
|
+
const kind = isLeaf ? mapTypeToKind(option.type) : getContainerKind(option.keyPath, depth);
|
|
167
|
+
|
|
168
|
+
mergeDefinition(childDefinitions, {
|
|
169
|
+
key: childKey,
|
|
170
|
+
kind,
|
|
171
|
+
isDeprecated: isLeaf ? option.deprecated : false,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return [...childDefinitions.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const getReferenceRootDefinitions = () => getReferenceTableDefinitions([]);
|
|
179
|
+
|
|
180
|
+
const featureKeys = [
|
|
181
|
+
...new Set(
|
|
182
|
+
referenceOptions
|
|
183
|
+
.filter(
|
|
184
|
+
(option) =>
|
|
185
|
+
option.keyPath.length === 2 &&
|
|
186
|
+
option.keyPath[0] === 'features' &&
|
|
187
|
+
!isPlaceholderSegment(option.keyPath[1])
|
|
188
|
+
)
|
|
189
|
+
.map((option) => option.keyPath[1])
|
|
190
|
+
),
|
|
191
|
+
].sort((left, right) => left.localeCompare(right));
|
|
192
|
+
|
|
193
|
+
export const getReferenceFeatureKeys = () => featureKeys;
|
|
194
|
+
|
|
195
|
+
export const getReferenceCustomIdPlaceholder = (pathSegments = []) => {
|
|
196
|
+
const normalizedPath = normalizeSegments(pathSegments);
|
|
197
|
+
const depth = normalizedPath.length;
|
|
198
|
+
const placeholders = new Set();
|
|
199
|
+
|
|
200
|
+
referenceOptions.forEach((option) => {
|
|
201
|
+
if (!pathPrefixMatches(option.keyPath, normalizedPath)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (option.keyPath.length <= depth) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const childKey = option.keyPath[depth];
|
|
210
|
+
if (isCustomIdPlaceholder(childKey)) {
|
|
211
|
+
placeholders.add(childKey);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const [firstMatch] = [...placeholders];
|
|
216
|
+
return firstMatch || null;
|
|
217
|
+
};
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const CONTROL_HINT = '↑/↓ move • PgUp/PgDn page • Home/End jump • Enter: open section or edit • Del: unset value • ←/Backspace: back • r: reload • q: quit';
|
|
2
|
+
export const EDIT_CONTROL_HINT = '↑/↓ choose • PgUp/PgDn page • Home/End jump • Enter: save • Esc/Backspace/←: cancel • Del: delete char (text input) • r: reload • q: quit';
|
|
3
|
+
export const BRAND = 'CODEX CONFIGURATOR';
|
|
4
|
+
export const CONFIG_TAGS = ['Node.js', 'React', 'Ink', 'TOML'];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const isBackspaceKey = (input, key) =>
|
|
2
|
+
key.backspace === true || key.name === 'backspace' || input === '\b' || input === '\u007f';
|
|
3
|
+
|
|
4
|
+
export const isDeleteKey = (input, key) =>
|
|
5
|
+
key.delete === true || key.name === 'delete' || input === '\u001b[3~';
|
|
6
|
+
|
|
7
|
+
export const isPageUpKey = (input, key) =>
|
|
8
|
+
key.pageUp === true || key.name === 'pageup' || key.name === 'page-up' || input === '\u001b[5~';
|
|
9
|
+
|
|
10
|
+
export const isPageDownKey = (input, key) =>
|
|
11
|
+
key.pageDown === true || key.name === 'pagedown' || key.name === 'page-down' || input === '\u001b[6~';
|
|
12
|
+
|
|
13
|
+
export const isHomeKey = (input, key) =>
|
|
14
|
+
key.home === true ||
|
|
15
|
+
key.name === 'home' ||
|
|
16
|
+
input === '\u001b[H' ||
|
|
17
|
+
input === '\u001b[1~' ||
|
|
18
|
+
input === '\u001bOH';
|
|
19
|
+
|
|
20
|
+
export const isEndKey = (input, key) =>
|
|
21
|
+
key.end === true ||
|
|
22
|
+
key.name === 'end' ||
|
|
23
|
+
input === '\u001b[F' ||
|
|
24
|
+
input === '\u001b[4~' ||
|
|
25
|
+
input === '\u001bOF';
|
package/src/layout.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
2
|
+
|
|
3
|
+
export const pathToKey = (segments) => JSON.stringify(segments);
|
|
4
|
+
|
|
5
|
+
export const computePaneWidths = (terminalWidth, rows) => {
|
|
6
|
+
const available = Math.max(40, terminalWidth - 2);
|
|
7
|
+
const contentRows = rows.length === 0 ? [] : rows;
|
|
8
|
+
const longestRow = contentRows.reduce(
|
|
9
|
+
(max, row) => Math.max(max, String(row.label).length + 8),
|
|
10
|
+
26
|
|
11
|
+
);
|
|
12
|
+
const minLeftWidth = 30;
|
|
13
|
+
const minRightWidth = 24;
|
|
14
|
+
|
|
15
|
+
const leftNeed = Math.max(minLeftWidth, longestRow);
|
|
16
|
+
const leftWidth = clamp(leftNeed, minLeftWidth, available - minRightWidth - 2);
|
|
17
|
+
const rightWidth = available - leftWidth - 2;
|
|
18
|
+
|
|
19
|
+
return { leftWidth, rightWidth };
|
|
20
|
+
};
|