@dutodev/cli 0.4.0 → 0.4.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/README.md +4 -2
- package/dist/index.js +28 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,8 +67,9 @@ tunnels:
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
- Com `host:`, o `X-Forwarded-Host` não é enviado, para o app não ver o domínio público. Se quiser enviá-lo, coloque-o em `headers:`.
|
|
70
|
-
-
|
|
71
|
-
-
|
|
70
|
+
- Redirecionamentos voltam com o endereço público: um `Location: http://loja.meuapp.test:8080/login` chega ao navegador como `https://loja-<usuário>.duto.dev/login`. Isso vale também dentro de parâmetros codificados, como o `redirect_uri` de um login OAuth. Para desligar, use `rewrite_redirects: false` no túnel.
|
|
71
|
+
- Na linha de comando: `duto 8080 --host loja.meuapp.test` (e `--no-rewrite-redirects` para desligar a troca).
|
|
72
|
+
- Links dentro das páginas (HTML, JSON) não são reescritos: se o app montar URLs a partir do Host, elas só abrem na sua máquina.
|
|
72
73
|
|
|
73
74
|
## A tela
|
|
74
75
|
|
|
@@ -100,6 +101,7 @@ Fora de um terminal interativo (pipe, CI) ou com `--no-ui`, a CLI imprime um log
|
|
|
100
101
|
|---|---|
|
|
101
102
|
| `--name <nome>` | Endereço fixo `<nome>-<usuário>.duto.dev` |
|
|
102
103
|
| `--host <host>` | Host que o serviço local recebe no lugar do público |
|
|
104
|
+
| `--no-rewrite-redirects` | Com `--host`, mantém os redirecionamentos como o serviço mandou |
|
|
103
105
|
| `--no-ui` / `--ui` | Desliga / força a tela cheia |
|
|
104
106
|
|
|
105
107
|
## Configuração
|
package/dist/index.js
CHANGED
|
@@ -240,7 +240,7 @@ var CONFIG_FILES = ["duto.yml", "duto.yaml"];
|
|
|
240
240
|
var LABEL = "[a-z0-9](?:[a-z0-9-]*[a-z0-9])?";
|
|
241
241
|
var HOST_RE = new RegExp(`^${LABEL}(?:\\.${LABEL})*(?::\\d{1,5})?$`);
|
|
242
242
|
var HEADER_NAME_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
243
|
-
var TUNNEL_KEYS = ["port", "host", "headers"];
|
|
243
|
+
var TUNNEL_KEYS = ["port", "host", "headers", "rewrite_redirects"];
|
|
244
244
|
function parseHost(raw) {
|
|
245
245
|
const host = raw.trim().toLowerCase();
|
|
246
246
|
return HOST_RE.test(host) ? host : null;
|
|
@@ -282,7 +282,7 @@ function parseConfig(text, file = CONFIG_FILES[0]) {
|
|
|
282
282
|
const body = pair.value;
|
|
283
283
|
for (const item of body.items) {
|
|
284
284
|
const key = isScalar(item.key) ? String(item.key.value) : "";
|
|
285
|
-
if (!TUNNEL_KEYS.includes(key)) throw fail3(`chave desconhecida em "${raw}": ${key} (use port, host ou
|
|
285
|
+
if (!TUNNEL_KEYS.includes(key)) throw fail3(`chave desconhecida em "${raw}": ${key} (use port, host, headers ou rewrite_redirects)`, line);
|
|
286
286
|
}
|
|
287
287
|
const hostNode = body.get("host", true);
|
|
288
288
|
if (hostNode !== void 0) {
|
|
@@ -309,6 +309,13 @@ function parseConfig(text, file = CONFIG_FILES[0]) {
|
|
|
309
309
|
}
|
|
310
310
|
spec.headers = headers;
|
|
311
311
|
}
|
|
312
|
+
const rewriteNode = body.get("rewrite_redirects", true);
|
|
313
|
+
if (rewriteNode !== void 0) {
|
|
314
|
+
const value = isScalar(rewriteNode) ? rewriteNode.value : void 0;
|
|
315
|
+
if (typeof value !== "boolean") throw fail3(`rewrite_redirects de "${raw}" deve ser true ou false`, line);
|
|
316
|
+
if (!spec.host) throw fail3(`rewrite_redirects de "${raw}" s\xF3 vale junto com host:`, line);
|
|
317
|
+
if (value === false) spec.rewriteRedirects = false;
|
|
318
|
+
}
|
|
312
319
|
}
|
|
313
320
|
const users = seen.get(port) ?? [];
|
|
314
321
|
const clash = users.find((u) => !u.host || !spec.host || u.host === spec.host);
|
|
@@ -433,6 +440,12 @@ function rewrite(h, route) {
|
|
|
433
440
|
}
|
|
434
441
|
return out;
|
|
435
442
|
}
|
|
443
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
444
|
+
function rewriteLocation(location, localHost2, publicHost) {
|
|
445
|
+
const host = escapeRe(localHost2.replace(/:\d+$/, ""));
|
|
446
|
+
const re = new RegExp(`https?(://|%3A%2F%2F)${host}(?::\\d+|%3A\\d+)?(?=$|[/?#&]|%2F|%3F|%23|%26)`, "gi");
|
|
447
|
+
return location.replace(re, (_m, sep) => `https${sep}${publicHost}`);
|
|
448
|
+
}
|
|
436
449
|
function hostOf(req) {
|
|
437
450
|
return (req.headers.host ?? "").replace(/:\d+$/, "").toLowerCase();
|
|
438
451
|
}
|
|
@@ -473,6 +486,9 @@ async function startProxy(o) {
|
|
|
473
486
|
(up) => {
|
|
474
487
|
const resCap = new Capture(TEXT.test(up.headers["content-type"] ?? ""), max);
|
|
475
488
|
let bytes = 0;
|
|
489
|
+
if (route.host && route.rewriteRedirects !== false && typeof up.headers.location === "string") {
|
|
490
|
+
up.headers.location = rewriteLocation(up.headers.location, route.host, hostOf(req));
|
|
491
|
+
}
|
|
476
492
|
res.writeHead(up.statusCode ?? 502, up.statusMessage, up.headers);
|
|
477
493
|
up.on("data", (c) => {
|
|
478
494
|
bytes += c.length;
|
|
@@ -690,7 +706,7 @@ var Session = class {
|
|
|
690
706
|
this.emit();
|
|
691
707
|
}
|
|
692
708
|
blank(spec, adHoc) {
|
|
693
|
-
return { key: keyOf(spec), name: spec.name, port: spec.port, ...spec.host ? { host: spec.host } : {}, ...spec.headers ? { headers: spec.headers } : {}, url: null, hostname: null, endpointId: null, status: "opening", error: null, adHoc, requests: 0, errors: 0 };
|
|
709
|
+
return { key: keyOf(spec), name: spec.name, port: spec.port, ...spec.host ? { host: spec.host } : {}, ...spec.headers ? { headers: spec.headers } : {}, ...spec.rewriteRedirects === false ? { rewriteRedirects: false } : {}, url: null, hostname: null, endpointId: null, status: "opening", error: null, adHoc, requests: 0, errors: 0 };
|
|
694
710
|
}
|
|
695
711
|
async open(t, existing, retrying = false) {
|
|
696
712
|
if (this.stopped) return;
|
|
@@ -714,7 +730,7 @@ var Session = class {
|
|
|
714
730
|
t.hostname = res.hostname;
|
|
715
731
|
t.endpointId = res.endpointId;
|
|
716
732
|
t.error = null;
|
|
717
|
-
this.routes.set(res.hostname.toLowerCase(), { tunnel: t.key, port: t.port, host: t.host, headers: t.headers });
|
|
733
|
+
this.routes.set(res.hostname.toLowerCase(), { tunnel: t.key, port: t.port, host: t.host, headers: t.headers, rewriteRedirects: t.rewriteRedirects });
|
|
718
734
|
t.status = await isPortListening(t.port) ? "live" : "no-listener";
|
|
719
735
|
this.ensureCloudflared(res.tunnelToken);
|
|
720
736
|
this.emit();
|
|
@@ -904,9 +920,9 @@ async function runSession(specs, source, flags) {
|
|
|
904
920
|
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname(), cloudflaredPath, source });
|
|
905
921
|
if (wantsUi(flags)) {
|
|
906
922
|
const { runFullscreen } = await import("./fullscreen-5437X7MP.js");
|
|
907
|
-
process.exit(await runFullscreen(session, specs, "0.4.
|
|
923
|
+
process.exit(await runFullscreen(session, specs, "0.4.1"));
|
|
908
924
|
}
|
|
909
|
-
console.log(`${mark()} ${paint("muted", `${"0.4.
|
|
925
|
+
console.log(`${mark()} ${paint("muted", `${"0.4.1"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
|
|
910
926
|
const stopLog = runPlain(session, (l) => console.log(l));
|
|
911
927
|
const stopped = new Promise((resolve) => {
|
|
912
928
|
const onSignal = () => {
|
|
@@ -948,11 +964,13 @@ ${paint("signal", "Dica:")} crie um duto.yml com os t\xFAneis do projeto e rode
|
|
|
948
964
|
const port = parsePort(rawPort);
|
|
949
965
|
const name = parseName(flags.name) ?? null;
|
|
950
966
|
const host = parseHostFlag(flags.host);
|
|
951
|
-
|
|
967
|
+
const keepRedirects = flags.rewriteRedirects === false;
|
|
968
|
+
if (keepRedirects && !host) fail2("--no-rewrite-redirects s\xF3 vale junto com --host");
|
|
969
|
+
return runSession([{ name, port, ...host ? { host } : {}, ...keepRedirects ? { rewriteRedirects: false } : {} }], "linha de comando", flags);
|
|
952
970
|
}
|
|
953
971
|
var cli = cac("duto");
|
|
954
|
-
cli.command("[port]", "Sem porta: abre os t\xFAneis do duto.yml. Com porta: abre um t\xFAnel para ela").option("--name <nome>", "Nome do endere\xE7o fixo (ex.: web \u2192 https://web-<usu\xE1rio>.duto.dev)").option("--host <host>", "Host que o servi\xE7o local recebe no lugar do p\xFAblico (ex.: meuapp.test)").option("--no-ui", "Sem tela cheia: s\xF3 um log das requisi\xE7\xF5es (\xFAtil em scripts)").option("--ui", "For\xE7a a tela cheia (no Windows ela vem desligada)").action((port, opts) => tunnel(port, opts));
|
|
955
|
-
cli.command("http <port>", "O mesmo que duto <porta> (para quem vem do ngrok)").option("--name <nome>", "Nome do endere\xE7o fixo").option("--host <host>", "Host que o servi\xE7o local recebe no lugar do p\xFAblico").option("--no-ui", "Sem tela cheia").action((port, opts) => tunnel(port, opts));
|
|
972
|
+
cli.command("[port]", "Sem porta: abre os t\xFAneis do duto.yml. Com porta: abre um t\xFAnel para ela").option("--name <nome>", "Nome do endere\xE7o fixo (ex.: web \u2192 https://web-<usu\xE1rio>.duto.dev)").option("--host <host>", "Host que o servi\xE7o local recebe no lugar do p\xFAblico (ex.: meuapp.test)").option("--no-rewrite-redirects", "Com --host: n\xE3o troca o host local pelo p\xFAblico nos redirecionamentos").option("--no-ui", "Sem tela cheia: s\xF3 um log das requisi\xE7\xF5es (\xFAtil em scripts)").option("--ui", "For\xE7a a tela cheia (no Windows ela vem desligada)").action((port, opts) => tunnel(port, opts));
|
|
973
|
+
cli.command("http <port>", "O mesmo que duto <porta> (para quem vem do ngrok)").option("--name <nome>", "Nome do endere\xE7o fixo").option("--host <host>", "Host que o servi\xE7o local recebe no lugar do p\xFAblico").option("--no-rewrite-redirects", "Com --host: mant\xE9m os redirecionamentos como o servi\xE7o mandou").option("--no-ui", "Sem tela cheia").action((port, opts) => tunnel(port, opts));
|
|
956
974
|
cli.command("login <token>", "Salva o authtoken (pegue em https://duto.dev)").action(async (token) => {
|
|
957
975
|
try {
|
|
958
976
|
const me = await createApiClient({ baseUrl: apiUrl(), token: String(token) }).me();
|
|
@@ -991,7 +1009,7 @@ cli.help(
|
|
|
991
1009
|
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")
|
|
992
1010
|
}))
|
|
993
1011
|
);
|
|
994
|
-
cli.version("0.4.
|
|
1012
|
+
cli.version("0.4.1");
|
|
995
1013
|
try {
|
|
996
1014
|
cli.parse(process.argv, { run: false });
|
|
997
1015
|
await cli.runMatchedCommand();
|