@cliftonc/cfld 0.2.4 → 0.2.5
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/dist/cli.js +98 -54
- package/dist/{dashboard-GA37Z4H4.js → dashboard-ZJVAPOTE.js} +127 -49
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1206,23 +1206,15 @@ async function launch(ctx) {
|
|
|
1206
1206
|
const target = `localhost:${ctx.port}`;
|
|
1207
1207
|
const interactive = isInteractive() && !ctx.noUi;
|
|
1208
1208
|
let dashboard;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
split: Boolean(ctx.exec),
|
|
1219
|
-
devLabel: ctx.exec ? describeCommand(ctx.exec) : void 0,
|
|
1220
|
-
tunnelLogs: [],
|
|
1221
|
-
devLogs: []
|
|
1222
|
-
});
|
|
1223
|
-
} else {
|
|
1224
|
-
step("Setting up your tunnel\u2026");
|
|
1225
|
-
}
|
|
1209
|
+
let runner;
|
|
1210
|
+
let child;
|
|
1211
|
+
let result;
|
|
1212
|
+
let poll;
|
|
1213
|
+
let metricsPort = 0;
|
|
1214
|
+
let shuttingDown = false;
|
|
1215
|
+
let started = false;
|
|
1216
|
+
let busy = false;
|
|
1217
|
+
const childAbort = new AbortController();
|
|
1226
1218
|
const setupStep = (s) => dashboard ? dashboard.log(s, "tunnel") : step(s);
|
|
1227
1219
|
const setupDone = (s) => dashboard ? dashboard.log(s, "tunnel") : done(s);
|
|
1228
1220
|
const emitLine = (line) => {
|
|
@@ -1236,11 +1228,6 @@ async function launch(ctx) {
|
|
|
1236
1228
|
if (dashboard) dashboard.log(line, "dev");
|
|
1237
1229
|
else process.stderr.write(line + "\n");
|
|
1238
1230
|
};
|
|
1239
|
-
let runner;
|
|
1240
|
-
let child;
|
|
1241
|
-
let poll;
|
|
1242
|
-
let shuttingDown = false;
|
|
1243
|
-
const childAbort = new AbortController();
|
|
1244
1231
|
const shutdown = (opts = {}) => {
|
|
1245
1232
|
if (shuttingDown) return;
|
|
1246
1233
|
shuttingDown = true;
|
|
@@ -1253,6 +1240,65 @@ ${opts.reason}`);
|
|
|
1253
1240
|
if (child) reapChild(child).then(finish, finish);
|
|
1254
1241
|
else finish();
|
|
1255
1242
|
};
|
|
1243
|
+
const startTunnel = (port) => startRunner({
|
|
1244
|
+
configPath: result.configPath,
|
|
1245
|
+
tunnelName: result.tunnelName,
|
|
1246
|
+
certPath: result.certPath,
|
|
1247
|
+
metricsPort: port,
|
|
1248
|
+
onLine: (line) => emitLine(line),
|
|
1249
|
+
onStatus: (status) => {
|
|
1250
|
+
if (status === "reconnecting") dashboard?.update({ status: "reconnecting" });
|
|
1251
|
+
else if (status === "connecting" && !dashboard) note("Reconnecting\u2026");
|
|
1252
|
+
},
|
|
1253
|
+
onFatal: (message) => {
|
|
1254
|
+
warn(message);
|
|
1255
|
+
shutdown({ code: 1 });
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
const startDevServer = () => startChild({
|
|
1259
|
+
command: ctx.exec,
|
|
1260
|
+
cwd: ctx.cwd,
|
|
1261
|
+
env: ctx.noEnv ? void 0 : { [ctx.envKey]: url },
|
|
1262
|
+
onLine: (line) => emitDevLine(line),
|
|
1263
|
+
onExit: (code, signal) => {
|
|
1264
|
+
childAbort.abort();
|
|
1265
|
+
const how = code !== null ? `exited (code ${code})` : `was terminated${signal ? ` (${signal})` : ""}`;
|
|
1266
|
+
shutdown({
|
|
1267
|
+
reason: `Your dev command ${how} \u2014 stopping. Your tunnel URL is preserved.`,
|
|
1268
|
+
code: code ?? 1
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
const doRestartDev = async () => {
|
|
1273
|
+
if (!ctx.exec || !child) return;
|
|
1274
|
+
dashboard?.log("\u21BB restarting dev server\u2026", "dev");
|
|
1275
|
+
await reapChild(child);
|
|
1276
|
+
if (shuttingDown) return;
|
|
1277
|
+
child = startDevServer();
|
|
1278
|
+
};
|
|
1279
|
+
const doRestartTunnel = async () => {
|
|
1280
|
+
if (!runner || !result) return;
|
|
1281
|
+
dashboard?.log("\u21BB restarting tunnel\u2026", "tunnel");
|
|
1282
|
+
dashboard?.update({ status: "reconnecting" });
|
|
1283
|
+
runner.stop();
|
|
1284
|
+
metricsPort = await findFreePort();
|
|
1285
|
+
runner = await startTunnel(metricsPort);
|
|
1286
|
+
const r = await waitForReady(metricsPort).catch(() => void 0);
|
|
1287
|
+
if (r && !shuttingDown) dashboard?.update({ status: "live", connections: r.connections });
|
|
1288
|
+
};
|
|
1289
|
+
const guard = (fn) => () => {
|
|
1290
|
+
if (busy || shuttingDown || !started) return;
|
|
1291
|
+
busy = true;
|
|
1292
|
+
void Promise.resolve(fn()).finally(() => {
|
|
1293
|
+
busy = false;
|
|
1294
|
+
});
|
|
1295
|
+
};
|
|
1296
|
+
const restartDev = guard(doRestartDev);
|
|
1297
|
+
const restartTunnel = guard(doRestartTunnel);
|
|
1298
|
+
const restartAll = guard(async () => {
|
|
1299
|
+
await doRestartTunnel();
|
|
1300
|
+
await doRestartDev();
|
|
1301
|
+
});
|
|
1256
1302
|
process.once(
|
|
1257
1303
|
"SIGINT",
|
|
1258
1304
|
() => shutdown({ reason: "Stopping \u2014 your URL is preserved. Re-run `cfld`." })
|
|
@@ -1262,24 +1308,35 @@ ${opts.reason}`);
|
|
|
1262
1308
|
() => shutdown({ reason: "Stopping \u2014 your URL is preserved." })
|
|
1263
1309
|
);
|
|
1264
1310
|
process.once("SIGHUP", () => shutdown({ reason: "Terminal closed \u2014 stopping." }));
|
|
1311
|
+
if (interactive) {
|
|
1312
|
+
const { startDashboard } = await import("./dashboard-ZJVAPOTE.js");
|
|
1313
|
+
dashboard = startDashboard(
|
|
1314
|
+
{
|
|
1315
|
+
url,
|
|
1316
|
+
target,
|
|
1317
|
+
tunnelName: ctx.tunnelName,
|
|
1318
|
+
status: "connecting",
|
|
1319
|
+
connections: 0,
|
|
1320
|
+
requests: 0,
|
|
1321
|
+
split: Boolean(ctx.exec),
|
|
1322
|
+
devLabel: ctx.exec ? describeCommand(ctx.exec) : void 0,
|
|
1323
|
+
tunnelLogs: [],
|
|
1324
|
+
devLogs: []
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
restartAll,
|
|
1328
|
+
restartTunnel,
|
|
1329
|
+
restartDev: ctx.exec ? restartDev : void 0
|
|
1330
|
+
}
|
|
1331
|
+
);
|
|
1332
|
+
} else {
|
|
1333
|
+
step("Setting up your tunnel\u2026");
|
|
1334
|
+
}
|
|
1265
1335
|
if (ctx.exec) {
|
|
1266
1336
|
if (!dashboard) step(`Starting \`${describeCommand(ctx.exec)}\`\u2026`);
|
|
1267
|
-
child =
|
|
1268
|
-
command: ctx.exec,
|
|
1269
|
-
cwd: ctx.cwd,
|
|
1270
|
-
env: ctx.noEnv ? void 0 : { [ctx.envKey]: url },
|
|
1271
|
-
onLine: (line) => emitDevLine(line),
|
|
1272
|
-
onExit: (code, signal) => {
|
|
1273
|
-
childAbort.abort();
|
|
1274
|
-
const how = code !== null ? `exited (code ${code})` : `was terminated${signal ? ` (${signal})` : ""}`;
|
|
1275
|
-
shutdown({
|
|
1276
|
-
reason: `Your dev command ${how} \u2014 stopping. Your tunnel URL is preserved.`,
|
|
1277
|
-
code: code ?? 1
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1337
|
+
child = startDevServer();
|
|
1281
1338
|
}
|
|
1282
|
-
|
|
1339
|
+
result = await ctx.runReconcile({
|
|
1283
1340
|
onStep: (s) => setupStep(s),
|
|
1284
1341
|
confirmOverwrite: interactive ? (h, t) => {
|
|
1285
1342
|
const ask = () => askConfirm(`${h} already points to ${t}. Repoint it to this tunnel?`);
|
|
@@ -1299,22 +1356,8 @@ ${opts.reason}`);
|
|
|
1299
1356
|
result,
|
|
1300
1357
|
(msg) => dashboard ? dashboard.log(msg, "tunnel") : info(msg)
|
|
1301
1358
|
);
|
|
1302
|
-
|
|
1303
|
-
runner = await
|
|
1304
|
-
configPath: result.configPath,
|
|
1305
|
-
tunnelName: result.tunnelName,
|
|
1306
|
-
certPath: result.certPath,
|
|
1307
|
-
metricsPort,
|
|
1308
|
-
onLine: (line) => emitLine(line),
|
|
1309
|
-
onStatus: (status) => {
|
|
1310
|
-
if (status === "reconnecting") dashboard?.update({ status: "reconnecting" });
|
|
1311
|
-
else if (status === "connecting" && !dashboard) note("Reconnecting\u2026");
|
|
1312
|
-
},
|
|
1313
|
-
onFatal: (message) => {
|
|
1314
|
-
warn(message);
|
|
1315
|
-
shutdown({ code: 1 });
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1359
|
+
metricsPort = await findFreePort();
|
|
1360
|
+
runner = await startTunnel(metricsPort);
|
|
1318
1361
|
const ready = await waitForReady(metricsPort).catch((err) => {
|
|
1319
1362
|
child?.stop();
|
|
1320
1363
|
runner?.stop();
|
|
@@ -1324,6 +1367,7 @@ ${opts.reason}`);
|
|
|
1324
1367
|
err instanceof Error ? err.message : void 0
|
|
1325
1368
|
);
|
|
1326
1369
|
});
|
|
1370
|
+
started = true;
|
|
1327
1371
|
if (child && !shuttingDown) {
|
|
1328
1372
|
if (dashboard) dashboard.update({ status: "waiting", connections: ready.connections });
|
|
1329
1373
|
else step(`Waiting for your dev server on :${ctx.port}\u2026`);
|
|
@@ -1509,7 +1553,7 @@ ${opts.reason ?? "Stopped ephemeral tunnel."}`);
|
|
|
1509
1553
|
});
|
|
1510
1554
|
}
|
|
1511
1555
|
if (interactive) {
|
|
1512
|
-
const { startDashboard } = await import("./dashboard-
|
|
1556
|
+
const { startDashboard } = await import("./dashboard-ZJVAPOTE.js");
|
|
1513
1557
|
dashboard = startDashboard({
|
|
1514
1558
|
url,
|
|
1515
1559
|
target,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/ui/dashboard.tsx
|
|
4
|
+
import { spawn } from "child_process";
|
|
4
5
|
import "react";
|
|
5
|
-
import { Box, render, Text } from "ink";
|
|
6
|
+
import { Box, render, Text, useApp, useInput } from "ink";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var VIEW_ORDER = ["dev", "split", "tunnel"];
|
|
7
9
|
var MAX_BUFFER = 1e3;
|
|
8
10
|
var HEADER_ROWS = 5;
|
|
9
11
|
function statusDot(status) {
|
|
@@ -18,36 +20,53 @@ function statusDot(status) {
|
|
|
18
20
|
return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CC CONNECTING" });
|
|
19
21
|
}
|
|
20
22
|
}
|
|
23
|
+
function hotkeyHints(state) {
|
|
24
|
+
const dev = Boolean(state.split);
|
|
25
|
+
const row1 = state.canRestart ? "r restart all" : "";
|
|
26
|
+
const row2 = state.canRestart ? dev ? "d restart dev \xB7 t restart tunnel" : "t restart tunnel" : "";
|
|
27
|
+
const row3 = [dev ? "\u2039 \u203A cols" : "", "o open", "^C quit"].filter(Boolean).join(" \xB7 ");
|
|
28
|
+
return [row1, row2, row3];
|
|
29
|
+
}
|
|
21
30
|
function Header(state) {
|
|
22
31
|
const latency = state.avgLatencyMs !== void 0 ? ` \u26A1 ${state.avgLatencyMs}ms avg` : "";
|
|
32
|
+
const [h1, h2, h3] = hotkeyHints(state);
|
|
23
33
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
24
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
36
|
+
statusDot(state.status),
|
|
37
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
38
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: state.url })
|
|
39
|
+
] }),
|
|
40
|
+
h1 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
41
|
+
" ",
|
|
42
|
+
h1
|
|
43
|
+
] }) : null
|
|
28
44
|
] }),
|
|
29
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
45
|
+
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
30
46
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
31
47
|
"\u2192 ",
|
|
32
|
-
state.target
|
|
33
|
-
] }),
|
|
34
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
48
|
+
state.target,
|
|
35
49
|
" ",
|
|
36
50
|
state.connections,
|
|
37
|
-
" edge conns"
|
|
38
|
-
|
|
51
|
+
" edge conns",
|
|
52
|
+
state.ephemeral ? " \xB7 ephemeral" : ""
|
|
53
|
+
] }),
|
|
54
|
+
h2 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
55
|
+
" ",
|
|
56
|
+
h2
|
|
57
|
+
] }) : null
|
|
39
58
|
] }),
|
|
40
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
59
|
+
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
41
60
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
42
61
|
"\u2191 ",
|
|
43
62
|
state.requests,
|
|
44
63
|
" reqs",
|
|
45
64
|
latency
|
|
46
65
|
] }),
|
|
47
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
] })
|
|
66
|
+
h3 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
67
|
+
" ",
|
|
68
|
+
h3
|
|
69
|
+
] }) : null
|
|
51
70
|
] })
|
|
52
71
|
] });
|
|
53
72
|
}
|
|
@@ -82,51 +101,110 @@ function LogColumn({
|
|
|
82
101
|
}
|
|
83
102
|
function Dashboard(state) {
|
|
84
103
|
const termRows = process.stdout.rows || 24;
|
|
104
|
+
const view = state.split ? state.view ?? "split" : "tunnel";
|
|
85
105
|
const titleRows = state.split ? 1 : 0;
|
|
86
106
|
const logRows = Math.max(3, termRows - HEADER_ROWS - titleRows - 1);
|
|
107
|
+
const devColumn = (divider) => /* @__PURE__ */ jsx(
|
|
108
|
+
LogColumn,
|
|
109
|
+
{
|
|
110
|
+
title: state.devLabel ?? "dev server",
|
|
111
|
+
titleColor: "cyan",
|
|
112
|
+
lines: state.devLogs,
|
|
113
|
+
rows: logRows,
|
|
114
|
+
placeholder: "waiting for dev-server output\u2026",
|
|
115
|
+
divider
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
const tunnelColumn = (divider) => /* @__PURE__ */ jsx(
|
|
119
|
+
LogColumn,
|
|
120
|
+
{
|
|
121
|
+
title: state.split ? "tunnel" : void 0,
|
|
122
|
+
titleColor: "blue",
|
|
123
|
+
lines: state.tunnelLogs,
|
|
124
|
+
rows: logRows,
|
|
125
|
+
placeholder: state.split ? "waiting for tunnel activity\u2026" : state.ephemeral ? "waiting for traffic\u2026 (Ctrl-C stops; this URL is temporary)" : "waiting for traffic\u2026 (Ctrl-C stops; your URL is preserved)",
|
|
126
|
+
divider
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
let body;
|
|
130
|
+
if (view === "split") {
|
|
131
|
+
body = /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexDirection: "row", paddingX: 1, paddingTop: 1, children: [
|
|
132
|
+
devColumn(),
|
|
133
|
+
tunnelColumn(true)
|
|
134
|
+
] });
|
|
135
|
+
} else {
|
|
136
|
+
body = /* @__PURE__ */ jsx(Box, { flexGrow: 1, paddingX: 1, paddingTop: 1, children: view === "dev" ? devColumn() : tunnelColumn() });
|
|
137
|
+
}
|
|
87
138
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [
|
|
88
139
|
/* @__PURE__ */ jsx(Header, { ...state }),
|
|
89
|
-
|
|
90
|
-
/* @__PURE__ */ jsx(
|
|
91
|
-
LogColumn,
|
|
92
|
-
{
|
|
93
|
-
title: state.devLabel ?? "dev server",
|
|
94
|
-
titleColor: "cyan",
|
|
95
|
-
lines: state.devLogs,
|
|
96
|
-
rows: logRows,
|
|
97
|
-
placeholder: "waiting for dev-server output\u2026"
|
|
98
|
-
}
|
|
99
|
-
),
|
|
100
|
-
/* @__PURE__ */ jsx(
|
|
101
|
-
LogColumn,
|
|
102
|
-
{
|
|
103
|
-
title: "tunnel",
|
|
104
|
-
titleColor: "blue",
|
|
105
|
-
lines: state.tunnelLogs,
|
|
106
|
-
rows: logRows,
|
|
107
|
-
placeholder: "waiting for tunnel activity\u2026",
|
|
108
|
-
divider: true
|
|
109
|
-
}
|
|
110
|
-
)
|
|
111
|
-
] }) : /* @__PURE__ */ jsx(Box, { flexGrow: 1, paddingX: 1, paddingTop: 1, children: /* @__PURE__ */ jsx(
|
|
112
|
-
LogColumn,
|
|
113
|
-
{
|
|
114
|
-
lines: state.tunnelLogs,
|
|
115
|
-
rows: logRows,
|
|
116
|
-
placeholder: state.ephemeral ? "waiting for traffic\u2026 (Ctrl-C stops; this URL is temporary)" : "waiting for traffic\u2026 (Ctrl-C stops; your URL is preserved)"
|
|
117
|
-
}
|
|
118
|
-
) })
|
|
140
|
+
body
|
|
119
141
|
] });
|
|
120
142
|
}
|
|
143
|
+
function App({
|
|
144
|
+
state,
|
|
145
|
+
actions,
|
|
146
|
+
onCycleView,
|
|
147
|
+
onOpen
|
|
148
|
+
}) {
|
|
149
|
+
const { exit } = useApp();
|
|
150
|
+
useInput((input, key) => {
|
|
151
|
+
if (key.leftArrow || input === "<" || input === ",") return onCycleView(-1);
|
|
152
|
+
if (key.rightArrow || input === ">" || input === ".") return onCycleView(1);
|
|
153
|
+
switch (input.toLowerCase()) {
|
|
154
|
+
case "r":
|
|
155
|
+
return actions.restartAll?.();
|
|
156
|
+
case "d":
|
|
157
|
+
return actions.restartDev?.();
|
|
158
|
+
case "t":
|
|
159
|
+
return actions.restartTunnel?.();
|
|
160
|
+
case "o":
|
|
161
|
+
return onOpen();
|
|
162
|
+
case "q":
|
|
163
|
+
return exit();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return /* @__PURE__ */ jsx(Dashboard, { ...state });
|
|
167
|
+
}
|
|
168
|
+
function openInBrowser(url) {
|
|
169
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
170
|
+
try {
|
|
171
|
+
spawn(cmd, [url], {
|
|
172
|
+
stdio: "ignore",
|
|
173
|
+
detached: true,
|
|
174
|
+
shell: process.platform === "win32"
|
|
175
|
+
}).unref();
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
121
179
|
var ENTER_ALT_SCREEN = "\x1B[?1049h";
|
|
122
180
|
var LEAVE_ALT_SCREEN = "\x1B[?1049l";
|
|
123
|
-
function startDashboard(initial) {
|
|
181
|
+
function startDashboard(initial, actions = {}) {
|
|
124
182
|
const state = {
|
|
125
183
|
...initial,
|
|
184
|
+
view: initial.view ?? (initial.split ? "split" : "tunnel"),
|
|
185
|
+
canRestart: Boolean(actions.restartAll || actions.restartTunnel),
|
|
126
186
|
tunnelLogs: [...initial.tunnelLogs],
|
|
127
187
|
devLogs: [...initial.devLogs]
|
|
128
188
|
};
|
|
129
|
-
const
|
|
189
|
+
const cycleView = (delta) => {
|
|
190
|
+
if (!state.split) return;
|
|
191
|
+
const i = VIEW_ORDER.indexOf(state.view ?? "split");
|
|
192
|
+
const next = VIEW_ORDER[Math.min(VIEW_ORDER.length - 1, Math.max(0, i + delta))];
|
|
193
|
+
if (next !== state.view) {
|
|
194
|
+
state.view = next;
|
|
195
|
+
rerender();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const element = () => /* @__PURE__ */ jsx(
|
|
199
|
+
App,
|
|
200
|
+
{
|
|
201
|
+
state,
|
|
202
|
+
actions,
|
|
203
|
+
onCycleView: cycleView,
|
|
204
|
+
onOpen: () => openInBrowser(state.url)
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
const mount = () => render(element(), {
|
|
130
208
|
stdout: process.stdout,
|
|
131
209
|
// We drive graceful shutdown ourselves; ink's Ctrl-C simply resolves
|
|
132
210
|
// waitUntilExit, and the caller then stops the runner (preserving state).
|
|
@@ -136,7 +214,7 @@ function startDashboard(initial) {
|
|
|
136
214
|
let instance = mount();
|
|
137
215
|
let paused = false;
|
|
138
216
|
const rerender = () => {
|
|
139
|
-
if (!paused) instance.rerender(
|
|
217
|
+
if (!paused) instance.rerender(element());
|
|
140
218
|
};
|
|
141
219
|
let altActive = true;
|
|
142
220
|
const leaveAltScreen = () => {
|