@cliftonc/cfld 0.2.2 → 0.2.4
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 +150 -104
- package/dist/{dashboard-Q6WI32AK.js → dashboard-GA37Z4H4.js} +22 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -637,13 +637,13 @@ async function startRunner(options) {
|
|
|
637
637
|
);
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
|
-
const
|
|
640
|
+
const delay2 = BACKOFF_MS[Math.min(rapidFailures, BACKOFF_MS.length - 1)];
|
|
641
641
|
options.onStatus?.("reconnecting");
|
|
642
642
|
restartTimer = setTimeout(() => {
|
|
643
643
|
if (stopped) return;
|
|
644
644
|
options.onStatus?.("connecting");
|
|
645
645
|
spawnOnce();
|
|
646
|
-
},
|
|
646
|
+
}, delay2);
|
|
647
647
|
restartTimer.unref();
|
|
648
648
|
});
|
|
649
649
|
};
|
|
@@ -700,13 +700,15 @@ function startChild(options) {
|
|
|
700
700
|
const exited = new Promise((resolve) => {
|
|
701
701
|
markExited = resolve;
|
|
702
702
|
});
|
|
703
|
+
const ownPgid = getpgid(process.pid);
|
|
703
704
|
const trackedPgids = /* @__PURE__ */ new Set();
|
|
705
|
+
const track = (g) => {
|
|
706
|
+
if (g > 1 && g !== process.pid && g !== ownPgid) trackedPgids.add(g);
|
|
707
|
+
};
|
|
708
|
+
if (child.pid !== void 0) track(child.pid);
|
|
704
709
|
const trackTree = () => {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
for (const g of descendantPgids(pid)) {
|
|
708
|
-
if (g > 1 && g !== process.pid) trackedPgids.add(g);
|
|
709
|
-
}
|
|
710
|
+
if (child.pid === void 0) return;
|
|
711
|
+
for (const g of descendantPgids(child.pid)) track(g);
|
|
710
712
|
};
|
|
711
713
|
const poll = setInterval(trackTree, options.pollTreeMs ?? 1500);
|
|
712
714
|
poll.unref();
|
|
@@ -729,8 +731,6 @@ function startChild(options) {
|
|
|
729
731
|
});
|
|
730
732
|
const signalTree = (signal) => {
|
|
731
733
|
trackTree();
|
|
732
|
-
const pid = child.pid;
|
|
733
|
-
if (pid !== void 0) trySignalGroup(pid, signal);
|
|
734
734
|
for (const g of trackedPgids) trySignalGroup(g, signal);
|
|
735
735
|
};
|
|
736
736
|
return {
|
|
@@ -738,15 +738,15 @@ function startChild(options) {
|
|
|
738
738
|
get pid() {
|
|
739
739
|
return child.pid;
|
|
740
740
|
},
|
|
741
|
+
treeAlive() {
|
|
742
|
+
if (trackedPgids.size === 0) return false;
|
|
743
|
+
const live = livePgids();
|
|
744
|
+
for (const g of trackedPgids) if (live.has(g)) return true;
|
|
745
|
+
return false;
|
|
746
|
+
},
|
|
741
747
|
stop() {
|
|
742
|
-
if (stopped) return;
|
|
743
748
|
stopped = true;
|
|
744
|
-
clearInterval(poll);
|
|
745
749
|
signalTree("SIGINT");
|
|
746
|
-
const t = setTimeout(() => {
|
|
747
|
-
if (child.exitCode === null && !child.signalCode) signalTree("SIGKILL");
|
|
748
|
-
}, 4e3);
|
|
749
|
-
t.unref();
|
|
750
750
|
},
|
|
751
751
|
kill() {
|
|
752
752
|
clearInterval(poll);
|
|
@@ -793,18 +793,36 @@ function trySignalGroup(pgid, signal) {
|
|
|
793
793
|
} catch {
|
|
794
794
|
}
|
|
795
795
|
}
|
|
796
|
-
function
|
|
796
|
+
function getpgid(pid) {
|
|
797
|
+
try {
|
|
798
|
+
const n = Number(execSync(`ps -o pgid= -p ${pid}`, { encoding: "utf8" }).trim());
|
|
799
|
+
return Number.isInteger(n) ? n : void 0;
|
|
800
|
+
} catch {
|
|
801
|
+
return void 0;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function livePgids() {
|
|
805
|
+
const live = /* @__PURE__ */ new Set();
|
|
806
|
+
try {
|
|
807
|
+
for (const line of execSync("ps -A -o pgid=", { encoding: "utf8" }).split("\n")) {
|
|
808
|
+
const n = Number(line.trim());
|
|
809
|
+
if (Number.isInteger(n) && n > 0) live.add(n);
|
|
810
|
+
}
|
|
811
|
+
} catch {
|
|
812
|
+
}
|
|
813
|
+
return live;
|
|
814
|
+
}
|
|
815
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
816
|
+
async function reapChild(child, graceMs = 3e3) {
|
|
797
817
|
child.stop();
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
child.exited.then(done2);
|
|
807
|
-
});
|
|
818
|
+
await Promise.race([child.exited, delay(Math.min(graceMs, 1200))]);
|
|
819
|
+
const deadline = Date.now() + graceMs + 5e3;
|
|
820
|
+
for (; ; ) {
|
|
821
|
+
child.kill();
|
|
822
|
+
if (!child.treeAlive()) return;
|
|
823
|
+
if (Date.now() >= deadline) return;
|
|
824
|
+
await delay(200);
|
|
825
|
+
}
|
|
808
826
|
}
|
|
809
827
|
function wireLines2(child, stream, onLine) {
|
|
810
828
|
const source = stream === "stdout" ? child.stdout : child.stderr;
|
|
@@ -1155,44 +1173,45 @@ async function runCommand(flags) {
|
|
|
1155
1173
|
}
|
|
1156
1174
|
const primary = routes[0];
|
|
1157
1175
|
const certPath = await ensureCert(zone, { interactive });
|
|
1158
|
-
|
|
1159
|
-
{ slug, tunnelName, zone, certPath, routes, projectDir: cwd, force: flags.force },
|
|
1160
|
-
{
|
|
1161
|
-
onStep: (s) => step(s),
|
|
1162
|
-
confirmOverwrite: interactive ? (h, t) => askConfirm(`${h} already points to ${t}. Repoint it to this tunnel?`) : void 0
|
|
1163
|
-
}
|
|
1164
|
-
);
|
|
1165
|
-
done(result.created ? `Created tunnel ${tunnelName}` : `Reusing tunnel ${tunnelName}`);
|
|
1166
|
-
for (const r of routes) done(`DNS ${r.hostname} \u2192 :${r.port}`);
|
|
1167
|
-
writeProjectConfig(cwd, {
|
|
1168
|
-
name: slug,
|
|
1169
|
-
zone,
|
|
1170
|
-
uuid: result.uuid,
|
|
1171
|
-
hostname: primary.hostname,
|
|
1172
|
-
port: primary.port,
|
|
1173
|
-
envKey
|
|
1174
|
-
});
|
|
1175
|
-
if (ensureGitignored(cwd, ".cfld.json")) info("Added .cfld.json to .gitignore.");
|
|
1176
|
-
await launch(result, {
|
|
1176
|
+
await launch({
|
|
1177
1177
|
cwd,
|
|
1178
|
+
slug,
|
|
1179
|
+
zone,
|
|
1178
1180
|
envKey,
|
|
1179
1181
|
noEnv: flags.noEnv,
|
|
1182
|
+
noUi: flags.noUi,
|
|
1183
|
+
url: `https://${primary.hostname}`,
|
|
1180
1184
|
port: primary.port,
|
|
1181
|
-
|
|
1185
|
+
tunnelName,
|
|
1186
|
+
exec: flags.exec,
|
|
1187
|
+
runReconcile: (events) => reconcile(
|
|
1188
|
+
{ slug, tunnelName, zone, certPath, routes, projectDir: cwd, force: flags.force },
|
|
1189
|
+
events
|
|
1190
|
+
),
|
|
1191
|
+
onReconciled: (result, notify) => {
|
|
1192
|
+
writeProjectConfig(cwd, {
|
|
1193
|
+
name: slug,
|
|
1194
|
+
zone,
|
|
1195
|
+
uuid: result.uuid,
|
|
1196
|
+
hostname: primary.hostname,
|
|
1197
|
+
port: primary.port,
|
|
1198
|
+
envKey
|
|
1199
|
+
});
|
|
1200
|
+
if (ensureGitignored(cwd, ".cfld.json")) notify("Added .cfld.json to .gitignore.");
|
|
1201
|
+
}
|
|
1182
1202
|
});
|
|
1183
1203
|
}
|
|
1184
|
-
async function launch(
|
|
1185
|
-
const
|
|
1186
|
-
const url = `https://${result.hostname}`;
|
|
1204
|
+
async function launch(ctx) {
|
|
1205
|
+
const url = ctx.url;
|
|
1187
1206
|
const target = `localhost:${ctx.port}`;
|
|
1188
|
-
const interactive = isInteractive();
|
|
1207
|
+
const interactive = isInteractive() && !ctx.noUi;
|
|
1189
1208
|
let dashboard;
|
|
1190
1209
|
if (interactive) {
|
|
1191
|
-
const { startDashboard } = await import("./dashboard-
|
|
1210
|
+
const { startDashboard } = await import("./dashboard-GA37Z4H4.js");
|
|
1192
1211
|
dashboard = startDashboard({
|
|
1193
1212
|
url,
|
|
1194
1213
|
target,
|
|
1195
|
-
tunnelName:
|
|
1214
|
+
tunnelName: ctx.tunnelName,
|
|
1196
1215
|
status: "connecting",
|
|
1197
1216
|
connections: 0,
|
|
1198
1217
|
requests: 0,
|
|
@@ -1202,8 +1221,10 @@ async function launch(result, ctx) {
|
|
|
1202
1221
|
devLogs: []
|
|
1203
1222
|
});
|
|
1204
1223
|
} else {
|
|
1205
|
-
step("
|
|
1224
|
+
step("Setting up your tunnel\u2026");
|
|
1206
1225
|
}
|
|
1226
|
+
const setupStep = (s) => dashboard ? dashboard.log(s, "tunnel") : step(s);
|
|
1227
|
+
const setupDone = (s) => dashboard ? dashboard.log(s, "tunnel") : done(s);
|
|
1207
1228
|
const emitLine = (line) => {
|
|
1208
1229
|
const formatted = formatLogLine(line);
|
|
1209
1230
|
if (!formatted) return;
|
|
@@ -1228,25 +1249,19 @@ async function launch(result, ctx) {
|
|
|
1228
1249
|
dashboard?.stop();
|
|
1229
1250
|
if (opts.reason) note(`
|
|
1230
1251
|
${opts.reason}`);
|
|
1231
|
-
const
|
|
1232
|
-
if (child) reapChild(child).then(
|
|
1233
|
-
else
|
|
1252
|
+
const finish = () => process.exit(opts.code ?? 0);
|
|
1253
|
+
if (child) reapChild(child).then(finish, finish);
|
|
1254
|
+
else finish();
|
|
1234
1255
|
};
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
},
|
|
1245
|
-
onFatal: (message) => {
|
|
1246
|
-
warn(message);
|
|
1247
|
-
shutdown({ code: 1 });
|
|
1248
|
-
}
|
|
1249
|
-
});
|
|
1256
|
+
process.once(
|
|
1257
|
+
"SIGINT",
|
|
1258
|
+
() => shutdown({ reason: "Stopping \u2014 your URL is preserved. Re-run `cfld`." })
|
|
1259
|
+
);
|
|
1260
|
+
process.once(
|
|
1261
|
+
"SIGTERM",
|
|
1262
|
+
() => shutdown({ reason: "Stopping \u2014 your URL is preserved." })
|
|
1263
|
+
);
|
|
1264
|
+
process.once("SIGHUP", () => shutdown({ reason: "Terminal closed \u2014 stopping." }));
|
|
1250
1265
|
if (ctx.exec) {
|
|
1251
1266
|
if (!dashboard) step(`Starting \`${describeCommand(ctx.exec)}\`\u2026`);
|
|
1252
1267
|
child = startChild({
|
|
@@ -1264,16 +1279,42 @@ ${opts.reason}`);
|
|
|
1264
1279
|
}
|
|
1265
1280
|
});
|
|
1266
1281
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
() =>
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
);
|
|
1276
|
-
|
|
1282
|
+
const result = await ctx.runReconcile({
|
|
1283
|
+
onStep: (s) => setupStep(s),
|
|
1284
|
+
confirmOverwrite: interactive ? (h, t) => {
|
|
1285
|
+
const ask = () => askConfirm(`${h} already points to ${t}. Repoint it to this tunnel?`);
|
|
1286
|
+
return dashboard ? dashboard.suspend(ask) : ask();
|
|
1287
|
+
} : void 0
|
|
1288
|
+
}).catch(async (err) => {
|
|
1289
|
+
dashboard?.stop();
|
|
1290
|
+
if (child) await reapChild(child);
|
|
1291
|
+
throw err;
|
|
1292
|
+
});
|
|
1293
|
+
if (shuttingDown) return;
|
|
1294
|
+
setupDone(
|
|
1295
|
+
result.created ? `Created tunnel ${result.tunnelName}` : `Reusing tunnel ${result.tunnelName}`
|
|
1296
|
+
);
|
|
1297
|
+
for (const r of result.routes) setupDone(`DNS ${r.hostname} \u2192 :${r.port}`);
|
|
1298
|
+
ctx.onReconciled?.(
|
|
1299
|
+
result,
|
|
1300
|
+
(msg) => dashboard ? dashboard.log(msg, "tunnel") : info(msg)
|
|
1301
|
+
);
|
|
1302
|
+
const metricsPort = await findFreePort();
|
|
1303
|
+
runner = await startRunner({
|
|
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
|
+
});
|
|
1277
1318
|
const ready = await waitForReady(metricsPort).catch((err) => {
|
|
1278
1319
|
child?.stop();
|
|
1279
1320
|
runner?.stop();
|
|
@@ -1411,7 +1452,7 @@ async function startQuickTunnel(port, opts = {}) {
|
|
|
1411
1452
|
async function quickCommand(flags) {
|
|
1412
1453
|
const port = await resolvePort2(flags);
|
|
1413
1454
|
const target = `localhost:${port}`;
|
|
1414
|
-
const interactive = isInteractive();
|
|
1455
|
+
const interactive = isInteractive() && !flags.noUi;
|
|
1415
1456
|
const metricsPort = await findFreePort();
|
|
1416
1457
|
const envKey = flags.envKey ?? "PUBLIC_URL";
|
|
1417
1458
|
if (!interactive) step(`Starting an ephemeral tunnel to ${target}\u2026`);
|
|
@@ -1444,9 +1485,9 @@ async function quickCommand(flags) {
|
|
|
1444
1485
|
dashboard?.stop();
|
|
1445
1486
|
note(`
|
|
1446
1487
|
${opts.reason ?? "Stopped ephemeral tunnel."}`);
|
|
1447
|
-
const
|
|
1448
|
-
if (child) reapChild(child).then(
|
|
1449
|
-
else
|
|
1488
|
+
const finish = () => process.exit(opts.code ?? 0);
|
|
1489
|
+
if (child) reapChild(child).then(finish, finish);
|
|
1490
|
+
else finish();
|
|
1450
1491
|
};
|
|
1451
1492
|
tunnel = await startQuickTunnel(port, { metricsPort, onLine: emitLine });
|
|
1452
1493
|
const url = tunnel.url;
|
|
@@ -1468,7 +1509,7 @@ ${opts.reason ?? "Stopped ephemeral tunnel."}`);
|
|
|
1468
1509
|
});
|
|
1469
1510
|
}
|
|
1470
1511
|
if (interactive) {
|
|
1471
|
-
const { startDashboard } = await import("./dashboard-
|
|
1512
|
+
const { startDashboard } = await import("./dashboard-GA37Z4H4.js");
|
|
1472
1513
|
dashboard = startDashboard({
|
|
1473
1514
|
url,
|
|
1474
1515
|
target,
|
|
@@ -1484,10 +1525,9 @@ ${opts.reason ?? "Stopped ephemeral tunnel."}`);
|
|
|
1484
1525
|
ephemeral: true
|
|
1485
1526
|
});
|
|
1486
1527
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
}
|
|
1528
|
+
process.once("SIGINT", () => shutdown());
|
|
1529
|
+
process.once("SIGTERM", () => shutdown());
|
|
1530
|
+
process.once("SIGHUP", () => shutdown());
|
|
1491
1531
|
if (child) {
|
|
1492
1532
|
if (!dashboard) step(`Waiting for your dev server on :${port}\u2026`);
|
|
1493
1533
|
const up = await waitForPort(port, { signal: childAbort.signal });
|
|
@@ -1839,24 +1879,27 @@ async function upCommand(name) {
|
|
|
1839
1879
|
}
|
|
1840
1880
|
const slug = entry.name.replace(/^cfld-/, "");
|
|
1841
1881
|
const routes = entry.routes?.length ? entry.routes : [{ hostname: entry.hostname, port: entry.port }];
|
|
1842
|
-
|
|
1843
|
-
{
|
|
1844
|
-
slug,
|
|
1845
|
-
tunnelName: entry.name,
|
|
1846
|
-
zone: entry.zone,
|
|
1847
|
-
certPath: cert,
|
|
1848
|
-
routes,
|
|
1849
|
-
projectDir: entry.projectDir
|
|
1850
|
-
},
|
|
1851
|
-
{ onStep: (s) => step(s) }
|
|
1852
|
-
);
|
|
1853
|
-
done(result.created ? `Created tunnel ${entry.name}` : `Reusing tunnel ${entry.name}`);
|
|
1854
|
-
await launch(result, {
|
|
1882
|
+
await launch({
|
|
1855
1883
|
cwd: entry.projectDir,
|
|
1884
|
+
slug,
|
|
1885
|
+
zone: entry.zone,
|
|
1856
1886
|
envKey: "PUBLIC_URL",
|
|
1857
1887
|
noEnv: true,
|
|
1858
1888
|
// don't touch a .env we may not be sitting in
|
|
1859
|
-
|
|
1889
|
+
url: `https://${entry.hostname}`,
|
|
1890
|
+
port: entry.port,
|
|
1891
|
+
tunnelName: entry.name,
|
|
1892
|
+
runReconcile: (events) => reconcile(
|
|
1893
|
+
{
|
|
1894
|
+
slug,
|
|
1895
|
+
tunnelName: entry.name,
|
|
1896
|
+
zone: entry.zone,
|
|
1897
|
+
certPath: cert,
|
|
1898
|
+
routes,
|
|
1899
|
+
projectDir: entry.projectDir
|
|
1900
|
+
},
|
|
1901
|
+
events
|
|
1902
|
+
)
|
|
1860
1903
|
});
|
|
1861
1904
|
}
|
|
1862
1905
|
|
|
@@ -2237,6 +2280,7 @@ ${pc7.bold("Options")}
|
|
|
2237
2280
|
--zone <domain> Cloudflare domain to use (e.g. example.com)
|
|
2238
2281
|
--env <KEY> .env key to write the URL to (default: PUBLIC_URL)
|
|
2239
2282
|
--no-env Don't write the URL to .env
|
|
2283
|
+
--no-ui Plain line-by-line output instead of the live dashboard
|
|
2240
2284
|
--exec <cmd> Dev command to run + supervise (shell form of \`-- <cmd>\`)
|
|
2241
2285
|
--force Skip confirmation (destroy)
|
|
2242
2286
|
-h, --help Show this help
|
|
@@ -2257,6 +2301,7 @@ async function main(rawArgv) {
|
|
|
2257
2301
|
zone: { type: "string" },
|
|
2258
2302
|
env: { type: "string" },
|
|
2259
2303
|
"no-env": { type: "boolean" },
|
|
2304
|
+
"no-ui": { type: "boolean" },
|
|
2260
2305
|
exec: { type: "string" },
|
|
2261
2306
|
quick: { type: "boolean" },
|
|
2262
2307
|
force: { type: "boolean" },
|
|
@@ -2280,6 +2325,7 @@ async function main(rawArgv) {
|
|
|
2280
2325
|
zone: values.zone,
|
|
2281
2326
|
envKey: values.env,
|
|
2282
2327
|
noEnv: values["no-env"],
|
|
2328
|
+
noUi: values["no-ui"],
|
|
2283
2329
|
quick: values.quick,
|
|
2284
2330
|
force: values.force,
|
|
2285
2331
|
reauth: values.reauth,
|
|
@@ -126,14 +126,18 @@ function startDashboard(initial) {
|
|
|
126
126
|
tunnelLogs: [...initial.tunnelLogs],
|
|
127
127
|
devLogs: [...initial.devLogs]
|
|
128
128
|
};
|
|
129
|
-
|
|
130
|
-
const instance = render(/* @__PURE__ */ jsx(Dashboard, { ...state }), {
|
|
129
|
+
const mount = () => render(/* @__PURE__ */ jsx(Dashboard, { ...state }), {
|
|
131
130
|
stdout: process.stdout,
|
|
132
131
|
// We drive graceful shutdown ourselves; ink's Ctrl-C simply resolves
|
|
133
132
|
// waitUntilExit, and the caller then stops the runner (preserving state).
|
|
134
133
|
exitOnCtrlC: true
|
|
135
134
|
});
|
|
136
|
-
|
|
135
|
+
process.stdout.write(ENTER_ALT_SCREEN);
|
|
136
|
+
let instance = mount();
|
|
137
|
+
let paused = false;
|
|
138
|
+
const rerender = () => {
|
|
139
|
+
if (!paused) instance.rerender(/* @__PURE__ */ jsx(Dashboard, { ...state }));
|
|
140
|
+
};
|
|
137
141
|
let altActive = true;
|
|
138
142
|
const leaveAltScreen = () => {
|
|
139
143
|
if (!altActive) return;
|
|
@@ -141,6 +145,7 @@ function startDashboard(initial) {
|
|
|
141
145
|
process.stdout.write(LEAVE_ALT_SCREEN);
|
|
142
146
|
};
|
|
143
147
|
const onResize = () => {
|
|
148
|
+
if (paused) return;
|
|
144
149
|
instance.clear();
|
|
145
150
|
rerender();
|
|
146
151
|
};
|
|
@@ -159,6 +164,20 @@ function startDashboard(initial) {
|
|
|
159
164
|
}
|
|
160
165
|
rerender();
|
|
161
166
|
},
|
|
167
|
+
async suspend(fn) {
|
|
168
|
+
paused = true;
|
|
169
|
+
instance.unmount();
|
|
170
|
+
leaveAltScreen();
|
|
171
|
+
try {
|
|
172
|
+
return await fn();
|
|
173
|
+
} finally {
|
|
174
|
+
process.stdout.write(ENTER_ALT_SCREEN);
|
|
175
|
+
altActive = true;
|
|
176
|
+
instance = mount();
|
|
177
|
+
paused = false;
|
|
178
|
+
rerender();
|
|
179
|
+
}
|
|
180
|
+
},
|
|
162
181
|
waitUntilExit() {
|
|
163
182
|
return instance.waitUntilExit();
|
|
164
183
|
},
|