codex-configurator 0.2.4 → 0.2.6
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 +45 -17
- package/index.js +1467 -839
- package/package.json +4 -2
- package/src/appState.js +80 -0
- package/src/components/ConfigNavigator.js +633 -430
- package/src/components/Header.js +26 -50
- package/src/configFeatures.js +39 -175
- package/src/configHelp.js +20 -236
- package/src/configParser.js +117 -32
- package/src/configReference.js +1002 -35
- package/src/constants.js +10 -3
- package/src/fileContext.js +118 -0
- package/src/interaction.js +69 -25
- package/src/layout.js +80 -15
- package/src/reference/config-schema.json +2120 -0
- package/src/schemaRuntimeSync.js +282 -0
- package/src/ui/commands.js +699 -0
- package/src/ui/panes/CommandBar.js +90 -0
- package/src/ui/panes/HelpBubble.js +26 -0
- package/src/ui/panes/LayoutShell.js +17 -0
- package/src/ui/panes/StatusLine.js +123 -0
- package/src/variantPresets.js +268 -0
- package/src/reference/config-reference.json +0 -3494
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
|
|
4
|
+
export const CommandBar = ({
|
|
5
|
+
appMode,
|
|
6
|
+
isCommandMode,
|
|
7
|
+
commandInput,
|
|
8
|
+
commandMessage,
|
|
9
|
+
modeHint,
|
|
10
|
+
}) => {
|
|
11
|
+
const isAutocomplete = isCommandMode && typeof commandInput === 'string';
|
|
12
|
+
const cleanInput = isAutocomplete
|
|
13
|
+
? commandInput.replace(/^:/, '').trim().toLowerCase()
|
|
14
|
+
: '';
|
|
15
|
+
const isError = Boolean(commandMessage);
|
|
16
|
+
|
|
17
|
+
return React.createElement(
|
|
18
|
+
Box,
|
|
19
|
+
{
|
|
20
|
+
paddingX: 1,
|
|
21
|
+
paddingY: 0,
|
|
22
|
+
marginTop: 0,
|
|
23
|
+
minHeight: 1,
|
|
24
|
+
borderStyle: 'round',
|
|
25
|
+
borderColor:
|
|
26
|
+
appMode === 'edit'
|
|
27
|
+
? 'yellow'
|
|
28
|
+
: isCommandMode
|
|
29
|
+
? 'magenta'
|
|
30
|
+
: 'gray',
|
|
31
|
+
flexDirection: 'column',
|
|
32
|
+
},
|
|
33
|
+
isCommandMode
|
|
34
|
+
? React.createElement(
|
|
35
|
+
Box,
|
|
36
|
+
{ flexDirection: 'row', gap: 1 },
|
|
37
|
+
React.createElement(
|
|
38
|
+
Text,
|
|
39
|
+
{ color: 'yellow', wrap: 'truncate-end', bold: true },
|
|
40
|
+
`${commandInput || ':'}`,
|
|
41
|
+
React.createElement(
|
|
42
|
+
Text,
|
|
43
|
+
{ color: 'whiteBright' },
|
|
44
|
+
'█',
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
React.createElement(
|
|
48
|
+
Text,
|
|
49
|
+
{ color: isError ? 'redBright' : 'gray' },
|
|
50
|
+
isError ? commandMessage : modeHint || '',
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
: React.createElement(
|
|
54
|
+
Text,
|
|
55
|
+
{ color: 'gray', wrap: 'truncate-end' },
|
|
56
|
+
commandMessage || modeHint || '',
|
|
57
|
+
),
|
|
58
|
+
isCommandMode
|
|
59
|
+
? React.createElement(
|
|
60
|
+
Box,
|
|
61
|
+
{ flexDirection: 'row', gap: 2, paddingX: 1, marginTop: 0 },
|
|
62
|
+
...[
|
|
63
|
+
{ cmd: 'quit', desc: 'exit app' },
|
|
64
|
+
{ cmd: 'file', desc: 'switch file' },
|
|
65
|
+
{ cmd: 'filter', desc: 'search' },
|
|
66
|
+
{ cmd: 'reload', desc: 'refresh' },
|
|
67
|
+
].map(({ cmd, desc }) => {
|
|
68
|
+
const matches = cmd.startsWith(cleanInput);
|
|
69
|
+
return React.createElement(
|
|
70
|
+
Text,
|
|
71
|
+
{
|
|
72
|
+
key: cmd,
|
|
73
|
+
color:
|
|
74
|
+
cleanInput && matches
|
|
75
|
+
? 'cyanBright'
|
|
76
|
+
: 'gray',
|
|
77
|
+
bold: cleanInput && matches,
|
|
78
|
+
},
|
|
79
|
+
`:${cmd} `,
|
|
80
|
+
React.createElement(
|
|
81
|
+
Text,
|
|
82
|
+
{ color: 'gray', dimColor: true },
|
|
83
|
+
`${desc}`,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
: null,
|
|
89
|
+
);
|
|
90
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
|
|
4
|
+
const HINTS = {
|
|
5
|
+
browse: '↑/↓/PgUp/PgDn navigate • Enter open/edit • Del unset • ←/Backspace parent • :filter • :file • :reload • :quit',
|
|
6
|
+
edit: '↑/↓/PgUp/PgDn pick • Enter confirm • Esc cancel • Del remove char (text)',
|
|
7
|
+
filter: 'Type to filter • Enter/Esc done • Del clear last • Ctrl+U clear',
|
|
8
|
+
'file-switch': '↑/↓ or PgUp/PgDn choose file • Enter switch • Esc cancel',
|
|
9
|
+
command: 'Quick execute • Esc to cancel',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const HelpBubble = ({ appMode }) =>
|
|
13
|
+
React.createElement(
|
|
14
|
+
Box,
|
|
15
|
+
{
|
|
16
|
+
borderStyle: 'round',
|
|
17
|
+
borderColor: 'blue',
|
|
18
|
+
paddingX: 1,
|
|
19
|
+
marginTop: 1,
|
|
20
|
+
},
|
|
21
|
+
React.createElement(
|
|
22
|
+
Text,
|
|
23
|
+
{ color: 'blue' },
|
|
24
|
+
HINTS[appMode] || HINTS.browse,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
|
|
4
|
+
export const LayoutShell = ({ children }) =>
|
|
5
|
+
React.createElement(
|
|
6
|
+
Box,
|
|
7
|
+
{
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
position: 'relative',
|
|
10
|
+
paddingX: 0,
|
|
11
|
+
paddingY: 0,
|
|
12
|
+
gap: 0,
|
|
13
|
+
width: '100%',
|
|
14
|
+
height: '100%',
|
|
15
|
+
},
|
|
16
|
+
...children,
|
|
17
|
+
);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { formatActiveFileSummary } from '../../layout.js';
|
|
4
|
+
|
|
5
|
+
const SEMVER_PREFIX_PATTERN = /^\d+\.\d+\.\d+(?:[-+._][0-9A-Za-z.-]+)*$/;
|
|
6
|
+
const CHECKING_BADGE_TEXT_COLOR = 'white';
|
|
7
|
+
const CHECKING_BADGE_BACKGROUND_COLOR = 'green';
|
|
8
|
+
|
|
9
|
+
const formatCodexVersion = (version) => {
|
|
10
|
+
const versionText = String(version || '').trim();
|
|
11
|
+
if (!versionText) {
|
|
12
|
+
return '—';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return SEMVER_PREFIX_PATTERN.test(versionText)
|
|
16
|
+
? `v${versionText}`
|
|
17
|
+
: versionText;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const StatusLine = ({
|
|
21
|
+
codexVersion,
|
|
22
|
+
codexVersionStatus,
|
|
23
|
+
isSchemaCheckInProgress,
|
|
24
|
+
schemaCheckStatusText,
|
|
25
|
+
activeConfigFile,
|
|
26
|
+
appMode,
|
|
27
|
+
}) => {
|
|
28
|
+
const codexStatusText = codexVersionStatus || 'Checking Codex version';
|
|
29
|
+
const schemaStatus =
|
|
30
|
+
String(schemaCheckStatusText || '').trim() || 'Checking schema';
|
|
31
|
+
const statusText = codexStatusText;
|
|
32
|
+
const isUpToDate = !isSchemaCheckInProgress && statusText === 'Up to date';
|
|
33
|
+
const isCodexChecking =
|
|
34
|
+
!isSchemaCheckInProgress &&
|
|
35
|
+
String(statusText).trim() === 'Checking Codex version';
|
|
36
|
+
const isUpdateAvailable =
|
|
37
|
+
!isSchemaCheckInProgress &&
|
|
38
|
+
typeof statusText === 'string' &&
|
|
39
|
+
statusText.startsWith('Update available');
|
|
40
|
+
const shouldShowCodexStatus =
|
|
41
|
+
!isSchemaCheckInProgress && !isUpToDate && !isCodexChecking;
|
|
42
|
+
const statusPrefix = isUpdateAvailable ? '⚠️ ' : '';
|
|
43
|
+
const statusTextColor = isUpToDate ? 'white' : 'black';
|
|
44
|
+
const statusBackgroundColor = isUpToDate ? undefined : 'yellow';
|
|
45
|
+
const isCheckingLabelVisible = isSchemaCheckInProgress || isCodexChecking;
|
|
46
|
+
const versionText = isCheckingLabelVisible
|
|
47
|
+
? ` ${isSchemaCheckInProgress ? schemaStatus : codexStatusText} `
|
|
48
|
+
: `Using Codex ${formatCodexVersion(codexVersion)}`;
|
|
49
|
+
const versionTextColor = isCheckingLabelVisible
|
|
50
|
+
? CHECKING_BADGE_TEXT_COLOR
|
|
51
|
+
: 'white';
|
|
52
|
+
const versionTextBackgroundColor = isCheckingLabelVisible
|
|
53
|
+
? CHECKING_BADGE_BACKGROUND_COLOR
|
|
54
|
+
: undefined;
|
|
55
|
+
const codexStatusTextLabel = `${statusText} `;
|
|
56
|
+
const activeFileSummary = formatActiveFileSummary(activeConfigFile);
|
|
57
|
+
|
|
58
|
+
return React.createElement(
|
|
59
|
+
Box,
|
|
60
|
+
{
|
|
61
|
+
paddingX: 1,
|
|
62
|
+
paddingY: 0,
|
|
63
|
+
marginTop: 1,
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
justifyContent: 'space-between',
|
|
66
|
+
backgroundColor: 'blue',
|
|
67
|
+
width: '100%',
|
|
68
|
+
},
|
|
69
|
+
React.createElement(
|
|
70
|
+
Box,
|
|
71
|
+
{ flexDirection: 'row', gap: 2 },
|
|
72
|
+
React.createElement(
|
|
73
|
+
Text,
|
|
74
|
+
{ color: 'white', backgroundColor: 'magenta', bold: true },
|
|
75
|
+
` ${appMode.toUpperCase()} `,
|
|
76
|
+
),
|
|
77
|
+
React.createElement(
|
|
78
|
+
Text,
|
|
79
|
+
{ color: 'white', bold: true },
|
|
80
|
+
`📝 ${activeFileSummary}`,
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
|
|
84
|
+
React.createElement(
|
|
85
|
+
Box,
|
|
86
|
+
{ flexDirection: 'row', gap: 2 },
|
|
87
|
+
React.createElement(
|
|
88
|
+
Text,
|
|
89
|
+
{
|
|
90
|
+
color: versionTextColor,
|
|
91
|
+
backgroundColor: versionTextBackgroundColor,
|
|
92
|
+
bold: isCheckingLabelVisible,
|
|
93
|
+
},
|
|
94
|
+
versionText,
|
|
95
|
+
),
|
|
96
|
+
shouldShowCodexStatus
|
|
97
|
+
? React.createElement(
|
|
98
|
+
Box,
|
|
99
|
+
{ flexDirection: 'row' },
|
|
100
|
+
statusPrefix
|
|
101
|
+
? React.createElement(
|
|
102
|
+
Text,
|
|
103
|
+
{
|
|
104
|
+
color: statusTextColor,
|
|
105
|
+
backgroundColor: statusBackgroundColor,
|
|
106
|
+
},
|
|
107
|
+
` ${statusPrefix} `,
|
|
108
|
+
)
|
|
109
|
+
: null,
|
|
110
|
+
React.createElement(
|
|
111
|
+
Text,
|
|
112
|
+
{
|
|
113
|
+
color: statusTextColor,
|
|
114
|
+
backgroundColor: statusBackgroundColor,
|
|
115
|
+
bold: true,
|
|
116
|
+
},
|
|
117
|
+
codexStatusTextLabel,
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
: null,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
const toStringArray = (value) =>
|
|
2
|
+
Array.isArray(value) ? value.map((item) => String(item)) : [];
|
|
3
|
+
|
|
4
|
+
const toObject = (value) =>
|
|
5
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
6
|
+
? { ...value }
|
|
7
|
+
: {};
|
|
8
|
+
|
|
9
|
+
const uniqueScalars = (values) => {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const ordered = [];
|
|
12
|
+
|
|
13
|
+
values.forEach((value) => {
|
|
14
|
+
const text = String(value);
|
|
15
|
+
if (seen.has(text)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
seen.add(text);
|
|
20
|
+
ordered.push(text);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return ordered;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const normalizeObjectVariant = (variant, index) => ({
|
|
27
|
+
kind: 'object',
|
|
28
|
+
id: String(variant?.id || `object_${index + 1}`),
|
|
29
|
+
label: String(variant?.label || `object_${index + 1}`),
|
|
30
|
+
requiredKeys: toStringArray(variant?.requiredKeys),
|
|
31
|
+
fixedValues: toObject(variant?.fixedValues),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const uniquifyPresetLabels = (items) => {
|
|
35
|
+
const seen = new Map();
|
|
36
|
+
|
|
37
|
+
return items.map((item) => {
|
|
38
|
+
const baseLabel = String(item.label);
|
|
39
|
+
const occurrence = (seen.get(baseLabel) || 0) + 1;
|
|
40
|
+
seen.set(baseLabel, occurrence);
|
|
41
|
+
|
|
42
|
+
if (occurrence === 1) {
|
|
43
|
+
return item;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...item,
|
|
48
|
+
label: `${baseLabel} (${occurrence})`,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const isObjectValue = (value) =>
|
|
54
|
+
value !== null &&
|
|
55
|
+
typeof value === 'object' &&
|
|
56
|
+
!Array.isArray(value);
|
|
57
|
+
|
|
58
|
+
export const objectMatchesVariant = (value, variant) => {
|
|
59
|
+
if (!isObjectValue(value) || !variant || typeof variant !== 'object') {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fixedValues = toObject(variant.fixedValues);
|
|
64
|
+
const requiredKeys = toStringArray(variant.requiredKeys);
|
|
65
|
+
const fixedPairs = Object.entries(fixedValues);
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
fixedPairs.some(([fixedKey, fixedValue]) => !Object.is(value[fixedKey], fixedValue))
|
|
69
|
+
) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return requiredKeys.every((requiredKey) =>
|
|
74
|
+
Object.prototype.hasOwnProperty.call(value, requiredKey)
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const buildVariantSelectorOptions = (variantMeta) => {
|
|
79
|
+
if (!variantMeta || variantMeta.kind !== 'scalar_object') {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const scalarOptions = uniqueScalars(toStringArray(variantMeta.scalarOptions));
|
|
84
|
+
const objectVariants = Array.isArray(variantMeta.objectVariants)
|
|
85
|
+
? variantMeta.objectVariants.map(normalizeObjectVariant)
|
|
86
|
+
: [];
|
|
87
|
+
|
|
88
|
+
const rawOptions = [
|
|
89
|
+
...scalarOptions.map((value) => ({
|
|
90
|
+
kind: 'scalar',
|
|
91
|
+
value,
|
|
92
|
+
label: value,
|
|
93
|
+
})),
|
|
94
|
+
...objectVariants.map((variant) => ({
|
|
95
|
+
...variant,
|
|
96
|
+
label: `${variant.label} /`,
|
|
97
|
+
})),
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
return uniquifyPresetLabels(rawOptions);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const seedObjectVariant = (currentValue, selectedVariant, resolveDefaultValue) => {
|
|
104
|
+
const seededObject = objectMatchesVariant(currentValue, selectedVariant)
|
|
105
|
+
? { ...currentValue }
|
|
106
|
+
: {};
|
|
107
|
+
const fixedValues = toObject(selectedVariant.fixedValues);
|
|
108
|
+
const requiredKeys = toStringArray(selectedVariant.requiredKeys);
|
|
109
|
+
|
|
110
|
+
Object.entries(fixedValues).forEach(([fixedKey, fixedValue]) => {
|
|
111
|
+
seededObject[String(fixedKey)] = fixedValue;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
requiredKeys.forEach((requiredKey) => {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(seededObject, requiredKey)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
seededObject[requiredKey] = resolveDefaultValue(requiredKey);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return seededObject;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const shallowObjectEquals = (left, right) => {
|
|
126
|
+
if (!isObjectValue(left) || !isObjectValue(right)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const leftKeys = Object.keys(left);
|
|
131
|
+
const rightKeys = Object.keys(right);
|
|
132
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return leftKeys.every((key) =>
|
|
137
|
+
Object.prototype.hasOwnProperty.call(right, key) && Object.is(left[key], right[key])
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const applyVariantSelection = ({
|
|
142
|
+
currentValue,
|
|
143
|
+
selectedVariant,
|
|
144
|
+
resolveDefaultValue,
|
|
145
|
+
}) => {
|
|
146
|
+
if (!selectedVariant || typeof selectedVariant !== 'object') {
|
|
147
|
+
return {
|
|
148
|
+
changed: false,
|
|
149
|
+
isObjectSelection: false,
|
|
150
|
+
isObjectVariantSwitch: false,
|
|
151
|
+
navigateToObject: false,
|
|
152
|
+
nextValue: currentValue,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (selectedVariant.kind === 'object') {
|
|
157
|
+
const resolver = typeof resolveDefaultValue === 'function'
|
|
158
|
+
? resolveDefaultValue
|
|
159
|
+
: () => ({});
|
|
160
|
+
const nextValue = seedObjectVariant(currentValue, selectedVariant, resolver);
|
|
161
|
+
const isCurrentObjectValue = isObjectValue(currentValue);
|
|
162
|
+
const isObjectVariantSwitch =
|
|
163
|
+
isCurrentObjectValue && !objectMatchesVariant(currentValue, selectedVariant);
|
|
164
|
+
const changed = !shallowObjectEquals(currentValue, nextValue);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
changed,
|
|
168
|
+
isObjectSelection: true,
|
|
169
|
+
isObjectVariantSwitch,
|
|
170
|
+
navigateToObject: true,
|
|
171
|
+
nextValue,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const nextScalarValue = String(selectedVariant.value);
|
|
176
|
+
return {
|
|
177
|
+
changed: isObjectValue(currentValue) || !Object.is(currentValue, nextScalarValue),
|
|
178
|
+
isObjectSelection: false,
|
|
179
|
+
isObjectVariantSwitch: false,
|
|
180
|
+
navigateToObject: false,
|
|
181
|
+
nextValue: nextScalarValue,
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const asPathSegments = (value) =>
|
|
186
|
+
Array.isArray(value) ? [...value] : [];
|
|
187
|
+
|
|
188
|
+
const isContainerValue = (value) =>
|
|
189
|
+
isObjectValue(value) || Array.isArray(value);
|
|
190
|
+
|
|
191
|
+
export const resolveObjectVariantNavigationPath = ({
|
|
192
|
+
basePath,
|
|
193
|
+
nextValue,
|
|
194
|
+
preferredKey = null,
|
|
195
|
+
}) => {
|
|
196
|
+
const normalizedBasePath = asPathSegments(basePath);
|
|
197
|
+
if (!isObjectValue(nextValue)) {
|
|
198
|
+
return normalizedBasePath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const preferredSegment = preferredKey === null ? '' : String(preferredKey);
|
|
202
|
+
if (preferredSegment && Object.prototype.hasOwnProperty.call(nextValue, preferredSegment)) {
|
|
203
|
+
const preferredValue = nextValue[preferredSegment];
|
|
204
|
+
if (isContainerValue(preferredValue)) {
|
|
205
|
+
return [...normalizedBasePath, preferredSegment];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const nestedContainerKeys = Object.keys(nextValue).filter((key) =>
|
|
210
|
+
isContainerValue(nextValue[key])
|
|
211
|
+
);
|
|
212
|
+
if (nestedContainerKeys.length === 1) {
|
|
213
|
+
return [...normalizedBasePath, nestedContainerKeys[0]];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return normalizedBasePath;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const getTopLevelObjectSegments = (variantMeta) => {
|
|
220
|
+
const fromSchemaPaths = Array.isArray(variantMeta?.objectSchemaPaths)
|
|
221
|
+
? variantMeta.objectSchemaPaths
|
|
222
|
+
.map((path) => String(path || '').split('.')[0])
|
|
223
|
+
.filter((segment) => segment.length > 0)
|
|
224
|
+
: [];
|
|
225
|
+
const fromRequiredKeys = Array.isArray(variantMeta?.objectVariants)
|
|
226
|
+
? variantMeta.objectVariants.flatMap((variant) =>
|
|
227
|
+
Array.isArray(variant?.requiredKeys)
|
|
228
|
+
? variant.requiredKeys.map((key) => String(key))
|
|
229
|
+
: []
|
|
230
|
+
)
|
|
231
|
+
: [];
|
|
232
|
+
|
|
233
|
+
return new Set([...fromSchemaPaths, ...fromRequiredKeys]);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const resolveMixedVariantBackNavigationPath = ({
|
|
237
|
+
pathSegments,
|
|
238
|
+
resolveVariantMeta,
|
|
239
|
+
}) => {
|
|
240
|
+
const normalizedPath = asPathSegments(pathSegments);
|
|
241
|
+
if (normalizedPath.length < 2 || typeof resolveVariantMeta !== 'function') {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const parentPath = normalizedPath.slice(0, -1);
|
|
246
|
+
const parentKey = String(parentPath[parentPath.length - 1] || '');
|
|
247
|
+
if (!parentKey) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const basePath = parentPath.slice(0, -1);
|
|
252
|
+
const variantMeta = resolveVariantMeta(basePath, parentKey);
|
|
253
|
+
if (variantMeta?.kind !== 'scalar_object') {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const currentSegment = String(normalizedPath[normalizedPath.length - 1] || '');
|
|
258
|
+
if (!currentSegment) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const topLevelObjectSegments = getTopLevelObjectSegments(variantMeta);
|
|
263
|
+
if (!topLevelObjectSegments.has(currentSegment)) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return basePath;
|
|
268
|
+
};
|