modforge-cli 0.1.6__tar.gz → 0.1.8__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.
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/PKG-INFO +1 -1
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/pyproject.toml +1 -1
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/__version__.py +1 -1
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/cli.py +83 -206
- modforge_cli-0.1.8/src/modforge_cli/core/__init__.py +23 -0
- modforge_cli-0.1.8/src/modforge_cli/core/utils.py +149 -0
- modforge_cli-0.1.6/src/modforge_cli/core/__init__.py +0 -6
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/LICENSE +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/README.md +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/__init__.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/__main__.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/api/__init__.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/api/modrinth.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/core/downloader.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/core/models.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/core/policy.py +0 -0
- {modforge_cli-0.1.6 → modforge_cli-0.1.8}/src/modforge_cli/core/resolver.py +0 -0
|
@@ -2,11 +2,9 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import shutil
|
|
4
4
|
import subprocess
|
|
5
|
-
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import Optional
|
|
8
7
|
|
|
9
|
-
import aiohttp
|
|
10
8
|
import typer
|
|
11
9
|
from pyfiglet import figlet_format
|
|
12
10
|
from rich.console import Console
|
|
@@ -16,8 +14,16 @@ from rich.table import Table
|
|
|
16
14
|
from rich.text import Text
|
|
17
15
|
|
|
18
16
|
from modforge_cli.api import ModrinthAPIConfig
|
|
19
|
-
from modforge_cli.core import Manifest
|
|
17
|
+
from modforge_cli.core import Manifest
|
|
20
18
|
from modforge_cli.core import ModPolicy, ModResolver
|
|
19
|
+
from modforge_cli.core import (
|
|
20
|
+
self_update,
|
|
21
|
+
install_fabric,
|
|
22
|
+
get_manifest,
|
|
23
|
+
perform_add,
|
|
24
|
+
ensure_config_file,
|
|
25
|
+
run,
|
|
26
|
+
)
|
|
21
27
|
|
|
22
28
|
# Import version info
|
|
23
29
|
try:
|
|
@@ -46,116 +52,25 @@ DEFAULT_MODRINTH_API_URL = "https://raw.githubusercontent.com/Frank1o3/ModForge-
|
|
|
46
52
|
DEFAULT_POLICY_URL = "https://raw.githubusercontent.com/Frank1o3/ModForge-CLI/refs/heads/main/configs/policy.json"
|
|
47
53
|
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
if path.exists():
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
-
|
|
55
|
-
console.print(f"[yellow]Missing {label} config.[/yellow] Downloading default…")
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
import urllib.request
|
|
59
|
-
|
|
60
|
-
urllib.request.urlretrieve(url, path)
|
|
61
|
-
console.print(f"[green]✓ {label} config installed at {path}[/green]")
|
|
62
|
-
except Exception as e:
|
|
63
|
-
console.print(f"[red]Failed to download {label} config:[/red] {e}")
|
|
64
|
-
raise typer.Exit(1)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
ensure_config_file(
|
|
68
|
-
MODRINTH_API,
|
|
69
|
-
DEFAULT_MODRINTH_API_URL,
|
|
70
|
-
"Modrinth API",
|
|
71
|
-
)
|
|
55
|
+
ensure_config_file(MODRINTH_API, DEFAULT_MODRINTH_API_URL, "Modrinth API", console)
|
|
72
56
|
|
|
73
|
-
ensure_config_file(
|
|
74
|
-
POLICY_PATH,
|
|
75
|
-
DEFAULT_POLICY_URL,
|
|
76
|
-
"Policy",
|
|
77
|
-
)
|
|
57
|
+
ensure_config_file(POLICY_PATH, DEFAULT_POLICY_URL, "Policy", console)
|
|
78
58
|
|
|
79
59
|
|
|
80
60
|
api = ModrinthAPIConfig(MODRINTH_API)
|
|
81
61
|
|
|
82
62
|
|
|
83
|
-
# --- Async Helper ---
|
|
84
|
-
async def get_api_session():
|
|
85
|
-
"""Returns a session with the correct ModForge-CLI headers."""
|
|
86
|
-
return aiohttp.ClientSession(
|
|
87
|
-
headers={"User-Agent": f"{__author__}/ModForge-CLI/{__version__}"},
|
|
88
|
-
raise_for_status=True,
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def get_manifest(path: Path = Path.cwd()) -> Optional[Manifest]:
|
|
93
|
-
p = path / "ModForge-CLI.json"
|
|
94
|
-
if not p.exists():
|
|
95
|
-
return None
|
|
96
|
-
try:
|
|
97
|
-
return Manifest.model_validate_json(p.read_text())
|
|
98
|
-
except Exception as e:
|
|
99
|
-
console.print(e)
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def install_fabric(
|
|
104
|
-
installer: Path,
|
|
105
|
-
mc_version: str,
|
|
106
|
-
loader_version: str,
|
|
107
|
-
game_dir: Path,
|
|
108
|
-
):
|
|
109
|
-
subprocess.run(
|
|
110
|
-
[
|
|
111
|
-
"java",
|
|
112
|
-
"-jar",
|
|
113
|
-
installer,
|
|
114
|
-
"client",
|
|
115
|
-
"-mcversion",
|
|
116
|
-
mc_version,
|
|
117
|
-
"-loader",
|
|
118
|
-
loader_version,
|
|
119
|
-
"-dir",
|
|
120
|
-
str(game_dir),
|
|
121
|
-
"-noprofile",
|
|
122
|
-
],
|
|
123
|
-
check=True,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def detect_install_method() -> str:
|
|
128
|
-
prefix = Path(sys.prefix)
|
|
129
|
-
|
|
130
|
-
if "pipx" in prefix.parts:
|
|
131
|
-
return "pipx"
|
|
132
|
-
return "pip"
|
|
133
|
-
|
|
134
|
-
def self_update():
|
|
135
|
-
method = detect_install_method()
|
|
136
|
-
|
|
137
|
-
if method == "pipx":
|
|
138
|
-
console.print("[cyan]Updating ModForge-CLI using pipx...[/cyan]")
|
|
139
|
-
subprocess.run(["pipx", "upgrade", "ModForge-CLI"], check=True)
|
|
140
|
-
|
|
141
|
-
else:
|
|
142
|
-
console.print("[cyan]Updating ModForge-CLI using pip...[/cyan]")
|
|
143
|
-
subprocess.run(
|
|
144
|
-
[sys.executable, "-m", "pip", "install", "--upgrade", "ModForge-CLI"],
|
|
145
|
-
check=True,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
console.print("[green]ModForge-CLI updated successfully.[/green]")
|
|
149
|
-
|
|
150
|
-
|
|
151
63
|
def render_banner():
|
|
152
|
-
"""Renders a high-quality stylized banner"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
64
|
+
"""Renders a high-quality stylized banner that respects terminal width"""
|
|
65
|
+
# Get the current terminal width
|
|
66
|
+
width = console.width
|
|
67
|
+
|
|
68
|
+
# Use a smaller font or logic if the terminal is very narrow
|
|
69
|
+
font = "slant" if width > 60 else "small"
|
|
70
|
+
|
|
71
|
+
ascii_art = figlet_format("ModForge-CLI", font=font)
|
|
156
72
|
banner_text = Text(ascii_art, style="bold cyan")
|
|
157
73
|
|
|
158
|
-
# Add extra info line
|
|
159
74
|
info_line = Text.assemble(
|
|
160
75
|
(" ⛏ ", "yellow"),
|
|
161
76
|
(f"v{__version__}", "bold white"),
|
|
@@ -164,14 +79,16 @@ def render_banner():
|
|
|
164
79
|
(f"{__author__}", "bold magenta"),
|
|
165
80
|
)
|
|
166
81
|
|
|
167
|
-
#
|
|
82
|
+
# Use expand=False so the panel shrinks to fit the text,
|
|
83
|
+
# but stays within terminal bounds.
|
|
168
84
|
console.print(
|
|
169
85
|
Panel(
|
|
170
86
|
Text.assemble(banner_text, "\n", info_line),
|
|
171
87
|
border_style="blue",
|
|
172
88
|
padding=(1, 2),
|
|
173
|
-
expand=False,
|
|
174
|
-
)
|
|
89
|
+
expand=False,
|
|
90
|
+
),
|
|
91
|
+
justify="left"
|
|
175
92
|
)
|
|
176
93
|
|
|
177
94
|
|
|
@@ -186,32 +103,43 @@ def main_callback(
|
|
|
186
103
|
ModForge-CLI: A powerful Minecraft modpack manager for Modrinth.
|
|
187
104
|
"""
|
|
188
105
|
if version:
|
|
189
|
-
console.print(f"ModForge-CLI Version: [bold cyan]{__version__}[/bold cyan]")
|
|
106
|
+
console.print(f"ModForge-CLI Version: [bold cyan]{__version__}[/bold cyan]", width=console.width)
|
|
190
107
|
raise typer.Exit()
|
|
191
108
|
|
|
192
109
|
# If no command is provided (e.g., just 'ModForge-CLI')
|
|
193
110
|
if ctx.invoked_subcommand is None:
|
|
194
111
|
render_banner()
|
|
195
|
-
console.print("\n[bold yellow]Usage:[/bold yellow] ModForge-CLI [COMMAND] [ARGS]...")
|
|
196
|
-
console.print("\n[bold cyan]Core Commands:[/bold cyan]")
|
|
197
|
-
console.print(" [green]setup[/green] Initialize a new modpack project")
|
|
198
|
-
console.print(" [green]ls[/green] List all registered projects")
|
|
199
|
-
console.print(" [green]add[/green] Add a mod/resource/shader to manifest")
|
|
200
112
|
console.print(
|
|
201
|
-
"
|
|
113
|
+
"\n[bold yellow]Usage:[/bold yellow] ModForge-CLI [COMMAND] [ARGS]...", width=console.width
|
|
114
|
+
)
|
|
115
|
+
console.print("\n[bold cyan]Core Commands:[/bold cyan]", width=console.width)
|
|
116
|
+
console.print(" [green]setup[/green] Initialize a new modpack project", width=console.width)
|
|
117
|
+
console.print(" [green]ls[/green] List all registered projects", width=console.width)
|
|
118
|
+
console.print(" [green]add[/green] Add a mod/resource/shader to manifest", width=console.width)
|
|
119
|
+
console.print(
|
|
120
|
+
" [green]build[/green] Download files and setup loader version", width=console.width
|
|
121
|
+
)
|
|
122
|
+
console.print(" [green]export[/green] Create the final .mrpack zip", width=console.width)
|
|
123
|
+
console.print(
|
|
124
|
+
" [green]remove[/green] Removes a modpack that you have locally.", width=console.width
|
|
202
125
|
)
|
|
203
|
-
console.print(" [green]export[/green] Create the final .mrpack zip")
|
|
204
126
|
|
|
205
|
-
console.print(
|
|
127
|
+
console.print(
|
|
128
|
+
"\nRun [white]ModForge-CLI --help[/white] for full command details.\n", width=console.width
|
|
129
|
+
)
|
|
206
130
|
|
|
207
131
|
|
|
208
132
|
@app.command()
|
|
209
|
-
def setup(
|
|
133
|
+
def setup(
|
|
134
|
+
name: str,
|
|
135
|
+
mc: str = "1.21.1",
|
|
136
|
+
loader: str = "fabric",
|
|
137
|
+
loader_version: str = FABRIC_LOADER_VERSION,
|
|
138
|
+
):
|
|
210
139
|
"""Initialize the working directory for a new pack"""
|
|
211
140
|
pack_dir = Path.cwd() / name
|
|
212
141
|
pack_dir.mkdir(parents=True, exist_ok=True)
|
|
213
142
|
|
|
214
|
-
|
|
215
143
|
# Standard ModForge-CLI structure (The Watermark)
|
|
216
144
|
for folder in [
|
|
217
145
|
"mods",
|
|
@@ -222,7 +150,9 @@ def setup(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version:
|
|
|
222
150
|
]:
|
|
223
151
|
(pack_dir / folder).mkdir(parents=True, exist_ok=True)
|
|
224
152
|
|
|
225
|
-
manifest: Manifest = Manifest(
|
|
153
|
+
manifest: Manifest = Manifest(
|
|
154
|
+
name=name, minecraft=mc, loader=loader, loader_version=loader_version
|
|
155
|
+
)
|
|
226
156
|
(pack_dir / "ModForge-CLI.json").write_text(manifest.model_dump_json(indent=4))
|
|
227
157
|
|
|
228
158
|
# Register globally
|
|
@@ -240,12 +170,11 @@ def setup(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version:
|
|
|
240
170
|
"name": name,
|
|
241
171
|
"dependencies": {"minecraft": mc, loader: "*"},
|
|
242
172
|
"files": [],
|
|
243
|
-
|
|
244
173
|
}
|
|
245
174
|
(pack_dir / "modrinth.index.json").write_text(json.dumps(index_data, indent=2))
|
|
246
175
|
|
|
247
176
|
console.print(
|
|
248
|
-
f"Project [bold cyan]{name}[/bold cyan] ready at {pack_dir}", style="green"
|
|
177
|
+
f"Project [bold cyan]{name}[/bold cyan] ready at {pack_dir}", style="green", width=console.width
|
|
249
178
|
)
|
|
250
179
|
|
|
251
180
|
|
|
@@ -269,72 +198,34 @@ def add(name: str, project_type: str = "mod", pack_name: str = "testpack"):
|
|
|
269
198
|
pack_path = Path(registry[pack_name])
|
|
270
199
|
manifest_file = pack_path / "ModForge-CLI.json"
|
|
271
200
|
|
|
272
|
-
manifest = get_manifest(pack_path)
|
|
201
|
+
manifest = get_manifest(console, pack_path)
|
|
273
202
|
if not manifest:
|
|
274
203
|
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}")
|
|
275
204
|
return
|
|
276
205
|
|
|
277
|
-
|
|
278
|
-
async with await get_api_session() as session:
|
|
279
|
-
url = api.search(
|
|
280
|
-
name,
|
|
281
|
-
game_versions=[manifest.minecraft],
|
|
282
|
-
loaders=[manifest.loader],
|
|
283
|
-
project_type=project_type,
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
async with session.get(url) as response:
|
|
287
|
-
results = SearchResult.model_validate_json(await response.text())
|
|
288
|
-
|
|
289
|
-
if not results or not results.hits:
|
|
290
|
-
console.print(f"[red]No {project_type} found for '{name}'")
|
|
291
|
-
return
|
|
292
|
-
|
|
293
|
-
# Match slug
|
|
294
|
-
target_hit = next(
|
|
295
|
-
(h for h in results.hits if h.slug == name), results.hits[0]
|
|
296
|
-
)
|
|
297
|
-
slug = target_hit.slug
|
|
298
|
-
|
|
299
|
-
# 3. Modify the existing manifest object
|
|
300
|
-
# Only 'mod' will reach here currently due to the check above
|
|
301
|
-
target_list = {
|
|
302
|
-
"mod": manifest.mods,
|
|
303
|
-
"resourcepack": manifest.resourcepacks,
|
|
304
|
-
"shaderpack": manifest.shaderpacks,
|
|
305
|
-
}.get(project_type, manifest.mods)
|
|
306
|
-
|
|
307
|
-
if slug not in target_list:
|
|
308
|
-
target_list.append(slug)
|
|
309
|
-
manifest_file.write_text(manifest.model_dump_json(indent=4))
|
|
310
|
-
console.print(f"Added [green]{slug}[/green] to {project_type}s")
|
|
311
|
-
else:
|
|
312
|
-
console.print(f"{slug} is already in the manifest.")
|
|
313
|
-
|
|
314
|
-
asyncio.run(perform_add())
|
|
206
|
+
asyncio.run(perform_add(api, name, manifest, project_type, console, manifest_file))
|
|
315
207
|
|
|
316
208
|
|
|
317
209
|
@app.command()
|
|
318
210
|
def resolve(pack_name: str = "testpack"):
|
|
319
|
-
|
|
320
211
|
# 1. Load Registry and Manifest
|
|
321
212
|
registry = json.loads(REGISTRY_PATH.read_text())
|
|
322
213
|
if pack_name not in registry:
|
|
323
|
-
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.")
|
|
214
|
+
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.", width=console.width)
|
|
324
215
|
return
|
|
325
216
|
|
|
326
217
|
pack_path = Path(registry[pack_name])
|
|
327
218
|
manifest_file = pack_path / "ModForge-CLI.json"
|
|
328
219
|
|
|
329
|
-
manifest = get_manifest(pack_path)
|
|
220
|
+
manifest = get_manifest(console, pack_path)
|
|
330
221
|
if not manifest:
|
|
331
|
-
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}")
|
|
222
|
+
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}", width=console.width)
|
|
332
223
|
return
|
|
333
224
|
|
|
334
225
|
# 2. Run Resolution Logic
|
|
335
226
|
console.print(
|
|
336
227
|
f"Resolving dependencies for [bold cyan]{pack_name}[/bold cyan]...",
|
|
337
|
-
style="yellow",
|
|
228
|
+
style="yellow", width=console.width
|
|
338
229
|
)
|
|
339
230
|
policy = ModPolicy(POLICY_PATH)
|
|
340
231
|
resolver = ModResolver(
|
|
@@ -351,12 +242,12 @@ def resolve(pack_name: str = "testpack"):
|
|
|
351
242
|
# 4. Save back to ModForge-CLI.json
|
|
352
243
|
try:
|
|
353
244
|
manifest_file.write_text(manifest.model_dump_json(indent=4))
|
|
354
|
-
console.print(f"Successfully updated [bold]{manifest_file.name}[/bold]")
|
|
245
|
+
console.print(f"Successfully updated [bold]{manifest_file.name}[/bold]", width=console.width)
|
|
355
246
|
console.print(
|
|
356
|
-
f"Total mods resolved: [bold green]{len(manifest.mods)}[/bold green]"
|
|
247
|
+
f"Total mods resolved: [bold green]{len(manifest.mods)}[/bold green]", width=console.width
|
|
357
248
|
)
|
|
358
249
|
except Exception as e:
|
|
359
|
-
console.print(f"[red]Error saving manifest:[/red] {e}")
|
|
250
|
+
console.print(f"[red]Error saving manifest:[/red] {e}", width=console.width)
|
|
360
251
|
|
|
361
252
|
# Optional: Print a summary table of the IDs
|
|
362
253
|
if manifest.mods:
|
|
@@ -364,7 +255,7 @@ def resolve(pack_name: str = "testpack"):
|
|
|
364
255
|
table.add_column("Project ID", style="green")
|
|
365
256
|
for mod_id in manifest.mods:
|
|
366
257
|
table.add_row(mod_id)
|
|
367
|
-
console.print(table)
|
|
258
|
+
console.print(table, width=console.width)
|
|
368
259
|
|
|
369
260
|
|
|
370
261
|
@app.command()
|
|
@@ -374,15 +265,15 @@ def build(pack_name: str = "testpack"):
|
|
|
374
265
|
# 1. Load Registry and Manifest
|
|
375
266
|
registry = json.loads(REGISTRY_PATH.read_text())
|
|
376
267
|
if pack_name not in registry:
|
|
377
|
-
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.")
|
|
268
|
+
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.", width=console.width)
|
|
378
269
|
return
|
|
379
270
|
|
|
380
271
|
pack_path = Path(registry[pack_name])
|
|
381
272
|
manifest_file = pack_path / "ModForge-CLI.json"
|
|
382
273
|
|
|
383
|
-
manifest = get_manifest(pack_path)
|
|
274
|
+
manifest = get_manifest(console, pack_path)
|
|
384
275
|
if not manifest:
|
|
385
|
-
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}")
|
|
276
|
+
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}", width=console.width)
|
|
386
277
|
return
|
|
387
278
|
|
|
388
279
|
pack_root = Path.cwd() / manifest.name
|
|
@@ -391,24 +282,9 @@ def build(pack_name: str = "testpack"):
|
|
|
391
282
|
|
|
392
283
|
mods_dir.mkdir(exist_ok=True)
|
|
393
284
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
raise_for_status=True,
|
|
398
|
-
) as session:
|
|
399
|
-
downloader = ModDownloader(
|
|
400
|
-
api=api,
|
|
401
|
-
mc_version=manifest.minecraft,
|
|
402
|
-
loader=manifest.loader,
|
|
403
|
-
output_dir=mods_dir,
|
|
404
|
-
index_file=index_file,
|
|
405
|
-
session=session,
|
|
406
|
-
)
|
|
407
|
-
await downloader.download_all(manifest.mods)
|
|
408
|
-
|
|
409
|
-
console.print(f"🛠 Building [bold cyan]{manifest.name}[/bold cyan]...")
|
|
410
|
-
asyncio.run(run())
|
|
411
|
-
console.print("✨ Build complete. Mods downloaded and indexed.", style="green")
|
|
285
|
+
console.print(f"🛠 Building [bold cyan]{manifest.name}[/bold cyan]...", width=console.width)
|
|
286
|
+
asyncio.run(run(api, manifest, mods_dir, index_file))
|
|
287
|
+
console.print("✨ Build complete. Mods downloaded and indexed.", style="green", width=console.width)
|
|
412
288
|
|
|
413
289
|
|
|
414
290
|
@app.command()
|
|
@@ -418,35 +294,35 @@ def export(pack_name: str = "testpack"):
|
|
|
418
294
|
# 1. Load Registry and Manifest
|
|
419
295
|
registry = json.loads(REGISTRY_PATH.read_text())
|
|
420
296
|
if pack_name not in registry:
|
|
421
|
-
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.")
|
|
297
|
+
console.print(f"[red]Error:[/red] Pack '{pack_name}' not found in registry.", width=console.width)
|
|
422
298
|
return
|
|
423
299
|
|
|
424
300
|
pack_path = Path(registry[pack_name])
|
|
425
301
|
manifest_file = pack_path / "ModForge-CLI.json"
|
|
426
302
|
|
|
427
|
-
manifest = get_manifest(pack_path)
|
|
303
|
+
manifest = get_manifest(console, pack_path)
|
|
428
304
|
if not manifest:
|
|
429
|
-
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}")
|
|
305
|
+
console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}", width=console.width)
|
|
430
306
|
return
|
|
431
307
|
loader_version = manifest.loader_version or FABRIC_LOADER_VERSION
|
|
432
308
|
|
|
433
|
-
console.print("📦 Finalizing pack...", style="cyan")
|
|
309
|
+
console.print("📦 Finalizing pack...", style="cyan", width=console.width)
|
|
434
310
|
|
|
435
311
|
mods_dir = Path.cwd() / manifest.name / "mods"
|
|
436
312
|
if not mods_dir.exists() or not any(mods_dir.iterdir()):
|
|
437
|
-
console.print("[red]No mods found. Run `ModForge-CLI build` first.[/red]")
|
|
313
|
+
console.print("[red]No mods found. Run `ModForge-CLI build` first.[/red]", width=console.width)
|
|
438
314
|
raise typer.Exit(1)
|
|
439
315
|
|
|
440
316
|
if manifest.loader == "fabric":
|
|
441
317
|
installer = Path.cwd() / manifest.name / ".fabric-installer.jar"
|
|
442
318
|
|
|
443
319
|
if not installer.exists():
|
|
444
|
-
console.print("Downloading Fabric installer...")
|
|
320
|
+
console.print("Downloading Fabric installer...", width=console.width)
|
|
445
321
|
import urllib.request
|
|
446
322
|
|
|
447
323
|
urllib.request.urlretrieve(FABRIC_INSTALLER_URL, installer)
|
|
448
324
|
|
|
449
|
-
console.print("Installing Fabric...")
|
|
325
|
+
console.print("Installing Fabric...", width=console.width)
|
|
450
326
|
install_fabric(
|
|
451
327
|
installer=installer,
|
|
452
328
|
mc_version=manifest.minecraft,
|
|
@@ -470,20 +346,20 @@ def export(pack_name: str = "testpack"):
|
|
|
470
346
|
root_dir=Path.cwd(),
|
|
471
347
|
)
|
|
472
348
|
|
|
473
|
-
console.print(f"✅ Exported {zip_path.name}", style="green bold")
|
|
349
|
+
console.print(f"✅ Exported {zip_path.name}", style="green bold", width=console.width)
|
|
474
350
|
|
|
475
351
|
|
|
476
352
|
@app.command()
|
|
477
353
|
def remove(pack_name: str):
|
|
478
354
|
"""Completely remove a modpack and unregister it"""
|
|
479
355
|
if not REGISTRY_PATH.exists():
|
|
480
|
-
console.print("[red]No registry found.[/red]")
|
|
356
|
+
console.print("[red]No registry found.[/red]", width=console.width)
|
|
481
357
|
raise typer.Exit(1)
|
|
482
358
|
|
|
483
359
|
registry = json.loads(REGISTRY_PATH.read_text())
|
|
484
360
|
|
|
485
361
|
if pack_name not in registry:
|
|
486
|
-
console.print(f"[red]Pack '{pack_name}' not found in registry.[/red]")
|
|
362
|
+
console.print(f"[red]Pack '{pack_name}' not found in registry.[/red]", width=console.width)
|
|
487
363
|
raise typer.Exit(1)
|
|
488
364
|
|
|
489
365
|
pack_path = Path(registry[pack_name])
|
|
@@ -494,7 +370,7 @@ def remove(pack_name: str):
|
|
|
494
370
|
f"[white]{pack_name}[/white]\n"
|
|
495
371
|
f"[dim]{pack_path}[/dim]",
|
|
496
372
|
title="⚠️ Destructive Action",
|
|
497
|
-
border_style="red",
|
|
373
|
+
border_style="red", width=console.width
|
|
498
374
|
)
|
|
499
375
|
)
|
|
500
376
|
|
|
@@ -508,10 +384,10 @@ def remove(pack_name: str):
|
|
|
508
384
|
shutil.rmtree(pack_path)
|
|
509
385
|
else:
|
|
510
386
|
console.print(
|
|
511
|
-
f"[yellow]Warning:[/yellow] Pack directory does not exist: {pack_path}"
|
|
387
|
+
f"[yellow]Warning:[/yellow] Pack directory does not exist: {pack_path}", width=console.width
|
|
512
388
|
)
|
|
513
389
|
except Exception as e:
|
|
514
|
-
console.print(f"[red]Failed to delete pack directory:[/red] {e}")
|
|
390
|
+
console.print(f"[red]Failed to delete pack directory:[/red] {e}", width=console.width)
|
|
515
391
|
raise typer.Exit(1)
|
|
516
392
|
|
|
517
393
|
# Update registry
|
|
@@ -520,7 +396,7 @@ def remove(pack_name: str):
|
|
|
520
396
|
|
|
521
397
|
console.print(
|
|
522
398
|
f"🗑️ Removed pack [bold cyan]{pack_name}[/bold cyan] successfully.",
|
|
523
|
-
style="green",
|
|
399
|
+
style="green", width=console.width
|
|
524
400
|
)
|
|
525
401
|
|
|
526
402
|
|
|
@@ -538,7 +414,8 @@ def list_projects():
|
|
|
538
414
|
|
|
539
415
|
for name, path in registry.items():
|
|
540
416
|
table.add_row(name, path)
|
|
541
|
-
console.print(table)
|
|
417
|
+
console.print(table, width=console.width)
|
|
418
|
+
|
|
542
419
|
|
|
543
420
|
@app.command()
|
|
544
421
|
def self_update_cmd():
|
|
@@ -546,7 +423,7 @@ def self_update_cmd():
|
|
|
546
423
|
Update ModForge-CLI to the latest version.
|
|
547
424
|
"""
|
|
548
425
|
try:
|
|
549
|
-
self_update()
|
|
426
|
+
self_update(console)
|
|
550
427
|
except subprocess.CalledProcessError:
|
|
551
428
|
raise typer.Exit(code=1)
|
|
552
429
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .policy import ModPolicy
|
|
2
|
+
from .resolver import ModResolver
|
|
3
|
+
from .downloader import ModDownloader
|
|
4
|
+
from .models import Manifest, Hit, SearchResult, ProjectVersion, ProjectVersionList
|
|
5
|
+
from .utils import ensure_config_file, install_fabric, run, get_api_session, get_manifest, self_update, perform_add
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ModPolicy",
|
|
9
|
+
"ModResolver",
|
|
10
|
+
"Manifest",
|
|
11
|
+
"Hit",
|
|
12
|
+
"SearchResult",
|
|
13
|
+
"ProjectVersion",
|
|
14
|
+
"ProjectVersionList",
|
|
15
|
+
"ModDownloader",
|
|
16
|
+
"ensure_config_file",
|
|
17
|
+
"install_fabric",
|
|
18
|
+
"run",
|
|
19
|
+
"get_api_session",
|
|
20
|
+
"get_manifest",
|
|
21
|
+
"self_update",
|
|
22
|
+
"perform_add"
|
|
23
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import urllib.request
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from modforge_cli.core.models import Manifest, SearchResult
|
|
7
|
+
from modforge_cli.api import ModrinthAPIConfig
|
|
8
|
+
from modforge_cli.core import ModDownloader
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
import aiohttp
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from modforge_cli.__version__ import __author__, __version__
|
|
16
|
+
except ImportError:
|
|
17
|
+
__version__ = "unknown"
|
|
18
|
+
__author__ = "Frank1o3"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ensure_config_file(path: Path, url: str, label: str, console: Console):
|
|
22
|
+
if path.exists():
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
console.print(f"[yellow]Missing {label} config.[/yellow] Downloading default…")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
urllib.request.urlretrieve(url, path)
|
|
31
|
+
console.print(f"[green]✓ {label} config installed at {path}[/green]")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
console.print(f"[red]Failed to download {label} config:[/red] {e}")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
|
|
36
|
+
# --- Async Helper ---
|
|
37
|
+
async def get_api_session():
|
|
38
|
+
"""Returns a session with the correct ModForge-CLI headers."""
|
|
39
|
+
return aiohttp.ClientSession(
|
|
40
|
+
headers={"User-Agent": f"{__author__}/ModForge-CLI/{__version__}"},
|
|
41
|
+
raise_for_status=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_manifest(console: Console, path: Path = Path.cwd()) -> Optional[Manifest]:
|
|
46
|
+
p = path / "ModForge-CLI.json"
|
|
47
|
+
if not p.exists():
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
return Manifest.model_validate_json(p.read_text())
|
|
51
|
+
except Exception as e:
|
|
52
|
+
console.print(e)
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
def install_fabric(
|
|
56
|
+
installer: Path,
|
|
57
|
+
mc_version: str,
|
|
58
|
+
loader_version: str,
|
|
59
|
+
game_dir: Path,
|
|
60
|
+
):
|
|
61
|
+
subprocess.run(
|
|
62
|
+
[
|
|
63
|
+
"java",
|
|
64
|
+
"-jar",
|
|
65
|
+
installer,
|
|
66
|
+
"client",
|
|
67
|
+
"-mcversion",
|
|
68
|
+
mc_version,
|
|
69
|
+
"-loader",
|
|
70
|
+
loader_version,
|
|
71
|
+
"-dir",
|
|
72
|
+
str(game_dir),
|
|
73
|
+
"-noprofile",
|
|
74
|
+
],
|
|
75
|
+
check=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def detect_install_method() -> str:
|
|
80
|
+
prefix = Path(sys.prefix)
|
|
81
|
+
|
|
82
|
+
if "pipx" in prefix.parts:
|
|
83
|
+
return "pipx"
|
|
84
|
+
return "pip"
|
|
85
|
+
|
|
86
|
+
def self_update(console: Console):
|
|
87
|
+
method = detect_install_method()
|
|
88
|
+
|
|
89
|
+
if method == "pipx":
|
|
90
|
+
console.print("[cyan]Updating ModForge-CLI using pipx...[/cyan]")
|
|
91
|
+
subprocess.run(["pipx", "upgrade", "ModForge-CLI"], check=True)
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
console.print("[cyan]Updating ModForge-CLI using pip...[/cyan]")
|
|
95
|
+
subprocess.run(
|
|
96
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "ModForge-CLI"],
|
|
97
|
+
check=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
console.print("[green]ModForge-CLI updated successfully.[/green]")
|
|
101
|
+
|
|
102
|
+
async def run(api:ModrinthAPIConfig, manifest:Manifest, mods_dir:Path, index_file:Path):
|
|
103
|
+
async with await get_api_session() as session:
|
|
104
|
+
downloader = ModDownloader(
|
|
105
|
+
api=api,
|
|
106
|
+
mc_version=manifest.minecraft,
|
|
107
|
+
loader=manifest.loader,
|
|
108
|
+
output_dir=mods_dir,
|
|
109
|
+
index_file=index_file,
|
|
110
|
+
session=session,
|
|
111
|
+
)
|
|
112
|
+
await downloader.download_all(manifest.mods)
|
|
113
|
+
|
|
114
|
+
async def perform_add(api:ModrinthAPIConfig, name:str, manifest:Manifest, project_type:str, console: Console, manifest_file:Path):
|
|
115
|
+
async with await get_api_session() as session:
|
|
116
|
+
url = api.search(
|
|
117
|
+
name,
|
|
118
|
+
game_versions=[manifest.minecraft],
|
|
119
|
+
loaders=[manifest.loader],
|
|
120
|
+
project_type=project_type,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
async with session.get(url) as response:
|
|
124
|
+
results = SearchResult.model_validate_json(await response.text())
|
|
125
|
+
|
|
126
|
+
if not results or not results.hits:
|
|
127
|
+
console.print(f"[red]No {project_type} found for '{name}'")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Match slug
|
|
131
|
+
target_hit = next(
|
|
132
|
+
(h for h in results.hits if h.slug == name), results.hits[0]
|
|
133
|
+
)
|
|
134
|
+
slug = target_hit.slug
|
|
135
|
+
|
|
136
|
+
# 3. Modify the existing manifest object
|
|
137
|
+
# Only 'mod' will reach here currently due to the check above
|
|
138
|
+
target_list = {
|
|
139
|
+
"mod": manifest.mods,
|
|
140
|
+
"resourcepack": manifest.resourcepacks,
|
|
141
|
+
"shaderpack": manifest.shaderpacks,
|
|
142
|
+
}.get(project_type, manifest.mods)
|
|
143
|
+
|
|
144
|
+
if slug not in target_list:
|
|
145
|
+
target_list.append(slug)
|
|
146
|
+
manifest_file.write_text(manifest.model_dump_json(indent=4))
|
|
147
|
+
console.print(f"Added [green]{slug}[/green] to {project_type}s")
|
|
148
|
+
else:
|
|
149
|
+
console.print(f"{slug} is already in the manifest.")
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
from .policy import ModPolicy
|
|
2
|
-
from .resolver import ModResolver
|
|
3
|
-
from .downloader import ModDownloader
|
|
4
|
-
from .models import Manifest, Hit, SearchResult, ProjectVersion, ProjectVersionList
|
|
5
|
-
|
|
6
|
-
__all__ = ["ModPolicy", "ModResolver", "Manifest", "Hit", "SearchResult", "ProjectVersion", "ProjectVersionList", "ModDownloader"]
|
|
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
|