@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": "173203baaf36935d04eb81c89f1d016131a40442",
9
+ "revision": "043b3b2a0e3ba03b8d18666bb3fef59c588bfa16",
10
10
  "artifact": "runtime/dev_environment_wsl.py"
11
11
  },
12
- "targetGitBlob": "6731910246fb99887c58b9f3a8a14fec1bf02c01",
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
- if not command_path("fnm"):
90
- return False, "fnm is missing in WSL; install fnm inside the distro before rerunning /setup", "MISSING"
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
- return verify.returncode == 0 and version in text.lstrip("v"), text, "PASS" if verify.returncode == 0 else "FAILED"
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, "
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.14.1",
4
+ "version": "4.14.3",
5
5
  "defaultProfile": "greenfield-project",
6
6
  "profiles": {
7
7
  "greenfield-project": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.14.1",
4
+ "version": "4.14.3",
5
5
  "components": {
6
6
  "core": {
7
7
  "revision": "b51cb62bcc9630748168c9fd1900c6109cafb587",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mriekki/project-forge",
3
- "version": "4.14.1",
3
+ "version": "4.14.3",
4
4
  "description": "Public consumer distribution and updater for Project Forge engineering assets.",
5
5
  "type": "module",
6
6
  "bin": {