@mriekki/project-forge 4.14.1 → 4.14.3
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.
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
"asset": "assets/bootstrap-migrations/wsl-setup/setup-wsl.py",
|
|
7
7
|
"source": {
|
|
8
8
|
"component": "bootstrap",
|
|
9
|
-
"revision": "
|
|
9
|
+
"revision": "043b3b2a0e3ba03b8d18666bb3fef59c588bfa16",
|
|
10
10
|
"artifact": "runtime/dev_environment_wsl.py"
|
|
11
11
|
},
|
|
12
|
-
"targetGitBlob": "
|
|
13
|
-
"supportedPredecessorGitBlobs": [
|
|
12
|
+
"targetGitBlob": "0b959b4e85508016f6e8c12d403a94b2eed46c1f",
|
|
13
|
+
"supportedPredecessorGitBlobs": [
|
|
14
|
+
"6731910246fb99887c58b9f3a8a14fec1bf02c01",
|
|
15
|
+
"883f7bbadd2de4bc0dcb601ee2285f88a0085b52"
|
|
16
|
+
],
|
|
14
17
|
"supportedBootstrapRevisions": [
|
|
15
|
-
"0c45eb332a815ad79ea061443f337500b9dc5712"
|
|
18
|
+
"0c45eb332a815ad79ea061443f337500b9dc5712",
|
|
19
|
+
"173203baaf36935d04eb81c89f1d016131a40442",
|
|
20
|
+
"9f0f7fd4e0e2220250f6fd5b035706a7d15834af"
|
|
16
21
|
],
|
|
17
22
|
"allowMissing": true
|
|
18
23
|
}
|
|
@@ -8,6 +8,7 @@ import re
|
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
10
|
import sys
|
|
11
|
+
import tempfile
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
@@ -85,24 +86,163 @@ def ensure_apt_package(package: str) -> None:
|
|
|
85
86
|
raise SetupError(f"Unable to install required Ubuntu package: {package}")
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def fnm_install_dir() -> Path:
|
|
90
|
+
xdg_data_home = os.environ.get("XDG_DATA_HOME")
|
|
91
|
+
base = Path(xdg_data_home).expanduser() if xdg_data_home else Path.home() / ".local" / "share"
|
|
92
|
+
return base / "fnm"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def refresh_fnm_path() -> None:
|
|
96
|
+
install_dir = fnm_install_dir()
|
|
97
|
+
fnm_binary = install_dir / "fnm"
|
|
98
|
+
if not fnm_binary.is_file():
|
|
99
|
+
return
|
|
100
|
+
current = os.environ.get("PATH", "")
|
|
101
|
+
prefix = str(install_dir)
|
|
102
|
+
parts = current.split(os.pathsep) if current else []
|
|
103
|
+
if prefix not in parts:
|
|
104
|
+
os.environ["PATH"] = prefix + (os.pathsep + current if current else "")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def ensure_fnm_available(check_only: bool) -> tuple[bool, str, str]:
|
|
108
|
+
refresh_fnm_path()
|
|
109
|
+
path = command_path("fnm")
|
|
110
|
+
if path:
|
|
111
|
+
result = run(["fnm", "--version"])
|
|
112
|
+
detail = "\n".join(filter(None, [(result.stdout or "").strip(), (result.stderr or "").strip()]))
|
|
113
|
+
return result.returncode == 0, detail or path, "PASS" if result.returncode == 0 else "FAILED"
|
|
114
|
+
|
|
115
|
+
if check_only:
|
|
116
|
+
return False, "fnm is missing in WSL", "MISSING"
|
|
117
|
+
|
|
118
|
+
ensure_apt_package("curl")
|
|
119
|
+
ensure_apt_package("unzip")
|
|
120
|
+
install = run(
|
|
121
|
+
["bash", "-lc", "curl -fsSL https://fnm.vercel.app/install | bash"],
|
|
122
|
+
capture=False,
|
|
123
|
+
)
|
|
124
|
+
if install.returncode != 0:
|
|
125
|
+
return False, "fnm installer failed", "FAILED"
|
|
126
|
+
|
|
127
|
+
refresh_fnm_path()
|
|
128
|
+
path = command_path("fnm")
|
|
129
|
+
if not path:
|
|
130
|
+
return False, f"fnm installer completed but {fnm_install_dir() / 'fnm'} is unavailable", "FAILED"
|
|
131
|
+
|
|
132
|
+
verify = run(["fnm", "--version"])
|
|
133
|
+
detail = "\n".join(filter(None, [(verify.stdout or "").strip(), (verify.stderr or "").strip()]))
|
|
134
|
+
return verify.returncode == 0, detail or path, "PASS" if verify.returncode == 0 else "FAILED"
|
|
135
|
+
|
|
136
|
+
|
|
88
137
|
def ensure_fnm_node(version: str | None, check_only: bool) -> tuple[bool, str, str]:
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
fnm_ok, fnm_detail, fnm_state = ensure_fnm_available(check_only)
|
|
139
|
+
if not fnm_ok:
|
|
140
|
+
return False, fnm_detail, fnm_state
|
|
91
141
|
if not version:
|
|
92
|
-
return True, "fnm available", "PASS"
|
|
142
|
+
return True, fnm_detail or "fnm available", "PASS"
|
|
143
|
+
|
|
93
144
|
result = run(["fnm", "exec", f"--using={version}", "--", "node", "--version"])
|
|
94
145
|
detail = "\n".join(filter(None, [(result.stdout or "").strip(), (result.stderr or "").strip()]))
|
|
95
146
|
if result.returncode == 0 and version in detail.lstrip("v"):
|
|
96
147
|
return True, detail, "PASS"
|
|
97
148
|
if check_only:
|
|
98
149
|
return False, detail or f"Node {version} is not installed through fnm", "VERSION_MISMATCH"
|
|
150
|
+
|
|
99
151
|
install = run(["fnm", "install", version], capture=False)
|
|
100
152
|
if install.returncode != 0:
|
|
101
153
|
return False, f"fnm failed to install Node {version}", "FAILED"
|
|
102
|
-
run(["fnm", "default", version], capture=False)
|
|
154
|
+
default = run(["fnm", "default", version], capture=False)
|
|
155
|
+
if default.returncode != 0:
|
|
156
|
+
return False, f"fnm installed Node {version} but failed to make it the default", "FAILED"
|
|
157
|
+
|
|
103
158
|
verify = run(["fnm", "exec", f"--using={version}", "--", "node", "--version"])
|
|
104
159
|
text = (verify.stdout or verify.stderr or "").strip()
|
|
105
|
-
|
|
160
|
+
ok = verify.returncode == 0 and version in text.lstrip("v")
|
|
161
|
+
return ok, text or f"Node {version} verification failed", "PASS" if ok else "FAILED"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def global_dotnet_sdk_version() -> str | None:
|
|
165
|
+
global_json = ROOT / "global.json"
|
|
166
|
+
if not global_json.exists():
|
|
167
|
+
return None
|
|
168
|
+
sdk = read_json(global_json).get("sdk")
|
|
169
|
+
if not isinstance(sdk, dict):
|
|
170
|
+
return None
|
|
171
|
+
version = sdk.get("version")
|
|
172
|
+
return version if isinstance(version, str) else None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def ensure_dotnet_sdk(
|
|
176
|
+
spec: dict[str, Any],
|
|
177
|
+
version: str | None,
|
|
178
|
+
check_only: bool,
|
|
179
|
+
) -> tuple[bool, str, str]:
|
|
180
|
+
ok, detail = version_ok(spec, version)
|
|
181
|
+
if ok:
|
|
182
|
+
return True, detail, "PASS"
|
|
183
|
+
if check_only:
|
|
184
|
+
return False, detail, "MISSING" if detail == "missing" else "VERSION_MISMATCH"
|
|
185
|
+
|
|
186
|
+
if not version:
|
|
187
|
+
return False, ".NET SDK version is required for automatic WSL installation", "FAILED"
|
|
188
|
+
major = version.split(".", 1)[0]
|
|
189
|
+
if not re.fullmatch(r"\d+", major):
|
|
190
|
+
return False, f"Unsupported .NET SDK version: {version}", "FAILED"
|
|
191
|
+
|
|
192
|
+
ensure_apt_package(f"dotnet-sdk-{major}.0")
|
|
193
|
+
ok, detail = version_ok(spec, version)
|
|
194
|
+
if ok:
|
|
195
|
+
return True, detail, "PASS"
|
|
196
|
+
|
|
197
|
+
sdk_version = global_dotnet_sdk_version()
|
|
198
|
+
if not sdk_version:
|
|
199
|
+
return False, (
|
|
200
|
+
detail
|
|
201
|
+
or f".NET SDK {version} is installed, but the repository resolver still cannot select a compatible SDK"
|
|
202
|
+
), "FAILED"
|
|
203
|
+
if not re.fullmatch(r"\d+\.\d+\.\d+", sdk_version):
|
|
204
|
+
return False, f"Unsupported global.json .NET SDK version: {sdk_version}", "FAILED"
|
|
205
|
+
|
|
206
|
+
declared_parts = version.split(".")
|
|
207
|
+
sdk_parts = sdk_version.split(".")
|
|
208
|
+
if sdk_parts[: len(declared_parts)] != declared_parts:
|
|
209
|
+
return False, (
|
|
210
|
+
f"global.json requires .NET SDK {sdk_version}, outside the declared setup family {version}"
|
|
211
|
+
), "FAILED"
|
|
212
|
+
|
|
213
|
+
dotnet = command_path("dotnet")
|
|
214
|
+
if not dotnet:
|
|
215
|
+
return False, "dotnet remained unavailable after Ubuntu SDK installation", "FAILED"
|
|
216
|
+
install_root = Path(dotnet).resolve().parent
|
|
217
|
+
|
|
218
|
+
ensure_apt_package("curl")
|
|
219
|
+
with tempfile.TemporaryDirectory(prefix="project-forge-dotnet-") as temp_dir:
|
|
220
|
+
installer = Path(temp_dir) / "dotnet-install.sh"
|
|
221
|
+
download = run(
|
|
222
|
+
["curl", "-fsSL", "https://dot.net/v1/dotnet-install.sh", "-o", str(installer)],
|
|
223
|
+
capture=False,
|
|
224
|
+
)
|
|
225
|
+
if download.returncode != 0:
|
|
226
|
+
return False, "Unable to download the official .NET install script", "FAILED"
|
|
227
|
+
|
|
228
|
+
install = run(
|
|
229
|
+
[
|
|
230
|
+
"sudo",
|
|
231
|
+
"bash",
|
|
232
|
+
str(installer),
|
|
233
|
+
"--version",
|
|
234
|
+
sdk_version,
|
|
235
|
+
"--install-dir",
|
|
236
|
+
str(install_root),
|
|
237
|
+
"--skip-non-versioned-files",
|
|
238
|
+
],
|
|
239
|
+
capture=False,
|
|
240
|
+
)
|
|
241
|
+
if install.returncode != 0:
|
|
242
|
+
return False, f"Unable to install .NET SDK {sdk_version}", "FAILED"
|
|
243
|
+
|
|
244
|
+
ok, detail = version_ok(spec, version)
|
|
245
|
+
return ok, detail, "PASS" if ok else "FAILED"
|
|
106
246
|
|
|
107
247
|
|
|
108
248
|
def ensure_tool(entry: dict[str, Any], check_only: bool) -> tuple[bool, str, str]:
|
|
@@ -110,8 +250,12 @@ def ensure_tool(entry: dict[str, Any], check_only: bool) -> tuple[bool, str, str
|
|
|
110
250
|
spec = read_json(CATALOG / f"{tool_id}.json")
|
|
111
251
|
version = entry.get("version")
|
|
112
252
|
|
|
253
|
+
if tool_id == "fnm":
|
|
254
|
+
return ensure_fnm_available(check_only)
|
|
113
255
|
if tool_id == "node":
|
|
114
256
|
return ensure_fnm_node(version, check_only)
|
|
257
|
+
if tool_id == "dotnet":
|
|
258
|
+
return ensure_dotnet_sdk(spec, version, check_only)
|
|
115
259
|
|
|
116
260
|
ok, detail = version_ok(spec, version)
|
|
117
261
|
if ok:
|
|
@@ -130,10 +274,15 @@ def ensure_tool(entry: dict[str, Any], check_only: bool) -> tuple[bool, str, str
|
|
|
130
274
|
if check_only:
|
|
131
275
|
return False, detail, "MISSING" if detail == "missing" else "VERSION_MISMATCH"
|
|
132
276
|
|
|
277
|
+
if tool_id == "python":
|
|
278
|
+
ensure_apt_package("python3")
|
|
279
|
+
ensure_apt_package("python-is-python3")
|
|
280
|
+
ok, detail = version_ok(spec, version)
|
|
281
|
+
return ok, detail, "PASS" if ok else "FAILED"
|
|
282
|
+
|
|
133
283
|
apt_packages = {
|
|
134
284
|
"git": "git",
|
|
135
285
|
"github-cli": "gh",
|
|
136
|
-
"python": "python3",
|
|
137
286
|
"sqlite": "sqlite3",
|
|
138
287
|
"ssh": "openssh-client",
|
|
139
288
|
"go": "golang-go",
|
|
@@ -153,16 +302,6 @@ def ensure_tool(entry: dict[str, Any], check_only: bool) -> tuple[bool, str, str
|
|
|
153
302
|
"Docker is unavailable inside WSL. Install/start Docker Desktop on Windows and enable "
|
|
154
303
|
"WSL integration for this distro; do not install Docker Engine inside WSL."
|
|
155
304
|
), "MISSING"
|
|
156
|
-
if tool_id == "fnm":
|
|
157
|
-
return False, (
|
|
158
|
-
"fnm is required inside WSL for Project-declared Node versions. Install fnm in this "
|
|
159
|
-
"distro, then rerun /setup."
|
|
160
|
-
), "MISSING"
|
|
161
|
-
if tool_id == "dotnet":
|
|
162
|
-
return False, (
|
|
163
|
-
f".NET SDK {version or ''} is required inside WSL. Install the matching Microsoft .NET SDK "
|
|
164
|
-
"for Ubuntu in this distro, then rerun /setup."
|
|
165
|
-
).strip(), "MISSING" if detail == "missing" else "VERSION_MISMATCH"
|
|
166
305
|
if tool_id == "java":
|
|
167
306
|
return False, (
|
|
168
307
|
f"Java {version or ''} is required inside WSL. Install the matching JDK in this distro, "
|
package/assets/components.json
CHANGED