devlinker 1.4.2__tar.gz → 1.4.3__tar.gz
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.
- {devlinker-1.4.2 → devlinker-1.4.3}/MANIFEST.in +0 -1
- {devlinker-1.4.2/devlinker.egg-info → devlinker-1.4.3}/PKG-INFO +20 -3
- {devlinker-1.4.2 → devlinker-1.4.3}/README.md +19 -2
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/main.py +7 -3
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/proxy.py +8 -0
- devlinker-1.4.3/devlinker/share.py +64 -0
- {devlinker-1.4.2 → devlinker-1.4.3/devlinker.egg-info}/PKG-INFO +20 -3
- {devlinker-1.4.2 → devlinker-1.4.3}/pyproject.toml +1 -1
- devlinker-1.4.2/devlinker/share.py +0 -32
- {devlinker-1.4.2 → devlinker-1.4.3}/LICENSE +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/__init__.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/config.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detection_state.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detector.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/detector_ai.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/devlinker_loader_instant.html +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/devlinker_loader_snippet.html +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/doctor.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/fix.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/fixer.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/global_state.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/inspect.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/logger.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/monitor.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/runner.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker/tunnel.py +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/SOURCES.txt +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/dependency_links.txt +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/entry_points.txt +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/requires.txt +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/devlinker.egg-info/top_level.txt +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/setup.cfg +0 -0
- {devlinker-1.4.2 → devlinker-1.4.3}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlinker
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.3
|
|
4
4
|
Summary: A lightweight proxy that combines your frontend and backend into one link for easy development and sharing.
|
|
5
5
|
Author-email: Mani <mani1028@users.noreply.github.com>
|
|
6
6
|
Requires-Python: >=3.7
|
|
@@ -130,6 +130,7 @@ If DevLinker helps you ship faster, consider supporting the project:
|
|
|
130
130
|
- `devlinker support` — Show UPI support QR code in terminal
|
|
131
131
|
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
132
132
|
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
133
|
+
- `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
|
|
133
134
|
- `devlinker unshare` — Disable public tunnel at runtime
|
|
134
135
|
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
135
136
|
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
@@ -278,14 +279,16 @@ devlinker --docker
|
|
|
278
279
|
|
|
279
280
|
## Tunnel and Sharing Modes
|
|
280
281
|
|
|
281
|
-
By default, DevLinker starts **fast local proxy only** (no tunnel).
|
|
282
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
|
|
283
|
+
|
|
284
|
+
For access from another network, start with a public tunnel using the `--url` flag:
|
|
282
285
|
|
|
283
286
|
|
|
284
287
|
```bash
|
|
285
288
|
devlinker --url
|
|
286
289
|
```
|
|
287
290
|
|
|
288
|
-
This
|
|
291
|
+
This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
|
|
289
292
|
|
|
290
293
|
```text
|
|
291
294
|
🌍 Enabling public tunnel...
|
|
@@ -296,6 +299,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
|
|
|
296
299
|
ℹ Share this link with collaborators.
|
|
297
300
|
```
|
|
298
301
|
|
|
302
|
+
If you already started DevLinker and want to turn on sharing without restarting, use:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
devlinker share
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
If you use a custom proxy port, pass it explicitly:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
devlinker share --proxy-port 18000
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
|
|
315
|
+
|
|
299
316
|
To force tunnel off (even if --url is passed):
|
|
300
317
|
|
|
301
318
|
```bash
|
|
@@ -110,6 +110,7 @@ If DevLinker helps you ship faster, consider supporting the project:
|
|
|
110
110
|
- `devlinker support` — Show UPI support QR code in terminal
|
|
111
111
|
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
112
112
|
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
113
|
+
- `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
|
|
113
114
|
- `devlinker unshare` — Disable public tunnel at runtime
|
|
114
115
|
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
115
116
|
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
@@ -258,14 +259,16 @@ devlinker --docker
|
|
|
258
259
|
|
|
259
260
|
## Tunnel and Sharing Modes
|
|
260
261
|
|
|
261
|
-
By default, DevLinker starts **fast local proxy only** (no tunnel).
|
|
262
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
|
|
263
|
+
|
|
264
|
+
For access from another network, start with a public tunnel using the `--url` flag:
|
|
262
265
|
|
|
263
266
|
|
|
264
267
|
```bash
|
|
265
268
|
devlinker --url
|
|
266
269
|
```
|
|
267
270
|
|
|
268
|
-
This
|
|
271
|
+
This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
|
|
269
272
|
|
|
270
273
|
```text
|
|
271
274
|
🌍 Enabling public tunnel...
|
|
@@ -276,6 +279,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
|
|
|
276
279
|
ℹ Share this link with collaborators.
|
|
277
280
|
```
|
|
278
281
|
|
|
282
|
+
If you already started DevLinker and want to turn on sharing without restarting, use:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
devlinker share
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
If you use a custom proxy port, pass it explicitly:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
devlinker share --proxy-port 18000
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
|
|
295
|
+
|
|
279
296
|
To force tunnel off (even if --url is passed):
|
|
280
297
|
|
|
281
298
|
```bash
|
|
@@ -33,7 +33,7 @@ except ImportError: # pragma: no cover - fallback when rich is unavailable
|
|
|
33
33
|
|
|
34
34
|
from . import __version__
|
|
35
35
|
from .detector import check_port, detect_ports, is_vite_port
|
|
36
|
-
from .proxy import start_proxy
|
|
36
|
+
from .proxy import start_proxy, wait_for_proxy_startup
|
|
37
37
|
from .runner import detect_backend_port, start_servers
|
|
38
38
|
from .tunnel import start_tunnel
|
|
39
39
|
from .doctor import doctor
|
|
@@ -43,6 +43,7 @@ from .share import share, unshare
|
|
|
43
43
|
from .config import load_config
|
|
44
44
|
from .inspect import inspect
|
|
45
45
|
from .monitor import monitor
|
|
46
|
+
from .global_state import STATE
|
|
46
47
|
|
|
47
48
|
SUPPORT_UPI_ID = "devlinker@upi"
|
|
48
49
|
SUPPORT_UPI_LINK = "upi://pay?pa=devlinker@upi&pn=DevLinker&cu=INR&tn=Support%20DevLinker%20Project%20🚀"
|
|
@@ -602,6 +603,7 @@ def _run_proxy(
|
|
|
602
603
|
)
|
|
603
604
|
|
|
604
605
|
proxy_port = _select_proxy_port(proxy_port)
|
|
606
|
+
STATE["proxy_port"] = proxy_port
|
|
605
607
|
_write_frontend_api_env(proxy_port)
|
|
606
608
|
|
|
607
609
|
if not live_status:
|
|
@@ -619,8 +621,10 @@ def _run_proxy(
|
|
|
619
621
|
enable_debug_logs=debug,
|
|
620
622
|
)
|
|
621
623
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
+
if not wait_for_proxy_startup(timeout=5.0):
|
|
625
|
+
raise click.ClickException(
|
|
626
|
+
f"Proxy failed to start on port {proxy_port}. Check whether the port is already in use."
|
|
627
|
+
)
|
|
624
628
|
|
|
625
629
|
if live_status:
|
|
626
630
|
live_status.update("Proxy", f"✔ Active ({proxy_port})", style="green")
|
|
@@ -26,6 +26,7 @@ _printed_fixes = set()
|
|
|
26
26
|
_printed_live_header = False
|
|
27
27
|
LIVE_REQUEST_LOGGING_ENABLED = False
|
|
28
28
|
MAX_RECENT_REQUESTS = 200
|
|
29
|
+
PROXY_READY_EVENT = threading.Event()
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
def _format_request_context(path: str, method: str | None, status: int, target: str) -> str:
|
|
@@ -175,6 +176,7 @@ def _apply_cors_headers(headers: Dict[str, str], request: Request) -> Dict[str,
|
|
|
175
176
|
async def _on_startup() -> None:
|
|
176
177
|
global HTTP_CLIENT
|
|
177
178
|
HTTP_CLIENT = httpx.AsyncClient(timeout=15.0, follow_redirects=False)
|
|
179
|
+
PROXY_READY_EVENT.set()
|
|
178
180
|
|
|
179
181
|
|
|
180
182
|
@app.on_event("shutdown")
|
|
@@ -183,6 +185,7 @@ async def _on_shutdown() -> None:
|
|
|
183
185
|
if HTTP_CLIENT is not None:
|
|
184
186
|
await HTTP_CLIENT.aclose()
|
|
185
187
|
HTTP_CLIENT = None
|
|
188
|
+
PROXY_READY_EVENT.clear()
|
|
186
189
|
|
|
187
190
|
|
|
188
191
|
def _connection_header_tokens(headers: Dict[str, str]) -> set[str]:
|
|
@@ -720,9 +723,14 @@ def start_proxy(
|
|
|
720
723
|
BACKEND = backend_port
|
|
721
724
|
LIVE_REQUEST_LOGGING_ENABLED = enable_debug_logs
|
|
722
725
|
_printed_live_header = False
|
|
726
|
+
PROXY_READY_EVENT.clear()
|
|
723
727
|
|
|
724
728
|
thread = threading.Thread(
|
|
725
729
|
target=lambda: uvicorn.run(app, host="0.0.0.0", port=proxy_port, log_level="warning"),
|
|
726
730
|
daemon=True,
|
|
727
731
|
)
|
|
728
732
|
thread.start()
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def wait_for_proxy_startup(timeout: float = 5.0) -> bool:
|
|
736
|
+
return PROXY_READY_EVENT.wait(timeout)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
from devlinker.global_state import STATE
|
|
5
|
+
from devlinker.tunnel import start_tunnel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_COMMON_PROXY_PORTS = (8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007, 8008, 8009, 8010, 18000)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _is_devlinker_proxy(port: int) -> bool:
|
|
12
|
+
try:
|
|
13
|
+
response = requests.get(f"http://127.0.0.1:{port}/__devlinker/dashboard", timeout=0.5)
|
|
14
|
+
except requests.RequestException:
|
|
15
|
+
return False
|
|
16
|
+
return response.status_code == 200 and "API Logs Dashboard" in response.text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _resolve_proxy_port(requested_port: int | None) -> int:
|
|
20
|
+
if requested_port is not None:
|
|
21
|
+
if _is_devlinker_proxy(requested_port):
|
|
22
|
+
return requested_port
|
|
23
|
+
raise click.ClickException(
|
|
24
|
+
f"No DevLinker proxy is running on port {requested_port}. Start devlinker first, or pass the correct --proxy-port."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
for candidate in _COMMON_PROXY_PORTS:
|
|
28
|
+
if _is_devlinker_proxy(candidate):
|
|
29
|
+
return candidate
|
|
30
|
+
|
|
31
|
+
raise click.ClickException(
|
|
32
|
+
"No running DevLinker proxy was found. Start devlinker first, or pass --proxy-port <port>."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@click.command()
|
|
36
|
+
@click.option("--proxy-port", type=int, default=None, help="Proxy port to tunnel. Auto-detect when omitted.")
|
|
37
|
+
def share(proxy_port: int | None):
|
|
38
|
+
"""Enable public tunnel at runtime (no restart)."""
|
|
39
|
+
if STATE["tunnel"]:
|
|
40
|
+
click.secho("⚠️ Already shared", fg="yellow")
|
|
41
|
+
return
|
|
42
|
+
try:
|
|
43
|
+
resolved_proxy_port = _resolve_proxy_port(proxy_port)
|
|
44
|
+
STATE["proxy_port"] = resolved_proxy_port
|
|
45
|
+
provider, url = start_tunnel(resolved_proxy_port)
|
|
46
|
+
STATE["tunnel"] = url
|
|
47
|
+
click.secho("\n🌍 Public Sharing Enabled\n" + ("─" * 24), fg="green", bold=True)
|
|
48
|
+
click.secho("✔ Tunnel connected", fg="green")
|
|
49
|
+
click.secho(f"\nPublic URL:\n{url}\n", fg="cyan", bold=True)
|
|
50
|
+
click.secho("📤 Share this link with your team", fg="magenta")
|
|
51
|
+
except Exception as exc:
|
|
52
|
+
click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
|
|
53
|
+
click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
|
|
54
|
+
|
|
55
|
+
@click.command()
|
|
56
|
+
def unshare():
|
|
57
|
+
"""Disable public tunnel at runtime (no restart)."""
|
|
58
|
+
if not STATE["tunnel"]:
|
|
59
|
+
click.secho("⚠️ No active tunnel", fg="yellow")
|
|
60
|
+
return
|
|
61
|
+
from devlinker.tunnel import stop_tunnel
|
|
62
|
+
stop_tunnel()
|
|
63
|
+
STATE["tunnel"] = None
|
|
64
|
+
click.secho("🛑 Sharing stopped", fg="red", bold=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlinker
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.3
|
|
4
4
|
Summary: A lightweight proxy that combines your frontend and backend into one link for easy development and sharing.
|
|
5
5
|
Author-email: Mani <mani1028@users.noreply.github.com>
|
|
6
6
|
Requires-Python: >=3.7
|
|
@@ -130,6 +130,7 @@ If DevLinker helps you ship faster, consider supporting the project:
|
|
|
130
130
|
- `devlinker support` — Show UPI support QR code in terminal
|
|
131
131
|
- `devlinker --url` — Start with public tunnel (Cloudflare/ngrok)
|
|
132
132
|
- `devlinker share` — Enable public tunnel at runtime (no restart)
|
|
133
|
+
- `devlinker share --proxy-port 18000` — Enable public tunnel for a custom proxy port
|
|
133
134
|
- `devlinker unshare` — Disable public tunnel at runtime
|
|
134
135
|
- `devlinker doctor` — Diagnose issues, see categorized problems and fixes
|
|
135
136
|
- `devlinker fix` — Auto-fix common issues (env, API paths, config)
|
|
@@ -278,14 +279,16 @@ devlinker --docker
|
|
|
278
279
|
|
|
279
280
|
## Tunnel and Sharing Modes
|
|
280
281
|
|
|
281
|
-
By default, DevLinker starts **fast local proxy only** (no tunnel).
|
|
282
|
+
By default, DevLinker starts **fast local proxy only** (no tunnel). It prints a LAN URL when it can detect a local network interface, and you can share that link with devices on the same Wi-Fi/LAN.
|
|
283
|
+
|
|
284
|
+
For access from another network, start with a public tunnel using the `--url` flag:
|
|
282
285
|
|
|
283
286
|
|
|
284
287
|
```bash
|
|
285
288
|
devlinker --url
|
|
286
289
|
```
|
|
287
290
|
|
|
288
|
-
This
|
|
291
|
+
This starts the proxy and opens a public tunnel (Cloudflare or ngrok). The output will show:
|
|
289
292
|
|
|
290
293
|
```text
|
|
291
294
|
🌍 Enabling public tunnel...
|
|
@@ -296,6 +299,20 @@ This will start the proxy and open a public tunnel (Cloudflare or ngrok). The ou
|
|
|
296
299
|
ℹ Share this link with collaborators.
|
|
297
300
|
```
|
|
298
301
|
|
|
302
|
+
If you already started DevLinker and want to turn on sharing without restarting, use:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
devlinker share
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
If you use a custom proxy port, pass it explicitly:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
devlinker share --proxy-port 18000
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
If your friend is on the same Wi-Fi/LAN, use the printed LAN URL like `http://192.168.x.x:<proxy-port>`. If they are outside your network, use the public tunnel URL instead.
|
|
315
|
+
|
|
299
316
|
To force tunnel off (even if --url is passed):
|
|
300
317
|
|
|
301
318
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devlinker"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.3"
|
|
8
8
|
description = "A lightweight proxy that combines your frontend and backend into one link for easy development and sharing."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Mani", email = "mani1028@users.noreply.github.com" }
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import click
|
|
3
|
-
from devlinker.global_state import STATE
|
|
4
|
-
from devlinker.tunnel import start_tunnel
|
|
5
|
-
|
|
6
|
-
@click.command()
|
|
7
|
-
def share():
|
|
8
|
-
"""Enable public tunnel at runtime (no restart)."""
|
|
9
|
-
if STATE["tunnel"]:
|
|
10
|
-
click.secho("⚠️ Already shared", fg="yellow")
|
|
11
|
-
return
|
|
12
|
-
try:
|
|
13
|
-
provider, url = start_tunnel(STATE["proxy_port"])
|
|
14
|
-
STATE["tunnel"] = url
|
|
15
|
-
click.secho("\n🌍 Public Sharing Enabled\n" + ("─" * 24), fg="green", bold=True)
|
|
16
|
-
click.secho("✔ Tunnel connected", fg="green")
|
|
17
|
-
click.secho(f"\nPublic URL:\n{url}\n", fg="cyan", bold=True)
|
|
18
|
-
click.secho("📤 Share this link with your team", fg="magenta")
|
|
19
|
-
except Exception as exc:
|
|
20
|
-
click.secho(f"[WARN] Tunnel failed: {exc}", fg="red")
|
|
21
|
-
click.secho("[INFO] Next step: install cloudflared or configure ngrok auth.", fg="yellow")
|
|
22
|
-
|
|
23
|
-
@click.command()
|
|
24
|
-
def unshare():
|
|
25
|
-
"""Disable public tunnel at runtime (no restart)."""
|
|
26
|
-
if not STATE["tunnel"]:
|
|
27
|
-
click.secho("⚠️ No active tunnel", fg="yellow")
|
|
28
|
-
return
|
|
29
|
-
from devlinker.tunnel import stop_tunnel
|
|
30
|
-
stop_tunnel()
|
|
31
|
-
STATE["tunnel"] = None
|
|
32
|
-
click.secho("🛑 Sharing stopped", fg="red", bold=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|