@dutodev/cli 0.4.2 → 0.4.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/README.md +2 -0
- package/dist/index.js +55 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,6 +106,8 @@ Com os túneis no ar, o terminal mostra cada túnel (URL, destino local, requisi
|
|
|
106
106
|
| `tab`, `↑↓` | Alterna entre túneis e requisições e move a seleção |
|
|
107
107
|
| `q` ou Ctrl+C | Fecha os túneis e sai |
|
|
108
108
|
|
|
109
|
+
Se o túnel está no ar mas nada responde na porta local, quem abrir o endereço no navegador vê uma página do Duto que tenta de novo a cada 3 segundos e volta sozinha quando o serviço sobe. Clientes que não são navegador (`curl`, APIs) recebem a mesma resposta em texto. Em ambos os casos o status é `503` com `Retry-After: 3`.
|
|
110
|
+
|
|
109
111
|
Fora de um terminal interativo (pipe, CI) ou com `--no-ui`, a CLI imprime um log simples. No Windows a tela cheia vem desligada; use `--ui` para tentar.
|
|
110
112
|
|
|
111
113
|
## Comandos
|
package/dist/index.js
CHANGED
|
@@ -362,6 +362,52 @@ async function isPortListening(port, timeoutMs = 500) {
|
|
|
362
362
|
// src/session/proxy.ts
|
|
363
363
|
import { Agent, createServer, request } from "http";
|
|
364
364
|
import { connect as connect2 } from "net";
|
|
365
|
+
|
|
366
|
+
// src/session/errorPage.ts
|
|
367
|
+
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
368
|
+
function wantsHtml(accept) {
|
|
369
|
+
return /\btext\/html\b/i.test(accept ?? "");
|
|
370
|
+
}
|
|
371
|
+
function unavailablePage(o) {
|
|
372
|
+
const tunnel2 = esc(o.tunnel);
|
|
373
|
+
const local = `localhost:${o.port}`;
|
|
374
|
+
return `<!doctype html>
|
|
375
|
+
<html lang="pt-BR">
|
|
376
|
+
<head>
|
|
377
|
+
<meta charset="utf-8">
|
|
378
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
379
|
+
${o.retry ? '<meta http-equiv="refresh" content="3">\n' : ""}<meta name="robots" content="noindex">
|
|
380
|
+
<title>Nada respondendo em ${local} \xB7 duto</title>
|
|
381
|
+
<style>
|
|
382
|
+
:root{--bg:#f6f6f3;--fg:#0f1110;--muted:#5f6360;--line:#dcddd8;--card:#fff;--signal:#2dd4bf;--signal-text:#0f766e}
|
|
383
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0b0d0c;--fg:#e9ebe7;--muted:#8a8f8b;--line:#222624;--card:#111413;--signal-text:#2dd4bf}}
|
|
384
|
+
*{box-sizing:border-box}
|
|
385
|
+
body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px 16px;background:var(--bg);color:var(--fg);font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
386
|
+
main{width:100%;max-width:520px;border:1px solid var(--line);border-radius:.3rem;background:var(--card);padding:28px}
|
|
387
|
+
.mark{display:flex;align-items:center;gap:8px;font:600 13px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.02em;color:var(--muted);margin-bottom:22px}
|
|
388
|
+
.dot{width:8px;height:8px;border-radius:50%;border:1.5px solid var(--signal)}
|
|
389
|
+
h1{font-size:21px;line-height:1.3;margin:0 0 10px;font-weight:650;letter-spacing:-.01em}
|
|
390
|
+
p{margin:0 0 12px;color:var(--muted)}
|
|
391
|
+
code{font:13px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fg)}
|
|
392
|
+
h1 code{font-size:.86em;font-weight:600}
|
|
393
|
+
.retry{margin-top:20px;padding-top:14px;border-top:1px solid var(--line);font-size:13px;color:var(--muted)}
|
|
394
|
+
.retry b{color:var(--signal-text);font-weight:600}
|
|
395
|
+
</style>
|
|
396
|
+
</head>
|
|
397
|
+
<body>
|
|
398
|
+
<main>
|
|
399
|
+
<div class="mark"><span class="dot"></span>duto \xB7 ${tunnel2}</div>
|
|
400
|
+
<h1>Nada respondendo em <code>${local}</code></h1>
|
|
401
|
+
<p>O t\xFAnel <code>${tunnel2}</code> est\xE1 no ar, mas o servi\xE7o local n\xE3o respondeu. Suba o servidor na m\xE1quina que abriu o t\xFAnel.</p>
|
|
402
|
+
<p>Se voc\xEA s\xF3 recebeu este link, avise quem o enviou.</p>
|
|
403
|
+
<div class="retry">${o.retry ? "<b>Tentando de novo a cada 3 s.</b> A p\xE1gina volta sozinha quando o servi\xE7o responder." : "Recarregue a p\xE1gina quando o servi\xE7o estiver no ar."}</div>
|
|
404
|
+
</main>
|
|
405
|
+
</body>
|
|
406
|
+
</html>
|
|
407
|
+
`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/session/proxy.ts
|
|
365
411
|
var TEXT = /^(text\/|application\/(json|xml|javascript|x-www-form-urlencoded|graphql)|[^;]*\+(json|xml))/i;
|
|
366
412
|
var MAX_BODY = 64 * 1024;
|
|
367
413
|
var LOCAL_AGENT = new Agent({ keepAlive: false });
|
|
@@ -504,12 +550,13 @@ async function startProxy(o) {
|
|
|
504
550
|
});
|
|
505
551
|
upstream.on("error", (err) => {
|
|
506
552
|
if (res.destroyed && res.writableFinished === false && !res.headersSent) return;
|
|
507
|
-
const
|
|
553
|
+
const html = wantsHtml(req.headers.accept);
|
|
554
|
+
const msg = html ? unavailablePage({ tunnel: route.tunnel, port: route.port, retry: req.method === "GET" }) : `Nada escutando em localhost:${route.port}. Suba o servidor e tente de novo.`;
|
|
508
555
|
if (!res.headersSent) {
|
|
509
|
-
res.writeHead(
|
|
510
|
-
res.end(msg);
|
|
556
|
+
res.writeHead(503, { "content-type": html ? "text/html; charset=utf-8" : "text/plain; charset=utf-8", "cache-control": "no-store", "retry-after": "3" });
|
|
557
|
+
res.end(req.method === "HEAD" ? void 0 : msg);
|
|
511
558
|
} else res.destroy();
|
|
512
|
-
o.store.update(id, { status:
|
|
559
|
+
o.store.update(id, { status: 503, durationMs: Date.now() - started, bytes: res.headersSent ? 0 : Buffer.byteLength(msg), error: err.message || err.code || err.name, reqBody: reqCap.text() });
|
|
513
560
|
});
|
|
514
561
|
req.on("data", (c) => reqCap.push(c));
|
|
515
562
|
req.pipe(upstream);
|
|
@@ -558,7 +605,7 @@ async function startProxy(o) {
|
|
|
558
605
|
client.pipe(target);
|
|
559
606
|
});
|
|
560
607
|
const fail3 = (err) => {
|
|
561
|
-
o.store.update(id, { status:
|
|
608
|
+
o.store.update(id, { status: 503, durationMs: Date.now() - started, error: err.message });
|
|
562
609
|
client.destroy();
|
|
563
610
|
};
|
|
564
611
|
target.on("error", fail3);
|
|
@@ -910,9 +957,9 @@ async function runSession(specs, source, flags) {
|
|
|
910
957
|
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname(), cloudflaredPath, source });
|
|
911
958
|
if (wantsUi(flags)) {
|
|
912
959
|
const { runFullscreen } = await import("./fullscreen-5437X7MP.js");
|
|
913
|
-
process.exit(await runFullscreen(session, specs, "0.4.
|
|
960
|
+
process.exit(await runFullscreen(session, specs, "0.4.4"));
|
|
914
961
|
}
|
|
915
|
-
console.log(`${mark()} ${paint("muted", `${"0.4.
|
|
962
|
+
console.log(`${mark()} ${paint("muted", `${"0.4.4"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
|
|
916
963
|
const stopLog = runPlain(session, (l) => console.log(l));
|
|
917
964
|
const stopped = new Promise((resolve) => {
|
|
918
965
|
const onSignal = () => {
|
|
@@ -999,7 +1046,7 @@ cli.help(
|
|
|
999
1046
|
body: s.body.replace(/^\s*For more info, run any command with the `--help` flag:/m, " Mais detalhes de um comando: duto <comando> --help").replace("Display this message", "Mostra esta ajuda").replace("Display version number", "Mostra a vers\xE3o")
|
|
1000
1047
|
}))
|
|
1001
1048
|
);
|
|
1002
|
-
cli.version("0.4.
|
|
1049
|
+
cli.version("0.4.4");
|
|
1003
1050
|
try {
|
|
1004
1051
|
cli.parse(process.argv, { run: false });
|
|
1005
1052
|
await cli.runMatchedCommand();
|