modforge-cli 0.1.6__tar.gz → 0.1.7__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.7
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.7"
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.7"
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,108 +52,14 @@ 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
-
55
+ ensure_config_file(MODRINTH_API, DEFAULT_MODRINTH_API_URL, "Modrinth API", console)
66
56
 
67
- ensure_config_file(
68
- MODRINTH_API,
69
- DEFAULT_MODRINTH_API_URL,
70
- "Modrinth API",
71
- )
72
-
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
64
  """Renders a high-quality stylized banner"""
153
65
  ascii_art = figlet_format("ModForge-CLI", font="slant")
@@ -192,7 +104,9 @@ def main_callback(
192
104
  # If no command is provided (e.g., just 'ModForge-CLI')
193
105
  if ctx.invoked_subcommand is None:
194
106
  render_banner()
195
- console.print("\n[bold yellow]Usage:[/bold yellow] ModForge-CLI [COMMAND] [ARGS]...")
107
+ console.print(
108
+ "\n[bold yellow]Usage:[/bold yellow] ModForge-CLI [COMMAND] [ARGS]..."
109
+ )
196
110
  console.print("\n[bold cyan]Core Commands:[/bold cyan]")
197
111
  console.print(" [green]setup[/green] Initialize a new modpack project")
198
112
  console.print(" [green]ls[/green] List all registered projects")
@@ -201,17 +115,26 @@ def main_callback(
201
115
  " [green]build[/green] Download files and setup loader version"
202
116
  )
203
117
  console.print(" [green]export[/green] Create the final .mrpack zip")
118
+ console.print(
119
+ " [green]remove[/green] Removes a modpack that you have locally."
120
+ )
204
121
 
205
- console.print("\nRun [white]ModForge-CLI --help[/white] for full command details.\n")
122
+ console.print(
123
+ "\nRun [white]ModForge-CLI --help[/white] for full command details.\n"
124
+ )
206
125
 
207
126
 
208
127
  @app.command()
209
- def setup(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version: str = FABRIC_LOADER_VERSION):
128
+ def setup(
129
+ name: str,
130
+ mc: str = "1.21.1",
131
+ loader: str = "fabric",
132
+ loader_version: str = FABRIC_LOADER_VERSION,
133
+ ):
210
134
  """Initialize the working directory for a new pack"""
211
135
  pack_dir = Path.cwd() / name
212
136
  pack_dir.mkdir(parents=True, exist_ok=True)
213
137
 
214
-
215
138
  # Standard ModForge-CLI structure (The Watermark)
216
139
  for folder in [
217
140
  "mods",
@@ -222,7 +145,9 @@ def setup(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version:
222
145
  ]:
223
146
  (pack_dir / folder).mkdir(parents=True, exist_ok=True)
224
147
 
225
- manifest: Manifest = Manifest(name=name, minecraft=mc, loader=loader, loader_version=loader_version)
148
+ manifest: Manifest = Manifest(
149
+ name=name, minecraft=mc, loader=loader, loader_version=loader_version
150
+ )
226
151
  (pack_dir / "ModForge-CLI.json").write_text(manifest.model_dump_json(indent=4))
227
152
 
228
153
  # Register globally
@@ -240,7 +165,6 @@ def setup(name: str, mc: str = "1.21.1", loader: str = "fabric", loader_version:
240
165
  "name": name,
241
166
  "dependencies": {"minecraft": mc, loader: "*"},
242
167
  "files": [],
243
-
244
168
  }
245
169
  (pack_dir / "modrinth.index.json").write_text(json.dumps(index_data, indent=2))
246
170
 
@@ -274,49 +198,11 @@ def add(name: str, project_type: str = "mod", pack_name: str = "testpack"):
274
198
  console.print(f"[red]Error:[/red] Could not load manifest at {manifest_file}")
275
199
  return
276
200
 
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())
201
+ asyncio.run(perform_add(api, name, manifest, project_type, console, manifest_file))
315
202
 
316
203
 
317
204
  @app.command()
318
205
  def resolve(pack_name: str = "testpack"):
319
-
320
206
  # 1. Load Registry and Manifest
321
207
  registry = json.loads(REGISTRY_PATH.read_text())
322
208
  if pack_name not in registry:
@@ -391,23 +277,8 @@ def build(pack_name: str = "testpack"):
391
277
 
392
278
  mods_dir.mkdir(exist_ok=True)
393
279
 
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
280
  console.print(f"🛠 Building [bold cyan]{manifest.name}[/bold cyan]...")
410
- asyncio.run(run())
281
+ asyncio.run(run(api, manifest, mods_dir, index_file))
411
282
  console.print("✨ Build complete. Mods downloaded and indexed.", style="green")
412
283
 
413
284
 
@@ -540,6 +411,7 @@ def list_projects():
540
411
  table.add_row(name, path)
541
412
  console.print(table)
542
413
 
414
+
543
415
  @app.command()
544
416
  def self_update_cmd():
545
417
  """
@@ -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