Update __init__.py

This commit is contained in:
Den Delimarsky 🌺
2025-09-12 10:39:51 -07:00
parent 5787bb5537
commit af3cf934e5

View File

@@ -30,7 +30,7 @@ import tempfile
import shutil import shutil
import json import json
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, Tuple
import typer import typer
import httpx import httpx
@@ -402,7 +402,7 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
os.chdir(original_cwd) os.chdir(original_cwd)
def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None): def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False) -> Tuple[Path, dict]:
repo_owner = "github" repo_owner = "github"
repo_name = "spec-kit" repo_name = "spec-kit"
if client is None: if client is None:
@@ -414,11 +414,19 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
try: try:
response = client.get(api_url, timeout=30, follow_redirects=True) response = client.get(api_url, timeout=30, follow_redirects=True)
response.raise_for_status() status = response.status_code
if status != 200:
msg = f"GitHub API returned {status} for {api_url}"
if debug:
msg += f"\nResponse headers: {response.headers}\nBody (truncated 500): {response.text[:500]}"
raise RuntimeError(msg)
try:
release_data = response.json() release_data = response.json()
except httpx.RequestError as e: except ValueError as je:
if verbose: raise RuntimeError(f"Failed to parse release JSON: {je}\nRaw (truncated 400): {response.text[:400]}")
console.print(f"[red]Error fetching release information:[/red] {e}") except Exception as e:
console.print(f"[red]Error fetching release information[/red]")
console.print(Panel(str(e), title="Fetch Error", border_style="red"))
raise typer.Exit(1) raise typer.Exit(1)
# Find the template asset for the specified AI assistant # Find the template asset for the specified AI assistant
@@ -429,11 +437,9 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
] ]
if not matching_assets: if not matching_assets:
if verbose: console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]")
console.print(f"[red]Error:[/red] No template found for AI assistant '{ai_assistant}'") asset_names = [a.get('name','?') for a in release_data.get('assets', [])]
console.print(f"[yellow]Available assets:[/yellow]") console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow"))
for asset in release_data.get("assets", []):
console.print(f" - {asset['name']}")
raise typer.Exit(1) raise typer.Exit(1)
# Use the first matching asset # Use the first matching asset
@@ -453,8 +459,10 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
console.print(f"[cyan]Downloading template...[/cyan]") console.print(f"[cyan]Downloading template...[/cyan]")
try: try:
with client.stream("GET", download_url, timeout=30, follow_redirects=True) as response: with client.stream("GET", download_url, timeout=60, follow_redirects=True) as response:
response.raise_for_status() if response.status_code != 200:
body_sample = response.text[:400]
raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}")
total_size = int(response.headers.get('content-length', 0)) total_size = int(response.headers.get('content-length', 0))
with open(zip_path, 'wb') as f: with open(zip_path, 'wb') as f:
if total_size == 0: if total_size == 0:
@@ -477,11 +485,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
else: else:
for chunk in response.iter_bytes(chunk_size=8192): for chunk in response.iter_bytes(chunk_size=8192):
f.write(chunk) f.write(chunk)
except httpx.RequestError as e: except Exception as e:
if verbose: console.print(f"[red]Error downloading template[/red]")
console.print(f"[red]Error downloading template:[/red] {e}") detail = str(e)
if zip_path.exists(): if zip_path.exists():
zip_path.unlink() zip_path.unlink()
console.print(Panel(detail, title="Download Error", border_style="red"))
raise typer.Exit(1) raise typer.Exit(1)
if verbose: if verbose:
console.print(f"Downloaded: {filename}") console.print(f"Downloaded: {filename}")
@@ -494,7 +503,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
return zip_path, metadata return zip_path, metadata
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None) -> Path: def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False) -> Path:
"""Download the latest release and extract it to create a new project. """Download the latest release and extract it to create a new project.
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup) Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
""" """
@@ -510,7 +519,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
script_type=script_type, script_type=script_type,
verbose=verbose and tracker is None, verbose=verbose and tracker is None,
show_progress=(tracker is None), show_progress=(tracker is None),
client=client client=client,
debug=debug
) )
if tracker: if tracker:
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)") tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
@@ -627,6 +637,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
else: else:
if verbose: if verbose:
console.print(f"[red]Error extracting template:[/red] {e}") console.print(f"[red]Error extracting template:[/red] {e}")
if debug:
console.print(Panel(str(e), title="Extraction Error", border_style="red"))
# Clean up project directory if created and not current directory # Clean up project directory if created and not current directory
if not is_current_dir and project_path.exists(): if not is_current_dir and project_path.exists():
shutil.rmtree(project_path) shutil.rmtree(project_path)
@@ -702,6 +714,7 @@ def init(
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"), here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"), skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
): ):
""" """
Initialize a new Specify project from the latest template. Initialize a new Specify project from the latest template.
@@ -856,7 +869,7 @@ def init(
local_ssl_context = ssl_context if verify else False local_ssl_context = ssl_context if verify else False
local_client = httpx.Client(verify=local_ssl_context) local_client = httpx.Client(verify=local_ssl_context)
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client) download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug)
# Ensure scripts are executable (POSIX) # Ensure scripts are executable (POSIX)
ensure_executable_scripts(project_path, tracker=tracker) ensure_executable_scripts(project_path, tracker=tracker)
@@ -879,6 +892,14 @@ def init(
tracker.complete("final", "project ready") tracker.complete("final", "project ready")
except Exception as e: except Exception as e:
tracker.error("final", str(e)) tracker.error("final", str(e))
console.print(Panel(f"Initialization failed: {e}", title="Failure", border_style="red"))
if debug:
env_info = [
f"Python: {sys.version.split()[0]}",
f"Platform: {sys.platform}",
f"CWD: {Path.cwd()}",
]
console.print(Panel("\n".join(env_info), title="Debug Environment", border_style="magenta"))
if not here and project_path.exists(): if not here and project_path.exists():
shutil.rmtree(project_path) shutil.rmtree(project_path)
raise typer.Exit(1) raise typer.Exit(1)