@bugbug-io/cli 13.39.1
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/AGENTS.md +103 -0
- package/README.md +229 -0
- package/bin/bugbug.mjs +3 -0
- package/dist/NavigatorApp-WSJMM3OT.js +2410 -0
- package/dist/app-A4ZLLFP5.js +21 -0
- package/dist/chunk-NTNHB6R6.js +6098 -0
- package/dist/chunk-UZVYEMEZ.js +37 -0
- package/dist/index.js +1442 -0
- package/dist/render-UGRPBJOT.js +7 -0
- package/package.json +63 -0
|
@@ -0,0 +1,2410 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseSuiteRunDetails,
|
|
3
|
+
BaseTestRunDetails,
|
|
4
|
+
DEFAULT_RESERVED_TERMINAL_ROWS,
|
|
5
|
+
EscProvider,
|
|
6
|
+
MenuList,
|
|
7
|
+
ROUTES,
|
|
8
|
+
SelectableOption,
|
|
9
|
+
SelectionPrefix,
|
|
10
|
+
ShortcutProvider,
|
|
11
|
+
Status,
|
|
12
|
+
ViewLayout,
|
|
13
|
+
VirtualizedList,
|
|
14
|
+
analytics,
|
|
15
|
+
defaultJunitReportPath,
|
|
16
|
+
detectFormat,
|
|
17
|
+
exportProject,
|
|
18
|
+
exportTest,
|
|
19
|
+
formatCwd,
|
|
20
|
+
formatDuration,
|
|
21
|
+
formatErrorMessage,
|
|
22
|
+
formatRunStatus,
|
|
23
|
+
getCliConfig,
|
|
24
|
+
getIdentity,
|
|
25
|
+
getProjectById,
|
|
26
|
+
getSuite,
|
|
27
|
+
getSuiteDetails,
|
|
28
|
+
getSuiteRunDetails,
|
|
29
|
+
getSuiteRunTestRuns,
|
|
30
|
+
getTest,
|
|
31
|
+
getTestDetails,
|
|
32
|
+
getTestRunDetails,
|
|
33
|
+
getTestRunJunitReport,
|
|
34
|
+
getVersion,
|
|
35
|
+
hasProjectConfigFile,
|
|
36
|
+
importProject,
|
|
37
|
+
importTest,
|
|
38
|
+
initProject,
|
|
39
|
+
isAuthenticationError,
|
|
40
|
+
isAuthorizationError,
|
|
41
|
+
listProfiles,
|
|
42
|
+
listProjects,
|
|
43
|
+
listSuiteRuns,
|
|
44
|
+
listTestRunDebugArtifacts,
|
|
45
|
+
listTestRuns,
|
|
46
|
+
loadSuiteTableResources,
|
|
47
|
+
loadTestTableResources,
|
|
48
|
+
login,
|
|
49
|
+
logout,
|
|
50
|
+
openExternalUrl,
|
|
51
|
+
runSuite,
|
|
52
|
+
runTest,
|
|
53
|
+
switchProject,
|
|
54
|
+
updateSdkConfig,
|
|
55
|
+
useEscHandler,
|
|
56
|
+
useShortcutBlocker,
|
|
57
|
+
useShortcutHandler,
|
|
58
|
+
useTheme,
|
|
59
|
+
userCwd,
|
|
60
|
+
writeReportXml
|
|
61
|
+
} from "./chunk-NTNHB6R6.js";
|
|
62
|
+
|
|
63
|
+
// src/app/NavigatorApp.tsx
|
|
64
|
+
import { useState as useState17 } from "react";
|
|
65
|
+
import { RouterProvider } from "react-router";
|
|
66
|
+
|
|
67
|
+
// src/app/contexts/navigatorSession/NavigatorSessionContext.tsx
|
|
68
|
+
import { createContext, useCallback, useContext, useState } from "react";
|
|
69
|
+
import { jsx } from "react/jsx-runtime";
|
|
70
|
+
var applyAuthState = (config, authState) => ({
|
|
71
|
+
...config,
|
|
72
|
+
...authState.token !== void 0 ? { token: authState.token } : {},
|
|
73
|
+
...authState.projectId !== void 0 ? { projectId: authState.projectId } : {}
|
|
74
|
+
});
|
|
75
|
+
var getAuthStatus = (config, authState) => ({
|
|
76
|
+
kind: config.token && config.projectId ? "ready" : "prompt",
|
|
77
|
+
token: config.token,
|
|
78
|
+
projectId: config.projectId,
|
|
79
|
+
isUserToken: authState.isUserToken === true,
|
|
80
|
+
apiUrl: config.apiUrl
|
|
81
|
+
});
|
|
82
|
+
var syncInitialSdkConfig = () => {
|
|
83
|
+
const config = getCliConfig();
|
|
84
|
+
if (config.token) {
|
|
85
|
+
updateSdkConfig({
|
|
86
|
+
apiToken: config.token,
|
|
87
|
+
...config.projectId ? { projectId: config.projectId } : {}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
};
|
|
92
|
+
var NavigatorSessionContext = createContext(null);
|
|
93
|
+
var NavigatorSessionProvider = ({ children }) => {
|
|
94
|
+
const [authState, setAuthState] = useState(
|
|
95
|
+
() => syncInitialSdkConfig()
|
|
96
|
+
);
|
|
97
|
+
const [authError, setAuthError] = useState(null);
|
|
98
|
+
const config = applyAuthState(getCliConfig(), authState);
|
|
99
|
+
const auth = getAuthStatus(config, authState);
|
|
100
|
+
const completeSession = useCallback(
|
|
101
|
+
(token, projectId, options = {}) => {
|
|
102
|
+
try {
|
|
103
|
+
const trimmedToken = token.trim();
|
|
104
|
+
if (!trimmedToken) throw new Error("Token must not be empty.");
|
|
105
|
+
const trimmedProjectId = projectId.trim();
|
|
106
|
+
if (!trimmedProjectId) throw new Error("Project id must not be empty.");
|
|
107
|
+
const nextAuthState = {
|
|
108
|
+
token: trimmedToken,
|
|
109
|
+
projectId: trimmedProjectId,
|
|
110
|
+
isUserToken: options.isUserToken === true
|
|
111
|
+
};
|
|
112
|
+
updateSdkConfig({ apiToken: nextAuthState.token, projectId: nextAuthState.projectId });
|
|
113
|
+
analytics.refreshWithToken(trimmedToken);
|
|
114
|
+
analytics.trackEvent("cli_session_configured", { success: true });
|
|
115
|
+
if (config.projectId !== trimmedProjectId) {
|
|
116
|
+
analytics.trackEvent("cli_session_project_switched", { success: true });
|
|
117
|
+
}
|
|
118
|
+
analytics.trackEvent("cli_session_started");
|
|
119
|
+
setAuthState(nextAuthState);
|
|
120
|
+
setAuthError(null);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
analytics.trackEvent("cli_session_configured", { success: false });
|
|
123
|
+
setAuthError(err instanceof Error ? err.message : String(err));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[config.projectId]
|
|
127
|
+
);
|
|
128
|
+
const clearSession = useCallback(() => {
|
|
129
|
+
setAuthState({});
|
|
130
|
+
setAuthError(null);
|
|
131
|
+
}, []);
|
|
132
|
+
const setSessionProject = useCallback((projectId) => {
|
|
133
|
+
try {
|
|
134
|
+
const trimmedProjectId = projectId.trim();
|
|
135
|
+
if (!trimmedProjectId) throw new Error("Project id must not be empty.");
|
|
136
|
+
updateSdkConfig({ projectId: trimmedProjectId });
|
|
137
|
+
setAuthState((current) => ({ ...current, projectId: trimmedProjectId }));
|
|
138
|
+
setAuthError(null);
|
|
139
|
+
analytics.trackEvent("cli_session_project_switched", { success: true });
|
|
140
|
+
} catch (err) {
|
|
141
|
+
analytics.trackEvent("cli_session_project_switched", { success: false });
|
|
142
|
+
setAuthError(err instanceof Error ? err.message : String(err));
|
|
143
|
+
}
|
|
144
|
+
}, []);
|
|
145
|
+
return /* @__PURE__ */ jsx(
|
|
146
|
+
NavigatorSessionContext.Provider,
|
|
147
|
+
{
|
|
148
|
+
value: { auth, config, authError, completeSession, clearSession, setSessionProject },
|
|
149
|
+
children
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
var useNavigatorSession = () => {
|
|
154
|
+
const ctx = useContext(NavigatorSessionContext);
|
|
155
|
+
if (!ctx) {
|
|
156
|
+
throw new Error("useNavigatorSession must be used within a NavigatorSessionProvider.");
|
|
157
|
+
}
|
|
158
|
+
return ctx;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/app/router.tsx
|
|
162
|
+
import { createMemoryRouter, createRoutesFromElements, Route } from "react-router";
|
|
163
|
+
|
|
164
|
+
// src/app/Navigator.tsx
|
|
165
|
+
import { Box as Box4, useApp, useInput } from "ink";
|
|
166
|
+
import { useState as useState4 } from "react";
|
|
167
|
+
import { Outlet, useLocation as useLocation3, useNavigate as useNavigate2 } from "react-router";
|
|
168
|
+
|
|
169
|
+
// src/app/components/AuthGate/AuthGate.tsx
|
|
170
|
+
import { useEffect as useEffect2 } from "react";
|
|
171
|
+
import { useLocation, useNavigate } from "react-router";
|
|
172
|
+
|
|
173
|
+
// src/app/hooks/useConnectionStatus.ts
|
|
174
|
+
import { useEffect, useState as useState2 } from "react";
|
|
175
|
+
var PING_INTERVAL_MS = 15e3;
|
|
176
|
+
var getConnectionStatus = async () => {
|
|
177
|
+
try {
|
|
178
|
+
const identity = await getIdentity();
|
|
179
|
+
return { state: "ok", message: "API connectivity successful", identity };
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (isAuthenticationError(err) || isAuthorizationError(err)) {
|
|
182
|
+
return { state: "unauthenticated", message: "API token is invalid or missing" };
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
state: "error",
|
|
186
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var useConnectionStatus = (enabled) => {
|
|
191
|
+
const [status, setStatus] = useState2({ state: "checking" });
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!enabled) {
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
let cancelled = false;
|
|
197
|
+
const ping = async () => {
|
|
198
|
+
const nextStatus = await getConnectionStatus();
|
|
199
|
+
if (!cancelled) setStatus(nextStatus);
|
|
200
|
+
};
|
|
201
|
+
ping();
|
|
202
|
+
const interval = setInterval(() => {
|
|
203
|
+
ping();
|
|
204
|
+
}, PING_INTERVAL_MS);
|
|
205
|
+
return () => {
|
|
206
|
+
cancelled = true;
|
|
207
|
+
clearInterval(interval);
|
|
208
|
+
};
|
|
209
|
+
}, [enabled]);
|
|
210
|
+
return status;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/app/components/AuthGate/AuthGate.tsx
|
|
214
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
215
|
+
var INIT_ROUTE = "/init";
|
|
216
|
+
var AuthGate = ({ children }) => {
|
|
217
|
+
const { auth } = useNavigatorSession();
|
|
218
|
+
const location = useLocation();
|
|
219
|
+
const navigate = useNavigate();
|
|
220
|
+
const onInitRoute = location.pathname === INIT_ROUTE;
|
|
221
|
+
const hasConfiguredAuth = auth.kind === "ready";
|
|
222
|
+
const connectionStatus = useConnectionStatus(hasConfiguredAuth);
|
|
223
|
+
const authCheckPending = hasConfiguredAuth && connectionStatus.state === "checking";
|
|
224
|
+
const authRejected = hasConfiguredAuth && connectionStatus.state === "unauthenticated";
|
|
225
|
+
const authenticated = hasConfiguredAuth && !authRejected;
|
|
226
|
+
useEffect2(() => {
|
|
227
|
+
if (!authCheckPending && !authenticated && !onInitRoute) {
|
|
228
|
+
navigate(INIT_ROUTE, {
|
|
229
|
+
state: {
|
|
230
|
+
redirectUrl: location.pathname,
|
|
231
|
+
onlyThisSession: true,
|
|
232
|
+
...authRejected ? { ignoreConfiguredAuth: true } : {}
|
|
233
|
+
},
|
|
234
|
+
replace: true
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}, [authCheckPending, authenticated, authRejected, onInitRoute, location.pathname, navigate]);
|
|
238
|
+
if (authCheckPending) return null;
|
|
239
|
+
if (authenticated) return /* @__PURE__ */ jsx2(Fragment, { children: children(true) });
|
|
240
|
+
if (onInitRoute) return /* @__PURE__ */ jsx2(Fragment, { children: children(false) });
|
|
241
|
+
return null;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/app/components/Header/Header.tsx
|
|
245
|
+
import { Box, Text } from "ink";
|
|
246
|
+
|
|
247
|
+
// src/app/hooks/useFeatureQuery.ts
|
|
248
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
|
|
249
|
+
var useFeatureQuery = (fn, deps) => {
|
|
250
|
+
const [data, setData] = useState3(void 0);
|
|
251
|
+
const [isFetching, setIsFetching] = useState3(true);
|
|
252
|
+
const [error, setError] = useState3(void 0);
|
|
253
|
+
const [refreshIndex, setRefreshIndex] = useState3(0);
|
|
254
|
+
useEffect3(() => {
|
|
255
|
+
let cancelled = false;
|
|
256
|
+
setIsFetching(true);
|
|
257
|
+
setError(void 0);
|
|
258
|
+
const run = async () => {
|
|
259
|
+
try {
|
|
260
|
+
const result = await fn();
|
|
261
|
+
if (!cancelled) setData(result);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (!cancelled) setError(formatErrorMessage(err));
|
|
264
|
+
} finally {
|
|
265
|
+
if (!cancelled) setIsFetching(false);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
run().catch((err) => {
|
|
269
|
+
if (!cancelled) {
|
|
270
|
+
setError(formatErrorMessage(err));
|
|
271
|
+
setIsFetching(false);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return () => {
|
|
275
|
+
cancelled = true;
|
|
276
|
+
};
|
|
277
|
+
}, [refreshIndex, ...deps]);
|
|
278
|
+
const refetch = useCallback2(() => {
|
|
279
|
+
setRefreshIndex((index) => index + 1);
|
|
280
|
+
}, []);
|
|
281
|
+
return {
|
|
282
|
+
data,
|
|
283
|
+
isLoading: isFetching && data === void 0,
|
|
284
|
+
isFetching,
|
|
285
|
+
isError: error !== void 0,
|
|
286
|
+
error,
|
|
287
|
+
refetch
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/app/hooks/useCurrentProject.ts
|
|
292
|
+
var useCurrentProject = (enabled) => {
|
|
293
|
+
const { auth } = useNavigatorSession();
|
|
294
|
+
const projectId = enabled ? auth.projectId : void 0;
|
|
295
|
+
const query = useFeatureQuery(
|
|
296
|
+
() => projectId ? getProjectById(projectId) : Promise.resolve(null),
|
|
297
|
+
[projectId]
|
|
298
|
+
);
|
|
299
|
+
return query.data ?? void 0;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/app/components/Header/Header.constants.ts
|
|
303
|
+
var VERSION = getVersion();
|
|
304
|
+
var BUGBUG_LOGO_ASCII = [
|
|
305
|
+
"\u2584 \u2584 ",
|
|
306
|
+
"\u2588\u2588\u2588 \u2588\u2588\u2588 ",
|
|
307
|
+
"\u2580\u2580\u2580 \u2580\u2580\u2580 ",
|
|
308
|
+
"\u2584\u2584 \u2584\u2584 ",
|
|
309
|
+
" \u2580\u2580\u2580\u2580 "
|
|
310
|
+
].join("\n");
|
|
311
|
+
var STATUS_LABEL = {
|
|
312
|
+
checking: "Connecting...",
|
|
313
|
+
ok: "Authenticated",
|
|
314
|
+
unauthenticated: "Unauthenticated",
|
|
315
|
+
error: "Disconnected"
|
|
316
|
+
};
|
|
317
|
+
var STATUS_COLOR = {
|
|
318
|
+
checking: "yellow",
|
|
319
|
+
ok: void 0,
|
|
320
|
+
unauthenticated: "yellow",
|
|
321
|
+
error: "red"
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/app/components/Header/Header.tsx
|
|
325
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
326
|
+
var Header = ({ authenticated }) => {
|
|
327
|
+
const { theme } = useTheme();
|
|
328
|
+
const status = useConnectionStatus(authenticated);
|
|
329
|
+
const project = useCurrentProject(authenticated);
|
|
330
|
+
const identity = authenticated ? status.identity : void 0;
|
|
331
|
+
const state = authenticated ? status.state : "unauthenticated";
|
|
332
|
+
const showDetail = authenticated && (status.state === "error" || status.state === "unauthenticated") && !!status.message;
|
|
333
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
334
|
+
/* @__PURE__ */ jsx3(Box, { marginRight: 1, children: /* @__PURE__ */ jsx3(Text, { color: theme.colors.brand, children: BUGBUG_LOGO_ASCII }) }),
|
|
335
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
336
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
337
|
+
/* @__PURE__ */ jsx3(Text, { bold: true, children: "BugBug" }),
|
|
338
|
+
" ",
|
|
339
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
340
|
+
"v",
|
|
341
|
+
VERSION
|
|
342
|
+
] })
|
|
343
|
+
] }),
|
|
344
|
+
/* @__PURE__ */ jsx3(Text, { dimColor: true, children: "Your interactive quality buddy" }),
|
|
345
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
346
|
+
/* @__PURE__ */ jsx3(Text, { dimColor: true, children: "Status: " }),
|
|
347
|
+
/* @__PURE__ */ jsx3(Text, { color: STATUS_COLOR[state], dimColor: state === "ok", children: STATUS_LABEL[state] }),
|
|
348
|
+
authenticated && identity && /* @__PURE__ */ jsx3(Text, { dimColor: true, children: identity.tokenType === "user" ? ` (logged in as ${identity.name})` : ` (via ${identity.tokenType} token)` }),
|
|
349
|
+
showDetail ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
350
|
+
" (",
|
|
351
|
+
status.message,
|
|
352
|
+
")"
|
|
353
|
+
] }) : null
|
|
354
|
+
] }),
|
|
355
|
+
authenticated && project ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
356
|
+
"Project: ",
|
|
357
|
+
project.name ?? "Untitled project",
|
|
358
|
+
" at ",
|
|
359
|
+
project.organizationName
|
|
360
|
+
] }) : /* @__PURE__ */ jsx3(Text, { dimColor: true, children: " " }),
|
|
361
|
+
/* @__PURE__ */ jsx3(Text, { dimColor: true, children: formatCwd(userCwd()) })
|
|
362
|
+
] })
|
|
363
|
+
] });
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/features/tui/components/Breadcrumb/Breadcrumb.tsx
|
|
367
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
368
|
+
import { useLocation as useLocation2 } from "react-router";
|
|
369
|
+
|
|
370
|
+
// src/app/routeLabels.ts
|
|
371
|
+
var CRUMB_LABELS = {
|
|
372
|
+
"/tests": "Tests",
|
|
373
|
+
"/tests/list": "List",
|
|
374
|
+
"/tests/import": "Import",
|
|
375
|
+
"/tests/run": "Run",
|
|
376
|
+
"/tests/:testId": "Details",
|
|
377
|
+
"/tests/:testId/run": "Run",
|
|
378
|
+
"/tests/:testId/lastRun": "Show last run",
|
|
379
|
+
"/tests/:testId/export": "Export",
|
|
380
|
+
"/tests/:testId/open": "Open in BugBug",
|
|
381
|
+
"/testruns": "Test runs",
|
|
382
|
+
"/testruns/list": "List",
|
|
383
|
+
"/testruns/:runId": "Details",
|
|
384
|
+
"/testruns/:runId/debugArtifacts": "Show run artifacts",
|
|
385
|
+
"/testruns/:runId/junitReport": "Download JUnit report",
|
|
386
|
+
"/testruns/:runId/open": "Open in BugBug",
|
|
387
|
+
"/suites": "Suites",
|
|
388
|
+
"/suites/list": "List",
|
|
389
|
+
"/suites/run": "Run",
|
|
390
|
+
"/suites/:suiteId": "Details",
|
|
391
|
+
"/suites/:suiteId/run": "Run",
|
|
392
|
+
"/suites/:suiteId/lastRun": "Show last run",
|
|
393
|
+
"/suites/:suiteId/open": "Open in BugBug",
|
|
394
|
+
"/suiteruns": "Suite runs",
|
|
395
|
+
"/suiteruns/list": "List",
|
|
396
|
+
"/suiteruns/:runId": "Details",
|
|
397
|
+
"/suiteruns/:runId/showTestRuns": "Show test runs",
|
|
398
|
+
"/suiteruns/:runId/open": "Open in BugBug",
|
|
399
|
+
"/profiles": "Profiles",
|
|
400
|
+
"/profiles/list": "List",
|
|
401
|
+
"/project": "Project",
|
|
402
|
+
"/project/export": "Export",
|
|
403
|
+
"/project/import": "Import",
|
|
404
|
+
"/settings": "Settings",
|
|
405
|
+
"/settings/setSessionToken": "Set session token",
|
|
406
|
+
"/logout": "Logout"
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/features/tui/tui.utils.ts
|
|
410
|
+
var ROOT_LABEL = "Main menu";
|
|
411
|
+
var normalizePath = (pathname) => {
|
|
412
|
+
const trimmed = pathname.replace(/\/+$/, "");
|
|
413
|
+
return trimmed === "" ? "/" : trimmed;
|
|
414
|
+
};
|
|
415
|
+
var pathSegments = (pathname) => normalizePath(pathname).split("/").filter(Boolean);
|
|
416
|
+
var matchesPattern = (pattern, pathname) => {
|
|
417
|
+
const patternSegments = pathSegments(pattern);
|
|
418
|
+
const currentSegments = pathSegments(pathname);
|
|
419
|
+
if (patternSegments.length !== currentSegments.length) return false;
|
|
420
|
+
return patternSegments.every(
|
|
421
|
+
(segment, index) => segment.startsWith(":") || segment === currentSegments[index]
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
var findLabel = (pathname, labels) => {
|
|
425
|
+
const normalizedPath = normalizePath(pathname);
|
|
426
|
+
const exact = labels[normalizedPath];
|
|
427
|
+
if (exact) return exact;
|
|
428
|
+
const wildcard = Object.entries(labels).find(
|
|
429
|
+
([pattern]) => matchesPattern(pattern, normalizedPath)
|
|
430
|
+
);
|
|
431
|
+
return wildcard?.[1];
|
|
432
|
+
};
|
|
433
|
+
var buildBreadcrumb = (pathname, labels) => {
|
|
434
|
+
const parts = [ROOT_LABEL];
|
|
435
|
+
const segments = pathSegments(pathname);
|
|
436
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
437
|
+
const prefix = `/${segments.slice(0, index + 1).join("/")}`;
|
|
438
|
+
const label = findLabel(prefix, labels);
|
|
439
|
+
if (label && parts[parts.length - 1] !== label) parts.push(label);
|
|
440
|
+
}
|
|
441
|
+
return parts.join(" \u203A ");
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/features/tui/components/Breadcrumb/Breadcrumb.tsx
|
|
445
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
446
|
+
var Breadcrumb = () => {
|
|
447
|
+
const { pathname } = useLocation2();
|
|
448
|
+
return /* @__PURE__ */ jsx4(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text2, { bold: true, color: "cyan", children: buildBreadcrumb(pathname, CRUMB_LABELS) }) });
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/features/tui/components/ShortcutsGrid/ShortcutsGrid.tsx
|
|
452
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
453
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
454
|
+
var GLOBAL_SHORTCUTS = [
|
|
455
|
+
{ shortcut: "t", label: "Tests" },
|
|
456
|
+
{ shortcut: "r", label: "Test runs" },
|
|
457
|
+
{ shortcut: "s", label: "Suites" },
|
|
458
|
+
{ shortcut: "u", label: "Suite runs" },
|
|
459
|
+
{ shortcut: "f", label: "Profiles" },
|
|
460
|
+
{ shortcut: "p", label: "Project" },
|
|
461
|
+
{ shortcut: ",", label: "Settings" },
|
|
462
|
+
{ shortcut: "Esc", label: "Back" },
|
|
463
|
+
{ shortcut: "q", label: "Close at main menu" },
|
|
464
|
+
{ shortcut: "Ctrl-C", label: "Exit" }
|
|
465
|
+
];
|
|
466
|
+
var ACTION_SHORTCUTS = [
|
|
467
|
+
{ shortcut: "Enter", label: "Select" },
|
|
468
|
+
{ shortcut: "/", label: "Search" },
|
|
469
|
+
{ shortcut: "l", label: "List or logs" },
|
|
470
|
+
{ shortcut: "r", label: "Run" },
|
|
471
|
+
{ shortcut: "i", label: "Import" },
|
|
472
|
+
{ shortcut: "e", label: "Export" },
|
|
473
|
+
{ shortcut: "o", label: "Open in BugBug" },
|
|
474
|
+
{ shortcut: "h", label: "Last run" },
|
|
475
|
+
{ shortcut: "a", label: "Artifacts" },
|
|
476
|
+
{ shortcut: "t", label: "Suite run tests" }
|
|
477
|
+
];
|
|
478
|
+
var ShortcutColumn = ({ title, items }) => /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginRight: 4, children: [
|
|
479
|
+
/* @__PURE__ */ jsx5(Text3, { bold: true, children: title }),
|
|
480
|
+
items.map(({ shortcut, label }) => /* @__PURE__ */ jsxs2(Text3, { children: [
|
|
481
|
+
/* @__PURE__ */ jsx5(Text3, { color: "cyan", children: shortcut.padEnd(7) }),
|
|
482
|
+
label
|
|
483
|
+
] }, `${shortcut}-${label}`))
|
|
484
|
+
] });
|
|
485
|
+
var ShortcutsGrid = () => /* @__PURE__ */ jsxs2(Box3, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
486
|
+
/* @__PURE__ */ jsx5(ShortcutColumn, { title: "Global", items: GLOBAL_SHORTCUTS }),
|
|
487
|
+
/* @__PURE__ */ jsx5(ShortcutColumn, { title: "Actions", items: ACTION_SHORTCUTS })
|
|
488
|
+
] });
|
|
489
|
+
|
|
490
|
+
// src/features/tui/ShortcutController.constants.ts
|
|
491
|
+
var GLOBAL_SHORTCUT_PRIORITY = -100;
|
|
492
|
+
var HELP_SHORTCUT_PRIORITY = 100;
|
|
493
|
+
|
|
494
|
+
// src/app/Navigator.tsx
|
|
495
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
496
|
+
var Navigator = () => {
|
|
497
|
+
const [shortcutsVisible, setShortcutsVisible] = useState4(false);
|
|
498
|
+
return /* @__PURE__ */ jsx6(ShortcutProvider, { children: /* @__PURE__ */ jsx6(
|
|
499
|
+
NavigatorShell,
|
|
500
|
+
{
|
|
501
|
+
shortcutsVisible,
|
|
502
|
+
onToggleShortcuts: () => setShortcutsVisible((visible) => !visible)
|
|
503
|
+
}
|
|
504
|
+
) });
|
|
505
|
+
};
|
|
506
|
+
var NavigatorShell = ({
|
|
507
|
+
shortcutsVisible,
|
|
508
|
+
onToggleShortcuts
|
|
509
|
+
}) => {
|
|
510
|
+
const { exit } = useApp();
|
|
511
|
+
const navigate = useNavigate2();
|
|
512
|
+
const location = useLocation3();
|
|
513
|
+
const atRoot = location.pathname === ROUTES.root();
|
|
514
|
+
useInput((input, key) => {
|
|
515
|
+
if (key.ctrl && input === "c") {
|
|
516
|
+
exit();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (atRoot && (input === "q" || input === "Q")) {
|
|
520
|
+
exit();
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
useShortcutHandler(
|
|
524
|
+
({ input }) => {
|
|
525
|
+
if (input !== "?") return false;
|
|
526
|
+
onToggleShortcuts();
|
|
527
|
+
return true;
|
|
528
|
+
},
|
|
529
|
+
{ priority: HELP_SHORTCUT_PRIORITY }
|
|
530
|
+
);
|
|
531
|
+
useShortcutHandler(
|
|
532
|
+
({ input }) => {
|
|
533
|
+
const route = {
|
|
534
|
+
t: "/tests",
|
|
535
|
+
r: ROUTES.testRuns.list(),
|
|
536
|
+
s: "/suites",
|
|
537
|
+
u: ROUTES.suiteRuns.list(),
|
|
538
|
+
p: "/project",
|
|
539
|
+
",": "/settings"
|
|
540
|
+
}[input];
|
|
541
|
+
if (!route) return false;
|
|
542
|
+
navigate(route);
|
|
543
|
+
return true;
|
|
544
|
+
},
|
|
545
|
+
{ priority: GLOBAL_SHORTCUT_PRIORITY }
|
|
546
|
+
);
|
|
547
|
+
return /* @__PURE__ */ jsx6(Box4, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx6(AuthGate, { children: (authenticated) => /* @__PURE__ */ jsxs3(
|
|
548
|
+
EscProvider,
|
|
549
|
+
{
|
|
550
|
+
canGoBack: !atRoot,
|
|
551
|
+
onUnhandled: () => {
|
|
552
|
+
if (!atRoot) navigate(-1);
|
|
553
|
+
},
|
|
554
|
+
children: [
|
|
555
|
+
/* @__PURE__ */ jsx6(Header, { authenticated }),
|
|
556
|
+
authenticated && /* @__PURE__ */ jsx6(Breadcrumb, {}),
|
|
557
|
+
/* @__PURE__ */ jsx6(Outlet, {}),
|
|
558
|
+
shortcutsVisible && /* @__PURE__ */ jsx6(ShortcutsGrid, {})
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
) }) });
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/app/views/logout/Logout.tsx
|
|
565
|
+
import { StatusMessage } from "@inkjs/ui";
|
|
566
|
+
import { useApp as useApp2 } from "ink";
|
|
567
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
568
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
569
|
+
var Logout = () => {
|
|
570
|
+
const { exit } = useApp2();
|
|
571
|
+
const [error, setError] = useState5(null);
|
|
572
|
+
useEffect4(() => {
|
|
573
|
+
let cleared;
|
|
574
|
+
try {
|
|
575
|
+
({ cleared } = logout());
|
|
576
|
+
} catch (err) {
|
|
577
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
process.stdout.write(cleared ? "Logged out.\n" : "Already logged out.\n");
|
|
581
|
+
exit();
|
|
582
|
+
}, [exit]);
|
|
583
|
+
return /* @__PURE__ */ jsx7(ViewLayout, { title: "Logout", hintsHidden: true, children: error && /* @__PURE__ */ jsx7(StatusMessage, { variant: "error", children: `Error: ${error}` }) });
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// src/app/hooks/useIsUserToken.ts
|
|
587
|
+
var useIsUserToken = () => {
|
|
588
|
+
const { auth } = useNavigatorSession();
|
|
589
|
+
const needsIdentity = auth.kind === "ready" && !auth.isUserToken;
|
|
590
|
+
const query = useFeatureQuery(
|
|
591
|
+
async () => needsIdentity ? (await getIdentity()).tokenType : null,
|
|
592
|
+
[needsIdentity, auth.token]
|
|
593
|
+
);
|
|
594
|
+
return auth.isUserToken || query.data === "user";
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/app/views/MainMenu.tsx
|
|
598
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
599
|
+
var MainMenu = () => {
|
|
600
|
+
const isUserToken = useIsUserToken();
|
|
601
|
+
const canSwitchProject = !hasProjectConfigFile();
|
|
602
|
+
const options = [
|
|
603
|
+
{ label: "Tests", shortcut: "t", description: "Run, list, import tests", to: "/tests" },
|
|
604
|
+
{
|
|
605
|
+
label: "Test runs",
|
|
606
|
+
shortcut: "r",
|
|
607
|
+
description: "Browse recent test runs",
|
|
608
|
+
to: ROUTES.testRuns.root()
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
label: "Suites",
|
|
612
|
+
shortcut: "s",
|
|
613
|
+
description: "Run and list test suites",
|
|
614
|
+
to: "/suites"
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
label: "Suite runs",
|
|
618
|
+
shortcut: "u",
|
|
619
|
+
description: "Browse recent suite runs",
|
|
620
|
+
to: ROUTES.suiteRuns.root()
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
label: "Profiles",
|
|
624
|
+
shortcut: "f",
|
|
625
|
+
description: "Manage profiles",
|
|
626
|
+
to: "/profiles"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
label: "Current project",
|
|
630
|
+
shortcut: "p",
|
|
631
|
+
description: "Import or export current project",
|
|
632
|
+
to: "/project"
|
|
633
|
+
},
|
|
634
|
+
...canSwitchProject ? [
|
|
635
|
+
{
|
|
636
|
+
label: "Switch project",
|
|
637
|
+
shortcut: "w",
|
|
638
|
+
description: "Pick another project for this session",
|
|
639
|
+
to: "/project/switch"
|
|
640
|
+
}
|
|
641
|
+
] : [],
|
|
642
|
+
...isUserToken ? [
|
|
643
|
+
{
|
|
644
|
+
label: "Logout",
|
|
645
|
+
shortcut: "l",
|
|
646
|
+
description: "Clear the current CLI session",
|
|
647
|
+
to: "/logout"
|
|
648
|
+
}
|
|
649
|
+
] : []
|
|
650
|
+
];
|
|
651
|
+
return /* @__PURE__ */ jsx8(ViewLayout, { children: /* @__PURE__ */ jsx8(MenuList, { options }) });
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// src/app/hooks/useProfiles.ts
|
|
655
|
+
var useProfilesList = () => useFeatureQuery(() => listProfiles(), []);
|
|
656
|
+
|
|
657
|
+
// src/features/profiles/profiles.utils.ts
|
|
658
|
+
var profileColumns = [
|
|
659
|
+
{ key: "name", label: "Name", width: 36 },
|
|
660
|
+
{ key: "default", label: "Default", width: 10 }
|
|
661
|
+
];
|
|
662
|
+
var getProfileRow = (profile) => ({
|
|
663
|
+
key: profile.id,
|
|
664
|
+
cells: {
|
|
665
|
+
name: profile.name,
|
|
666
|
+
default: profile.isDefault ? "yes" : "no"
|
|
667
|
+
},
|
|
668
|
+
value: profile
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// src/features/tui/components/BaseResourceListScreen/BaseResourceListScreen.tsx
|
|
672
|
+
import { Spinner, StatusMessage as StatusMessage2 } from "@inkjs/ui";
|
|
673
|
+
import { Box as Box7 } from "ink";
|
|
674
|
+
import { useState as useState6 } from "react";
|
|
675
|
+
|
|
676
|
+
// src/features/tui/components/TableList/TableList.tsx
|
|
677
|
+
import { Box as Box6, Text as Text5 } from "ink";
|
|
678
|
+
|
|
679
|
+
// src/features/tui/components/TableList/TableList.utils.ts
|
|
680
|
+
var fitCell = (value, width) => {
|
|
681
|
+
const text = value === null || value === void 0 || value === "" ? "-" : String(value);
|
|
682
|
+
const visible = text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text;
|
|
683
|
+
return visible.padEnd(width, " ");
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// src/features/tui/components/TableList/TableListRow.tsx
|
|
687
|
+
import { Box as Box5, Text as Text4 } from "ink";
|
|
688
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
689
|
+
var TableListRow = ({
|
|
690
|
+
columns,
|
|
691
|
+
row,
|
|
692
|
+
selected
|
|
693
|
+
}) => /* @__PURE__ */ jsx9(SelectableOption, { selected, children: ({ color, backgroundColor }) => /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
694
|
+
/* @__PURE__ */ jsx9(SelectionPrefix, { selected }),
|
|
695
|
+
columns.map((column, i) => {
|
|
696
|
+
const sep = i === 0 ? "" : " ";
|
|
697
|
+
const cell = row.cells[column.key];
|
|
698
|
+
if (column.render) {
|
|
699
|
+
return /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
700
|
+
sep ? /* @__PURE__ */ jsx9(Text4, { backgroundColor, children: sep }) : null,
|
|
701
|
+
/* @__PURE__ */ jsx9(Box5, { width: column.width, children: column.render(cell, row, {
|
|
702
|
+
selected,
|
|
703
|
+
width: column.width,
|
|
704
|
+
color,
|
|
705
|
+
backgroundColor
|
|
706
|
+
}) })
|
|
707
|
+
] }, column.key);
|
|
708
|
+
}
|
|
709
|
+
return /* @__PURE__ */ jsxs4(Text4, { color, backgroundColor, children: [
|
|
710
|
+
sep,
|
|
711
|
+
fitCell(cell, column.width)
|
|
712
|
+
] }, column.key);
|
|
713
|
+
})
|
|
714
|
+
] }) });
|
|
715
|
+
|
|
716
|
+
// src/features/tui/components/TableList/TableList.tsx
|
|
717
|
+
import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
718
|
+
var TableList = ({
|
|
719
|
+
columns,
|
|
720
|
+
rows,
|
|
721
|
+
navigationDisabled = false,
|
|
722
|
+
onSelect
|
|
723
|
+
}) => /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
724
|
+
/* @__PURE__ */ jsx10(Box6, { children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
725
|
+
" ",
|
|
726
|
+
columns.map((column) => fitCell(column.label, column.width)).join(" ")
|
|
727
|
+
] }) }),
|
|
728
|
+
/* @__PURE__ */ jsx10(
|
|
729
|
+
VirtualizedList,
|
|
730
|
+
{
|
|
731
|
+
items: rows,
|
|
732
|
+
reservedTerminalRows: DEFAULT_RESERVED_TERMINAL_ROWS + 1,
|
|
733
|
+
navigationDisabled,
|
|
734
|
+
getKey: (row) => row.key,
|
|
735
|
+
renderItem: (row, _rowIndex, selected) => /* @__PURE__ */ jsx10(TableListRow, { columns, row, selected: !navigationDisabled && selected }),
|
|
736
|
+
onSelect: (row) => onSelect?.(row)
|
|
737
|
+
}
|
|
738
|
+
)
|
|
739
|
+
] });
|
|
740
|
+
|
|
741
|
+
// src/features/tui/components/BaseResourceListScreen/BaseResourceListScreen.tsx
|
|
742
|
+
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
743
|
+
var BaseResourceListScreen = ({
|
|
744
|
+
title,
|
|
745
|
+
loadingLabel,
|
|
746
|
+
emptyMessage,
|
|
747
|
+
resources,
|
|
748
|
+
isLoading,
|
|
749
|
+
isError,
|
|
750
|
+
error,
|
|
751
|
+
columns,
|
|
752
|
+
getRow,
|
|
753
|
+
searchable,
|
|
754
|
+
query = "",
|
|
755
|
+
onSearch,
|
|
756
|
+
onSelectResource
|
|
757
|
+
}) => {
|
|
758
|
+
const rows = resources ?? [];
|
|
759
|
+
const [searchMode, setSearchMode] = useState6(false);
|
|
760
|
+
const handleSelect = (row) => {
|
|
761
|
+
if (row.value !== void 0 && onSelectResource) onSelectResource(row.value);
|
|
762
|
+
};
|
|
763
|
+
return /* @__PURE__ */ jsxs6(
|
|
764
|
+
ViewLayout,
|
|
765
|
+
{
|
|
766
|
+
title: query ? `Search: "${query}"` : title,
|
|
767
|
+
searchable,
|
|
768
|
+
onSearch,
|
|
769
|
+
onSearchModeChange: setSearchMode,
|
|
770
|
+
children: [
|
|
771
|
+
isLoading && /* @__PURE__ */ jsx11(Box7, { children: /* @__PURE__ */ jsx11(Spinner, { label: loadingLabel }) }),
|
|
772
|
+
isError && /* @__PURE__ */ jsx11(StatusMessage2, { variant: "error", children: error }),
|
|
773
|
+
!isLoading && !isError && rows.length === 0 && !query && /* @__PURE__ */ jsx11(StatusMessage2, { variant: "warning", children: emptyMessage }),
|
|
774
|
+
!isLoading && !isError && (rows.length > 0 || query) && /* @__PURE__ */ jsx11(
|
|
775
|
+
TableList,
|
|
776
|
+
{
|
|
777
|
+
columns,
|
|
778
|
+
rows: rows.map(getRow),
|
|
779
|
+
navigationDisabled: searchMode,
|
|
780
|
+
onSelect: handleSelect
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
]
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/app/views/profiles/ProfilesList.tsx
|
|
789
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
790
|
+
var ProfilesList = () => {
|
|
791
|
+
const request = useProfilesList();
|
|
792
|
+
return /* @__PURE__ */ jsx12(
|
|
793
|
+
BaseResourceListScreen,
|
|
794
|
+
{
|
|
795
|
+
title: "Profiles",
|
|
796
|
+
loadingLabel: "Loading profiles...",
|
|
797
|
+
emptyMessage: "No profiles found",
|
|
798
|
+
resources: request.data,
|
|
799
|
+
isLoading: request.isLoading,
|
|
800
|
+
isError: request.isError,
|
|
801
|
+
error: request.error,
|
|
802
|
+
columns: profileColumns,
|
|
803
|
+
getRow: getProfileRow
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// src/app/views/profiles/ProfilesMenu.tsx
|
|
809
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
810
|
+
import { useEffect as useEffect5 } from "react";
|
|
811
|
+
import { useNavigate as useNavigate3 } from "react-router";
|
|
812
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
813
|
+
var ProfilesMenu = () => {
|
|
814
|
+
const navigate = useNavigate3();
|
|
815
|
+
useEffect5(() => {
|
|
816
|
+
navigate("/profiles/list", { replace: true });
|
|
817
|
+
}, [navigate]);
|
|
818
|
+
return /* @__PURE__ */ jsx13(Spinner2, { label: "Loading..." });
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// src/app/views/project/ProjectExport.tsx
|
|
822
|
+
import { Spinner as Spinner3, StatusMessage as StatusMessage3 } from "@inkjs/ui";
|
|
823
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
824
|
+
|
|
825
|
+
// src/app/hooks/useFeatureMutation.ts
|
|
826
|
+
import { useCallback as useCallback3, useRef, useState as useState7 } from "react";
|
|
827
|
+
var useFeatureMutation = (fn) => {
|
|
828
|
+
const fnRef = useRef(fn);
|
|
829
|
+
const activeMutationRef = useRef(0);
|
|
830
|
+
const [state, setState] = useState7({
|
|
831
|
+
data: void 0,
|
|
832
|
+
error: void 0,
|
|
833
|
+
status: "idle"
|
|
834
|
+
});
|
|
835
|
+
fnRef.current = fn;
|
|
836
|
+
const trigger = useCallback3(async (arg) => {
|
|
837
|
+
const mutationId = activeMutationRef.current + 1;
|
|
838
|
+
activeMutationRef.current = mutationId;
|
|
839
|
+
setState({
|
|
840
|
+
data: void 0,
|
|
841
|
+
error: void 0,
|
|
842
|
+
status: "pending"
|
|
843
|
+
});
|
|
844
|
+
try {
|
|
845
|
+
const data = await fnRef.current(arg);
|
|
846
|
+
if (activeMutationRef.current === mutationId) {
|
|
847
|
+
setState({ data, error: void 0, status: "fulfilled" });
|
|
848
|
+
}
|
|
849
|
+
return { data };
|
|
850
|
+
} catch (err) {
|
|
851
|
+
const error = formatErrorMessage(err);
|
|
852
|
+
if (activeMutationRef.current === mutationId) {
|
|
853
|
+
setState({ data: void 0, error, status: "rejected" });
|
|
854
|
+
}
|
|
855
|
+
return { error };
|
|
856
|
+
}
|
|
857
|
+
}, []);
|
|
858
|
+
return [
|
|
859
|
+
trigger,
|
|
860
|
+
{
|
|
861
|
+
data: state.data,
|
|
862
|
+
error: state.error,
|
|
863
|
+
isLoading: state.status === "pending",
|
|
864
|
+
isSuccess: state.status === "fulfilled",
|
|
865
|
+
isError: state.status === "rejected"
|
|
866
|
+
}
|
|
867
|
+
];
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// src/features/tui/components/TextInput/TextInput.tsx
|
|
871
|
+
import { Box as Box8, Text as Text6 } from "ink";
|
|
872
|
+
import InkTextInput from "ink-text-input";
|
|
873
|
+
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
874
|
+
var TextInput = (props) => {
|
|
875
|
+
useShortcutBlocker();
|
|
876
|
+
return /* @__PURE__ */ jsxs7(Box8, { children: [
|
|
877
|
+
/* @__PURE__ */ jsx14(Text6, { color: "cyan", children: "\u203A " }),
|
|
878
|
+
/* @__PURE__ */ jsx14(InkTextInput, { ...props })
|
|
879
|
+
] });
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/app/views/project/ProjectExport.tsx
|
|
883
|
+
import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
884
|
+
var ProjectExport = () => {
|
|
885
|
+
const [outputValue, setOutputValue] = useState8("");
|
|
886
|
+
const [output, setOutput] = useState8(null);
|
|
887
|
+
const [exportProjectMutation, exportProjectResult] = useFeatureMutation(
|
|
888
|
+
(outputPath) => exportProject(outputPath || void 0)
|
|
889
|
+
);
|
|
890
|
+
useEffect6(() => {
|
|
891
|
+
if (output !== null) void exportProjectMutation(output);
|
|
892
|
+
}, [exportProjectMutation, output]);
|
|
893
|
+
if (output === null) {
|
|
894
|
+
return /* @__PURE__ */ jsx15(ViewLayout, { title: "Output file (blank = auto)", hints: "confirm", hintsPosition: "bottom", children: /* @__PURE__ */ jsx15(TextInput, { value: outputValue, onChange: setOutputValue, onSubmit: setOutput }) });
|
|
895
|
+
}
|
|
896
|
+
return /* @__PURE__ */ jsxs8(ViewLayout, { hintsHidden: true, children: [
|
|
897
|
+
exportProjectResult.isLoading && /* @__PURE__ */ jsx15(Spinner3, { label: "Exporting project..." }),
|
|
898
|
+
exportProjectResult.isError && /* @__PURE__ */ jsx15(StatusMessage3, { variant: "error", children: exportProjectResult.error }),
|
|
899
|
+
exportProjectResult.data && /* @__PURE__ */ jsx15(StatusMessage3, { variant: "success", children: `Exported project to ${exportProjectResult.data.outPath} (${exportProjectResult.data.size} bytes)` })
|
|
900
|
+
] });
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/app/views/project/ProjectImport.tsx
|
|
904
|
+
import { Spinner as Spinner4, StatusMessage as StatusMessage4 } from "@inkjs/ui";
|
|
905
|
+
import { useEffect as useEffect7, useState as useState10 } from "react";
|
|
906
|
+
import { useNavigate as useNavigate4 } from "react-router";
|
|
907
|
+
|
|
908
|
+
// src/features/tui/components/FilePicker/FilePicker.tsx
|
|
909
|
+
import { readdirSync } from "fs";
|
|
910
|
+
import { dirname, join, resolve } from "path";
|
|
911
|
+
import { Box as Box9, Text as Text7 } from "ink";
|
|
912
|
+
import { useMemo, useState as useState9 } from "react";
|
|
913
|
+
|
|
914
|
+
// src/utils/filePicker.ts
|
|
915
|
+
import { extname } from "path";
|
|
916
|
+
var toFilePickerItems = (entries, allowedExtensions) => {
|
|
917
|
+
const normalizedExtensions = new Set(
|
|
918
|
+
allowedExtensions.map((extension) => extension.toLowerCase())
|
|
919
|
+
);
|
|
920
|
+
return entries.filter((entry) => {
|
|
921
|
+
if (entry.isDirectory()) return true;
|
|
922
|
+
return normalizedExtensions.has(extname(entry.name).toLowerCase());
|
|
923
|
+
}).map((entry) => {
|
|
924
|
+
const type = entry.isDirectory() ? "directory" : "file";
|
|
925
|
+
return {
|
|
926
|
+
key: `${type === "directory" ? "dir" : "file"}:${entry.name}`,
|
|
927
|
+
label: type === "directory" ? `${entry.name}/` : entry.name,
|
|
928
|
+
name: entry.name,
|
|
929
|
+
type
|
|
930
|
+
};
|
|
931
|
+
}).sort((left, right) => {
|
|
932
|
+
if (left.type !== right.type) return left.type === "directory" ? -1 : 1;
|
|
933
|
+
return left.name.localeCompare(right.name);
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/features/tui/components/FilePicker/FilePicker.tsx
|
|
938
|
+
import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
939
|
+
var FilePicker = ({
|
|
940
|
+
label,
|
|
941
|
+
extensions,
|
|
942
|
+
initialDir = userCwd(),
|
|
943
|
+
onSubmit,
|
|
944
|
+
onBack
|
|
945
|
+
}) => {
|
|
946
|
+
const [currentDir, setCurrentDir] = useState9(() => resolve(userCwd(), initialDir));
|
|
947
|
+
useEscHandler(() => {
|
|
948
|
+
onBack();
|
|
949
|
+
return true;
|
|
950
|
+
});
|
|
951
|
+
const { items, error } = useMemo(() => {
|
|
952
|
+
try {
|
|
953
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
954
|
+
const pickerItems = toFilePickerItems(entries, extensions);
|
|
955
|
+
const parent = dirname(currentDir);
|
|
956
|
+
const allItems = parent === currentDir ? pickerItems : [
|
|
957
|
+
{
|
|
958
|
+
key: "dir:..",
|
|
959
|
+
label: "../",
|
|
960
|
+
name: "..",
|
|
961
|
+
type: "directory"
|
|
962
|
+
},
|
|
963
|
+
...pickerItems
|
|
964
|
+
];
|
|
965
|
+
return { items: allItems, error: null };
|
|
966
|
+
} catch (err) {
|
|
967
|
+
return {
|
|
968
|
+
items: [],
|
|
969
|
+
error: err instanceof Error ? err.message : String(err)
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
}, [currentDir, extensions]);
|
|
973
|
+
const handleSelect = (item) => {
|
|
974
|
+
const selected = item.value;
|
|
975
|
+
if (!selected) return;
|
|
976
|
+
if (selected.type === "directory") {
|
|
977
|
+
setCurrentDir((dir) => resolve(dir, selected.name));
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
onSubmit(join(currentDir, selected.name));
|
|
981
|
+
};
|
|
982
|
+
return /* @__PURE__ */ jsx16(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(ViewLayout, { title: `${label}: ${currentDir}`, hints: "navigationOpenSelect", children: [
|
|
983
|
+
/* @__PURE__ */ jsx16(
|
|
984
|
+
MenuList,
|
|
985
|
+
{
|
|
986
|
+
options: items.map((item) => ({ label: item.label, value: item })),
|
|
987
|
+
onSelect: handleSelect
|
|
988
|
+
},
|
|
989
|
+
currentDir
|
|
990
|
+
),
|
|
991
|
+
items.length === 0 && /* @__PURE__ */ jsx16(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text7, { dimColor: true, children: "No matching files in this directory." }) }),
|
|
992
|
+
error && /* @__PURE__ */ jsx16(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text7, { color: "red", children: error }) })
|
|
993
|
+
] }) });
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/app/views/project/ProjectImport.tsx
|
|
997
|
+
import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
998
|
+
var ProjectImport = () => {
|
|
999
|
+
const navigate = useNavigate4();
|
|
1000
|
+
const [file, setFile] = useState10(null);
|
|
1001
|
+
const [importProjectMutation, importProjectResult] = useFeatureMutation(importProject);
|
|
1002
|
+
useEffect7(() => {
|
|
1003
|
+
if (file) importProjectMutation(file);
|
|
1004
|
+
}, [file, importProjectMutation]);
|
|
1005
|
+
if (!file) {
|
|
1006
|
+
return /* @__PURE__ */ jsx17(
|
|
1007
|
+
FilePicker,
|
|
1008
|
+
{
|
|
1009
|
+
label: "Select .zip file",
|
|
1010
|
+
extensions: [".zip"],
|
|
1011
|
+
onSubmit: setFile,
|
|
1012
|
+
onBack: () => navigate(-1)
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return /* @__PURE__ */ jsxs10(ViewLayout, { hintsHidden: true, children: [
|
|
1017
|
+
importProjectResult.isLoading && /* @__PURE__ */ jsx17(Spinner4, { label: `Importing project from ${file}...` }),
|
|
1018
|
+
importProjectResult.isError && /* @__PURE__ */ jsx17(StatusMessage4, { variant: "error", children: importProjectResult.error }),
|
|
1019
|
+
importProjectResult.data && /* @__PURE__ */ jsx17(StatusMessage4, { variant: "success", children: `Project import started from ${importProjectResult.data.path} (${importProjectResult.data.size} bytes)` })
|
|
1020
|
+
] });
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
// src/app/views/project/ProjectInit.tsx
|
|
1024
|
+
import { useApp as useApp3 } from "ink";
|
|
1025
|
+
import { useEffect as useEffect8, useState as useState12 } from "react";
|
|
1026
|
+
import { useLocation as useLocation4, useNavigate as useNavigate5 } from "react-router";
|
|
1027
|
+
|
|
1028
|
+
// src/app/components/ProjectPicker/ProjectPicker.tsx
|
|
1029
|
+
import { Spinner as Spinner5, StatusMessage as StatusMessage5 } from "@inkjs/ui";
|
|
1030
|
+
import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1031
|
+
var ProjectPicker = ({ onSelect, currentProjectId }) => {
|
|
1032
|
+
const projectsQuery = useFeatureQuery(listProjects, []);
|
|
1033
|
+
const projects = projectsQuery.data ?? [];
|
|
1034
|
+
return /* @__PURE__ */ jsxs11(ViewLayout, { title: "Choose BugBug project", hints: "navigationSelect", children: [
|
|
1035
|
+
projectsQuery.isLoading && /* @__PURE__ */ jsx18(Spinner5, { label: "Loading projects..." }),
|
|
1036
|
+
projectsQuery.isError && /* @__PURE__ */ jsx18(StatusMessage5, { variant: "error", children: `Error: ${projectsQuery.error ?? "Unknown error"}` }),
|
|
1037
|
+
!projectsQuery.isLoading && !projectsQuery.isError && projects.length === 0 && /* @__PURE__ */ jsx18(StatusMessage5, { variant: "warning", children: "No projects found" }),
|
|
1038
|
+
!projectsQuery.isLoading && !projectsQuery.isError && projects.length > 0 && /* @__PURE__ */ jsx18(
|
|
1039
|
+
MenuList,
|
|
1040
|
+
{
|
|
1041
|
+
options: projects.map((project) => ({
|
|
1042
|
+
key: project.id,
|
|
1043
|
+
label: project.id === currentProjectId ? ` ${project.name} (current)` : ` ${project.name}`,
|
|
1044
|
+
value: project
|
|
1045
|
+
})),
|
|
1046
|
+
onSelect: (item) => {
|
|
1047
|
+
if (item.value) onSelect(item.value);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
)
|
|
1051
|
+
] });
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// src/app/views/project/ProjectInitAuth.tsx
|
|
1055
|
+
import { StatusMessage as StatusMessage6 } from "@inkjs/ui";
|
|
1056
|
+
import { useState as useState11 } from "react";
|
|
1057
|
+
import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1058
|
+
var ProjectInitAuth = ({
|
|
1059
|
+
onTokenReceive,
|
|
1060
|
+
onTokenError,
|
|
1061
|
+
message
|
|
1062
|
+
}) => {
|
|
1063
|
+
const [method, setMethod] = useState11("choose");
|
|
1064
|
+
const [tokenInput, setTokenInput] = useState11("");
|
|
1065
|
+
const handleLogin = () => {
|
|
1066
|
+
void (async () => {
|
|
1067
|
+
try {
|
|
1068
|
+
const { token } = await login();
|
|
1069
|
+
updateSdkConfig({ apiToken: token });
|
|
1070
|
+
onTokenReceive(token, { isUserToken: true });
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
onTokenError(err instanceof Error ? err.message : String(err));
|
|
1073
|
+
}
|
|
1074
|
+
})();
|
|
1075
|
+
};
|
|
1076
|
+
const handleTokenSubmit = (value) => {
|
|
1077
|
+
const trimmed = value.trim();
|
|
1078
|
+
if (!trimmed) {
|
|
1079
|
+
onTokenError("Token must not be empty.");
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
try {
|
|
1083
|
+
updateSdkConfig({ apiToken: trimmed });
|
|
1084
|
+
onTokenReceive(trimmed, { isUserToken: false });
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
onTokenError(err instanceof Error ? err.message : String(err));
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
if (method === "token") {
|
|
1090
|
+
return /* @__PURE__ */ jsx19(EscProvider, { onUnhandled: () => setMethod("choose"), children: /* @__PURE__ */ jsxs12(ViewLayout, { title: "BugBug project API token", hints: "confirm", hintsPosition: "bottom", children: [
|
|
1091
|
+
/* @__PURE__ */ jsx19(
|
|
1092
|
+
TextInput,
|
|
1093
|
+
{
|
|
1094
|
+
value: tokenInput,
|
|
1095
|
+
onChange: setTokenInput,
|
|
1096
|
+
onSubmit: handleTokenSubmit,
|
|
1097
|
+
mask: "*"
|
|
1098
|
+
}
|
|
1099
|
+
),
|
|
1100
|
+
message && /* @__PURE__ */ jsx19(StatusMessage6, { variant: "error", children: `Error: ${message}` })
|
|
1101
|
+
] }) });
|
|
1102
|
+
}
|
|
1103
|
+
return /* @__PURE__ */ jsxs12(ViewLayout, { title: "How do you want to authenticate?", hints: "navigationSelect", children: [
|
|
1104
|
+
/* @__PURE__ */ jsx19(
|
|
1105
|
+
MenuList,
|
|
1106
|
+
{
|
|
1107
|
+
options: [
|
|
1108
|
+
{
|
|
1109
|
+
label: "Login using BugBug",
|
|
1110
|
+
description: "Authenticate via BugBug and use your user token",
|
|
1111
|
+
value: "login"
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
label: "Set API Token",
|
|
1115
|
+
description: "Paste a project or organization API token",
|
|
1116
|
+
value: "token"
|
|
1117
|
+
}
|
|
1118
|
+
],
|
|
1119
|
+
onSelect: (item) => {
|
|
1120
|
+
if (item.value === "login") handleLogin();
|
|
1121
|
+
else setMethod("token");
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
),
|
|
1125
|
+
message && /* @__PURE__ */ jsx19(StatusMessage6, { variant: "error", children: `Error: ${message}` })
|
|
1126
|
+
] });
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
// src/app/views/project/ProjectInit.tsx
|
|
1130
|
+
import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1131
|
+
var ProjectInit = () => {
|
|
1132
|
+
const { exit } = useApp3();
|
|
1133
|
+
const navigate = useNavigate5();
|
|
1134
|
+
const location = useLocation4();
|
|
1135
|
+
const state = location.state;
|
|
1136
|
+
const redirectUrl = state?.redirectUrl ?? "/";
|
|
1137
|
+
const onlyThisSession = state?.onlyThisSession ?? false;
|
|
1138
|
+
const ignoreConfiguredAuth = state?.ignoreConfiguredAuth ?? false;
|
|
1139
|
+
const { auth, config, authError, completeSession } = useNavigatorSession();
|
|
1140
|
+
const [token, setToken] = useState12(
|
|
1141
|
+
ignoreConfiguredAuth ? null : auth.token ?? null
|
|
1142
|
+
);
|
|
1143
|
+
const [usingUserToken, setUsingUserToken] = useState12(false);
|
|
1144
|
+
const [error, setError] = useState12(null);
|
|
1145
|
+
useEffect8(() => {
|
|
1146
|
+
if (auth.kind !== "ready" || ignoreConfiguredAuth) return;
|
|
1147
|
+
if (onlyThisSession) {
|
|
1148
|
+
navigate(redirectUrl, { replace: true });
|
|
1149
|
+
}
|
|
1150
|
+
}, [auth.kind, ignoreConfiguredAuth, navigate, redirectUrl, onlyThisSession, exit]);
|
|
1151
|
+
const handleTokenReceive = (newToken, { isUserToken }) => {
|
|
1152
|
+
setError(null);
|
|
1153
|
+
setUsingUserToken(isUserToken);
|
|
1154
|
+
setToken(newToken);
|
|
1155
|
+
};
|
|
1156
|
+
const handleTokenError = (errorMessage) => {
|
|
1157
|
+
setError(errorMessage);
|
|
1158
|
+
};
|
|
1159
|
+
const handleProjectSelectAndFinish = (project) => {
|
|
1160
|
+
if (!token) return;
|
|
1161
|
+
if (usingUserToken) switchProject(project.id, { currentProjectId: config.projectId });
|
|
1162
|
+
const completeProjectSession = () => {
|
|
1163
|
+
if (usingUserToken) completeSession(token, project.id, { isUserToken: true });
|
|
1164
|
+
else completeSession(token, project.id);
|
|
1165
|
+
};
|
|
1166
|
+
if (onlyThisSession) {
|
|
1167
|
+
completeProjectSession();
|
|
1168
|
+
navigate(redirectUrl, { replace: true });
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
(async () => {
|
|
1172
|
+
try {
|
|
1173
|
+
await initProject(token, project.id);
|
|
1174
|
+
completeProjectSession();
|
|
1175
|
+
exit();
|
|
1176
|
+
} catch (err) {
|
|
1177
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1178
|
+
}
|
|
1179
|
+
})();
|
|
1180
|
+
};
|
|
1181
|
+
const message = error ?? authError;
|
|
1182
|
+
return /* @__PURE__ */ jsxs13(EscProvider, { onUnhandled: exit, children: [
|
|
1183
|
+
!token && /* @__PURE__ */ jsx20(
|
|
1184
|
+
ProjectInitAuth,
|
|
1185
|
+
{
|
|
1186
|
+
onTokenReceive: handleTokenReceive,
|
|
1187
|
+
onTokenError: handleTokenError,
|
|
1188
|
+
message
|
|
1189
|
+
}
|
|
1190
|
+
),
|
|
1191
|
+
token && /* @__PURE__ */ jsx20(ProjectPicker, { onSelect: handleProjectSelectAndFinish, currentProjectId: auth.projectId })
|
|
1192
|
+
] });
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
// src/app/views/project/ProjectMenu.tsx
|
|
1196
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
1197
|
+
var ProjectMenu = () => {
|
|
1198
|
+
const canSwitchProject = !hasProjectConfigFile();
|
|
1199
|
+
return /* @__PURE__ */ jsx21(ViewLayout, { children: /* @__PURE__ */ jsx21(
|
|
1200
|
+
MenuList,
|
|
1201
|
+
{
|
|
1202
|
+
options: [
|
|
1203
|
+
{ label: "Export project (ZIP)", shortcut: "e", to: "/project/export" },
|
|
1204
|
+
...canSwitchProject ? [{ label: "Import project (ZIP)", shortcut: "i", to: "/project/import" }] : []
|
|
1205
|
+
]
|
|
1206
|
+
}
|
|
1207
|
+
) });
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
// src/app/views/project/ProjectSwitch.tsx
|
|
1211
|
+
import { useNavigate as useNavigate6 } from "react-router";
|
|
1212
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
1213
|
+
var ProjectSwitch = () => {
|
|
1214
|
+
const navigate = useNavigate6();
|
|
1215
|
+
const { config, setSessionProject } = useNavigatorSession();
|
|
1216
|
+
const handleSelect = (project) => {
|
|
1217
|
+
switchProject(project.id, { currentProjectId: config.projectId });
|
|
1218
|
+
setSessionProject(project.id);
|
|
1219
|
+
navigate("/", { replace: true });
|
|
1220
|
+
};
|
|
1221
|
+
return /* @__PURE__ */ jsx22(ProjectPicker, { onSelect: handleSelect, currentProjectId: config.projectId });
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// src/app/views/settings/SettingsMenu.tsx
|
|
1225
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1226
|
+
var SettingsMenu = () => {
|
|
1227
|
+
const isUserToken = useIsUserToken();
|
|
1228
|
+
const options = [
|
|
1229
|
+
{
|
|
1230
|
+
label: `Set current session token`,
|
|
1231
|
+
shortcut: "t",
|
|
1232
|
+
to: "/settings/setSessionToken"
|
|
1233
|
+
},
|
|
1234
|
+
...isUserToken ? [
|
|
1235
|
+
{
|
|
1236
|
+
label: "Logout",
|
|
1237
|
+
to: "/logout"
|
|
1238
|
+
}
|
|
1239
|
+
] : []
|
|
1240
|
+
];
|
|
1241
|
+
return /* @__PURE__ */ jsx23(ViewLayout, { children: /* @__PURE__ */ jsx23(MenuList, { options }) });
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// src/app/views/settings/SettingsSetSessionToken.tsx
|
|
1245
|
+
import { StatusMessage as StatusMessage7 } from "@inkjs/ui";
|
|
1246
|
+
import { useState as useState13 } from "react";
|
|
1247
|
+
import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1248
|
+
var SettingsSetSessionToken = () => {
|
|
1249
|
+
const { auth, completeSession } = useNavigatorSession();
|
|
1250
|
+
const [tokenValue, setTokenValue] = useState13("");
|
|
1251
|
+
const [token, setToken] = useState13(null);
|
|
1252
|
+
const [error, setError] = useState13(null);
|
|
1253
|
+
const [saved, setSaved] = useState13(false);
|
|
1254
|
+
const handleTokenSubmit = (value) => {
|
|
1255
|
+
const trimmed = value.trim();
|
|
1256
|
+
if (!trimmed) {
|
|
1257
|
+
setError("Token must not be empty.");
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
try {
|
|
1261
|
+
updateSdkConfig({ apiToken: trimmed });
|
|
1262
|
+
setError(null);
|
|
1263
|
+
setSaved(false);
|
|
1264
|
+
setToken(trimmed);
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const handleProjectSelect = (project) => {
|
|
1270
|
+
if (!token) return;
|
|
1271
|
+
completeSession(token, project.id);
|
|
1272
|
+
setSaved(true);
|
|
1273
|
+
};
|
|
1274
|
+
if (token === null) {
|
|
1275
|
+
return /* @__PURE__ */ jsxs14(ViewLayout, { title: "Project API token", hints: "confirm", hintsPosition: "bottom", children: [
|
|
1276
|
+
/* @__PURE__ */ jsx24(
|
|
1277
|
+
TextInput,
|
|
1278
|
+
{
|
|
1279
|
+
value: tokenValue,
|
|
1280
|
+
onChange: setTokenValue,
|
|
1281
|
+
onSubmit: handleTokenSubmit,
|
|
1282
|
+
mask: "*"
|
|
1283
|
+
}
|
|
1284
|
+
),
|
|
1285
|
+
error && /* @__PURE__ */ jsx24(StatusMessage7, { variant: "error", children: `Error: ${error}` })
|
|
1286
|
+
] });
|
|
1287
|
+
}
|
|
1288
|
+
if (!saved) {
|
|
1289
|
+
return /* @__PURE__ */ jsx24(ProjectPicker, { onSelect: handleProjectSelect, currentProjectId: auth.projectId });
|
|
1290
|
+
}
|
|
1291
|
+
return /* @__PURE__ */ jsx24(ViewLayout, { hintsHidden: true, children: /* @__PURE__ */ jsx24(StatusMessage7, { variant: "success", children: "Token saved and valid only for this session" }) });
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// src/app/views/suiteRuns/SuiteRunDetails.tsx
|
|
1295
|
+
import { Spinner as Spinner6, StatusMessage as StatusMessage8 } from "@inkjs/ui";
|
|
1296
|
+
import { Text as Text8 } from "ink";
|
|
1297
|
+
import { useLocation as useLocation5, useParams } from "react-router";
|
|
1298
|
+
|
|
1299
|
+
// src/app/hooks/useSuiteRuns.ts
|
|
1300
|
+
var useSuiteRunsList = () => useFeatureQuery(() => listSuiteRuns(), []);
|
|
1301
|
+
var useSuiteRunDetails = (runId) => useFeatureQuery(async () => {
|
|
1302
|
+
if (!runId) return void 0;
|
|
1303
|
+
return getSuiteRunDetails(runId);
|
|
1304
|
+
}, [runId]);
|
|
1305
|
+
|
|
1306
|
+
// src/features/suiteRuns/suiteRuns.utils.ts
|
|
1307
|
+
var getSuiteRunName = (run) => run.name || run.id;
|
|
1308
|
+
|
|
1309
|
+
// src/app/views/suiteRuns/SuiteRunDetails.tsx
|
|
1310
|
+
import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1311
|
+
var SuiteRunDetails = () => {
|
|
1312
|
+
const { runId } = useParams();
|
|
1313
|
+
const location = useLocation5();
|
|
1314
|
+
const state = location.state;
|
|
1315
|
+
const request = useSuiteRunDetails(runId);
|
|
1316
|
+
const title = request.data ? getSuiteRunName(request.data) : state?.suiteRunName;
|
|
1317
|
+
const menuState = title ? { suiteRunName: title } : void 0;
|
|
1318
|
+
if (!request.data || !runId) {
|
|
1319
|
+
return /* @__PURE__ */ jsxs15(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1320
|
+
request.isLoading && /* @__PURE__ */ jsx25(Spinner6, { label: "Loading suite run..." }),
|
|
1321
|
+
request.isError && /* @__PURE__ */ jsx25(StatusMessage8, { variant: "error", children: request.error }),
|
|
1322
|
+
!request.isLoading && !request.isError && !request.data && /* @__PURE__ */ jsx25(Text8, { color: "yellow", children: "Suite run not found." })
|
|
1323
|
+
] });
|
|
1324
|
+
}
|
|
1325
|
+
return /* @__PURE__ */ jsx25(BaseSuiteRunDetails, { runId, title, menuState });
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// src/app/views/suiteRuns/SuiteRunOpen.tsx
|
|
1329
|
+
import { Spinner as Spinner7, StatusMessage as StatusMessage9 } from "@inkjs/ui";
|
|
1330
|
+
import { Box as Box10, Text as Text9 } from "ink";
|
|
1331
|
+
import { useEffect as useEffect9 } from "react";
|
|
1332
|
+
import { useLocation as useLocation6, useParams as useParams2 } from "react-router";
|
|
1333
|
+
import { Fragment as Fragment2, jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1334
|
+
var WebUrlUnavailableMessage = () => /* @__PURE__ */ jsx26(StatusMessage9, { variant: "warning", children: "No web URL available for this run" });
|
|
1335
|
+
var OpenSuiteRun = ({ url }) => {
|
|
1336
|
+
const [openSuiteRun, openSuiteRunResult] = useFeatureMutation(async (url2) => {
|
|
1337
|
+
await openExternalUrl(url2);
|
|
1338
|
+
return url2;
|
|
1339
|
+
});
|
|
1340
|
+
useEffect9(() => {
|
|
1341
|
+
if (url) void openSuiteRun(url);
|
|
1342
|
+
}, [openSuiteRun, url]);
|
|
1343
|
+
if (!url) return /* @__PURE__ */ jsx26(WebUrlUnavailableMessage, {});
|
|
1344
|
+
return /* @__PURE__ */ jsxs16(Fragment2, { children: [
|
|
1345
|
+
openSuiteRunResult.isLoading && /* @__PURE__ */ jsx26(Spinner7, { label: "Opening BugBug..." }),
|
|
1346
|
+
openSuiteRunResult.isError && /* @__PURE__ */ jsx26(StatusMessage9, { variant: "error", children: openSuiteRunResult.error }),
|
|
1347
|
+
openSuiteRunResult.data && /* @__PURE__ */ jsxs16(Box10, { flexDirection: "column", children: [
|
|
1348
|
+
/* @__PURE__ */ jsx26(StatusMessage9, { variant: "success", children: "Opened in BugBug" }),
|
|
1349
|
+
/* @__PURE__ */ jsx26(Text9, { dimColor: true, children: openSuiteRunResult.data })
|
|
1350
|
+
] })
|
|
1351
|
+
] });
|
|
1352
|
+
};
|
|
1353
|
+
var SuiteRunOpen = () => {
|
|
1354
|
+
const { runId } = useParams2();
|
|
1355
|
+
const location = useLocation6();
|
|
1356
|
+
const state = location.state;
|
|
1357
|
+
const request = useSuiteRunDetails(runId);
|
|
1358
|
+
const title = request.data ? getSuiteRunName(request.data) : state?.suiteRunName ?? runId;
|
|
1359
|
+
return /* @__PURE__ */ jsxs16(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1360
|
+
request.isLoading && /* @__PURE__ */ jsx26(Spinner7, { label: "Loading..." }),
|
|
1361
|
+
request.isError && /* @__PURE__ */ jsx26(Text9, { color: "red", children: request.error }),
|
|
1362
|
+
!request.isLoading && !request.isError && /* @__PURE__ */ jsx26(OpenSuiteRun, { url: request.data?.webappUrl })
|
|
1363
|
+
] });
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
// src/app/views/suiteRuns/SuiteRunShowTestRuns.tsx
|
|
1367
|
+
import { Spinner as Spinner8 } from "@inkjs/ui";
|
|
1368
|
+
import { StatusMessage as StatusMessage10 } from "@inkjs/ui";
|
|
1369
|
+
import { useLocation as useLocation7, useParams as useParams3 } from "react-router";
|
|
1370
|
+
|
|
1371
|
+
// src/features/testRuns/components/TestRunsList.tsx
|
|
1372
|
+
import { useNavigate as useNavigate7 } from "react-router";
|
|
1373
|
+
|
|
1374
|
+
// src/features/testRuns/testRuns.utils.ts
|
|
1375
|
+
var getTestRunName = (run) => run.name || run.id;
|
|
1376
|
+
var DEBUG_ARTIFACT_LABELS = {
|
|
1377
|
+
logs_partial: "Download partial test run logs",
|
|
1378
|
+
logs: "Download test run logs",
|
|
1379
|
+
cloud_logs: "Download cloud logs",
|
|
1380
|
+
container_stats: "Download container stats",
|
|
1381
|
+
chrome_debug_logs: "Download Chrome debug logs",
|
|
1382
|
+
network_logs: "Download network logs (HAR)",
|
|
1383
|
+
dom_snapshot: "Show DOM snapshot"
|
|
1384
|
+
};
|
|
1385
|
+
var getDebugArtifactLabel = (artifact) => DEBUG_ARTIFACT_LABELS[artifact.kind];
|
|
1386
|
+
|
|
1387
|
+
// src/features/testRuns/components/TestRunsList.constants.tsx
|
|
1388
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
1389
|
+
var TEST_RUN_LIST_COLUMNS = [
|
|
1390
|
+
{ key: "name", label: "Name", width: 30 },
|
|
1391
|
+
{ key: "duration", label: "Duration", width: 8 },
|
|
1392
|
+
{ key: "profileName", label: "Profile", width: 8 },
|
|
1393
|
+
{ key: "triggeredBy", label: "Run by", width: 8 },
|
|
1394
|
+
{ key: "errorCode", label: "Error", width: 20 },
|
|
1395
|
+
{
|
|
1396
|
+
key: "status",
|
|
1397
|
+
label: "Status",
|
|
1398
|
+
width: 15,
|
|
1399
|
+
render: (_cell, row, ctx) => /* @__PURE__ */ jsx27(
|
|
1400
|
+
Status,
|
|
1401
|
+
{
|
|
1402
|
+
status: row.value?.status,
|
|
1403
|
+
autoRetried: row.value?.isAutoRetried,
|
|
1404
|
+
color: ctx.color,
|
|
1405
|
+
backgroundColor: ctx.backgroundColor
|
|
1406
|
+
}
|
|
1407
|
+
)
|
|
1408
|
+
}
|
|
1409
|
+
];
|
|
1410
|
+
|
|
1411
|
+
// src/features/testRuns/components/TestRunsList.utils.tsx
|
|
1412
|
+
var getTestRunRow = (run) => {
|
|
1413
|
+
return {
|
|
1414
|
+
key: run.id,
|
|
1415
|
+
cells: {
|
|
1416
|
+
name: getTestRunName(run),
|
|
1417
|
+
status: run.status ?? "",
|
|
1418
|
+
errorCode: run.errorCode ?? "",
|
|
1419
|
+
profileName: run.profileName ?? "",
|
|
1420
|
+
screenSizeType: run.screenSizeType ?? "",
|
|
1421
|
+
triggeredBy: run.triggeredBy ?? "",
|
|
1422
|
+
duration: formatDuration(run.duration, { started: run.started, ended: run.ended })
|
|
1423
|
+
},
|
|
1424
|
+
value: run
|
|
1425
|
+
};
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// src/features/testRuns/components/TestRunsList.tsx
|
|
1429
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
1430
|
+
var TestRunsList = ({
|
|
1431
|
+
title,
|
|
1432
|
+
loadingLabel = "Loading test runs...",
|
|
1433
|
+
emptyMessage = "No test runs found",
|
|
1434
|
+
testRuns,
|
|
1435
|
+
isLoading,
|
|
1436
|
+
isError,
|
|
1437
|
+
error
|
|
1438
|
+
}) => {
|
|
1439
|
+
const navigate = useNavigate7();
|
|
1440
|
+
return /* @__PURE__ */ jsx28(
|
|
1441
|
+
BaseResourceListScreen,
|
|
1442
|
+
{
|
|
1443
|
+
title,
|
|
1444
|
+
loadingLabel,
|
|
1445
|
+
emptyMessage,
|
|
1446
|
+
resources: testRuns,
|
|
1447
|
+
isLoading,
|
|
1448
|
+
isError,
|
|
1449
|
+
error,
|
|
1450
|
+
columns: TEST_RUN_LIST_COLUMNS,
|
|
1451
|
+
getRow: getTestRunRow,
|
|
1452
|
+
onSelectResource: (run) => navigate(ROUTES.testRuns.details(run.id), { state: { testRunName: getTestRunName(run) } })
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
// src/app/views/suiteRuns/SuiteRunShowTestRuns.tsx
|
|
1458
|
+
import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1459
|
+
var SuiteRunTestRuns = ({ run, title }) => {
|
|
1460
|
+
const request = useFeatureQuery(() => getSuiteRunTestRuns(run.id), [run.id]);
|
|
1461
|
+
return /* @__PURE__ */ jsx29(
|
|
1462
|
+
TestRunsList,
|
|
1463
|
+
{
|
|
1464
|
+
title,
|
|
1465
|
+
loadingLabel: `Loading test runs for ${getSuiteRunName(run)}...`,
|
|
1466
|
+
emptyMessage: "No test runs available",
|
|
1467
|
+
testRuns: request.data,
|
|
1468
|
+
isLoading: request.isLoading,
|
|
1469
|
+
isError: request.isError,
|
|
1470
|
+
error: request.error
|
|
1471
|
+
}
|
|
1472
|
+
);
|
|
1473
|
+
};
|
|
1474
|
+
var SuiteRunShowTestRuns = () => {
|
|
1475
|
+
const { runId } = useParams3();
|
|
1476
|
+
const location = useLocation7();
|
|
1477
|
+
const state = location.state;
|
|
1478
|
+
const request = useSuiteRunDetails(runId);
|
|
1479
|
+
const title = request.data ? `Test runs in "${getSuiteRunName(request.data)}"` : state?.suiteRunName ? `Test runs in "${state.suiteRunName}"` : void 0;
|
|
1480
|
+
if (!request.data) {
|
|
1481
|
+
return /* @__PURE__ */ jsxs17(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1482
|
+
request.isLoading && /* @__PURE__ */ jsx29(Spinner8, { label: "Loading..." }),
|
|
1483
|
+
request.isError && /* @__PURE__ */ jsx29(StatusMessage10, { variant: "error", children: request.error }),
|
|
1484
|
+
!request.isLoading && !request.isError && /* @__PURE__ */ jsx29(StatusMessage10, { variant: "warning", children: "No test runs available" })
|
|
1485
|
+
] });
|
|
1486
|
+
}
|
|
1487
|
+
return /* @__PURE__ */ jsx29(SuiteRunTestRuns, { run: request.data, title: title ?? "" });
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
// src/app/views/suiteRuns/SuiteRunsList.tsx
|
|
1491
|
+
import { useNavigate as useNavigate8 } from "react-router";
|
|
1492
|
+
|
|
1493
|
+
// src/app/views/suiteRuns/SuiteRunsList.constants.tsx
|
|
1494
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
1495
|
+
var SUITE_RUN_LIST_COLUMNS = [
|
|
1496
|
+
{ key: "name", label: "Name", width: 30 },
|
|
1497
|
+
{ key: "duration", label: "Duration", width: 10 },
|
|
1498
|
+
{ key: "profileName", label: "Profile", width: 8 },
|
|
1499
|
+
{ key: "triggeredBy", label: "Run by", width: 10 },
|
|
1500
|
+
{ key: "result", label: "Result", width: 14 },
|
|
1501
|
+
{
|
|
1502
|
+
key: "status",
|
|
1503
|
+
label: "Status",
|
|
1504
|
+
width: 10,
|
|
1505
|
+
render: (_cell, row, ctx) => /* @__PURE__ */ jsx30(Status, { status: row.value?.status, color: ctx.color, backgroundColor: ctx.backgroundColor })
|
|
1506
|
+
}
|
|
1507
|
+
];
|
|
1508
|
+
|
|
1509
|
+
// src/app/views/suiteRuns/SuiteRunsList.utils.tsx
|
|
1510
|
+
var getSuiteRunResult = (run) => {
|
|
1511
|
+
const testRuns = run.testRuns ?? [];
|
|
1512
|
+
if (testRuns.length === 0) return "";
|
|
1513
|
+
const passed = testRuns.filter((testRun) => testRun.status === "passed").length;
|
|
1514
|
+
return `${passed}/${testRuns.length} passed`;
|
|
1515
|
+
};
|
|
1516
|
+
var getSuiteRunRow = (run) => {
|
|
1517
|
+
return {
|
|
1518
|
+
key: run.id,
|
|
1519
|
+
cells: {
|
|
1520
|
+
name: getSuiteRunName(run),
|
|
1521
|
+
status: run.status ?? "",
|
|
1522
|
+
result: getSuiteRunResult(run),
|
|
1523
|
+
profileName: run.profileName ?? "",
|
|
1524
|
+
runMode: run.runMode ?? "",
|
|
1525
|
+
triggeredBy: run.triggeredBy ?? "",
|
|
1526
|
+
duration: formatDuration(run.duration, { started: run.started, ended: run.ended })
|
|
1527
|
+
},
|
|
1528
|
+
value: run
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
// src/app/views/suiteRuns/SuiteRunsList.tsx
|
|
1533
|
+
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
1534
|
+
var SuiteRunsList = () => {
|
|
1535
|
+
const navigate = useNavigate8();
|
|
1536
|
+
const request = useSuiteRunsList();
|
|
1537
|
+
return /* @__PURE__ */ jsx31(
|
|
1538
|
+
BaseResourceListScreen,
|
|
1539
|
+
{
|
|
1540
|
+
loadingLabel: "Loading suite runs...",
|
|
1541
|
+
emptyMessage: "No suite runs found",
|
|
1542
|
+
resources: request.data,
|
|
1543
|
+
isLoading: request.isLoading,
|
|
1544
|
+
isError: request.isError,
|
|
1545
|
+
error: request.error,
|
|
1546
|
+
columns: SUITE_RUN_LIST_COLUMNS,
|
|
1547
|
+
getRow: getSuiteRunRow,
|
|
1548
|
+
onSelectResource: (run) => navigate(ROUTES.suiteRuns.details(run.id), {
|
|
1549
|
+
state: { suiteRunName: getSuiteRunName(run) }
|
|
1550
|
+
})
|
|
1551
|
+
}
|
|
1552
|
+
);
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
// src/app/views/suiteRuns/SuiteRunsMenu.tsx
|
|
1556
|
+
import { useEffect as useEffect10 } from "react";
|
|
1557
|
+
import { useNavigate as useNavigate9 } from "react-router";
|
|
1558
|
+
var SuiteRunsMenu = () => {
|
|
1559
|
+
const navigate = useNavigate9();
|
|
1560
|
+
useEffect10(() => {
|
|
1561
|
+
navigate(ROUTES.suiteRuns.list(), { replace: true });
|
|
1562
|
+
}, [navigate]);
|
|
1563
|
+
return null;
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
// src/app/views/suites/RunSuite.tsx
|
|
1567
|
+
import { useNavigate as useNavigate10 } from "react-router";
|
|
1568
|
+
|
|
1569
|
+
// src/app/views/suites/BaseSuitesList.tsx
|
|
1570
|
+
import { useState as useState14 } from "react";
|
|
1571
|
+
|
|
1572
|
+
// src/app/hooks/useSuites.ts
|
|
1573
|
+
var useSuitesList = (query) => useFeatureQuery(() => loadSuiteTableResources(query), [query]);
|
|
1574
|
+
var useSuiteDetails = (suiteId) => useFeatureQuery(async () => {
|
|
1575
|
+
if (!suiteId) return void 0;
|
|
1576
|
+
return getSuiteDetails(suiteId);
|
|
1577
|
+
}, [suiteId]);
|
|
1578
|
+
|
|
1579
|
+
// src/features/suites/suites.constants.tsx
|
|
1580
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
1581
|
+
var SUITE_COLUMNS = [
|
|
1582
|
+
{ key: "name", label: "Name", width: 42 },
|
|
1583
|
+
{
|
|
1584
|
+
key: "status",
|
|
1585
|
+
label: "Status",
|
|
1586
|
+
width: 14,
|
|
1587
|
+
render: (_cell, row, ctx) => /* @__PURE__ */ jsx32(Status, { status: row.value?.status, color: ctx.color, backgroundColor: ctx.backgroundColor })
|
|
1588
|
+
}
|
|
1589
|
+
];
|
|
1590
|
+
|
|
1591
|
+
// src/features/suites/suites.utils.tsx
|
|
1592
|
+
var getSuiteRow = (suite) => ({
|
|
1593
|
+
key: suite.id,
|
|
1594
|
+
cells: {
|
|
1595
|
+
name: getSuiteName(suite),
|
|
1596
|
+
status: formatRunStatus(suite.status)
|
|
1597
|
+
},
|
|
1598
|
+
value: suite
|
|
1599
|
+
});
|
|
1600
|
+
var getSuiteName = (suite) => suite.name || suite.id || "Untitled";
|
|
1601
|
+
|
|
1602
|
+
// src/app/views/suites/BaseSuitesList.tsx
|
|
1603
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
1604
|
+
var BaseSuitesList = ({
|
|
1605
|
+
title,
|
|
1606
|
+
emptyMessage = "No suites found",
|
|
1607
|
+
onSelectSuite
|
|
1608
|
+
}) => {
|
|
1609
|
+
const [query, setQuery] = useState14("");
|
|
1610
|
+
const request = useSuitesList(query);
|
|
1611
|
+
return /* @__PURE__ */ jsx33(
|
|
1612
|
+
BaseResourceListScreen,
|
|
1613
|
+
{
|
|
1614
|
+
title,
|
|
1615
|
+
loadingLabel: "Loading suites...",
|
|
1616
|
+
emptyMessage,
|
|
1617
|
+
resources: request.data,
|
|
1618
|
+
isLoading: request.isLoading,
|
|
1619
|
+
isError: request.isError,
|
|
1620
|
+
error: request.error,
|
|
1621
|
+
columns: SUITE_COLUMNS,
|
|
1622
|
+
getRow: getSuiteRow,
|
|
1623
|
+
searchable: true,
|
|
1624
|
+
query,
|
|
1625
|
+
onSearch: (next) => setQuery(next.trim()),
|
|
1626
|
+
onSelectResource: onSelectSuite
|
|
1627
|
+
}
|
|
1628
|
+
);
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
// src/app/views/suites/RunSuite.tsx
|
|
1632
|
+
import { jsx as jsx34 } from "react/jsx-runtime";
|
|
1633
|
+
var RunSuite = () => {
|
|
1634
|
+
const navigate = useNavigate10();
|
|
1635
|
+
return /* @__PURE__ */ jsx34(
|
|
1636
|
+
BaseSuitesList,
|
|
1637
|
+
{
|
|
1638
|
+
title: "Select a suite to run:",
|
|
1639
|
+
emptyMessage: "No suites available",
|
|
1640
|
+
onSelectSuite: (suite) => navigate(ROUTES.suites.run(suite.id))
|
|
1641
|
+
}
|
|
1642
|
+
);
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
// src/app/views/suites/SuiteDetails.tsx
|
|
1646
|
+
import { Spinner as Spinner9, StatusMessage as StatusMessage11 } from "@inkjs/ui";
|
|
1647
|
+
import { Box as Box11 } from "ink";
|
|
1648
|
+
import { useLocation as useLocation8, useParams as useParams4 } from "react-router";
|
|
1649
|
+
import { jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1650
|
+
var SuiteDetails = () => {
|
|
1651
|
+
const { suiteId } = useParams4();
|
|
1652
|
+
const location = useLocation8();
|
|
1653
|
+
const state = location.state;
|
|
1654
|
+
const request = useSuiteDetails(suiteId);
|
|
1655
|
+
const title = request.data ? getSuiteName(request.data) : state?.suiteName;
|
|
1656
|
+
const detailsState = title ? { suiteName: title } : void 0;
|
|
1657
|
+
const data = request.data;
|
|
1658
|
+
const isNotFound = !request.isLoading && !request.isError && data === void 0;
|
|
1659
|
+
const isReady = !request.isLoading && !request.isError && data !== void 0;
|
|
1660
|
+
const hints = isReady ? void 0 : "none";
|
|
1661
|
+
const options = [
|
|
1662
|
+
{
|
|
1663
|
+
label: "Run",
|
|
1664
|
+
shortcut: "r",
|
|
1665
|
+
description: "Start a fresh run of this suite",
|
|
1666
|
+
to: ROUTES.suites.run(suiteId ?? ""),
|
|
1667
|
+
state: detailsState
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
label: "Show last run",
|
|
1671
|
+
shortcut: "h",
|
|
1672
|
+
description: "View the most recent run for this suite",
|
|
1673
|
+
to: ROUTES.suites.lastRun(suiteId ?? ""),
|
|
1674
|
+
state: detailsState
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
label: "Open in BugBug",
|
|
1678
|
+
shortcut: "o",
|
|
1679
|
+
description: "Open the suite page in your browser",
|
|
1680
|
+
to: ROUTES.suites.open(suiteId ?? "")
|
|
1681
|
+
}
|
|
1682
|
+
];
|
|
1683
|
+
return /* @__PURE__ */ jsxs18(ViewLayout, { title, hints, children: [
|
|
1684
|
+
request.isLoading && /* @__PURE__ */ jsx35(Box11, { children: /* @__PURE__ */ jsx35(Spinner9, { label: "Loading suite..." }) }),
|
|
1685
|
+
request.isError && /* @__PURE__ */ jsx35(StatusMessage11, { variant: "error", children: request.error }),
|
|
1686
|
+
isNotFound && /* @__PURE__ */ jsx35(StatusMessage11, { variant: "warning", children: "Suite not found" }),
|
|
1687
|
+
isReady && /* @__PURE__ */ jsx35(MenuList, { options })
|
|
1688
|
+
] });
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
// src/app/views/suites/SuiteDetailsLastRun.tsx
|
|
1692
|
+
import { Spinner as Spinner10, StatusMessage as StatusMessage12 } from "@inkjs/ui";
|
|
1693
|
+
import { Text as Text10 } from "ink";
|
|
1694
|
+
import { useLocation as useLocation9, useParams as useParams5 } from "react-router";
|
|
1695
|
+
import { jsx as jsx36, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1696
|
+
var SuiteDetailsLastRun = () => {
|
|
1697
|
+
const { suiteId } = useParams5();
|
|
1698
|
+
const location = useLocation9();
|
|
1699
|
+
const state = location.state;
|
|
1700
|
+
const request = useSuiteDetails(suiteId);
|
|
1701
|
+
const suite = request.data;
|
|
1702
|
+
const isReady = !request.isLoading && !request.isError;
|
|
1703
|
+
const title = suite ? getSuiteName(suite) : state?.suiteName ?? suiteId;
|
|
1704
|
+
if (!isReady || !suite?.lastRunId) {
|
|
1705
|
+
return /* @__PURE__ */ jsxs19(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1706
|
+
request.isLoading && /* @__PURE__ */ jsx36(Spinner10, { label: "Loading..." }),
|
|
1707
|
+
request.isError && /* @__PURE__ */ jsx36(Text10, { color: "red", children: request.error }),
|
|
1708
|
+
isReady && !suite?.lastRunId && /* @__PURE__ */ jsx36(StatusMessage12, { variant: "warning", children: `No previous run found for ${suite ? getSuiteName(suite) : suiteId ?? "suite"}` })
|
|
1709
|
+
] });
|
|
1710
|
+
}
|
|
1711
|
+
return /* @__PURE__ */ jsx36(
|
|
1712
|
+
BaseSuiteRunDetails,
|
|
1713
|
+
{
|
|
1714
|
+
runId: suite.lastRunId,
|
|
1715
|
+
title,
|
|
1716
|
+
menuState: { suiteRunName: title }
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
// src/app/views/suites/SuiteDetailsOpen.tsx
|
|
1722
|
+
import { StatusMessage as StatusMessage13 } from "@inkjs/ui";
|
|
1723
|
+
import { Spinner as Spinner11 } from "@inkjs/ui";
|
|
1724
|
+
import { Box as Box12, Text as Text11 } from "ink";
|
|
1725
|
+
import { useEffect as useEffect11 } from "react";
|
|
1726
|
+
import { useParams as useParams6 } from "react-router";
|
|
1727
|
+
import { jsx as jsx37, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1728
|
+
var WebUrlUnavailableMessage2 = () => /* @__PURE__ */ jsx37(StatusMessage13, { variant: "warning", children: "No web URL available for this suite" });
|
|
1729
|
+
var SuiteDetailsOpen = () => {
|
|
1730
|
+
const { suiteId } = useParams6();
|
|
1731
|
+
const request = useFeatureQuery(async () => {
|
|
1732
|
+
if (!suiteId) return void 0;
|
|
1733
|
+
return getSuite(suiteId);
|
|
1734
|
+
}, [suiteId]);
|
|
1735
|
+
const url = request.data?.webappUrl;
|
|
1736
|
+
const isReady = !request.isLoading && !request.isError;
|
|
1737
|
+
const [openSuite, openSuiteResult] = useFeatureMutation(async (url2) => {
|
|
1738
|
+
await openExternalUrl(url2);
|
|
1739
|
+
return url2;
|
|
1740
|
+
});
|
|
1741
|
+
useEffect11(() => {
|
|
1742
|
+
if (isReady && url) void openSuite(url);
|
|
1743
|
+
}, [isReady, openSuite, url]);
|
|
1744
|
+
return /* @__PURE__ */ jsxs20(ViewLayout, { hintsHidden: true, children: [
|
|
1745
|
+
request.isLoading && /* @__PURE__ */ jsx37(Spinner11, { label: "Loading..." }),
|
|
1746
|
+
request.isError && /* @__PURE__ */ jsx37(Text11, { color: "red", children: request.error }),
|
|
1747
|
+
isReady && !url && /* @__PURE__ */ jsx37(WebUrlUnavailableMessage2, {}),
|
|
1748
|
+
isReady && url && openSuiteResult.isLoading && /* @__PURE__ */ jsx37(Spinner11, { label: "Opening BugBug..." }),
|
|
1749
|
+
isReady && url && openSuiteResult.isError && /* @__PURE__ */ jsx37(StatusMessage13, { variant: "error", children: openSuiteResult.error }),
|
|
1750
|
+
isReady && url && openSuiteResult.data && /* @__PURE__ */ jsxs20(Box12, { flexDirection: "column", children: [
|
|
1751
|
+
/* @__PURE__ */ jsx37(StatusMessage13, { variant: "success", children: "Opened in BugBug" }),
|
|
1752
|
+
/* @__PURE__ */ jsx37(Text11, { dimColor: true, children: openSuiteResult.data })
|
|
1753
|
+
] })
|
|
1754
|
+
] });
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
// src/app/views/suites/SuiteDetailsRun.tsx
|
|
1758
|
+
import { Spinner as Spinner12, StatusMessage as StatusMessage14 } from "@inkjs/ui";
|
|
1759
|
+
import { useEffect as useEffect12 } from "react";
|
|
1760
|
+
import { useLocation as useLocation10, useNavigate as useNavigate11, useParams as useParams7 } from "react-router";
|
|
1761
|
+
import { jsx as jsx38, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1762
|
+
var SuiteDetailsRun = () => {
|
|
1763
|
+
const { suiteId } = useParams7();
|
|
1764
|
+
const location = useLocation10();
|
|
1765
|
+
const navigate = useNavigate11();
|
|
1766
|
+
const state = location.state;
|
|
1767
|
+
const title = state?.suiteName ?? suiteId;
|
|
1768
|
+
const [runSuiteMutation, runSuiteResult] = useFeatureMutation((id) => runSuite(id, {}));
|
|
1769
|
+
useEffect12(() => {
|
|
1770
|
+
if (!suiteId) return;
|
|
1771
|
+
const startRun = async () => {
|
|
1772
|
+
try {
|
|
1773
|
+
const result = await runSuiteMutation(suiteId);
|
|
1774
|
+
if ("data" in result) {
|
|
1775
|
+
navigate(ROUTES.suites.lastRun(suiteId), { replace: true, state });
|
|
1776
|
+
}
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
startRun();
|
|
1781
|
+
}, [navigate, runSuiteMutation, state, suiteId]);
|
|
1782
|
+
if (!runSuiteResult.data) {
|
|
1783
|
+
return /* @__PURE__ */ jsxs21(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1784
|
+
runSuiteResult.isError && /* @__PURE__ */ jsx38(StatusMessage14, { variant: "error", children: runSuiteResult.error }),
|
|
1785
|
+
!runSuiteResult.isError && /* @__PURE__ */ jsx38(Spinner12, { label: "Initializing..." })
|
|
1786
|
+
] });
|
|
1787
|
+
}
|
|
1788
|
+
return /* @__PURE__ */ jsx38(BaseSuiteRunDetails, { runId: runSuiteResult.data.id, title, menuState: state });
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1791
|
+
// src/app/views/suites/SuitesList.tsx
|
|
1792
|
+
import { useNavigate as useNavigate12 } from "react-router";
|
|
1793
|
+
import { jsx as jsx39 } from "react/jsx-runtime";
|
|
1794
|
+
var SuitesList = () => {
|
|
1795
|
+
const navigate = useNavigate12();
|
|
1796
|
+
return /* @__PURE__ */ jsx39(
|
|
1797
|
+
BaseSuitesList,
|
|
1798
|
+
{
|
|
1799
|
+
onSelectSuite: (suite) => navigate(ROUTES.suites.details(suite.id), { state: { suiteName: getSuiteName(suite) } })
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
// src/app/views/suites/SuitesMenu.tsx
|
|
1805
|
+
import { jsx as jsx40 } from "react/jsx-runtime";
|
|
1806
|
+
var SuitesMenu = () => {
|
|
1807
|
+
return /* @__PURE__ */ jsx40(ViewLayout, { children: /* @__PURE__ */ jsx40(
|
|
1808
|
+
MenuList,
|
|
1809
|
+
{
|
|
1810
|
+
options: [
|
|
1811
|
+
{ label: "List suites", shortcut: "l", to: "/suites/list" },
|
|
1812
|
+
{ label: "Run a suite", shortcut: "r", to: "/suites/run" }
|
|
1813
|
+
]
|
|
1814
|
+
}
|
|
1815
|
+
) });
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
// src/app/views/testRuns/TestRunDebugArtifacts.tsx
|
|
1819
|
+
import { Spinner as Spinner13, StatusMessage as StatusMessage15 } from "@inkjs/ui";
|
|
1820
|
+
import { useLocation as useLocation11, useParams as useParams8 } from "react-router";
|
|
1821
|
+
|
|
1822
|
+
// src/app/hooks/useTestRuns.ts
|
|
1823
|
+
var useTestRunsList = () => useFeatureQuery(() => listTestRuns(), []);
|
|
1824
|
+
var useTestRunDetails = (runId) => useFeatureQuery(async () => {
|
|
1825
|
+
if (!runId) return void 0;
|
|
1826
|
+
return getTestRunDetails(runId);
|
|
1827
|
+
}, [runId]);
|
|
1828
|
+
|
|
1829
|
+
// src/app/views/testRuns/TestRunDebugArtifacts.tsx
|
|
1830
|
+
import { jsx as jsx41 } from "react/jsx-runtime";
|
|
1831
|
+
var ArtifactsUnavailableMessage = () => /* @__PURE__ */ jsx41(StatusMessage15, { variant: "warning", children: "No debug artifacts available" });
|
|
1832
|
+
var DebugArtifacts = ({ runId }) => {
|
|
1833
|
+
const request = useFeatureQuery(() => listTestRunDebugArtifacts(runId), [runId]);
|
|
1834
|
+
if (request.isLoading) {
|
|
1835
|
+
return /* @__PURE__ */ jsx41(Spinner13, { label: `Loading debug artifacts for ${runId}...` });
|
|
1836
|
+
}
|
|
1837
|
+
if (request.isError) {
|
|
1838
|
+
return /* @__PURE__ */ jsx41(StatusMessage15, { variant: "error", children: request.error });
|
|
1839
|
+
}
|
|
1840
|
+
if (!request.data || request.data.length === 0) {
|
|
1841
|
+
return /* @__PURE__ */ jsx41(ArtifactsUnavailableMessage, {});
|
|
1842
|
+
}
|
|
1843
|
+
return /* @__PURE__ */ jsx41(
|
|
1844
|
+
MenuList,
|
|
1845
|
+
{
|
|
1846
|
+
options: request.data.map((artifact) => ({
|
|
1847
|
+
label: getDebugArtifactLabel(artifact),
|
|
1848
|
+
value: artifact
|
|
1849
|
+
})),
|
|
1850
|
+
onSelect: (item) => {
|
|
1851
|
+
const artifact = item.value;
|
|
1852
|
+
if (artifact?.file) {
|
|
1853
|
+
void openExternalUrl(artifact.file);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
);
|
|
1858
|
+
};
|
|
1859
|
+
var TestRunDebugArtifacts = () => {
|
|
1860
|
+
const { runId } = useParams8();
|
|
1861
|
+
const location = useLocation11();
|
|
1862
|
+
const state = location.state;
|
|
1863
|
+
const detailsRequest = useTestRunDetails(runId);
|
|
1864
|
+
const title = detailsRequest.data ? getTestRunName(detailsRequest.data) : state?.testRunName ?? "Debug artifacts";
|
|
1865
|
+
return /* @__PURE__ */ jsx41(
|
|
1866
|
+
ViewLayout,
|
|
1867
|
+
{
|
|
1868
|
+
title,
|
|
1869
|
+
hints: runId ? "navigationOpenSelect" : "none",
|
|
1870
|
+
hintsPosition: "bottom",
|
|
1871
|
+
children: runId ? /* @__PURE__ */ jsx41(DebugArtifacts, { runId }) : /* @__PURE__ */ jsx41(ArtifactsUnavailableMessage, {})
|
|
1872
|
+
}
|
|
1873
|
+
);
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
// src/app/views/testRuns/TestRunDetails.tsx
|
|
1877
|
+
import { Spinner as Spinner14, StatusMessage as StatusMessage16 } from "@inkjs/ui";
|
|
1878
|
+
import { Text as Text12 } from "ink";
|
|
1879
|
+
import { useLocation as useLocation12, useParams as useParams9 } from "react-router";
|
|
1880
|
+
import { jsx as jsx42, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1881
|
+
var TestRunDetails = () => {
|
|
1882
|
+
const { runId } = useParams9();
|
|
1883
|
+
const location = useLocation12();
|
|
1884
|
+
const state = location.state;
|
|
1885
|
+
const request = useTestRunDetails(runId);
|
|
1886
|
+
const title = request.data ? getTestRunName(request.data) : state?.testRunName;
|
|
1887
|
+
const menuState = title ? { testRunName: title } : void 0;
|
|
1888
|
+
if (!request.data || !runId) {
|
|
1889
|
+
return /* @__PURE__ */ jsxs22(ViewLayout, { title, hintsHidden: true, children: [
|
|
1890
|
+
request.isLoading && /* @__PURE__ */ jsx42(Spinner14, { label: "Loading test run..." }),
|
|
1891
|
+
request.isError && /* @__PURE__ */ jsx42(StatusMessage16, { variant: "error", children: request.error }),
|
|
1892
|
+
!request.isLoading && !request.isError && !request.data && /* @__PURE__ */ jsx42(Text12, { color: "yellow", children: "Test run not found." })
|
|
1893
|
+
] });
|
|
1894
|
+
}
|
|
1895
|
+
return /* @__PURE__ */ jsx42(BaseTestRunDetails, { runId, title, menuState });
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
// src/app/views/testRuns/TestRunJunitReport.tsx
|
|
1899
|
+
import { Spinner as Spinner15, StatusMessage as StatusMessage17 } from "@inkjs/ui";
|
|
1900
|
+
import { Text as Text13 } from "ink";
|
|
1901
|
+
import { useEffect as useEffect13 } from "react";
|
|
1902
|
+
import { useLocation as useLocation13, useParams as useParams10 } from "react-router";
|
|
1903
|
+
import { Fragment as Fragment3, jsx as jsx43, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1904
|
+
var DownloadReport = ({ runId }) => {
|
|
1905
|
+
const [downloadReport, downloadReportResult] = useFeatureMutation(async (runId2) => {
|
|
1906
|
+
const xml = await getTestRunJunitReport(runId2);
|
|
1907
|
+
return writeReportXml(xml, defaultJunitReportPath("test", runId2));
|
|
1908
|
+
});
|
|
1909
|
+
useEffect13(() => {
|
|
1910
|
+
void downloadReport(runId);
|
|
1911
|
+
}, [downloadReport, runId]);
|
|
1912
|
+
return /* @__PURE__ */ jsxs23(Fragment3, { children: [
|
|
1913
|
+
downloadReportResult.isLoading && /* @__PURE__ */ jsx43(Spinner15, { label: "Downloading JUnit report..." }),
|
|
1914
|
+
downloadReportResult.isError && /* @__PURE__ */ jsx43(StatusMessage17, { variant: "error", children: downloadReportResult.error }),
|
|
1915
|
+
downloadReportResult.data?.outPath && /* @__PURE__ */ jsx43(StatusMessage17, { variant: "success", children: `Saved JUnit report to ${downloadReportResult.data.outPath} (${downloadReportResult.data.size} chars)` })
|
|
1916
|
+
] });
|
|
1917
|
+
};
|
|
1918
|
+
var TestRunJunitReport = () => {
|
|
1919
|
+
const { runId } = useParams10();
|
|
1920
|
+
const location = useLocation13();
|
|
1921
|
+
const state = location.state;
|
|
1922
|
+
const request = useTestRunDetails(runId);
|
|
1923
|
+
const title = request.data ? getTestRunName(request.data) : state?.testRunName ?? runId;
|
|
1924
|
+
return /* @__PURE__ */ jsxs23(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1925
|
+
request.isLoading && /* @__PURE__ */ jsx43(Spinner15, { label: "Loading..." }),
|
|
1926
|
+
request.isError && /* @__PURE__ */ jsx43(Text13, { color: "red", children: request.error }),
|
|
1927
|
+
!request.isLoading && !request.isError && runId && /* @__PURE__ */ jsx43(DownloadReport, { runId }),
|
|
1928
|
+
!request.isLoading && !request.isError && !runId && /* @__PURE__ */ jsx43(StatusMessage17, { variant: "warning", children: "No test run selected" })
|
|
1929
|
+
] });
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1932
|
+
// src/app/views/testRuns/TestRunOpen.tsx
|
|
1933
|
+
import { Spinner as Spinner16, StatusMessage as StatusMessage18 } from "@inkjs/ui";
|
|
1934
|
+
import { Box as Box13, Text as Text14 } from "ink";
|
|
1935
|
+
import { useEffect as useEffect14 } from "react";
|
|
1936
|
+
import { useLocation as useLocation14, useParams as useParams11 } from "react-router";
|
|
1937
|
+
import { Fragment as Fragment4, jsx as jsx44, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1938
|
+
var WebUrlUnavailableMessage3 = () => /* @__PURE__ */ jsx44(StatusMessage18, { variant: "warning", children: "No web URL available for this run" });
|
|
1939
|
+
var OpenRun = ({ url }) => {
|
|
1940
|
+
const [openRun, openRunResult] = useFeatureMutation(async (url2) => {
|
|
1941
|
+
await openExternalUrl(url2);
|
|
1942
|
+
return url2;
|
|
1943
|
+
});
|
|
1944
|
+
useEffect14(() => {
|
|
1945
|
+
if (url) void openRun(url);
|
|
1946
|
+
}, [openRun, url]);
|
|
1947
|
+
if (!url) return /* @__PURE__ */ jsx44(WebUrlUnavailableMessage3, {});
|
|
1948
|
+
return /* @__PURE__ */ jsxs24(Fragment4, { children: [
|
|
1949
|
+
openRunResult.isLoading && /* @__PURE__ */ jsx44(Spinner16, { label: "Opening BugBug..." }),
|
|
1950
|
+
openRunResult.isError && /* @__PURE__ */ jsx44(StatusMessage18, { variant: "error", children: openRunResult.error }),
|
|
1951
|
+
openRunResult.data && /* @__PURE__ */ jsxs24(Box13, { flexDirection: "column", children: [
|
|
1952
|
+
/* @__PURE__ */ jsx44(StatusMessage18, { variant: "success", children: "Opened in BugBug" }),
|
|
1953
|
+
/* @__PURE__ */ jsx44(Text14, { dimColor: true, children: openRunResult.data })
|
|
1954
|
+
] })
|
|
1955
|
+
] });
|
|
1956
|
+
};
|
|
1957
|
+
var TestRunOpen = () => {
|
|
1958
|
+
const { runId } = useParams11();
|
|
1959
|
+
const location = useLocation14();
|
|
1960
|
+
const state = location.state;
|
|
1961
|
+
const request = useTestRunDetails(runId);
|
|
1962
|
+
const title = request.data ? getTestRunName(request.data) : state?.testRunName ?? runId;
|
|
1963
|
+
return /* @__PURE__ */ jsxs24(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
1964
|
+
request.isLoading && /* @__PURE__ */ jsx44(Spinner16, { label: "Loading..." }),
|
|
1965
|
+
request.isError && /* @__PURE__ */ jsx44(Text14, { color: "red", children: request.error }),
|
|
1966
|
+
!request.isLoading && !request.isError && /* @__PURE__ */ jsx44(OpenRun, { url: request.data?.webappUrl })
|
|
1967
|
+
] });
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
// src/app/views/testRuns/TestRunsList.tsx
|
|
1971
|
+
import { jsx as jsx45 } from "react/jsx-runtime";
|
|
1972
|
+
var TestRunsList2 = () => {
|
|
1973
|
+
const request = useTestRunsList();
|
|
1974
|
+
return /* @__PURE__ */ jsx45(
|
|
1975
|
+
TestRunsList,
|
|
1976
|
+
{
|
|
1977
|
+
testRuns: request.data,
|
|
1978
|
+
isLoading: request.isLoading,
|
|
1979
|
+
isError: request.isError,
|
|
1980
|
+
error: request.error
|
|
1981
|
+
}
|
|
1982
|
+
);
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
// src/app/views/testRuns/TestRunsMenu.tsx
|
|
1986
|
+
import { useEffect as useEffect15 } from "react";
|
|
1987
|
+
import { useNavigate as useNavigate13 } from "react-router";
|
|
1988
|
+
var TestRunsMenu = () => {
|
|
1989
|
+
const navigate = useNavigate13();
|
|
1990
|
+
useEffect15(() => {
|
|
1991
|
+
navigate(ROUTES.testRuns.list(), { replace: true });
|
|
1992
|
+
}, [navigate]);
|
|
1993
|
+
return null;
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
// src/app/views/tests/RunTest.tsx
|
|
1997
|
+
import { useNavigate as useNavigate14 } from "react-router";
|
|
1998
|
+
|
|
1999
|
+
// src/app/views/tests/BaseTestsList.tsx
|
|
2000
|
+
import { useState as useState15 } from "react";
|
|
2001
|
+
|
|
2002
|
+
// src/app/hooks/useTests.ts
|
|
2003
|
+
var useTestsList = (query) => useFeatureQuery(() => loadTestTableResources(query), [query]);
|
|
2004
|
+
var useTestDetails = (testId) => useFeatureQuery(async () => {
|
|
2005
|
+
if (!testId) return void 0;
|
|
2006
|
+
return getTestDetails(testId);
|
|
2007
|
+
}, [testId]);
|
|
2008
|
+
|
|
2009
|
+
// src/features/tests/tests.constants.tsx
|
|
2010
|
+
import { jsx as jsx46 } from "react/jsx-runtime";
|
|
2011
|
+
var TEST_COLUMNS = [
|
|
2012
|
+
{ key: "name", label: "Name", width: 42 },
|
|
2013
|
+
{
|
|
2014
|
+
key: "status",
|
|
2015
|
+
label: "Status",
|
|
2016
|
+
width: 14,
|
|
2017
|
+
render: (_cell, row, ctx) => /* @__PURE__ */ jsx46(Status, { status: row.value?.status, color: ctx.color, backgroundColor: ctx.backgroundColor })
|
|
2018
|
+
}
|
|
2019
|
+
];
|
|
2020
|
+
|
|
2021
|
+
// src/features/tests/tests.utils.tsx
|
|
2022
|
+
var getTestRow = (test) => ({
|
|
2023
|
+
key: test.id,
|
|
2024
|
+
cells: {
|
|
2025
|
+
name: getTestName(test),
|
|
2026
|
+
status: formatRunStatus(test.status)
|
|
2027
|
+
},
|
|
2028
|
+
value: test
|
|
2029
|
+
});
|
|
2030
|
+
var getTestName = (test) => test.name || test.id || "Untitled";
|
|
2031
|
+
|
|
2032
|
+
// src/app/views/tests/BaseTestsList.tsx
|
|
2033
|
+
import { jsx as jsx47 } from "react/jsx-runtime";
|
|
2034
|
+
var BaseTestsList = ({
|
|
2035
|
+
title,
|
|
2036
|
+
emptyMessage = "No tests found",
|
|
2037
|
+
onSelectTest
|
|
2038
|
+
}) => {
|
|
2039
|
+
const [query, setQuery] = useState15("");
|
|
2040
|
+
const request = useTestsList(query);
|
|
2041
|
+
return /* @__PURE__ */ jsx47(
|
|
2042
|
+
BaseResourceListScreen,
|
|
2043
|
+
{
|
|
2044
|
+
title,
|
|
2045
|
+
loadingLabel: "Loading tests...",
|
|
2046
|
+
emptyMessage,
|
|
2047
|
+
resources: request.data,
|
|
2048
|
+
isLoading: request.isLoading,
|
|
2049
|
+
isError: request.isError,
|
|
2050
|
+
error: request.error,
|
|
2051
|
+
columns: TEST_COLUMNS,
|
|
2052
|
+
getRow: getTestRow,
|
|
2053
|
+
searchable: true,
|
|
2054
|
+
query,
|
|
2055
|
+
onSearch: (next) => setQuery(next.trim()),
|
|
2056
|
+
onSelectResource: onSelectTest
|
|
2057
|
+
}
|
|
2058
|
+
);
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
// src/app/views/tests/RunTest.tsx
|
|
2062
|
+
import { jsx as jsx48 } from "react/jsx-runtime";
|
|
2063
|
+
var RunTest = () => {
|
|
2064
|
+
const navigate = useNavigate14();
|
|
2065
|
+
return /* @__PURE__ */ jsx48(
|
|
2066
|
+
BaseTestsList,
|
|
2067
|
+
{
|
|
2068
|
+
title: "Select a test to run:",
|
|
2069
|
+
emptyMessage: "No tests available",
|
|
2070
|
+
onSelectTest: (test) => navigate(ROUTES.tests.run(test.id))
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
// src/app/views/tests/TestDetails.tsx
|
|
2076
|
+
import { Spinner as Spinner17, StatusMessage as StatusMessage19 } from "@inkjs/ui";
|
|
2077
|
+
import { Box as Box14 } from "ink";
|
|
2078
|
+
import { useLocation as useLocation15, useParams as useParams12 } from "react-router";
|
|
2079
|
+
import { jsx as jsx49, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2080
|
+
var TestDetails = () => {
|
|
2081
|
+
const { testId } = useParams12();
|
|
2082
|
+
const location = useLocation15();
|
|
2083
|
+
const state = location.state;
|
|
2084
|
+
const request = useTestDetails(testId);
|
|
2085
|
+
const title = request.data ? getTestName(request.data) : state?.testName;
|
|
2086
|
+
const detailsState = title ? { testName: title } : void 0;
|
|
2087
|
+
const data = request.data;
|
|
2088
|
+
const isNotFound = !request.isLoading && !request.isError && data === void 0;
|
|
2089
|
+
const isReady = !request.isLoading && !request.isError && data !== void 0;
|
|
2090
|
+
const hints = isReady ? void 0 : "none";
|
|
2091
|
+
const options = [
|
|
2092
|
+
{
|
|
2093
|
+
label: "Run",
|
|
2094
|
+
shortcut: "r",
|
|
2095
|
+
description: "Start a fresh run of this test",
|
|
2096
|
+
to: ROUTES.tests.run(testId ?? ""),
|
|
2097
|
+
state: detailsState
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
label: "Show last run",
|
|
2101
|
+
shortcut: "h",
|
|
2102
|
+
description: "View the most recent run for this test",
|
|
2103
|
+
to: ROUTES.tests.lastRun(testId ?? ""),
|
|
2104
|
+
state: detailsState
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
label: "Export (YAML)",
|
|
2108
|
+
shortcut: "e",
|
|
2109
|
+
description: "Save the test definition to a YAML file in the current directory",
|
|
2110
|
+
to: ROUTES.tests.export(testId ?? "")
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
label: "Open in BugBug",
|
|
2114
|
+
shortcut: "o",
|
|
2115
|
+
description: "Open the test page in your browser",
|
|
2116
|
+
to: ROUTES.tests.open(testId ?? "")
|
|
2117
|
+
}
|
|
2118
|
+
];
|
|
2119
|
+
return /* @__PURE__ */ jsxs25(ViewLayout, { title, hints, children: [
|
|
2120
|
+
request.isLoading && /* @__PURE__ */ jsx49(Box14, { children: /* @__PURE__ */ jsx49(Spinner17, { label: "Loading test..." }) }),
|
|
2121
|
+
request.isError && /* @__PURE__ */ jsx49(StatusMessage19, { variant: "error", children: request.error }),
|
|
2122
|
+
isNotFound && /* @__PURE__ */ jsx49(StatusMessage19, { variant: "warning", children: "Test not found." }),
|
|
2123
|
+
isReady && /* @__PURE__ */ jsx49(MenuList, { options })
|
|
2124
|
+
] });
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/app/views/tests/TestDetailsExport.tsx
|
|
2128
|
+
import { StatusMessage as StatusMessage20 } from "@inkjs/ui";
|
|
2129
|
+
import { Spinner as Spinner18 } from "@inkjs/ui";
|
|
2130
|
+
import { useEffect as useEffect16 } from "react";
|
|
2131
|
+
import { useParams as useParams13 } from "react-router";
|
|
2132
|
+
import { jsx as jsx50, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2133
|
+
var TestDetailsExport = () => {
|
|
2134
|
+
const { testId } = useParams13();
|
|
2135
|
+
const [exportTestMutation, exportTestResult] = useFeatureMutation(
|
|
2136
|
+
(id) => exportTest(id, "yaml")
|
|
2137
|
+
);
|
|
2138
|
+
useEffect16(() => {
|
|
2139
|
+
if (testId) void exportTestMutation(testId);
|
|
2140
|
+
}, [exportTestMutation, testId]);
|
|
2141
|
+
return /* @__PURE__ */ jsxs26(ViewLayout, { hintsHidden: true, children: [
|
|
2142
|
+
exportTestResult.isLoading && /* @__PURE__ */ jsx50(Spinner18, { label: `Exporting ${testId}...` }),
|
|
2143
|
+
exportTestResult.isError && /* @__PURE__ */ jsx50(StatusMessage20, { variant: "error", children: exportTestResult.error }),
|
|
2144
|
+
exportTestResult.data && /* @__PURE__ */ jsx50(StatusMessage20, { variant: "success", children: `Exported to ${exportTestResult.data.outPath} (${exportTestResult.data.size} chars)` })
|
|
2145
|
+
] });
|
|
2146
|
+
};
|
|
2147
|
+
|
|
2148
|
+
// src/app/views/tests/TestDetailsLastRun.tsx
|
|
2149
|
+
import { Spinner as Spinner19, StatusMessage as StatusMessage21 } from "@inkjs/ui";
|
|
2150
|
+
import { Text as Text15 } from "ink";
|
|
2151
|
+
import { useLocation as useLocation16, useParams as useParams14 } from "react-router";
|
|
2152
|
+
import { jsx as jsx51, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2153
|
+
var TestDetailsLastRun = () => {
|
|
2154
|
+
const { testId } = useParams14();
|
|
2155
|
+
const location = useLocation16();
|
|
2156
|
+
const state = location.state;
|
|
2157
|
+
const request = useTestDetails(testId);
|
|
2158
|
+
const test = request.data;
|
|
2159
|
+
const isReady = !request.isLoading && !request.isError;
|
|
2160
|
+
const title = test ? getTestName(test) : state?.testName ?? testId;
|
|
2161
|
+
if (!isReady || !test?.lastRunId) {
|
|
2162
|
+
return /* @__PURE__ */ jsxs27(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
2163
|
+
request.isLoading && /* @__PURE__ */ jsx51(Spinner19, { label: "Loading..." }),
|
|
2164
|
+
request.isError && /* @__PURE__ */ jsx51(Text15, { color: "red", children: request.error }),
|
|
2165
|
+
isReady && !test?.lastRunId && /* @__PURE__ */ jsx51(StatusMessage21, { variant: "warning", children: `No previous run found for ${test ? getTestName(test) : testId ?? "test"}` })
|
|
2166
|
+
] });
|
|
2167
|
+
}
|
|
2168
|
+
return /* @__PURE__ */ jsx51(BaseTestRunDetails, { runId: test.lastRunId, title, menuState: { testRunName: title } });
|
|
2169
|
+
};
|
|
2170
|
+
|
|
2171
|
+
// src/app/views/tests/TestDetailsOpen.tsx
|
|
2172
|
+
import { StatusMessage as StatusMessage22 } from "@inkjs/ui";
|
|
2173
|
+
import { Spinner as Spinner20 } from "@inkjs/ui";
|
|
2174
|
+
import { Box as Box15, Text as Text16 } from "ink";
|
|
2175
|
+
import { useEffect as useEffect17 } from "react";
|
|
2176
|
+
import { useParams as useParams15 } from "react-router";
|
|
2177
|
+
import { jsx as jsx52, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2178
|
+
var WebUrlUnavailableMessage4 = () => /* @__PURE__ */ jsx52(StatusMessage22, { variant: "warning", children: "No web URL available for this test" });
|
|
2179
|
+
var TestDetailsOpen = () => {
|
|
2180
|
+
const { testId } = useParams15();
|
|
2181
|
+
const request = useFeatureQuery(async () => {
|
|
2182
|
+
if (!testId) return void 0;
|
|
2183
|
+
return getTest(testId);
|
|
2184
|
+
}, [testId]);
|
|
2185
|
+
const url = request.data?.webappUrl;
|
|
2186
|
+
const isReady = !request.isLoading && !request.isError;
|
|
2187
|
+
const [openTest, openTestResult] = useFeatureMutation(async (url2) => {
|
|
2188
|
+
await openExternalUrl(url2);
|
|
2189
|
+
return url2;
|
|
2190
|
+
});
|
|
2191
|
+
useEffect17(() => {
|
|
2192
|
+
if (isReady && url) void openTest(url);
|
|
2193
|
+
}, [isReady, openTest, url]);
|
|
2194
|
+
return /* @__PURE__ */ jsxs28(ViewLayout, { hintsHidden: true, children: [
|
|
2195
|
+
request.isLoading && /* @__PURE__ */ jsx52(Spinner20, { label: "Loading..." }),
|
|
2196
|
+
request.isError && /* @__PURE__ */ jsx52(Text16, { color: "red", children: request.error }),
|
|
2197
|
+
isReady && !url && /* @__PURE__ */ jsx52(WebUrlUnavailableMessage4, {}),
|
|
2198
|
+
isReady && url && openTestResult.isLoading && /* @__PURE__ */ jsx52(Spinner20, { label: "Opening BugBug..." }),
|
|
2199
|
+
isReady && url && openTestResult.isError && /* @__PURE__ */ jsx52(StatusMessage22, { variant: "error", children: openTestResult.error }),
|
|
2200
|
+
isReady && url && openTestResult.data && /* @__PURE__ */ jsxs28(Box15, { flexDirection: "column", children: [
|
|
2201
|
+
/* @__PURE__ */ jsx52(StatusMessage22, { variant: "success", children: "Opened in BugBug" }),
|
|
2202
|
+
/* @__PURE__ */ jsx52(Text16, { dimColor: true, children: openTestResult.data })
|
|
2203
|
+
] })
|
|
2204
|
+
] });
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
// src/app/views/tests/TestDetailsRun.tsx
|
|
2208
|
+
import { Spinner as Spinner21, StatusMessage as StatusMessage23 } from "@inkjs/ui";
|
|
2209
|
+
import { useEffect as useEffect18 } from "react";
|
|
2210
|
+
import { useLocation as useLocation17, useNavigate as useNavigate15, useParams as useParams16 } from "react-router";
|
|
2211
|
+
import { jsx as jsx53, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
2212
|
+
var TestDetailsRun = () => {
|
|
2213
|
+
const { testId } = useParams16();
|
|
2214
|
+
const location = useLocation17();
|
|
2215
|
+
const navigate = useNavigate15();
|
|
2216
|
+
const state = location.state;
|
|
2217
|
+
const title = state?.testName ?? testId;
|
|
2218
|
+
const [runTestMutation, runTestResult] = useFeatureMutation((id) => runTest(id, {}));
|
|
2219
|
+
useEffect18(() => {
|
|
2220
|
+
if (!testId) return;
|
|
2221
|
+
const startRun = async () => {
|
|
2222
|
+
try {
|
|
2223
|
+
const result = await runTestMutation(testId);
|
|
2224
|
+
if ("data" in result) {
|
|
2225
|
+
navigate(ROUTES.tests.lastRun(testId), { replace: true, state });
|
|
2226
|
+
}
|
|
2227
|
+
} catch {
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
startRun();
|
|
2231
|
+
}, [navigate, runTestMutation, state, testId]);
|
|
2232
|
+
if (!runTestResult.data) {
|
|
2233
|
+
return /* @__PURE__ */ jsxs29(ViewLayout, { title, hintsHidden: true, hintsPosition: "bottom", children: [
|
|
2234
|
+
runTestResult.isError && /* @__PURE__ */ jsx53(StatusMessage23, { variant: "error", children: runTestResult.error }),
|
|
2235
|
+
!runTestResult.isError && /* @__PURE__ */ jsx53(Spinner21, { label: "Initializing..." })
|
|
2236
|
+
] });
|
|
2237
|
+
}
|
|
2238
|
+
return /* @__PURE__ */ jsx53(BaseTestRunDetails, { runId: runTestResult.data.id, title, menuState: state });
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
// src/app/views/tests/TestImport.tsx
|
|
2242
|
+
import { Spinner as Spinner22, StatusMessage as StatusMessage24 } from "@inkjs/ui";
|
|
2243
|
+
import { useEffect as useEffect19, useState as useState16 } from "react";
|
|
2244
|
+
import { useNavigate as useNavigate16 } from "react-router";
|
|
2245
|
+
import { jsx as jsx54, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
2246
|
+
var TestImport = ({ initialDirectory } = {}) => {
|
|
2247
|
+
const navigate = useNavigate16();
|
|
2248
|
+
const [file, setFile] = useState16(null);
|
|
2249
|
+
const [conflictMode, setConflictMode] = useState16(null);
|
|
2250
|
+
const [importTestMutation, importTestResult] = useFeatureMutation(
|
|
2251
|
+
async ({ file: file2, conflictMode: conflictMode2 }) => {
|
|
2252
|
+
detectFormat(file2);
|
|
2253
|
+
return importTest(file2, {
|
|
2254
|
+
conflictMode: conflictMode2 || void 0
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
);
|
|
2258
|
+
useEffect19(() => {
|
|
2259
|
+
if (file && conflictMode !== null) {
|
|
2260
|
+
void importTestMutation({ file, conflictMode });
|
|
2261
|
+
}
|
|
2262
|
+
}, [conflictMode, file, importTestMutation]);
|
|
2263
|
+
useEscHandler(() => {
|
|
2264
|
+
if (!file || conflictMode !== null) return false;
|
|
2265
|
+
setFile(null);
|
|
2266
|
+
return true;
|
|
2267
|
+
});
|
|
2268
|
+
if (!file) {
|
|
2269
|
+
return /* @__PURE__ */ jsx54(
|
|
2270
|
+
FilePicker,
|
|
2271
|
+
{
|
|
2272
|
+
label: "Select .yaml/.yml/.zip file",
|
|
2273
|
+
initialDir: initialDirectory,
|
|
2274
|
+
extensions: [".yaml", ".yml", ".zip"],
|
|
2275
|
+
onSubmit: setFile,
|
|
2276
|
+
onBack: () => navigate(-1)
|
|
2277
|
+
}
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
if (conflictMode === null) {
|
|
2281
|
+
return /* @__PURE__ */ jsx54(ViewLayout, { title: "Conflict mode (blank = API default)", children: /* @__PURE__ */ jsx54(
|
|
2282
|
+
MenuList,
|
|
2283
|
+
{
|
|
2284
|
+
options: [
|
|
2285
|
+
{ label: "Default", value: "" },
|
|
2286
|
+
{ label: "Overwrite", value: "overwrite" }
|
|
2287
|
+
],
|
|
2288
|
+
onSelect: (item) => setConflictMode(item.value ?? "")
|
|
2289
|
+
}
|
|
2290
|
+
) });
|
|
2291
|
+
}
|
|
2292
|
+
return /* @__PURE__ */ jsxs30(ViewLayout, { hintsHidden: true, hintsPosition: "bottom", children: [
|
|
2293
|
+
importTestResult.isLoading && /* @__PURE__ */ jsx54(Spinner22, { label: `Importing ${file}...` }),
|
|
2294
|
+
importTestResult.isError && /* @__PURE__ */ jsx54(StatusMessage24, { variant: "error", children: importTestResult.error }),
|
|
2295
|
+
importTestResult.data && /* @__PURE__ */ jsx54(StatusMessage24, { variant: "success", children: `Imported test: ${importTestResult.data.id ?? "(unknown)"}${importTestResult.data.name ? ` (${importTestResult.data.name})` : ""}` })
|
|
2296
|
+
] });
|
|
2297
|
+
};
|
|
2298
|
+
|
|
2299
|
+
// src/app/views/tests/TestsList.tsx
|
|
2300
|
+
import { useNavigate as useNavigate17 } from "react-router";
|
|
2301
|
+
import { jsx as jsx55 } from "react/jsx-runtime";
|
|
2302
|
+
var TestsList = () => {
|
|
2303
|
+
const navigate = useNavigate17();
|
|
2304
|
+
return /* @__PURE__ */ jsx55(
|
|
2305
|
+
BaseTestsList,
|
|
2306
|
+
{
|
|
2307
|
+
onSelectTest: (test) => navigate(ROUTES.tests.details(test.id), { state: { testName: getTestName(test) } })
|
|
2308
|
+
}
|
|
2309
|
+
);
|
|
2310
|
+
};
|
|
2311
|
+
|
|
2312
|
+
// src/app/views/tests/TestsMenu.tsx
|
|
2313
|
+
import { jsx as jsx56 } from "react/jsx-runtime";
|
|
2314
|
+
var TestsMenu = () => {
|
|
2315
|
+
return /* @__PURE__ */ jsx56(ViewLayout, { children: /* @__PURE__ */ jsx56(
|
|
2316
|
+
MenuList,
|
|
2317
|
+
{
|
|
2318
|
+
options: [
|
|
2319
|
+
{ label: "List tests", shortcut: "l", to: "/tests/list" },
|
|
2320
|
+
{
|
|
2321
|
+
label: "Import test",
|
|
2322
|
+
shortcut: "i",
|
|
2323
|
+
description: "YAML or ZIP",
|
|
2324
|
+
to: "/tests/import"
|
|
2325
|
+
},
|
|
2326
|
+
{ label: "Run a test", shortcut: "r", to: "/tests/run" }
|
|
2327
|
+
]
|
|
2328
|
+
}
|
|
2329
|
+
) });
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
// src/app/router.tsx
|
|
2333
|
+
import { jsx as jsx57, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
2334
|
+
var createNavigatorRouter = ({
|
|
2335
|
+
initialRoute,
|
|
2336
|
+
initialRouteState
|
|
2337
|
+
} = {}) => {
|
|
2338
|
+
const getInitialEntries = () => {
|
|
2339
|
+
if (initialRoute) {
|
|
2340
|
+
const routeEntry = initialRouteState === void 0 ? initialRoute : { pathname: initialRoute, state: initialRouteState };
|
|
2341
|
+
return [ROUTES.root(), routeEntry];
|
|
2342
|
+
}
|
|
2343
|
+
return [ROUTES.root()];
|
|
2344
|
+
};
|
|
2345
|
+
const initialEntries = getInitialEntries();
|
|
2346
|
+
const router = createMemoryRouter(
|
|
2347
|
+
createRoutesFromElements(
|
|
2348
|
+
/* @__PURE__ */ jsxs31(Route, { element: /* @__PURE__ */ jsx57(Navigator, {}), children: [
|
|
2349
|
+
/* @__PURE__ */ jsx57(Route, { index: true, element: /* @__PURE__ */ jsx57(MainMenu, {}) }),
|
|
2350
|
+
/* @__PURE__ */ jsx57(Route, { path: "/init", element: /* @__PURE__ */ jsx57(ProjectInit, {}) }),
|
|
2351
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests", element: /* @__PURE__ */ jsx57(TestsMenu, {}) }),
|
|
2352
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/list", element: /* @__PURE__ */ jsx57(TestsList, {}) }),
|
|
2353
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/import", element: /* @__PURE__ */ jsx57(TestImport, {}) }),
|
|
2354
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/run", element: /* @__PURE__ */ jsx57(RunTest, {}) }),
|
|
2355
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/:testId", element: /* @__PURE__ */ jsx57(TestDetails, {}) }),
|
|
2356
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/:testId/run", element: /* @__PURE__ */ jsx57(TestDetailsRun, {}) }),
|
|
2357
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/:testId/lastRun", element: /* @__PURE__ */ jsx57(TestDetailsLastRun, {}) }),
|
|
2358
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/:testId/export", element: /* @__PURE__ */ jsx57(TestDetailsExport, {}) }),
|
|
2359
|
+
/* @__PURE__ */ jsx57(Route, { path: "/tests/:testId/open", element: /* @__PURE__ */ jsx57(TestDetailsOpen, {}) }),
|
|
2360
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns", element: /* @__PURE__ */ jsx57(TestRunsMenu, {}) }),
|
|
2361
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns/list", element: /* @__PURE__ */ jsx57(TestRunsList2, {}) }),
|
|
2362
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns/:runId", element: /* @__PURE__ */ jsx57(TestRunDetails, {}) }),
|
|
2363
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns/:runId/debugArtifacts", element: /* @__PURE__ */ jsx57(TestRunDebugArtifacts, {}) }),
|
|
2364
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns/:runId/junitReport", element: /* @__PURE__ */ jsx57(TestRunJunitReport, {}) }),
|
|
2365
|
+
/* @__PURE__ */ jsx57(Route, { path: "/testruns/:runId/open", element: /* @__PURE__ */ jsx57(TestRunOpen, {}) }),
|
|
2366
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites", element: /* @__PURE__ */ jsx57(SuitesMenu, {}) }),
|
|
2367
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/list", element: /* @__PURE__ */ jsx57(SuitesList, {}) }),
|
|
2368
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/run", element: /* @__PURE__ */ jsx57(RunSuite, {}) }),
|
|
2369
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/:suiteId", element: /* @__PURE__ */ jsx57(SuiteDetails, {}) }),
|
|
2370
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/:suiteId/run", element: /* @__PURE__ */ jsx57(SuiteDetailsRun, {}) }),
|
|
2371
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/:suiteId/lastRun", element: /* @__PURE__ */ jsx57(SuiteDetailsLastRun, {}) }),
|
|
2372
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suites/:suiteId/open", element: /* @__PURE__ */ jsx57(SuiteDetailsOpen, {}) }),
|
|
2373
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suiteruns", element: /* @__PURE__ */ jsx57(SuiteRunsMenu, {}) }),
|
|
2374
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suiteruns/list", element: /* @__PURE__ */ jsx57(SuiteRunsList, {}) }),
|
|
2375
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suiteruns/:runId", element: /* @__PURE__ */ jsx57(SuiteRunDetails, {}) }),
|
|
2376
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suiteruns/:runId/showTestRuns", element: /* @__PURE__ */ jsx57(SuiteRunShowTestRuns, {}) }),
|
|
2377
|
+
/* @__PURE__ */ jsx57(Route, { path: "/suiteruns/:runId/open", element: /* @__PURE__ */ jsx57(SuiteRunOpen, {}) }),
|
|
2378
|
+
/* @__PURE__ */ jsx57(Route, { path: "/profiles", element: /* @__PURE__ */ jsx57(ProfilesMenu, {}) }),
|
|
2379
|
+
/* @__PURE__ */ jsx57(Route, { path: "/profiles/list", element: /* @__PURE__ */ jsx57(ProfilesList, {}) }),
|
|
2380
|
+
/* @__PURE__ */ jsx57(Route, { path: "/project", element: /* @__PURE__ */ jsx57(ProjectMenu, {}) }),
|
|
2381
|
+
/* @__PURE__ */ jsx57(Route, { path: "/project/switch", element: /* @__PURE__ */ jsx57(ProjectSwitch, {}) }),
|
|
2382
|
+
/* @__PURE__ */ jsx57(Route, { path: "/project/export", element: /* @__PURE__ */ jsx57(ProjectExport, {}) }),
|
|
2383
|
+
/* @__PURE__ */ jsx57(Route, { path: "/project/import", element: /* @__PURE__ */ jsx57(ProjectImport, {}) }),
|
|
2384
|
+
/* @__PURE__ */ jsx57(Route, { path: "/settings", element: /* @__PURE__ */ jsx57(SettingsMenu, {}) }),
|
|
2385
|
+
/* @__PURE__ */ jsx57(Route, { path: "/settings/setSessionToken", element: /* @__PURE__ */ jsx57(SettingsSetSessionToken, {}) }),
|
|
2386
|
+
/* @__PURE__ */ jsx57(Route, { path: "/logout", element: /* @__PURE__ */ jsx57(Logout, {}) })
|
|
2387
|
+
] })
|
|
2388
|
+
),
|
|
2389
|
+
{ initialEntries, initialIndex: initialEntries.length - 1 }
|
|
2390
|
+
);
|
|
2391
|
+
let currentPathname = router.state.location.pathname;
|
|
2392
|
+
router.subscribe((state) => {
|
|
2393
|
+
const nextPathname = state.location.pathname;
|
|
2394
|
+
if (nextPathname === currentPathname) return;
|
|
2395
|
+
currentPathname = nextPathname;
|
|
2396
|
+
analytics.trackTuiNavigation(nextPathname);
|
|
2397
|
+
});
|
|
2398
|
+
return router;
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
// src/app/NavigatorApp.tsx
|
|
2402
|
+
import { jsx as jsx58 } from "react/jsx-runtime";
|
|
2403
|
+
var NavigatorApp = ({ initialRoute, initialRouteState }) => {
|
|
2404
|
+
const [router] = useState17(() => createNavigatorRouter({ initialRoute, initialRouteState }));
|
|
2405
|
+
return /* @__PURE__ */ jsx58(NavigatorSessionProvider, { children: /* @__PURE__ */ jsx58(RouterProvider, { router }) });
|
|
2406
|
+
};
|
|
2407
|
+
export {
|
|
2408
|
+
NavigatorApp
|
|
2409
|
+
};
|
|
2410
|
+
//# sourceMappingURL=NavigatorApp-WSJMM3OT.js.map
|