ndev-stack 0.3.0__tar.gz → 0.3.2__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.
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/PKG-INFO +2 -1
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/README.md +1 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/__init__.py +1 -1
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/mongodb/module.py +8 -3
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/postgres/module.py +8 -3
- ndev_stack-0.3.2/ndev/linux/commands/grok.py +97 -0
- ndev_stack-0.3.2/ndev/win/bin/shim.exe +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/cli.py +88 -7
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/fcgi.py +7 -3
- ndev_stack-0.3.2/ndev/win/core/grok.py +95 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/php.py +18 -28
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/redis_core.py +8 -5
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/setup.py +27 -27
- ndev_stack-0.3.2/ndev/win/core/shim.py +284 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/upgrade.py +12 -11
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/vhost.py +9 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/templates/vhost.conf.tmpl +2 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/templates/vhost_ssl.conf.tmpl +2 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/PKG-INFO +2 -1
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/SOURCES.txt +4 -1
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/pyproject.toml +2 -2
- ndev_stack-0.3.2/tests/test_shim.py +86 -0
- ndev_stack-0.3.0/ndev/linux/commands/grok.py +0 -74
- ndev_stack-0.3.0/ndev/win/core/grok.py +0 -54
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/__main__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/cli.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/config.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/constants.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/github.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/logger.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/manifest.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/base.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/mailpit/module.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/redis/manifest.json +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/builtin/redis/module.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/modules/manager.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/common/utils.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/chroot/manager.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/chroot/packages.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/chroot/shell.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/cli.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/available.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/clean.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/ctl.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/current.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/db.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/doctor.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/install.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/list.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/logs.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/mailpit.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/modules.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/mongodb.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/postgres.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/reload.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/restart.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/setup.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/start.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/status.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/stop.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/uninstall.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/update.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/upgrade.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/use.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/commands/vhost.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/builder.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/downloader.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/extensions.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/installer.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/resolver.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/php/templates.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/fpm.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/mailpit.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/pma.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/process.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/sockets.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/runtime/upgrade.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/linux/tui.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/main.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/tui.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/commands/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/__init__.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/db.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/elevate.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/ext.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/logs.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/mailpit.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/mkcert.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/paths.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/pma.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/core/services.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev/win/tui.py +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/dependency_links.txt +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/entry_points.txt +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/requires.txt +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/ndev_stack.egg-info/top_level.txt +0 -0
- {ndev_stack-0.3.0 → ndev_stack-0.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndev-stack
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Cross-platform developer stack manager for PHP, FastCGI/PHP-FPM, MariaDB, Redis, PostgreSQL, MongoDB, Nginx, SSL, and Mailpit on Linux and Windows
|
|
5
5
|
Author-email: Ankit Karan <ankit.karan99@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ankitkaran99/ndev
|
|
@@ -244,6 +244,7 @@ ndev ui
|
|
|
244
244
|
| `ndev upgrade` | Check for and upgrade stack components (Nginx, MariaDB, Redis, PMA, Mailpit, mkcert, Composer) |
|
|
245
245
|
| `ndev upgrade --check` | Check component versions against upstream releases without upgrading |
|
|
246
246
|
| `ndev shell` | Interactive developer subshell pre-loaded with PHP/Composer/MySQL/Redis on PATH |
|
|
247
|
+
| `ndev shim` | Manage Windows binary shims and PATH registration (`list`, `init`, `add`, `rm`) |
|
|
247
248
|
| `ndev doctor` | Run environment diagnostics and health checks |
|
|
248
249
|
| `ndev logs` | View and tail service and virtual host logs |
|
|
249
250
|
| `ndev clean` | Clean up downloads cache and stale runtime state |
|
|
@@ -223,6 +223,7 @@ ndev ui
|
|
|
223
223
|
| `ndev upgrade` | Check for and upgrade stack components (Nginx, MariaDB, Redis, PMA, Mailpit, mkcert, Composer) |
|
|
224
224
|
| `ndev upgrade --check` | Check component versions against upstream releases without upgrading |
|
|
225
225
|
| `ndev shell` | Interactive developer subshell pre-loaded with PHP/Composer/MySQL/Redis on PATH |
|
|
226
|
+
| `ndev shim` | Manage Windows binary shims and PATH registration (`list`, `init`, `add`, `rm`) |
|
|
226
227
|
| `ndev doctor` | Run environment diagnostics and health checks |
|
|
227
228
|
| `ndev logs` | View and tail service and virtual host logs |
|
|
228
229
|
| `ndev clean` | Clean up downloads cache and stale runtime state |
|
|
@@ -119,16 +119,21 @@ class Module:
|
|
|
119
119
|
except Exception:
|
|
120
120
|
pass
|
|
121
121
|
|
|
122
|
-
#
|
|
122
|
+
# Create tool shims in ~/.ndev/shims/
|
|
123
123
|
shim_dir = self.module_dir.parent.parent / "shims"
|
|
124
124
|
if shim_dir.exists():
|
|
125
125
|
for tool in ["mongod.exe", "mongos.exe", "mongosh.exe"]:
|
|
126
126
|
src_tool = self.bin_dir / tool
|
|
127
127
|
if src_tool.exists():
|
|
128
|
+
name = tool[:-4] if tool.lower().endswith(".exe") else tool
|
|
128
129
|
try:
|
|
129
|
-
|
|
130
|
+
from ndev.win.core import shim as win_shim
|
|
131
|
+
win_shim.create(name, str(src_tool), path_prepend=str(self.bin_dir), shim_dir=shim_dir)
|
|
130
132
|
except Exception:
|
|
131
|
-
|
|
133
|
+
try:
|
|
134
|
+
shutil.copy2(src_tool, shim_dir / tool)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
132
137
|
else:
|
|
133
138
|
# Linux: try installing via system package manager or apt
|
|
134
139
|
if not shutil.which("mongod"):
|
|
@@ -108,16 +108,21 @@ class Module:
|
|
|
108
108
|
except Exception:
|
|
109
109
|
pass
|
|
110
110
|
|
|
111
|
-
#
|
|
111
|
+
# Create key cli binary shims in ~/.ndev/shims/
|
|
112
112
|
shim_dir = self.module_dir.parent.parent / "shims"
|
|
113
113
|
if shim_dir.exists():
|
|
114
114
|
for tool in ["psql.exe", "pg_ctl.exe", "pg_dump.exe", "createdb.exe", "dropdb.exe"]:
|
|
115
115
|
src_tool = self.bin_dir / tool
|
|
116
116
|
if src_tool.exists():
|
|
117
|
+
name = tool[:-4] if tool.lower().endswith(".exe") else tool
|
|
117
118
|
try:
|
|
118
|
-
|
|
119
|
+
from ndev.win.core import shim as win_shim
|
|
120
|
+
win_shim.create(name, str(src_tool), path_prepend=str(self.bin_dir), shim_dir=shim_dir)
|
|
119
121
|
except Exception:
|
|
120
|
-
|
|
122
|
+
try:
|
|
123
|
+
shutil.copy2(src_tool, shim_dir / tool)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
121
126
|
else:
|
|
122
127
|
# Linux: try installing via system package manager
|
|
123
128
|
if not shutil.which("pg_ctl"):
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import subprocess
|
|
3
|
+
import shutil
|
|
4
|
+
import typer
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from ndev.common.logger import logger
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
def get_vhost_info() -> list[dict]:
|
|
12
|
+
nginx_dir = Path("/etc/nginx/sites-enabled")
|
|
13
|
+
vhosts = []
|
|
14
|
+
if not nginx_dir.exists():
|
|
15
|
+
return vhosts
|
|
16
|
+
|
|
17
|
+
for file in nginx_dir.glob("*.conf"):
|
|
18
|
+
try:
|
|
19
|
+
content = file.read_text()
|
|
20
|
+
domain = None
|
|
21
|
+
for line in content.splitlines():
|
|
22
|
+
line = line.strip()
|
|
23
|
+
if line.startswith("server_name"):
|
|
24
|
+
parts = line.split()
|
|
25
|
+
for p in parts[1:]:
|
|
26
|
+
p = p.rstrip(";").strip()
|
|
27
|
+
if p and p != "_":
|
|
28
|
+
domain = p
|
|
29
|
+
break
|
|
30
|
+
if domain:
|
|
31
|
+
break
|
|
32
|
+
if domain:
|
|
33
|
+
has_ssl = "listen 443 ssl" in content or "listen [::]:443 ssl" in content
|
|
34
|
+
vhosts.append({"domain": domain, "ssl": has_ssl})
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
return vhosts
|
|
38
|
+
|
|
39
|
+
def grok_cmd():
|
|
40
|
+
"""Proxy local Nginx vhosts over public internet using Ngrok."""
|
|
41
|
+
if not shutil.which("ngrok"):
|
|
42
|
+
logger.error("ngrok is not installed or not in PATH.")
|
|
43
|
+
raise typer.Exit(code=1)
|
|
44
|
+
|
|
45
|
+
vhosts = get_vhost_info()
|
|
46
|
+
if not vhosts:
|
|
47
|
+
logger.error("No vhosts found in /etc/nginx/sites-enabled.")
|
|
48
|
+
raise typer.Exit(code=1)
|
|
49
|
+
|
|
50
|
+
console.print("\n[bold]Available VHosts[/bold]")
|
|
51
|
+
console.print("----------------")
|
|
52
|
+
for i, v in enumerate(vhosts):
|
|
53
|
+
ssl_tag = " (HTTPS)" if v["ssl"] else ""
|
|
54
|
+
console.print(f" {i + 1}) {v['domain']}{ssl_tag}")
|
|
55
|
+
|
|
56
|
+
console.print("")
|
|
57
|
+
choice = typer.prompt("Select vhost index", type=int)
|
|
58
|
+
if choice < 1 or choice > len(vhosts):
|
|
59
|
+
logger.error("Invalid selection.")
|
|
60
|
+
raise typer.Exit(code=1)
|
|
61
|
+
|
|
62
|
+
selected = vhosts[choice - 1]
|
|
63
|
+
domain = selected["domain"]
|
|
64
|
+
is_ssl = selected["ssl"]
|
|
65
|
+
|
|
66
|
+
policy_content = f'''on_http_request:
|
|
67
|
+
- actions:
|
|
68
|
+
- type: add-headers
|
|
69
|
+
config:
|
|
70
|
+
headers:
|
|
71
|
+
Host: "{domain}"
|
|
72
|
+
|
|
73
|
+
on_http_response:
|
|
74
|
+
- expressions:
|
|
75
|
+
- 'res.status_code >= 300 && res.status_code < 400 && "location" in res.headers'
|
|
76
|
+
- 'res.headers["location"][0].contains("://{domain}")'
|
|
77
|
+
actions:
|
|
78
|
+
- type: add-headers
|
|
79
|
+
config:
|
|
80
|
+
headers:
|
|
81
|
+
Location: '${{res.headers["location"][0].replace("https://{domain}", conn.server_name.size() > 0 ? "https://" + conn.server_name : "").replace("http://{domain}", conn.server_name.size() > 0 ? "https://" + conn.server_name : "")}}'
|
|
82
|
+
'''
|
|
83
|
+
|
|
84
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
|
85
|
+
tmp.write(policy_content)
|
|
86
|
+
policy_file = tmp.name
|
|
87
|
+
|
|
88
|
+
logger.info(f"Selected domain: {domain}{' (HTTPS)' if is_ssl else ''}")
|
|
89
|
+
logger.info("Starting ngrok...")
|
|
90
|
+
target = "https://localhost:443" if is_ssl else "80"
|
|
91
|
+
cmd = ["ngrok", "http", target, "--traffic-policy-file", policy_file]
|
|
92
|
+
if is_ssl:
|
|
93
|
+
cmd.append("--upstream-tls-verify=false")
|
|
94
|
+
try:
|
|
95
|
+
subprocess.run(cmd)
|
|
96
|
+
finally:
|
|
97
|
+
Path(policy_file).unlink(missing_ok=True)
|
|
Binary file
|
|
@@ -33,6 +33,7 @@ from .core import (
|
|
|
33
33
|
redis_core,
|
|
34
34
|
services,
|
|
35
35
|
setup as setup_core,
|
|
36
|
+
shim,
|
|
36
37
|
upgrade as upgrade_core,
|
|
37
38
|
vhost as vhost_core,
|
|
38
39
|
)
|
|
@@ -375,8 +376,8 @@ def doctor():
|
|
|
375
376
|
table.add_row("ngrok Tunneling", "[yellow]MISSING[/yellow]", "Run `ndev setup` to install")
|
|
376
377
|
|
|
377
378
|
# Composer
|
|
378
|
-
|
|
379
|
-
if
|
|
379
|
+
composer_cmd = paths.SHIM_DIR / "composer.exe"
|
|
380
|
+
if composer_cmd.exists() or shutil.which("composer"):
|
|
380
381
|
table.add_row("Composer", "[green]OK[/green]", "Installed and available on CLI")
|
|
381
382
|
else:
|
|
382
383
|
table.add_row("Composer", "[yellow]MISSING[/yellow]", "Run `ndev setup` to install")
|
|
@@ -390,7 +391,7 @@ def doctor():
|
|
|
390
391
|
# PATH Check
|
|
391
392
|
shim_on_path = str(paths.SHIM_DIR).lower() in os.environ.get("PATH", "").lower()
|
|
392
393
|
path_status = "[green]OK[/green]" if shim_on_path else "[yellow]WARNING[/yellow]"
|
|
393
|
-
path_detail = f"Found on PATH ({paths.SHIM_DIR})" if shim_on_path else f"
|
|
394
|
+
path_detail = f"Found on PATH ({paths.SHIM_DIR})" if shim_on_path else f"Run `ndev shim init` or add {paths.SHIM_DIR} to PATH"
|
|
394
395
|
table.add_row("Shims on PATH", path_status, path_detail)
|
|
395
396
|
|
|
396
397
|
console.print(table)
|
|
@@ -1876,7 +1877,7 @@ def redis_cli(port: int):
|
|
|
1876
1877
|
|
|
1877
1878
|
@main.command(name="grok")
|
|
1878
1879
|
@click.option("--domain", default=None, help="Vhost domain to tunnel. Omit to select interactively.")
|
|
1879
|
-
@click.option("--ssl",
|
|
1880
|
+
@click.option("--ssl/--no-ssl", default=None, help="Tunnel HTTPS port 443 (defaults to auto-detecting vhost SSL)")
|
|
1880
1881
|
def grok(domain, ssl):
|
|
1881
1882
|
"""Tunnel a local virtual host to the public web via ngrok."""
|
|
1882
1883
|
if not domain:
|
|
@@ -1896,12 +1897,19 @@ def grok(domain, ssl):
|
|
|
1896
1897
|
raise click.ClickException("Invalid selection.")
|
|
1897
1898
|
|
|
1898
1899
|
domain = re.sub(r"^https?://", "", domain.strip().lower()).rstrip("/")
|
|
1899
|
-
|
|
1900
|
-
|
|
1900
|
+
v_meta = vhost_core.get_vhost(domain)
|
|
1901
|
+
is_ssl = ssl if ssl is not None else bool(v_meta and v_meta.get("ssl"))
|
|
1902
|
+
console.print(f"Starting ngrok tunnel for [bold cyan]{domain}[/bold cyan]{' (HTTPS)' if is_ssl else ''} -- Ctrl+C to stop.")
|
|
1903
|
+
proc, policy_file = grok_core.start_tunnel(domain, ssl=ssl)
|
|
1901
1904
|
try:
|
|
1902
1905
|
proc.wait()
|
|
1903
1906
|
except KeyboardInterrupt:
|
|
1904
1907
|
proc.terminate()
|
|
1908
|
+
finally:
|
|
1909
|
+
try:
|
|
1910
|
+
policy_file.unlink(missing_ok=True)
|
|
1911
|
+
except Exception:
|
|
1912
|
+
pass
|
|
1905
1913
|
|
|
1906
1914
|
|
|
1907
1915
|
@main.command(name="tunnel", hidden=True)
|
|
@@ -1994,9 +2002,10 @@ def setup(nginx, mariadb, mkcert, ngrok, composer, cacert, nginx_version, mariad
|
|
|
1994
2002
|
for name, path in results.items():
|
|
1995
2003
|
console.print(f"[bold green]✓[/bold green] Installed {name:<10} -> {path}")
|
|
1996
2004
|
|
|
2005
|
+
shim.ensure_on_path()
|
|
1997
2006
|
shim_on_path = str(paths.SHIM_DIR).lower() in os.environ.get("PATH", "").lower()
|
|
1998
2007
|
if not shim_on_path:
|
|
1999
|
-
console.print(f"\n[bold
|
|
2008
|
+
console.print(f"\n[bold green]✓[/bold green] Added [bold cyan]{paths.SHIM_DIR}[/bold cyan] to User PATH (open a new terminal to use shims).")
|
|
2000
2009
|
|
|
2001
2010
|
|
|
2002
2011
|
# ---- Component Upgrade (upgrade) --------------------------------------------
|
|
@@ -2372,6 +2381,78 @@ def mongodb_status_cmd():
|
|
|
2372
2381
|
console.print(f"MongoDB: {'[bold green]RUNNING[/bold green]' if st.get('running') else '[bold red]STOPPED[/bold red]'} ({st.get('details', '')})")
|
|
2373
2382
|
|
|
2374
2383
|
|
|
2384
|
+
# ---- Shim Management (shim / shims) -----------------------------------------
|
|
2385
|
+
|
|
2386
|
+
@main.group(name="shim")
|
|
2387
|
+
def shim_cmd():
|
|
2388
|
+
"""Manage Windows executable shims and PATH registration."""
|
|
2389
|
+
pass
|
|
2390
|
+
|
|
2391
|
+
|
|
2392
|
+
@shim_cmd.command(name="list")
|
|
2393
|
+
def shim_list_cmd():
|
|
2394
|
+
"""List all configured shims."""
|
|
2395
|
+
shims = shim.list_shims()
|
|
2396
|
+
if not shims:
|
|
2397
|
+
console.print("[yellow]No shims configured in ~/.ndev/shims.[/yellow]")
|
|
2398
|
+
return
|
|
2399
|
+
|
|
2400
|
+
table = Table(title="ndev Windows Shims")
|
|
2401
|
+
table.add_column("Command", style="bold cyan")
|
|
2402
|
+
table.add_column("Target Executable / Details", style="green")
|
|
2403
|
+
for name, target in shims.items():
|
|
2404
|
+
table.add_row(name, target)
|
|
2405
|
+
console.print(table)
|
|
2406
|
+
|
|
2407
|
+
|
|
2408
|
+
@shim_cmd.command(name="init")
|
|
2409
|
+
def shim_init_cmd():
|
|
2410
|
+
"""Ensure ~/.ndev/shims is added to the user PATH environment variable."""
|
|
2411
|
+
updated = shim.ensure_on_path()
|
|
2412
|
+
if updated:
|
|
2413
|
+
console.print(f"[bold green]✓ Added {paths.SHIM_DIR} to User PATH registry.[/bold green]")
|
|
2414
|
+
console.print("[dim]A WM_SETTINGCHANGE broadcast was sent. Open a new terminal to use shims.[/dim]")
|
|
2415
|
+
else:
|
|
2416
|
+
console.print(f"[green]✓ {paths.SHIM_DIR} is already present in your User PATH registry.[/green]")
|
|
2417
|
+
|
|
2418
|
+
|
|
2419
|
+
@shim_cmd.command(name="add")
|
|
2420
|
+
@click.argument("name")
|
|
2421
|
+
@click.argument("target")
|
|
2422
|
+
@click.option("--args", default="", help="Default arguments to pass to target executable")
|
|
2423
|
+
@click.option("--cwd", default=None, help="Working directory for execution")
|
|
2424
|
+
@click.option("--prepend", default=None, help="Directory to prepend to PATH for target")
|
|
2425
|
+
def shim_add_cmd(name, target, args, cwd, prepend):
|
|
2426
|
+
"""Create or update a custom native Windows .exe shim."""
|
|
2427
|
+
try:
|
|
2428
|
+
created = shim.create(name, target, args=args, cwd=cwd, path_prepend=prepend)
|
|
2429
|
+
console.print(f"[bold green]✓ Created shim '{name}' -> {created}[/bold green]")
|
|
2430
|
+
except Exception as e:
|
|
2431
|
+
raise click.ClickException(str(e))
|
|
2432
|
+
|
|
2433
|
+
|
|
2434
|
+
@shim_cmd.command(name="rm")
|
|
2435
|
+
@click.argument("name")
|
|
2436
|
+
def shim_rm_cmd(name):
|
|
2437
|
+
"""Remove a shim."""
|
|
2438
|
+
if shim.remove(name):
|
|
2439
|
+
console.print(f"[bold green]✓ Removed shim '{name}'.[/bold green]")
|
|
2440
|
+
else:
|
|
2441
|
+
console.print(f"[yellow]No shim named '{name}' found.[/yellow]")
|
|
2442
|
+
|
|
2443
|
+
|
|
2444
|
+
@main.group(name="shims", hidden=True)
|
|
2445
|
+
def shims_alias():
|
|
2446
|
+
"""Alias for shim."""
|
|
2447
|
+
pass
|
|
2448
|
+
|
|
2449
|
+
|
|
2450
|
+
shims_alias.add_command(shim_list_cmd, "list")
|
|
2451
|
+
shims_alias.add_command(shim_init_cmd, "init")
|
|
2452
|
+
shims_alias.add_command(shim_add_cmd, "add")
|
|
2453
|
+
shims_alias.add_command(shim_rm_cmd, "rm")
|
|
2454
|
+
|
|
2455
|
+
|
|
2375
2456
|
if __name__ == "__main__":
|
|
2376
2457
|
sys.exit(main())
|
|
2377
2458
|
|
|
@@ -209,8 +209,12 @@ def nginx_upstream_name(version: str, domain: str | None = None) -> str:
|
|
|
209
209
|
|
|
210
210
|
def render_upstream_block(version: str, domain: str | None = None) -> str:
|
|
211
211
|
workers = status(version)
|
|
212
|
-
if
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
if workers:
|
|
213
|
+
servers = "\n".join(f" server 127.0.0.1:{w.port};" for w in workers)
|
|
214
|
+
else:
|
|
215
|
+
cfg = paths.load_config()
|
|
216
|
+
ports = ports_for(version, cfg["fcgi_workers_per_version"], cfg["fcgi_base_port"])
|
|
217
|
+
servers = "\n".join(f" server 127.0.0.1:{p};" for p in ports)
|
|
215
218
|
u_name = nginx_upstream_name(version, domain=domain)
|
|
216
219
|
return f"upstream {u_name} {{\n{servers}\n}}\n"
|
|
220
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ngrok tunnel wrapper for ndev-win virtual hosts.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from . import paths, vhost
|
|
12
|
+
|
|
13
|
+
HTTP_PORT = 80
|
|
14
|
+
HTTPS_PORT = 443
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def list_vhosts() -> list[str]:
|
|
18
|
+
"""Return list of domain names with configured Nginx virtual hosts."""
|
|
19
|
+
return [v["domain"] for v in vhost.list_vhosts()]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _ngrok_exe() -> str:
|
|
23
|
+
# 1. Check shim dir
|
|
24
|
+
shim_exe = paths.SHIM_DIR / "ngrok.exe"
|
|
25
|
+
if shim_exe.exists():
|
|
26
|
+
return str(shim_exe)
|
|
27
|
+
|
|
28
|
+
# 2. Check config
|
|
29
|
+
cfg = paths.load_config()
|
|
30
|
+
path = cfg.get("ngrok_path")
|
|
31
|
+
if path and Path(path).exists():
|
|
32
|
+
return str(path)
|
|
33
|
+
|
|
34
|
+
# 3. Check system PATH
|
|
35
|
+
which_path = shutil.which("ngrok") or shutil.which("ngrok.exe")
|
|
36
|
+
if which_path:
|
|
37
|
+
return which_path
|
|
38
|
+
|
|
39
|
+
raise FileNotFoundError("ngrok isn't installed -- run `ndev setup` first")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def start_tunnel(domain: str, ssl: bool | None = None) -> tuple[subprocess.Popen, Path]:
|
|
43
|
+
"""
|
|
44
|
+
Start `ngrok http` against Nginx.
|
|
45
|
+
Rewrites the request Host header to `domain` and rewrites any response
|
|
46
|
+
redirect Location headers from the local domain back to the ngrok public URL.
|
|
47
|
+
Returns (Popen, policy_file_path).
|
|
48
|
+
"""
|
|
49
|
+
import re
|
|
50
|
+
clean_domain = re.sub(r"^https?://", "", domain.strip().lower()).rstrip("/")
|
|
51
|
+
vhost_meta = vhost.get_vhost(clean_domain)
|
|
52
|
+
known = list_vhosts()
|
|
53
|
+
if not vhost_meta and clean_domain not in known:
|
|
54
|
+
raise FileNotFoundError(
|
|
55
|
+
f"No vhost found for '{clean_domain}'. Known vhosts: {known or '(none)'}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Determine SSL mode: use explicit flag if provided, otherwise auto-detect from vhost config
|
|
59
|
+
is_ssl = ssl if ssl is not None else bool(vhost_meta and vhost_meta.get("ssl"))
|
|
60
|
+
|
|
61
|
+
target = f"https://localhost:{HTTPS_PORT}" if is_ssl else str(HTTP_PORT)
|
|
62
|
+
|
|
63
|
+
# Build traffic policy YAML to ensure:
|
|
64
|
+
# 1. Request Host header is rewritten to clean_domain so Nginx routes to the right vhost.
|
|
65
|
+
# 2. Response Location header rewriting prevents external devices from being redirected
|
|
66
|
+
# to the unresolvable local domain (e.g. *.local).
|
|
67
|
+
policy_content = f'''on_http_request:
|
|
68
|
+
- actions:
|
|
69
|
+
- type: add-headers
|
|
70
|
+
config:
|
|
71
|
+
headers:
|
|
72
|
+
Host: "{clean_domain}"
|
|
73
|
+
|
|
74
|
+
on_http_response:
|
|
75
|
+
- expressions:
|
|
76
|
+
- 'res.status_code >= 300 && res.status_code < 400 && "location" in res.headers'
|
|
77
|
+
- 'res.headers["location"][0].contains("://{clean_domain}")'
|
|
78
|
+
actions:
|
|
79
|
+
- type: add-headers
|
|
80
|
+
config:
|
|
81
|
+
headers:
|
|
82
|
+
Location: '${{res.headers["location"][0].replace("https://{clean_domain}", conn.server_name.size() > 0 ? "https://" + conn.server_name : "").replace("http://{clean_domain}", conn.server_name.size() > 0 ? "https://" + conn.server_name : "")}}'
|
|
83
|
+
'''
|
|
84
|
+
|
|
85
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
|
|
86
|
+
tmp.write(policy_content)
|
|
87
|
+
policy_file = Path(tmp.name)
|
|
88
|
+
|
|
89
|
+
cmd = [_ngrok_exe(), "http", target, "--traffic-policy-file", str(policy_file)]
|
|
90
|
+
if is_ssl:
|
|
91
|
+
cmd.append("--upstream-tls-verify=false")
|
|
92
|
+
|
|
93
|
+
proc = subprocess.Popen(cmd)
|
|
94
|
+
return proc, policy_file
|
|
95
|
+
|
|
@@ -15,7 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
import httpx
|
|
17
17
|
|
|
18
|
-
from . import paths
|
|
18
|
+
from . import paths, shim
|
|
19
19
|
|
|
20
20
|
RELEASES_INDEX_URL = "https://windows.php.net/downloads/releases/releases.json"
|
|
21
21
|
ARCHIVES_INDEX_URL = "https://windows.php.net/downloads/releases/archives/"
|
|
@@ -498,12 +498,9 @@ def uninstall(version: str) -> None:
|
|
|
498
498
|
if paths.CURRENT_FILE.exists():
|
|
499
499
|
paths.CURRENT_FILE.unlink(missing_ok=True)
|
|
500
500
|
# Remove all PHP and Composer shims
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"composer.bat", "composer.cmd", "composer.ps1",
|
|
505
|
-
]:
|
|
506
|
-
(paths.SHIM_DIR / shim).unlink(missing_ok=True)
|
|
501
|
+
shim.remove("php")
|
|
502
|
+
shim.remove("php-cgi")
|
|
503
|
+
shim.remove("composer")
|
|
507
504
|
|
|
508
505
|
|
|
509
506
|
def list_installed() -> list[str]:
|
|
@@ -570,37 +567,30 @@ def get_current_version() -> str | None:
|
|
|
570
567
|
def use(version: str) -> str:
|
|
571
568
|
"""
|
|
572
569
|
Point ndev shims (on PATH) at this version's php.exe and php-cgi.exe.
|
|
573
|
-
Generates
|
|
570
|
+
Generates binary .exe shims with .cmd fallback in ~/.ndev/shims.
|
|
574
571
|
Returns the resolved version string.
|
|
575
572
|
"""
|
|
576
573
|
resolved_ver = resolve_installed(version)
|
|
577
574
|
paths.ensure_dirs()
|
|
578
575
|
|
|
579
|
-
|
|
580
|
-
|
|
576
|
+
php_dir = paths.version_dir(resolved_ver)
|
|
577
|
+
exe_path = str(php_dir / "php.exe")
|
|
578
|
+
cgi_path = str(php_dir / "php-cgi.exe")
|
|
581
579
|
|
|
582
|
-
#
|
|
583
|
-
(
|
|
584
|
-
(
|
|
585
|
-
(paths.SHIM_DIR / "php.ps1").write_text(f'& "{exe_path}" @args\r\n', encoding="utf-8")
|
|
580
|
+
# Create native shims via shimgen with path_prepend for PHP DLLs (cleans any legacy scripts)
|
|
581
|
+
shim.create("php", exe_path, path_prepend=str(php_dir), clean_legacy=True)
|
|
582
|
+
shim.create("php-cgi", cgi_path, path_prepend=str(php_dir), clean_legacy=True)
|
|
586
583
|
|
|
587
|
-
#
|
|
588
|
-
(paths.SHIM_DIR / "php-cgi.bat").write_text(f'@echo off\r\n"{cgi_path}" %*\r\n', encoding="utf-8")
|
|
589
|
-
(paths.SHIM_DIR / "php-cgi.cmd").write_text(f'@echo off\r\n"{cgi_path}" %*\r\n', encoding="utf-8")
|
|
590
|
-
(paths.SHIM_DIR / "php-cgi.ps1").write_text(f'& "{cgi_path}" @args\r\n', encoding="utf-8")
|
|
591
|
-
|
|
592
|
-
# If composer.phar is present, create/update composer shims
|
|
584
|
+
# If composer.phar is present, create/update composer native shim
|
|
593
585
|
composer_phar = paths.SHIM_DIR / "composer.phar"
|
|
594
586
|
if composer_phar.exists():
|
|
595
587
|
clean_phar = str(composer_phar.resolve()).replace("\\", "/")
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
(paths.SHIM_DIR / "composer.ps1").write_text(
|
|
603
|
-
f'& "{exe_path}" "{clean_phar}" @args\r\n', encoding="utf-8"
|
|
588
|
+
shim.create(
|
|
589
|
+
"composer",
|
|
590
|
+
exe_path,
|
|
591
|
+
args=f'"{clean_phar}"',
|
|
592
|
+
path_prepend=str(php_dir),
|
|
593
|
+
clean_legacy=True,
|
|
604
594
|
)
|
|
605
595
|
|
|
606
596
|
paths.set_current_version(resolved_ver)
|
|
@@ -16,7 +16,7 @@ import time
|
|
|
16
16
|
import zipfile
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
|
-
from . import fcgi, paths, setup as setup_core
|
|
19
|
+
from . import fcgi, paths, setup as setup_core, shim
|
|
20
20
|
|
|
21
21
|
DEFAULT_PORT = 6379
|
|
22
22
|
DEFAULT_VERSION = "8.10.1"
|
|
@@ -103,12 +103,15 @@ def install(version: str = DEFAULT_VERSION) -> Path:
|
|
|
103
103
|
)
|
|
104
104
|
|
|
105
105
|
# Place redis-cli shim in ~/.ndev/shims/
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
cli_file = REDIS_DIR / "redis-cli.exe"
|
|
107
|
+
if cli_file.exists():
|
|
108
108
|
try:
|
|
109
|
-
|
|
109
|
+
shim.create("redis-cli", str(cli_file), path_prepend=str(REDIS_DIR))
|
|
110
110
|
except Exception:
|
|
111
|
-
|
|
111
|
+
try:
|
|
112
|
+
shutil.copy2(cli_file, paths.SHIM_DIR / "redis-cli.exe")
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
112
115
|
|
|
113
116
|
return REDIS_DIR
|
|
114
117
|
|
|
@@ -12,7 +12,7 @@ from typing import Optional
|
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
15
|
-
from . import paths
|
|
15
|
+
from . import paths, shim
|
|
16
16
|
|
|
17
17
|
# ---- Defaults ---------------------------------------------------------------
|
|
18
18
|
|
|
@@ -170,16 +170,13 @@ def install_mariadb(version: str = DEFAULT_MARIADB_VERSION) -> Path:
|
|
|
170
170
|
|
|
171
171
|
def _create_mariadb_shims() -> None:
|
|
172
172
|
paths.ensure_dirs()
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
bin_dir = paths.MARIADB_DIR / "bin"
|
|
174
|
+
mysql_exe = bin_dir / "mysql.exe"
|
|
175
|
+
mysqldump_exe = bin_dir / "mysqldump.exe"
|
|
175
176
|
if mysql_exe.exists():
|
|
176
|
-
(
|
|
177
|
-
(paths.SHIM_DIR / "mysql.cmd").write_text(f'@echo off\r\n"{mysql_exe}" %*\r\n', encoding="utf-8")
|
|
178
|
-
(paths.SHIM_DIR / "mysql.ps1").write_text(f'& "{mysql_exe}" @args\r\n', encoding="utf-8")
|
|
177
|
+
shim.create("mysql", str(mysql_exe), path_prepend=str(bin_dir), clean_legacy=True)
|
|
179
178
|
if mysqldump_exe.exists():
|
|
180
|
-
(
|
|
181
|
-
(paths.SHIM_DIR / "mysqldump.cmd").write_text(f'@echo off\r\n"{mysqldump_exe}" %*\r\n', encoding="utf-8")
|
|
182
|
-
(paths.SHIM_DIR / "mysqldump.ps1").write_text(f'& "{mysqldump_exe}" @args\r\n', encoding="utf-8")
|
|
179
|
+
shim.create("mysqldump", str(mysqldump_exe), path_prepend=str(bin_dir), clean_legacy=True)
|
|
183
180
|
|
|
184
181
|
|
|
185
182
|
def _init_mariadb_data_dir() -> None:
|
|
@@ -280,14 +277,28 @@ def install_ngrok() -> Path:
|
|
|
280
277
|
# ---- Composer -----------------------------------------------------------------
|
|
281
278
|
|
|
282
279
|
def install_composer() -> Path:
|
|
283
|
-
"""Download composer.phar and generate
|
|
280
|
+
"""Download composer.phar and generate native composer.exe shim."""
|
|
284
281
|
phar_dest = paths.SHIM_DIR / "composer.phar"
|
|
285
282
|
_download(COMPOSER_PHAR_URL, phar_dest)
|
|
286
283
|
|
|
287
284
|
clean_phar = str(phar_dest.resolve()).replace("\\", "/")
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
# If active PHP is installed, point composer shim directly to it
|
|
286
|
+
from . import php
|
|
287
|
+
curr_ver = php.get_current_version()
|
|
288
|
+
if curr_ver:
|
|
289
|
+
try:
|
|
290
|
+
exe_path = str(paths.version_dir(curr_ver) / "php.exe")
|
|
291
|
+
shim.create(
|
|
292
|
+
"composer",
|
|
293
|
+
exe_path,
|
|
294
|
+
args=f'"{clean_phar}"',
|
|
295
|
+
path_prepend=str(paths.version_dir(curr_ver)),
|
|
296
|
+
clean_legacy=True,
|
|
297
|
+
)
|
|
298
|
+
return phar_dest
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
291
302
|
return phar_dest
|
|
292
303
|
|
|
293
304
|
|
|
@@ -306,25 +317,14 @@ def install_cacert() -> Path:
|
|
|
306
317
|
|
|
307
318
|
|
|
308
319
|
def create_ndev_shims() -> Path:
|
|
309
|
-
"""Create ndev
|
|
320
|
+
"""Create ndev native executable shim in ~/.ndev/shims/ pointing to active python/ndev."""
|
|
310
321
|
import sys
|
|
311
322
|
paths.ensure_dirs()
|
|
312
323
|
scripts_dir = Path(sys.executable).parent
|
|
313
324
|
ndev_exe = scripts_dir / "ndev.exe"
|
|
314
325
|
if ndev_exe.exists():
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
else:
|
|
318
|
-
bat_target = f'"{sys.executable}" -m ndev_win.cli %*'
|
|
319
|
-
ps_target = f'& "{sys.executable}" -m ndev_win.cli @args'
|
|
320
|
-
|
|
321
|
-
bat_path = paths.SHIM_DIR / "ndev.bat"
|
|
322
|
-
cmd_path = paths.SHIM_DIR / "ndev.cmd"
|
|
323
|
-
ps_path = paths.SHIM_DIR / "ndev.ps1"
|
|
324
|
-
bat_path.write_text(f'@echo off\r\n{bat_target}\r\n', encoding="utf-8")
|
|
325
|
-
cmd_path.write_text(f'@echo off\r\n{bat_target}\r\n', encoding="utf-8")
|
|
326
|
-
ps_path.write_text(f'{ps_target}\r\n', encoding="utf-8")
|
|
327
|
-
return bat_path
|
|
326
|
+
return shim.create("ndev", str(ndev_exe), clean_legacy=True)
|
|
327
|
+
return shim.create("ndev", sys.executable, args="-m ndev.main", clean_legacy=True)
|
|
328
328
|
|
|
329
329
|
|
|
330
330
|
# ---- Orchestration ----------------------------------------------------------
|