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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modforge-cli
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: ModForge-CLI — a Modrinth-based Minecraft modpack builder
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "modforge-cli"
3
- version = "0.1.6"
3
+ version = "0.1.8"
4
4
  description = "ModForge-CLI — a Modrinth-based Minecraft modpack builder"
5
5
  authors = [{ name = "Frank1o3", email = "jahdy1o3@gmail.com" }]
6
6
  license = { text = "MIT" }
@@ -1,5 +1,5 @@
1
1
  """
2
2
  Auto-generated file. DO NOT EDIT.
3
3
  """
4
- __version__ = "0.1.6"
4
+ __version__ = "0.1.8"
5
5
  __author__ = "Frank1o3"
@@ -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, ModDownloader, SearchResult
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
- def ensure_config_file(path: Path, url: str, label: str):
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
- ascii_art = figlet_format("ModForge-CLI", font="slant")
154
-
155
- # Create a colorful gradient-like effect for the text
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
- # Wrap in a nice panel
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
- " [green]build[/green] Download files and setup loader version"
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("\nRun [white]ModForge-CLI --help[/white] for full command details.\n")
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(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version: str = FABRIC_LOADER_VERSION):
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(name=name, minecraft=mc, loader=loader, loader_version=loader_version)
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
- async def perform_add():
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
- async def run():
395
- async with aiohttp.ClientSession(
396
- headers={"User-Agent": f"{__author__}/ModForge-CLI/{__version__}"},
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