@mintlify/cli 4.0.1434 → 4.0.1438
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/bin/agent-harness/buildTaskPrompt.js +76 -0
- package/bin/agent-harness/discoverCodeBlocks.js +101 -0
- package/bin/agent-harness/executeCodeBlocks.js +220 -0
- package/bin/agent-harness/filterSelectedPages.js +10 -0
- package/bin/agent-harness/generateTestCode.js +293 -0
- package/bin/agent-harness/index.js +85 -0
- package/bin/agent-harness/loadAgentSdk.js +46 -0
- package/bin/agent-harness/mapWithConcurrency.js +26 -0
- package/bin/agent-harness/runSave.js +47 -0
- package/bin/agent-harness/setupFolders.js +59 -0
- package/bin/agent-harness/tasks/checkTestability.js +226 -0
- package/bin/agent-harness/types.js +35 -0
- package/bin/cli.js +10 -4
- package/bin/mintTest.js +96 -0
- package/bin/mintTestNav.js +35 -0
- package/bin/mintTestTree.js +94 -0
- package/bin/mintTestUi.js +610 -0
- package/bin/status.js +11 -4
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +13 -9
- package/src/agent-harness/MINT_TEST_SYSTEM_DESIGN.md +58 -0
- package/src/agent-harness/buildTaskPrompt.ts +82 -0
- package/src/agent-harness/discoverCodeBlocks.ts +90 -0
- package/src/agent-harness/executeCodeBlocks.ts +285 -0
- package/src/agent-harness/filterSelectedPages.ts +14 -0
- package/src/agent-harness/generateTestCode.ts +332 -0
- package/src/agent-harness/index.ts +109 -0
- package/src/agent-harness/loadAgentSdk.ts +35 -0
- package/src/agent-harness/mapWithConcurrency.ts +21 -0
- package/src/agent-harness/runSave.ts +38 -0
- package/src/agent-harness/setupFolders.ts +73 -0
- package/src/agent-harness/tasks/checkTestability.ts +247 -0
- package/src/agent-harness/types.ts +152 -0
- package/src/cli.tsx +15 -9
- package/src/mintTest.tsx +102 -0
- package/src/mintTestNav.ts +31 -0
- package/src/mintTestTree.ts +134 -0
- package/src/mintTestUi.tsx +988 -0
- package/src/status.tsx +11 -4
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Box, render, Text, useInput } from 'ink';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { DEFAULT_COMMAND_TIMEOUT_MS, DEFAULT_CONCURRENCY, clearRunSave, runCodeTests, } from './agent-harness/index.js';
|
|
15
|
+
import { toggleScopeSelection, visibleScopeRows } from './mintTestTree.js';
|
|
16
|
+
const HARNESSES = [
|
|
17
|
+
{ agent: 'claude', model: 'claude-opus-4-8', label: 'Claude Opus 4.8' },
|
|
18
|
+
{ agent: 'claude', model: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
|
|
19
|
+
{ agent: 'codex', model: 'claude-opus-4-8', label: 'Codex' },
|
|
20
|
+
];
|
|
21
|
+
export function countReport(tasks) {
|
|
22
|
+
const counts = { passed: 0, failed: 0, cancelled: 0, agentErrors: 0 };
|
|
23
|
+
for (const task of tasks) {
|
|
24
|
+
if (task.status === 'agent_error')
|
|
25
|
+
counts.agentErrors++;
|
|
26
|
+
else
|
|
27
|
+
counts[task.status]++;
|
|
28
|
+
}
|
|
29
|
+
return counts;
|
|
30
|
+
}
|
|
31
|
+
const STEPS = [
|
|
32
|
+
{ id: 'harnesses', label: 'Harnesses' },
|
|
33
|
+
{ id: 'scope', label: 'Scope' },
|
|
34
|
+
{ id: 'check', label: 'Check testability' },
|
|
35
|
+
{ id: 'generate', label: 'Generate tests' },
|
|
36
|
+
{ id: 'run', label: 'Run tests' },
|
|
37
|
+
{ id: 'report', label: 'Report' },
|
|
38
|
+
];
|
|
39
|
+
function formatTokens(tokens) {
|
|
40
|
+
if (tokens >= 1000000)
|
|
41
|
+
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
42
|
+
if (tokens >= 1000)
|
|
43
|
+
return `${Math.round(tokens / 1000)}k`;
|
|
44
|
+
return String(tokens);
|
|
45
|
+
}
|
|
46
|
+
function padTokens(tokens) {
|
|
47
|
+
return formatTokens(tokens).padStart(4);
|
|
48
|
+
}
|
|
49
|
+
export function formatDuration(durationMs) {
|
|
50
|
+
const seconds = Math.max(0, Math.floor(durationMs / 1000));
|
|
51
|
+
if (seconds < 60)
|
|
52
|
+
return `${seconds}s`;
|
|
53
|
+
return `${Math.floor(seconds / 60)}m ${String(seconds % 60).padStart(2, '0')}s`;
|
|
54
|
+
}
|
|
55
|
+
function padDuration(durationMs) {
|
|
56
|
+
return formatDuration(durationMs).padStart(7);
|
|
57
|
+
}
|
|
58
|
+
function padCount(value, total) {
|
|
59
|
+
return String(value).padStart(String(total).length);
|
|
60
|
+
}
|
|
61
|
+
function useTerminalSize() {
|
|
62
|
+
const [size, setSize] = useState(() => ({
|
|
63
|
+
columns: process.stdout.columns || 100,
|
|
64
|
+
rows: process.stdout.rows || 30,
|
|
65
|
+
}));
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const resize = () => setSize({ columns: process.stdout.columns || 100, rows: process.stdout.rows || 30 });
|
|
68
|
+
process.stdout.on('resize', resize);
|
|
69
|
+
return () => {
|
|
70
|
+
process.stdout.off('resize', resize);
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
return size;
|
|
74
|
+
}
|
|
75
|
+
function stepState(step, screen, activeRunStage) {
|
|
76
|
+
const order = ['harnesses', 'scope', 'check', 'generate', 'run', 'report'];
|
|
77
|
+
const screenStep = screen === 'resume'
|
|
78
|
+
? 'harnesses'
|
|
79
|
+
: screen === 'running' || screen === 'error'
|
|
80
|
+
? activeRunStage
|
|
81
|
+
: screen;
|
|
82
|
+
const current = order.indexOf(screenStep);
|
|
83
|
+
const index = order.indexOf(step);
|
|
84
|
+
if (screen === 'error' && index === current)
|
|
85
|
+
return 'error';
|
|
86
|
+
if (index < current || screen === 'report')
|
|
87
|
+
return 'complete';
|
|
88
|
+
if (index === current)
|
|
89
|
+
return 'running';
|
|
90
|
+
return 'pending';
|
|
91
|
+
}
|
|
92
|
+
function StepGlyph({ state }) {
|
|
93
|
+
if (state === 'complete')
|
|
94
|
+
return _jsx(Text, { color: "green", children: "\u2713" });
|
|
95
|
+
if (state === 'running')
|
|
96
|
+
return _jsx(Text, { color: "cyan", children: "\u25CF" });
|
|
97
|
+
if (state === 'error')
|
|
98
|
+
return _jsx(Text, { color: "red", children: "\u2717" });
|
|
99
|
+
return _jsx(Text, { dimColor: true, children: "\u25CB" });
|
|
100
|
+
}
|
|
101
|
+
const RESUME_OPTIONS = ['Resume', 'Start fresh'];
|
|
102
|
+
const EMPTY_SELECTION = new Set();
|
|
103
|
+
function ResumeScreen({ save, cursor }) {
|
|
104
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Resume previous run?" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Run ", _jsx(Text, { color: "cyan", children: save.runId })] }), _jsxs(Text, { dimColor: true, children: [save.selectedFiles.length, " ", save.selectedFiles.length === 1 ? 'page' : 'pages', " \u00B7", ' ', save.agents.join(', ')] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: RESUME_OPTIONS.map((option, index) => (_jsxs(Text, { color: cursor === index ? 'cyan' : undefined, children: [cursor === index ? '›' : ' ', " ", option] }, option))) })] }));
|
|
105
|
+
}
|
|
106
|
+
function HarnessScreen({ cursor }) {
|
|
107
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Which harness should generate and run the tests?" }), _jsx(Text, { dimColor: true, children: "The selected harness tests every page in the chosen scope." }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: HARNESSES.map((harness, index) => (_jsxs(Text, { color: cursor === index ? 'cyan' : undefined, children: [cursor === index ? '›' : ' ', " ", cursor === index ? '(•)' : '( )', " ", harness.label] }, harness.label))) })] }));
|
|
108
|
+
}
|
|
109
|
+
function ScopeScreen({ rows, cursor, height, selectedCount, selectedBlocks, testablePages, totalPages, outputDirectory, }) {
|
|
110
|
+
const viewport = Math.max(4, height - 18);
|
|
111
|
+
const start = Math.max(0, Math.min(cursor - Math.floor(viewport / 2), rows.length - viewport));
|
|
112
|
+
const visible = rows.slice(start, start + viewport);
|
|
113
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: testablePages }), ' ', "of ", totalPages, " total pages have untested code"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: selectedCount }), ' ', selectedCount === 1 ? 'page' : 'pages', " \u00B7", ' ', _jsx(Text, { color: "cyan", bold: true, children: selectedBlocks }), ' ', selectedBlocks === 1 ? 'code block' : 'code blocks', " will be tested"] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Tests generated at ", outputDirectory] })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { bold: true, children: "Choose the docs to test" }) }), _jsx(Text, { dimColor: true, children: "Folders select or clear every testable page beneath them." }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: visible.map((row, visibleIndex) => {
|
|
114
|
+
const index = start + visibleIndex;
|
|
115
|
+
const selection = row.selection === 'all' ? '[x]' : row.selection === 'some' ? '[-]' : '[ ]';
|
|
116
|
+
const disclosure = row.kind === 'directory' ? (row.expanded ? '▾' : '▸') : ' ';
|
|
117
|
+
return (_jsxs(Text, { color: index === cursor ? 'cyan' : undefined, children: [' '.repeat(row.depth * 2), index === cursor ? '›' : ' ', " ", selection, " ", disclosure, " ", row.label, ' ', _jsxs(Text, { dimColor: true, children: [row.blockCount, " ", row.blockCount === 1 ? 'code block' : 'code blocks'] })] }, row.id));
|
|
118
|
+
}) })] }));
|
|
119
|
+
}
|
|
120
|
+
const PHASE_LABELS = {
|
|
121
|
+
checking: 'validating code can be tested',
|
|
122
|
+
checked: 'code can be tested',
|
|
123
|
+
not_testable: 'no testable code',
|
|
124
|
+
generating: 'generating test code',
|
|
125
|
+
generated: 'test generated',
|
|
126
|
+
running: 'running tests',
|
|
127
|
+
passed: 'passed',
|
|
128
|
+
failed: 'tests failed',
|
|
129
|
+
agent_error: 'test could not be generated',
|
|
130
|
+
cancelled: 'cancelled',
|
|
131
|
+
};
|
|
132
|
+
const ACTIVE_PHASES = new Set(['checking', 'generating', 'running']);
|
|
133
|
+
const DONE_PHASES = new Set([
|
|
134
|
+
'passed',
|
|
135
|
+
'failed',
|
|
136
|
+
'agent_error',
|
|
137
|
+
'not_testable',
|
|
138
|
+
'cancelled',
|
|
139
|
+
]);
|
|
140
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
141
|
+
function fileStatus(row, spinner) {
|
|
142
|
+
if (row && ACTIVE_PHASES.has(row.phase))
|
|
143
|
+
return { glyph: spinner, color: 'green' };
|
|
144
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'not_testable')
|
|
145
|
+
return { glyph: '✗', color: 'yellow' };
|
|
146
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'failed' || (row === null || row === void 0 ? void 0 : row.phase) === 'agent_error')
|
|
147
|
+
return { glyph: '✗', color: 'red' };
|
|
148
|
+
return { glyph: '○', dim: true };
|
|
149
|
+
}
|
|
150
|
+
const SETTLED_PHASES = new Set([...DONE_PHASES, 'checked', 'generated']);
|
|
151
|
+
function directoryStatus(files, phases, spinner) {
|
|
152
|
+
const rows = files.map((file) => phases.get(file));
|
|
153
|
+
if (rows.some((row) => row && ACTIVE_PHASES.has(row.phase))) {
|
|
154
|
+
return { glyph: spinner, color: 'green' };
|
|
155
|
+
}
|
|
156
|
+
if (rows.every((row) => row && SETTLED_PHASES.has(row.phase))) {
|
|
157
|
+
if (rows.some((row) => row && ['failed', 'agent_error'].includes(row.phase))) {
|
|
158
|
+
return { glyph: '●', color: 'red' };
|
|
159
|
+
}
|
|
160
|
+
return rows.every((row) => row && DONE_PHASES.has(row.phase))
|
|
161
|
+
? { glyph: '●', color: 'green' }
|
|
162
|
+
: { glyph: '●', dim: true };
|
|
163
|
+
}
|
|
164
|
+
return { glyph: '○', dim: true };
|
|
165
|
+
}
|
|
166
|
+
function ActivityScreen({ stage, cancelling, harness, rows, cursor, phases, height, totalTasks, spinnerFrame, runStartedAt, now, }) {
|
|
167
|
+
var _a;
|
|
168
|
+
const spinner = (_a = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length]) !== null && _a !== void 0 ? _a : '⠋';
|
|
169
|
+
const updates = [...phases.values()];
|
|
170
|
+
const message = cancelling
|
|
171
|
+
? 'Cancelling agent and test processes'
|
|
172
|
+
: stage === 'check'
|
|
173
|
+
? 'Checking which code blocks can be tested'
|
|
174
|
+
: stage === 'generate'
|
|
175
|
+
? 'Agents are generating test projects'
|
|
176
|
+
: 'Running generated tests';
|
|
177
|
+
const done = updates.filter((update) => DONE_PHASES.has(update.phase)).length;
|
|
178
|
+
const notTestable = updates.filter((update) => update.phase === 'not_testable').length;
|
|
179
|
+
const created = updates.filter((update) => ['generated', 'running', 'passed', 'failed'].includes(update.phase)).length;
|
|
180
|
+
const tokens = updates.reduce((total, update) => total + update.tokens, 0);
|
|
181
|
+
const checked = updates.filter((update) => update.phase !== 'checking').length;
|
|
182
|
+
const progress = stage === 'check'
|
|
183
|
+
? `${padCount(checked, totalTasks)}/${totalTasks} pages checked`
|
|
184
|
+
: stage === 'generate'
|
|
185
|
+
? `${padCount(created, totalTasks - notTestable)}/${totalTasks - notTestable} tests created`
|
|
186
|
+
: `${padCount(done - notTestable, totalTasks - notTestable)}/${totalTasks - notTestable} tasks finished`;
|
|
187
|
+
const viewport = Math.max(4, height - 13);
|
|
188
|
+
const start = Math.max(0, Math.min(cursor - Math.floor(viewport / 2), rows.length - viewport));
|
|
189
|
+
const visible = rows.slice(start, start + viewport);
|
|
190
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [padDuration(now - runStartedAt), " ", _jsxs(Text, { dimColor: true, children: [padTokens(tokens), " tokens"] })] }), _jsxs(Text, { color: "green", children: [_jsx(Text, { children: spinner }), " ", message] }), _jsxs(Text, { dimColor: true, children: ["Harness: ", harness] }), _jsx(Text, { dimColor: true, children: progress })] }), _jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, flexDirection: "column", flexGrow: 1, children: visible.map((row, visibleIndex) => {
|
|
191
|
+
var _a;
|
|
192
|
+
const index = start + visibleIndex;
|
|
193
|
+
const disclosure = row.kind === 'directory' ? (row.expanded ? '▾' : '▸') : ' ';
|
|
194
|
+
const status = row.kind === 'directory'
|
|
195
|
+
? directoryStatus(row.selectableFiles, phases, spinner)
|
|
196
|
+
: fileStatus(row.file ? phases.get(row.file) : undefined, spinner);
|
|
197
|
+
const task = row.file ? phases.get(row.file) : undefined;
|
|
198
|
+
const settled = row.kind === 'directory'
|
|
199
|
+
? row.selectableFiles.filter((file) => {
|
|
200
|
+
var _a;
|
|
201
|
+
const phase = (_a = phases.get(file)) === null || _a === void 0 ? void 0 : _a.phase;
|
|
202
|
+
return phase !== undefined && SETTLED_PHASES.has(phase);
|
|
203
|
+
}).length
|
|
204
|
+
: 0;
|
|
205
|
+
return (_jsxs(Text, { color: index === cursor ? 'cyan' : undefined, wrap: "truncate-end", children: [' '.repeat(row.depth * 2), index === cursor ? '›' : ' ', ' ', _jsx(Text, { color: status.color, dimColor: status.dim, children: status.glyph }), ' ', disclosure, " ", row.label, row.kind === 'directory' ? (_jsxs(Text, { dimColor: true, children: [' ', padCount(settled, row.selectableFiles.length), "/", row.selectableFiles.length, ' ', "complete"] })) : null, task ? (_jsxs(Text, { dimColor: true, children: [" ", padDuration(((_a = task.finishedAt) !== null && _a !== void 0 ? _a : now) - task.startedAt)] })) : null] }, row.id));
|
|
206
|
+
}) })] }));
|
|
207
|
+
}
|
|
208
|
+
function reportFileStatus(row) {
|
|
209
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'passed')
|
|
210
|
+
return { glyph: '✓', color: 'green' };
|
|
211
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'failed' || (row === null || row === void 0 ? void 0 : row.phase) === 'agent_error')
|
|
212
|
+
return { glyph: '✗', color: 'red' };
|
|
213
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'not_testable')
|
|
214
|
+
return { glyph: '✗', color: 'yellow' };
|
|
215
|
+
if ((row === null || row === void 0 ? void 0 : row.phase) === 'cancelled')
|
|
216
|
+
return { glyph: '●', color: 'yellow' };
|
|
217
|
+
return { glyph: '○', dim: true };
|
|
218
|
+
}
|
|
219
|
+
function reportDirectoryStatus(files, phases) {
|
|
220
|
+
const rows = files.map((file) => phases.get(file));
|
|
221
|
+
if (rows.some((row) => (row === null || row === void 0 ? void 0 : row.phase) === 'failed' || (row === null || row === void 0 ? void 0 : row.phase) === 'agent_error')) {
|
|
222
|
+
return { glyph: '✗', color: 'red' };
|
|
223
|
+
}
|
|
224
|
+
if (rows.length > 0 && rows.every((row) => (row === null || row === void 0 ? void 0 : row.phase) === 'passed')) {
|
|
225
|
+
return { glyph: '✓', color: 'green' };
|
|
226
|
+
}
|
|
227
|
+
return { glyph: '●', color: 'yellow' };
|
|
228
|
+
}
|
|
229
|
+
function ReportScreen({ report, rows, cursor, phases, height, }) {
|
|
230
|
+
const statusColor = report.status === 'passed'
|
|
231
|
+
? 'green'
|
|
232
|
+
: report.status === 'completed'
|
|
233
|
+
? 'cyan'
|
|
234
|
+
: report.status === 'cancelled'
|
|
235
|
+
? 'yellow'
|
|
236
|
+
: 'red';
|
|
237
|
+
const heading = report.status === 'passed'
|
|
238
|
+
? 'All runnable tests passed'
|
|
239
|
+
: report.status === 'completed'
|
|
240
|
+
? 'Run completed'
|
|
241
|
+
: `Run ${report.status}`;
|
|
242
|
+
const viewport = Math.max(4, height - 12);
|
|
243
|
+
const start = Math.max(0, Math.min(cursor - Math.floor(viewport / 2), rows.length - viewport));
|
|
244
|
+
const visible = rows.slice(start, start + viewport);
|
|
245
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: statusColor, children: heading }), _jsxs(Text, { children: ["Finished in ", _jsx(Text, { bold: true, children: formatDuration(report.durationMs) })] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Report at ", report.reportPath] })] }), _jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, flexDirection: "column", flexGrow: 1, children: visible.map((row, visibleIndex) => {
|
|
246
|
+
const index = start + visibleIndex;
|
|
247
|
+
const disclosure = row.kind === 'directory' ? (row.expanded ? '▾' : '▸') : ' ';
|
|
248
|
+
const status = row.kind === 'directory'
|
|
249
|
+
? reportDirectoryStatus(row.selectableFiles, phases)
|
|
250
|
+
: reportFileStatus(row.file ? phases.get(row.file) : undefined);
|
|
251
|
+
const task = row.file ? phases.get(row.file) : undefined;
|
|
252
|
+
const passed = row.kind === 'directory'
|
|
253
|
+
? row.selectableFiles.filter((file) => { var _a; return ((_a = phases.get(file)) === null || _a === void 0 ? void 0 : _a.phase) === 'passed'; }).length
|
|
254
|
+
: 0;
|
|
255
|
+
return (_jsxs(Text, { color: index === cursor ? 'cyan' : undefined, wrap: "truncate-end", children: [' '.repeat(row.depth * 2), index === cursor ? '›' : ' ', ' ', _jsx(Text, { color: status.color, dimColor: status.dim, children: status.glyph }), ' ', disclosure, " ", row.label, row.kind === 'directory' ? (_jsxs(Text, { dimColor: true, children: [' ', padCount(passed, row.selectableFiles.length), "/", row.selectableFiles.length, " passed"] })) : task ? (_jsxs(Text, { dimColor: true, children: [' ', PHASE_LABELS[task.phase], task.finishedAt ? ` · ${formatDuration(task.finishedAt - task.startedAt)}` : ''] })) : null] }, row.id));
|
|
256
|
+
}) })] }));
|
|
257
|
+
}
|
|
258
|
+
function footerForScreen(screen, running, cancelled) {
|
|
259
|
+
if (running)
|
|
260
|
+
return '↑/↓/j/k move · ←/→/h/l collapse/expand · Ctrl-C cancel';
|
|
261
|
+
if (screen === 'resume')
|
|
262
|
+
return '↑/↓/j/k move · Enter confirm · Ctrl-C cancel';
|
|
263
|
+
if (screen === 'harnesses')
|
|
264
|
+
return '↑/↓/j/k move · Enter continue · Ctrl-C cancel';
|
|
265
|
+
if (screen === 'scope') {
|
|
266
|
+
return '↑/↓/j/k move · ←/→/h/l collapse/expand · Space select · a all · Enter start';
|
|
267
|
+
}
|
|
268
|
+
if (screen === 'report') {
|
|
269
|
+
return cancelled
|
|
270
|
+
? '↑/↓/j/k move · ←/→/h/l collapse/expand · Ctrl-C exit · save kept for resume'
|
|
271
|
+
: '↑/↓/j/k move · ←/→/h/l collapse/expand · Enter exit';
|
|
272
|
+
}
|
|
273
|
+
return 'Enter exit';
|
|
274
|
+
}
|
|
275
|
+
function MintTestApp({ targetPath, docsRoot, pages, autoSelectedFiles, totalPages, save, onFinish, }) {
|
|
276
|
+
var _a;
|
|
277
|
+
const size = useTerminalSize();
|
|
278
|
+
const files = useMemo(() => pages.map((page) => page.file), [pages]);
|
|
279
|
+
const outputDirectory = path.join(docsRoot, 'tests', 'mint-test');
|
|
280
|
+
const [screen, setScreen] = useState(save ? 'resume' : 'harnesses');
|
|
281
|
+
const [resumeCursor, setResumeCursor] = useState(0);
|
|
282
|
+
const [harnessIndex, setHarnessIndex] = useState(0);
|
|
283
|
+
const [runHarness, setRunHarness] = useState('');
|
|
284
|
+
const harness = (_a = HARNESSES[harnessIndex]) !== null && _a !== void 0 ? _a : HARNESSES[0];
|
|
285
|
+
const [selectedFiles, setSelectedFiles] = useState(() => new Set(autoSelectedFiles));
|
|
286
|
+
const [expandedDirectories, setExpandedDirectories] = useState(new Set());
|
|
287
|
+
const [scopeCursor, setScopeCursor] = useState(0);
|
|
288
|
+
const [activeRunStage, setActiveRunStage] = useState('check');
|
|
289
|
+
const [cancelling, setCancelling] = useState(false);
|
|
290
|
+
const [ctrlCArmed, setCtrlCArmed] = useState(false);
|
|
291
|
+
const ctrlCTimer = useRef(undefined);
|
|
292
|
+
const [taskPhases, setTaskPhases] = useState(new Map());
|
|
293
|
+
const [totalTasks, setTotalTasks] = useState(0);
|
|
294
|
+
const [runCursor, setRunCursor] = useState(0);
|
|
295
|
+
const [runExpanded, setRunExpanded] = useState(new Set());
|
|
296
|
+
const [runFiles, setRunFiles] = useState([]);
|
|
297
|
+
const [runStartedAt, setRunStartedAt] = useState(0);
|
|
298
|
+
const autoCollapsed = useRef(new Set());
|
|
299
|
+
const manualCursor = useRef(false);
|
|
300
|
+
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
301
|
+
const [now, setNow] = useState(() => Date.now());
|
|
302
|
+
const [report, setReport] = useState();
|
|
303
|
+
const [error, setError] = useState();
|
|
304
|
+
const abortController = useRef(undefined);
|
|
305
|
+
const finished = useRef(false);
|
|
306
|
+
const rows = useMemo(() => visibleScopeRows(pages, expandedDirectories, selectedFiles), [pages, expandedDirectories, selectedFiles]);
|
|
307
|
+
const phasesByFile = useMemo(() => new Map([...taskPhases.values()].map((row) => [row.file, row])), [taskPhases]);
|
|
308
|
+
const visibleRunFiles = useMemo(() => runFiles.filter((file) => { var _a; return activeRunStage === 'check' || ((_a = phasesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.phase) !== 'not_testable'; }), [runFiles, activeRunStage, phasesByFile]);
|
|
309
|
+
const runRows = useMemo(() => {
|
|
310
|
+
const included = new Set(visibleRunFiles);
|
|
311
|
+
return visibleScopeRows(pages.filter((page) => included.has(page.file)), runExpanded, EMPTY_SELECTION);
|
|
312
|
+
}, [pages, visibleRunFiles, runExpanded]);
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
if (screen !== 'running')
|
|
315
|
+
return;
|
|
316
|
+
const timer = setInterval(() => setSpinnerFrame((frame) => frame + 1), 120);
|
|
317
|
+
const clock = setInterval(() => setNow(Date.now()), 1000);
|
|
318
|
+
return () => {
|
|
319
|
+
clearInterval(timer);
|
|
320
|
+
clearInterval(clock);
|
|
321
|
+
};
|
|
322
|
+
}, [screen]);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
var _a;
|
|
325
|
+
if (screen !== 'running')
|
|
326
|
+
return;
|
|
327
|
+
const fileDone = (file) => {
|
|
328
|
+
var _a;
|
|
329
|
+
const phase = (_a = phasesByFile.get(file)) === null || _a === void 0 ? void 0 : _a.phase;
|
|
330
|
+
return phase !== undefined && DONE_PHASES.has(phase);
|
|
331
|
+
};
|
|
332
|
+
const directoryFiles = new Map();
|
|
333
|
+
for (const file of visibleRunFiles) {
|
|
334
|
+
const parts = file.split('/');
|
|
335
|
+
let currentPath = '';
|
|
336
|
+
for (const part of parts.slice(0, -1)) {
|
|
337
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
338
|
+
const id = `dir:${currentPath}`;
|
|
339
|
+
directoryFiles.set(id, [...((_a = directoryFiles.get(id)) !== null && _a !== void 0 ? _a : []), file]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const toCollapse = [...directoryFiles.entries()]
|
|
343
|
+
.filter(([id, files]) => !autoCollapsed.current.has(id) && files.every(fileDone))
|
|
344
|
+
.map(([id]) => id);
|
|
345
|
+
if (toCollapse.length > 0) {
|
|
346
|
+
for (const id of toCollapse)
|
|
347
|
+
autoCollapsed.current.add(id);
|
|
348
|
+
setRunExpanded((current) => {
|
|
349
|
+
const next = new Set(current);
|
|
350
|
+
for (const id of toCollapse)
|
|
351
|
+
next.delete(id);
|
|
352
|
+
return next;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const rowDone = (row) => {
|
|
356
|
+
const files = row.kind === 'directory' ? row.selectableFiles : row.file ? [row.file] : [];
|
|
357
|
+
return files.length > 0 && files.every(fileDone);
|
|
358
|
+
};
|
|
359
|
+
if (manualCursor.current)
|
|
360
|
+
return;
|
|
361
|
+
const current = runRows[runCursor];
|
|
362
|
+
if (runRows.length > 0 && (!current || rowDone(current))) {
|
|
363
|
+
const nextIndex = runRows.findIndex((row) => !rowDone(row));
|
|
364
|
+
if (nextIndex !== -1 && nextIndex !== runCursor)
|
|
365
|
+
setRunCursor(nextIndex);
|
|
366
|
+
}
|
|
367
|
+
}, [screen, phasesByFile, visibleRunFiles, runRows, runCursor]);
|
|
368
|
+
const finish = (exitCode) => {
|
|
369
|
+
if (finished.current)
|
|
370
|
+
return;
|
|
371
|
+
finished.current = true;
|
|
372
|
+
onFinish(exitCode);
|
|
373
|
+
};
|
|
374
|
+
const startRun = ({ runId, runAgents, runModel, runFiles: runFiles_, }) => {
|
|
375
|
+
var _a, _b;
|
|
376
|
+
if (abortController.current)
|
|
377
|
+
return;
|
|
378
|
+
const controller = new AbortController();
|
|
379
|
+
abortController.current = controller;
|
|
380
|
+
setTotalTasks(runAgents.length * runFiles_.length);
|
|
381
|
+
setRunHarness((_b = (_a = HARNESSES.find((entry) => entry.agent === runAgents[0] && entry.model === runModel)) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : `${runAgents.join(', ')} · ${runModel}`);
|
|
382
|
+
setRunCursor(0);
|
|
383
|
+
autoCollapsed.current = new Set();
|
|
384
|
+
manualCursor.current = false;
|
|
385
|
+
setRunStartedAt(Date.now());
|
|
386
|
+
setNow(Date.now());
|
|
387
|
+
setRunFiles(runFiles_);
|
|
388
|
+
setRunExpanded(new Set(runFiles_.flatMap((file) => {
|
|
389
|
+
const parts = file.split('/').slice(0, -1);
|
|
390
|
+
return parts.map((_, index) => `dir:${parts.slice(0, index + 1).join('/')}`);
|
|
391
|
+
})));
|
|
392
|
+
setScreen('running');
|
|
393
|
+
setActiveRunStage('check');
|
|
394
|
+
void runCodeTests({
|
|
395
|
+
path: targetPath,
|
|
396
|
+
agents: runAgents,
|
|
397
|
+
model: runModel,
|
|
398
|
+
selectedFiles: runFiles_,
|
|
399
|
+
outputDirectory,
|
|
400
|
+
runId,
|
|
401
|
+
concurrency: DEFAULT_CONCURRENCY,
|
|
402
|
+
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
403
|
+
signal: controller.signal,
|
|
404
|
+
onTaskUpdate: (update) => {
|
|
405
|
+
const phaseStage = update.phase === 'running'
|
|
406
|
+
? 'run'
|
|
407
|
+
: update.phase === 'generating' || update.phase === 'generated'
|
|
408
|
+
? 'generate'
|
|
409
|
+
: null;
|
|
410
|
+
if (phaseStage) {
|
|
411
|
+
setActiveRunStage((stage) => {
|
|
412
|
+
const order = ['check', 'generate', 'run'];
|
|
413
|
+
return order.indexOf(phaseStage) > order.indexOf(stage) ? phaseStage : stage;
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
setTaskPhases((current) => {
|
|
418
|
+
var _a;
|
|
419
|
+
const previous = current.get(update.id);
|
|
420
|
+
return new Map(current).set(update.id, Object.assign(Object.assign(Object.assign({}, update), { startedAt: (_a = previous === null || previous === void 0 ? void 0 : previous.startedAt) !== null && _a !== void 0 ? _a : now }), (ACTIVE_PHASES.has(update.phase) ? {} : { finishedAt: now })));
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
})
|
|
424
|
+
.then((nextReport) => {
|
|
425
|
+
setReport(nextReport);
|
|
426
|
+
const failedFiles = nextReport.tasks
|
|
427
|
+
.filter((task) => task.status === 'failed' || task.status === 'agent_error')
|
|
428
|
+
.map((task) => task.file);
|
|
429
|
+
setRunExpanded(new Set(failedFiles.flatMap((file) => {
|
|
430
|
+
const parts = file.split('/').slice(0, -1);
|
|
431
|
+
return parts.map((_, index) => `dir:${parts.slice(0, index + 1).join('/')}`);
|
|
432
|
+
})));
|
|
433
|
+
setRunCursor(0);
|
|
434
|
+
setScreen('report');
|
|
435
|
+
})
|
|
436
|
+
.catch((runError) => {
|
|
437
|
+
if (controller.signal.aborted) {
|
|
438
|
+
finish(130);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
setError(runError instanceof Error ? runError.message : String(runError));
|
|
442
|
+
setScreen('error');
|
|
443
|
+
});
|
|
444
|
+
};
|
|
445
|
+
useInput((input, key) => {
|
|
446
|
+
if (key.ctrl && input === 'c') {
|
|
447
|
+
if (ctrlCArmed) {
|
|
448
|
+
if (abortController.current) {
|
|
449
|
+
abortController.current.abort();
|
|
450
|
+
setCancelling(true);
|
|
451
|
+
}
|
|
452
|
+
finish(130);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
setCtrlCArmed(true);
|
|
456
|
+
if (ctrlCTimer.current)
|
|
457
|
+
clearTimeout(ctrlCTimer.current);
|
|
458
|
+
ctrlCTimer.current = setTimeout(() => setCtrlCArmed(false), 2000);
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (screen === 'running' || screen === 'report') {
|
|
463
|
+
const collapse = key.leftArrow || input === 'h';
|
|
464
|
+
const expand = key.rightArrow || input === 'l';
|
|
465
|
+
if (key.upArrow || input === 'k') {
|
|
466
|
+
manualCursor.current = true;
|
|
467
|
+
setRunCursor((current) => Math.max(0, current - 1));
|
|
468
|
+
}
|
|
469
|
+
else if (key.downArrow || input === 'j') {
|
|
470
|
+
manualCursor.current = true;
|
|
471
|
+
setRunCursor((current) => Math.max(0, Math.min(runRows.length - 1, current + 1)));
|
|
472
|
+
}
|
|
473
|
+
else if (collapse || expand) {
|
|
474
|
+
manualCursor.current = true;
|
|
475
|
+
const row = runRows[runCursor];
|
|
476
|
+
if ((row === null || row === void 0 ? void 0 : row.kind) !== 'directory')
|
|
477
|
+
return;
|
|
478
|
+
setRunExpanded((current) => {
|
|
479
|
+
const next = new Set(current);
|
|
480
|
+
if (collapse)
|
|
481
|
+
next.delete(row.id);
|
|
482
|
+
else
|
|
483
|
+
next.add(row.id);
|
|
484
|
+
return next;
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
else if (screen === 'report' && key.return && report) {
|
|
488
|
+
finish(report.status === 'passed' ? 0 : report.status === 'cancelled' ? 130 : 1);
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (screen === 'resume' && save) {
|
|
493
|
+
if (key.upArrow || input === 'k' || key.downArrow || input === 'j') {
|
|
494
|
+
setResumeCursor((current) => (current + 1) % RESUME_OPTIONS.length);
|
|
495
|
+
}
|
|
496
|
+
else if (key.return) {
|
|
497
|
+
if (resumeCursor === 0) {
|
|
498
|
+
setSelectedFiles(new Set(save.selectedFiles));
|
|
499
|
+
startRun({
|
|
500
|
+
runId: save.runId,
|
|
501
|
+
runAgents: save.agents,
|
|
502
|
+
runModel: save.model,
|
|
503
|
+
runFiles: save.selectedFiles,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
void clearRunSave(outputDirectory);
|
|
508
|
+
setScreen('harnesses');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (screen === 'harnesses') {
|
|
514
|
+
if (key.upArrow || input === 'k')
|
|
515
|
+
setHarnessIndex((current) => (current + HARNESSES.length - 1) % HARNESSES.length);
|
|
516
|
+
else if (key.downArrow || input === 'j')
|
|
517
|
+
setHarnessIndex((current) => (current + 1) % HARNESSES.length);
|
|
518
|
+
else if (key.return)
|
|
519
|
+
setScreen('scope');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (screen === 'scope') {
|
|
523
|
+
const collapse = key.leftArrow || input === 'h';
|
|
524
|
+
const expand = key.rightArrow || input === 'l';
|
|
525
|
+
if (key.upArrow || input === 'k')
|
|
526
|
+
setScopeCursor((current) => Math.max(0, current - 1));
|
|
527
|
+
else if (key.downArrow || input === 'j')
|
|
528
|
+
setScopeCursor((current) => Math.max(0, Math.min(rows.length - 1, current + 1)));
|
|
529
|
+
else if (collapse || expand) {
|
|
530
|
+
const row = rows[scopeCursor];
|
|
531
|
+
if ((row === null || row === void 0 ? void 0 : row.kind) !== 'directory')
|
|
532
|
+
return;
|
|
533
|
+
setExpandedDirectories((current) => {
|
|
534
|
+
const next = new Set(current);
|
|
535
|
+
if (collapse)
|
|
536
|
+
next.delete(row.id);
|
|
537
|
+
else
|
|
538
|
+
next.add(row.id);
|
|
539
|
+
return next;
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
else if (input === ' ') {
|
|
543
|
+
const row = rows[scopeCursor];
|
|
544
|
+
if (row)
|
|
545
|
+
setSelectedFiles((current) => toggleScopeSelection(current, row));
|
|
546
|
+
}
|
|
547
|
+
else if (input.toLowerCase() === 'a') {
|
|
548
|
+
setSelectedFiles((current) => (current.size === files.length ? new Set() : new Set(files)));
|
|
549
|
+
}
|
|
550
|
+
else if (key.return && (selectedFiles.size > 0 || files.length === 0)) {
|
|
551
|
+
startRun({
|
|
552
|
+
runId: null,
|
|
553
|
+
runAgents: [harness.agent],
|
|
554
|
+
runModel: harness.model,
|
|
555
|
+
runFiles: [...selectedFiles],
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (screen === 'error' && key.return) {
|
|
561
|
+
finish(1);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
const leftWidth = size.columns >= 90 ? 28 : 24;
|
|
565
|
+
const viewHeight = size.rows - 1;
|
|
566
|
+
const running = screen === 'running';
|
|
567
|
+
const fullBleed = running || (screen === 'report' && report !== undefined);
|
|
568
|
+
let content;
|
|
569
|
+
if (screen === 'resume' && save)
|
|
570
|
+
content = _jsx(ResumeScreen, { save: save, cursor: resumeCursor });
|
|
571
|
+
else if (screen === 'harnesses') {
|
|
572
|
+
content = _jsx(HarnessScreen, { cursor: harnessIndex });
|
|
573
|
+
}
|
|
574
|
+
else if (screen === 'scope') {
|
|
575
|
+
content = (_jsx(ScopeScreen, { rows: rows, cursor: scopeCursor, height: viewHeight, selectedCount: selectedFiles.size, selectedBlocks: pages.reduce((total, page) => total + (selectedFiles.has(page.file) ? page.blockCount : 0), 0), testablePages: pages.length, totalPages: totalPages, outputDirectory: outputDirectory }));
|
|
576
|
+
}
|
|
577
|
+
else if (screen === 'report' && report) {
|
|
578
|
+
content = (_jsx(ReportScreen, { report: report, rows: runRows, cursor: runCursor, phases: phasesByFile, height: viewHeight }));
|
|
579
|
+
}
|
|
580
|
+
else if (screen === 'error') {
|
|
581
|
+
content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", bold: true, children: "Mint test stopped" }), _jsx(Text, { children: error !== null && error !== void 0 ? error : 'Unknown error' })] }));
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
content = (_jsx(ActivityScreen, { stage: activeRunStage, cancelling: cancelling, harness: runHarness, rows: runRows, cursor: runCursor, phases: phasesByFile, height: viewHeight, totalTasks: totalTasks, spinnerFrame: spinnerFrame, runStartedAt: runStartedAt, now: now }));
|
|
585
|
+
}
|
|
586
|
+
return (_jsxs(Box, { flexDirection: "column", width: size.columns, height: viewHeight, overflow: "hidden", children: [_jsxs(Box, { flexGrow: 1, gap: 1, overflow: "hidden", children: [_jsxs(Box, { width: leftWidth, flexShrink: 0, borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Steps" }), STEPS.map((step) => {
|
|
587
|
+
const state = stepState(step.id, screen, activeRunStage);
|
|
588
|
+
return (_jsxs(Text, { color: state === 'running' ? 'cyan' : undefined, children: [_jsx(StepGlyph, { state: state }), " ", step.label] }, step.id));
|
|
589
|
+
})] }), _jsx(Box, { flexGrow: 1, borderStyle: fullBleed ? undefined : 'round', borderColor: "gray", paddingX: fullBleed ? 0 : 1, flexDirection: "column", overflow: "hidden", children: content })] }), _jsx(Text, { dimColor: true, children: ctrlCArmed
|
|
590
|
+
? 'Ctrl-C again to exit'
|
|
591
|
+
: footerForScreen(screen, running, (report === null || report === void 0 ? void 0 : report.status) === 'cancelled') })] }));
|
|
592
|
+
}
|
|
593
|
+
export function runMintTestUi(options) {
|
|
594
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
595
|
+
process.stdout.write('\u001B[?1049h\u001B[?25l');
|
|
596
|
+
let instance;
|
|
597
|
+
try {
|
|
598
|
+
return yield new Promise((resolve) => {
|
|
599
|
+
instance = render(_jsx(MintTestApp, Object.assign({}, options, { onFinish: resolve })), {
|
|
600
|
+
exitOnCtrlC: false,
|
|
601
|
+
patchConsole: false,
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
instance === null || instance === void 0 ? void 0 : instance.unmount();
|
|
607
|
+
process.stdout.write('\u001B[?25h\u001B[?1049l');
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
package/bin/status.js
CHANGED
|
@@ -21,23 +21,30 @@ const StatusResponseSchema = z.object({
|
|
|
21
21
|
org: z.object({ name: z.string() }),
|
|
22
22
|
subdomains: z.array(z.string()).default([]),
|
|
23
23
|
});
|
|
24
|
-
export function
|
|
24
|
+
export function getCliStatus(accessToken) {
|
|
25
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
26
|
try {
|
|
27
27
|
const res = yield fetch(`${API_URL}/api/cli/status`, {
|
|
28
28
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
29
29
|
});
|
|
30
30
|
if (!res.ok)
|
|
31
|
-
return
|
|
31
|
+
return null;
|
|
32
32
|
const json = yield res.json().catch(() => null);
|
|
33
33
|
const parsed = StatusResponseSchema.safeParse(json);
|
|
34
|
-
return parsed.success ? parsed.data
|
|
34
|
+
return parsed.success ? parsed.data : null;
|
|
35
35
|
}
|
|
36
36
|
catch (_a) {
|
|
37
|
-
return
|
|
37
|
+
return null;
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
+
export function getCliSubdomains(accessToken) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
var _a;
|
|
44
|
+
const status = yield getCliStatus(accessToken);
|
|
45
|
+
return (_a = status === null || status === void 0 ? void 0 : status.subdomains) !== null && _a !== void 0 ? _a : [];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
41
48
|
export function status() {
|
|
42
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
43
50
|
var _a;
|