ndev-stack 0.1.0__py3-none-any.whl
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/__init__.py +8 -0
- ndev/__main__.py +4 -0
- ndev/cli.py +24 -0
- ndev/common/__init__.py +3 -0
- ndev/common/config.py +114 -0
- ndev/common/constants.py +51 -0
- ndev/common/github.py +13 -0
- ndev/common/logger.py +11 -0
- ndev/common/manifest.py +41 -0
- ndev/common/utils.py +96 -0
- ndev/linux/__init__.py +1 -0
- ndev/linux/chroot/manager.py +63 -0
- ndev/linux/chroot/packages.py +91 -0
- ndev/linux/chroot/shell.py +9 -0
- ndev/linux/cli.py +235 -0
- ndev/linux/commands/available.py +38 -0
- ndev/linux/commands/clean.py +25 -0
- ndev/linux/commands/ctl.py +192 -0
- ndev/linux/commands/current.py +11 -0
- ndev/linux/commands/db.py +319 -0
- ndev/linux/commands/doctor.py +56 -0
- ndev/linux/commands/grok.py +75 -0
- ndev/linux/commands/install.py +39 -0
- ndev/linux/commands/list.py +47 -0
- ndev/linux/commands/logs.py +36 -0
- ndev/linux/commands/mailpit.py +82 -0
- ndev/linux/commands/reload.py +26 -0
- ndev/linux/commands/restart.py +34 -0
- ndev/linux/commands/setup.py +113 -0
- ndev/linux/commands/start.py +34 -0
- ndev/linux/commands/status.py +81 -0
- ndev/linux/commands/stop.py +34 -0
- ndev/linux/commands/uninstall.py +69 -0
- ndev/linux/commands/update.py +57 -0
- ndev/linux/commands/upgrade.py +81 -0
- ndev/linux/commands/use.py +108 -0
- ndev/linux/commands/vhost.py +350 -0
- ndev/linux/php/builder.py +183 -0
- ndev/linux/php/downloader.py +58 -0
- ndev/linux/php/extensions.py +146 -0
- ndev/linux/php/installer.py +42 -0
- ndev/linux/php/resolver.py +59 -0
- ndev/linux/php/templates.py +128 -0
- ndev/linux/runtime/fpm.py +117 -0
- ndev/linux/runtime/mailpit.py +244 -0
- ndev/linux/runtime/pma.py +223 -0
- ndev/linux/runtime/process.py +37 -0
- ndev/linux/runtime/sockets.py +16 -0
- ndev/linux/runtime/upgrade.py +431 -0
- ndev/linux/tui.py +1423 -0
- ndev/main.py +52 -0
- ndev/tui.py +23 -0
- ndev/win/__init__.py +1 -0
- ndev/win/cli.py +1898 -0
- ndev/win/commands/__init__.py +0 -0
- ndev/win/core/__init__.py +0 -0
- ndev/win/core/db.py +265 -0
- ndev/win/core/elevate.py +94 -0
- ndev/win/core/ext.py +241 -0
- ndev/win/core/fcgi.py +216 -0
- ndev/win/core/grok.py +55 -0
- ndev/win/core/logs.py +66 -0
- ndev/win/core/mailpit.py +236 -0
- ndev/win/core/mkcert.py +65 -0
- ndev/win/core/paths.py +85 -0
- ndev/win/core/php.py +533 -0
- ndev/win/core/pma.py +190 -0
- ndev/win/core/services.py +349 -0
- ndev/win/core/setup.py +361 -0
- ndev/win/core/upgrade.py +513 -0
- ndev/win/core/vhost.py +289 -0
- ndev/win/templates/vhost.conf.tmpl +33 -0
- ndev/win/templates/vhost_ssl.conf.tmpl +43 -0
- ndev/win/tui.py +1313 -0
- ndev_stack-0.1.0.dist-info/METADATA +553 -0
- ndev_stack-0.1.0.dist-info/RECORD +79 -0
- ndev_stack-0.1.0.dist-info/WHEEL +5 -0
- ndev_stack-0.1.0.dist-info/entry_points.txt +4 -0
- ndev_stack-0.1.0.dist-info/top_level.txt +1 -0
ndev/win/core/setup.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`ndev setup` -- download and configure native Windows binaries for Nginx,
|
|
3
|
+
MariaDB, mkcert, ngrok, and Composer.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import shutil
|
|
8
|
+
import stat
|
|
9
|
+
import subprocess
|
|
10
|
+
import urllib.request
|
|
11
|
+
import zipfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from . import paths
|
|
16
|
+
|
|
17
|
+
# ---- Defaults ---------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
DEFAULT_NGINX_VERSION = "1.30.4"
|
|
20
|
+
DEFAULT_MARIADB_VERSION = "11.4.5"
|
|
21
|
+
DEFAULT_MKCERT_VERSION = "1.4.4"
|
|
22
|
+
NGROK_STABLE_URL = "https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip"
|
|
23
|
+
COMPOSER_PHAR_URL = "https://getcomposer.org/composer-stable.phar"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _download(url: str, dest: Path) -> Path:
|
|
27
|
+
paths.ensure_dirs()
|
|
28
|
+
if dest.exists() and dest.stat().st_size > 0:
|
|
29
|
+
return dest
|
|
30
|
+
tmp = dest.with_suffix(dest.suffix + ".part")
|
|
31
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ndev/0.1.0"})
|
|
32
|
+
try:
|
|
33
|
+
with urllib.request.urlopen(req, timeout=180) as resp, open(tmp, "wb") as f:
|
|
34
|
+
chunk_size = 65536
|
|
35
|
+
while True:
|
|
36
|
+
chunk = resp.read(chunk_size)
|
|
37
|
+
if not chunk:
|
|
38
|
+
break
|
|
39
|
+
f.write(chunk)
|
|
40
|
+
if tmp.exists() and tmp.stat().st_size > 0:
|
|
41
|
+
if dest.exists():
|
|
42
|
+
dest.unlink()
|
|
43
|
+
tmp.rename(dest)
|
|
44
|
+
else:
|
|
45
|
+
raise RuntimeError(f"Download from {url} resulted in empty file.")
|
|
46
|
+
except Exception:
|
|
47
|
+
if tmp.exists():
|
|
48
|
+
tmp.unlink(missing_ok=True)
|
|
49
|
+
raise
|
|
50
|
+
return dest
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _overlay_zip_flatten_single_root(
|
|
54
|
+
zip_path: Path,
|
|
55
|
+
target: Path,
|
|
56
|
+
preserve_dirs: Optional[list[str]] = None,
|
|
57
|
+
preserve_files: Optional[list[str]] = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
preserve_dirs = preserve_dirs or []
|
|
60
|
+
preserve_files = preserve_files or []
|
|
61
|
+
paths.ensure_dirs()
|
|
62
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
extract_tmp = paths.DOWNLOADS_DIR / f"_extract_{target.name}"
|
|
65
|
+
if extract_tmp.exists():
|
|
66
|
+
shutil.rmtree(extract_tmp)
|
|
67
|
+
|
|
68
|
+
with zipfile.ZipFile(zip_path) as zf:
|
|
69
|
+
zf.extractall(extract_tmp)
|
|
70
|
+
|
|
71
|
+
inner_dirs = [d for d in extract_tmp.iterdir() if d.is_dir()]
|
|
72
|
+
inner_files = [f for f in extract_tmp.iterdir() if f.is_file()]
|
|
73
|
+
root_src = inner_dirs[0] if (len(inner_dirs) == 1 and not inner_files) else extract_tmp
|
|
74
|
+
|
|
75
|
+
def _copy_tree_recursive(src: Path, dst: Path) -> None:
|
|
76
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
for item in src.iterdir():
|
|
78
|
+
dest_item = dst / item.name
|
|
79
|
+
if item.is_dir():
|
|
80
|
+
# If directory is in preserve list (e.g. data/ in MariaDB) and already contains user files, DO NOT wipe it!
|
|
81
|
+
if item.name in preserve_dirs and dest_item.exists() and any(dest_item.iterdir()):
|
|
82
|
+
continue
|
|
83
|
+
_copy_tree_recursive(item, dest_item)
|
|
84
|
+
elif item.is_file():
|
|
85
|
+
# If file is in preserve list (e.g. nginx.conf, my.ini) and already exists, DO NOT overwrite it!
|
|
86
|
+
if item.name in preserve_files and dest_item.exists() and dest_item.stat().st_size > 0:
|
|
87
|
+
continue
|
|
88
|
+
shutil.copy2(item, dest_item)
|
|
89
|
+
|
|
90
|
+
_copy_tree_recursive(root_src, target)
|
|
91
|
+
shutil.rmtree(extract_tmp, ignore_errors=True)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---- Nginx ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def install_nginx(version: str = DEFAULT_NGINX_VERSION) -> Path:
|
|
97
|
+
url = f"https://nginx.org/download/nginx-{version}.zip"
|
|
98
|
+
zip_path = _download(url, paths.DOWNLOADS_DIR / f"nginx-{version}.zip")
|
|
99
|
+
|
|
100
|
+
# Non-destructively overlay binaries while preserving conf/ (ndev-vhosts) and logs/
|
|
101
|
+
_overlay_zip_flatten_single_root(
|
|
102
|
+
zip_path,
|
|
103
|
+
paths.NGINX_DIR,
|
|
104
|
+
preserve_dirs=["logs", "temp"],
|
|
105
|
+
preserve_files=["nginx.conf"],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
paths.NGINX_CONF_D.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
paths.NGINX_LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
(paths.NGINX_DIR / "temp").mkdir(parents=True, exist_ok=True)
|
|
111
|
+
_include_vhosts_in_main_conf()
|
|
112
|
+
return paths.NGINX_DIR
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _include_vhosts_in_main_conf() -> None:
|
|
116
|
+
"""Add `include ndev-vhosts/*.conf;` inside the http{} block in nginx.conf."""
|
|
117
|
+
# Ensure default fallback vhost exists so Nginx never fails wildcard glob
|
|
118
|
+
default_conf = paths.NGINX_CONF_D / "_default.conf"
|
|
119
|
+
if not default_conf.exists():
|
|
120
|
+
default_conf.write_text(
|
|
121
|
+
'server {\n'
|
|
122
|
+
' listen 80 default_server;\n'
|
|
123
|
+
' server_name _;\n'
|
|
124
|
+
' return 404 "ndev: No virtual host configured for this domain.\\n";\n'
|
|
125
|
+
'}\n',
|
|
126
|
+
encoding="utf-8"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
conf = paths.NGINX_DIR / "conf" / "nginx.conf"
|
|
130
|
+
if not conf.exists():
|
|
131
|
+
return
|
|
132
|
+
text = conf.read_text(encoding="utf-8", errors="ignore")
|
|
133
|
+
include_line = " include ndev-vhosts/*.conf;\n"
|
|
134
|
+
if "ndev-vhosts" in text:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
http_start = text.find("http {")
|
|
138
|
+
if http_start == -1:
|
|
139
|
+
return
|
|
140
|
+
brace_depth = 0
|
|
141
|
+
i = text.find("{", http_start)
|
|
142
|
+
for idx in range(i, len(text)):
|
|
143
|
+
if text[idx] == "{":
|
|
144
|
+
brace_depth += 1
|
|
145
|
+
elif text[idx] == "}":
|
|
146
|
+
brace_depth -= 1
|
|
147
|
+
if brace_depth == 0:
|
|
148
|
+
text = text[:idx] + include_line + text[idx:]
|
|
149
|
+
break
|
|
150
|
+
conf.write_text(text, encoding="utf-8")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---- MariaDB ------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def install_mariadb(version: str = DEFAULT_MARIADB_VERSION) -> Path:
|
|
156
|
+
url = f"https://archive.mariadb.org/mariadb-{version}/winx64-packages/mariadb-{version}-winx64.zip"
|
|
157
|
+
zip_path = _download(url, paths.DOWNLOADS_DIR / f"mariadb-{version}-winx64.zip")
|
|
158
|
+
|
|
159
|
+
# Non-destructively overlay binaries while preserving data/ and my.ini
|
|
160
|
+
_overlay_zip_flatten_single_root(
|
|
161
|
+
zip_path,
|
|
162
|
+
paths.MARIADB_DIR,
|
|
163
|
+
preserve_dirs=["data"],
|
|
164
|
+
preserve_files=["my.ini"],
|
|
165
|
+
)
|
|
166
|
+
_init_mariadb_data_dir()
|
|
167
|
+
_create_mariadb_shims()
|
|
168
|
+
return paths.MARIADB_DIR
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _create_mariadb_shims() -> None:
|
|
172
|
+
paths.ensure_dirs()
|
|
173
|
+
mysql_exe = paths.MARIADB_DIR / "bin" / "mysql.exe"
|
|
174
|
+
mysqldump_exe = paths.MARIADB_DIR / "bin" / "mysqldump.exe"
|
|
175
|
+
if mysql_exe.exists():
|
|
176
|
+
(paths.SHIM_DIR / "mysql.bat").write_text(f'@echo off\r\n"{mysql_exe}" %*\r\n', encoding="utf-8")
|
|
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")
|
|
179
|
+
if mysqldump_exe.exists():
|
|
180
|
+
(paths.SHIM_DIR / "mysqldump.bat").write_text(f'@echo off\r\n"{mysqldump_exe}" %*\r\n', encoding="utf-8")
|
|
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")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _init_mariadb_data_dir() -> None:
|
|
186
|
+
"""Initialize MariaDB data directory with mariadb-install-db.exe and generate isolated my.ini."""
|
|
187
|
+
data_dir = paths.MARIADB_DIR / "data"
|
|
188
|
+
clean_data_dir = str(data_dir.resolve()).replace("\\", "/")
|
|
189
|
+
clean_base_dir = str(paths.MARIADB_DIR.resolve()).replace("\\", "/")
|
|
190
|
+
|
|
191
|
+
# Generate custom my.ini for isolated local execution
|
|
192
|
+
my_ini = paths.MARIADB_DIR / "my.ini"
|
|
193
|
+
if not my_ini.exists():
|
|
194
|
+
my_ini_content = f"""[mysqld]
|
|
195
|
+
basedir = "{clean_base_dir}"
|
|
196
|
+
datadir = "{clean_data_dir}"
|
|
197
|
+
port = 3306
|
|
198
|
+
bind-address = 127.0.0.1
|
|
199
|
+
character-set-server = utf8mb4
|
|
200
|
+
collation-server = utf8mb4_unicode_ci
|
|
201
|
+
max_allowed_packet = 64M
|
|
202
|
+
default-storage-engine = INNODB
|
|
203
|
+
innodb_buffer_pool_size = 128M
|
|
204
|
+
|
|
205
|
+
[client]
|
|
206
|
+
port = 3306
|
|
207
|
+
default-character-set = utf8mb4
|
|
208
|
+
|
|
209
|
+
[mysql]
|
|
210
|
+
default-character-set = utf8mb4
|
|
211
|
+
"""
|
|
212
|
+
my_ini.write_text(my_ini_content, encoding="utf-8")
|
|
213
|
+
|
|
214
|
+
if data_dir.exists() and any(data_dir.iterdir()):
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
installer = paths.MARIADB_DIR / "bin" / "mariadb-install-db.exe"
|
|
218
|
+
if not installer.exists():
|
|
219
|
+
installer = paths.MARIADB_DIR / "bin" / "mysql_install_db.exe"
|
|
220
|
+
if not installer.exists():
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
for cmd in [
|
|
224
|
+
[str(installer), f"--datadir={clean_data_dir}", "--service=", "--password=root"],
|
|
225
|
+
[str(installer), f"--datadir={clean_data_dir}", "--password=root"],
|
|
226
|
+
[str(installer), f"--datadir={clean_data_dir}"],
|
|
227
|
+
]:
|
|
228
|
+
try:
|
|
229
|
+
res = subprocess.run(cmd, cwd=str(paths.MARIADB_DIR), capture_output=True, timeout=60)
|
|
230
|
+
if res.returncode == 0:
|
|
231
|
+
break
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---- mkcert -------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
def install_mkcert(version: str = DEFAULT_MKCERT_VERSION) -> Path:
|
|
239
|
+
url = f"https://github.com/FiloSottile/mkcert/releases/download/v{version}/mkcert-v{version}-windows-amd64.exe"
|
|
240
|
+
dest = paths.SHIM_DIR / "mkcert.exe"
|
|
241
|
+
_download(url, dest)
|
|
242
|
+
|
|
243
|
+
cfg = paths.load_config()
|
|
244
|
+
cfg["mkcert_path"] = str(dest)
|
|
245
|
+
paths.save_config(cfg)
|
|
246
|
+
|
|
247
|
+
# Automatically install root CA
|
|
248
|
+
try:
|
|
249
|
+
from . import mkcert
|
|
250
|
+
mkcert.ensure_ca_installed()
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
return dest
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ---- ngrok --------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
def install_ngrok() -> Path:
|
|
260
|
+
try:
|
|
261
|
+
zip_path = _download(NGROK_STABLE_URL, paths.DOWNLOADS_DIR / "ngrok-stable-windows-amd64.zip")
|
|
262
|
+
with zipfile.ZipFile(zip_path) as zf:
|
|
263
|
+
zf.extract("ngrok.exe", paths.SHIM_DIR)
|
|
264
|
+
dest = paths.SHIM_DIR / "ngrok.exe"
|
|
265
|
+
cfg = paths.load_config()
|
|
266
|
+
cfg["ngrok_path"] = str(dest)
|
|
267
|
+
paths.save_config(cfg)
|
|
268
|
+
return dest
|
|
269
|
+
except Exception as e:
|
|
270
|
+
error_msg = str(e)
|
|
271
|
+
if "virus" in error_msg.lower() or "unwanted" in error_msg.lower() or "225" in error_msg:
|
|
272
|
+
raise RuntimeError(
|
|
273
|
+
"Windows Defender / Antivirus blocked ngrok.exe (Heuristic False Positive). "
|
|
274
|
+
"Reverse-tunnel tools are frequently flagged by antivirus heuristic engines. "
|
|
275
|
+
"You can run `ndev setup --no-ngrok` to skip ngrok installation, or whitelist ~/.ndev/shims/ngrok.exe in Windows Security."
|
|
276
|
+
) from e
|
|
277
|
+
raise
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ---- Composer -----------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
def install_composer() -> Path:
|
|
283
|
+
"""Download composer.phar and generate composer.bat, composer.cmd, and composer.ps1 shims."""
|
|
284
|
+
phar_dest = paths.SHIM_DIR / "composer.phar"
|
|
285
|
+
_download(COMPOSER_PHAR_URL, phar_dest)
|
|
286
|
+
|
|
287
|
+
clean_phar = str(phar_dest.resolve()).replace("\\", "/")
|
|
288
|
+
(paths.SHIM_DIR / "composer.bat").write_text(f'@echo off\r\nphp "{clean_phar}" %*\r\n', encoding="utf-8")
|
|
289
|
+
(paths.SHIM_DIR / "composer.cmd").write_text(f'@echo off\r\nphp "{clean_phar}" %*\r\n', encoding="utf-8")
|
|
290
|
+
(paths.SHIM_DIR / "composer.ps1").write_text(f'& php "{clean_phar}" @args\r\n', encoding="utf-8")
|
|
291
|
+
return phar_dest
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ---- CA Certificates ---------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
def install_cacert() -> Path:
|
|
297
|
+
"""Download Mozilla root CA bundle for PHP cURL and OpenSSL."""
|
|
298
|
+
paths.ensure_dirs()
|
|
299
|
+
if not paths.CACERT_PATH.exists():
|
|
300
|
+
_download("https://curl.se/ca/cacert.pem", paths.CACERT_PATH)
|
|
301
|
+
# Re-apply php.ini updates across all installed PHP runtimes
|
|
302
|
+
from . import php
|
|
303
|
+
for v in php.list_installed():
|
|
304
|
+
php._configure_php_ini(paths.version_dir(v))
|
|
305
|
+
return paths.CACERT_PATH
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def create_ndev_shims() -> Path:
|
|
309
|
+
"""Create ndev.bat, ndev.cmd, and ndev.ps1 in ~/.ndev/shims/ pointing to active python/ndev."""
|
|
310
|
+
import sys
|
|
311
|
+
paths.ensure_dirs()
|
|
312
|
+
scripts_dir = Path(sys.executable).parent
|
|
313
|
+
ndev_exe = scripts_dir / "ndev.exe"
|
|
314
|
+
if ndev_exe.exists():
|
|
315
|
+
bat_target = f'"{ndev_exe}" %*'
|
|
316
|
+
ps_target = f'& "{ndev_exe}" @args'
|
|
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
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# ---- Orchestration ----------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
def run_setup(
|
|
333
|
+
nginx: bool = True,
|
|
334
|
+
mariadb: bool = True,
|
|
335
|
+
mkcert: bool = True,
|
|
336
|
+
ngrok: bool = True,
|
|
337
|
+
composer: bool = True,
|
|
338
|
+
cacert: bool = True,
|
|
339
|
+
versions: Optional[dict] = None,
|
|
340
|
+
) -> dict:
|
|
341
|
+
versions = versions or {}
|
|
342
|
+
results = {}
|
|
343
|
+
if nginx:
|
|
344
|
+
results["nginx"] = install_nginx(versions.get("nginx", DEFAULT_NGINX_VERSION))
|
|
345
|
+
if mariadb:
|
|
346
|
+
results["mariadb"] = install_mariadb(versions.get("mariadb", DEFAULT_MARIADB_VERSION))
|
|
347
|
+
if mkcert:
|
|
348
|
+
results["mkcert"] = install_mkcert(versions.get("mkcert", DEFAULT_MKCERT_VERSION))
|
|
349
|
+
if ngrok:
|
|
350
|
+
try:
|
|
351
|
+
results["ngrok"] = install_ngrok()
|
|
352
|
+
except Exception as e:
|
|
353
|
+
results["ngrok"] = f"Skipped: {e}"
|
|
354
|
+
if composer:
|
|
355
|
+
results["composer"] = install_composer()
|
|
356
|
+
if cacert:
|
|
357
|
+
results["cacert"] = install_cacert()
|
|
358
|
+
|
|
359
|
+
# Always ensure CLI shims
|
|
360
|
+
results["shims"] = create_ndev_shims()
|
|
361
|
+
return results
|