@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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +28 -10
  3. 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
- - Na linha de comando: `duto 8080 --host loja.meuapp.test`.
71
- - Links que o app montar a partir do Host trocado só abrem na sua máquina.
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 headers)`, line);
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.0"));
923
+ process.exit(await runFullscreen(session, specs, "0.4.1"));
908
924
  }
909
- console.log(`${mark()} ${paint("muted", `${"0.4.0"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
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
- return runSession([{ name, port, ...host ? { host } : {} }], "linha de comando", flags);
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.0");
1012
+ cli.version("0.4.1");
995
1013
  try {
996
1014
  cli.parse(process.argv, { run: false });
997
1015
  await cli.runMatchedCommand();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dutodev/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Exponha uma porta local em https://<nome>-<usuário>.duto.dev com um comando.",
5
5
  "license": "MIT",
6
6
  "type": "module",