Enhance HTTP client initialization with optional SSL verification and bump version to 0.0.3
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
description = "Setup tool for Specify spec-driven development projects"
|
description = "Setup tool for Specify spec-driven development projects"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -390,12 +390,11 @@ 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, *, verbose: bool = True, show_progress: bool = True):
|
def download_template_from_github(ai_assistant: str, download_dir: Path, *, verbose: bool = True, show_progress: bool = True, client: httpx.Client = None):
|
||||||
"""Download the latest template release from GitHub using HTTP requests.
|
|
||||||
Returns (zip_path, metadata_dict)
|
|
||||||
"""
|
|
||||||
repo_owner = "github"
|
repo_owner = "github"
|
||||||
repo_name = "spec-kit"
|
repo_name = "spec-kit"
|
||||||
|
if client is None:
|
||||||
|
client = httpx.Client(verify=ssl_context)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print("[cyan]Fetching latest release information...[/cyan]")
|
console.print("[cyan]Fetching latest release information...[/cyan]")
|
||||||
@@ -405,7 +404,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
response = client.get(api_url, timeout=30, follow_redirects=True)
|
response = client.get(api_url, timeout=30, follow_redirects=True)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
release_data = response.json()
|
release_data = response.json()
|
||||||
except client.RequestError as e:
|
except httpx.RequestError as e:
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print(f"[red]Error fetching release information:[/red] {e}")
|
console.print(f"[red]Error fetching release information:[/red] {e}")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -445,15 +444,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
with client.stream("GET", download_url, timeout=30, follow_redirects=True) as response:
|
with client.stream("GET", download_url, timeout=30, follow_redirects=True) as response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
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:
|
||||||
# No content-length header, download without progress
|
|
||||||
for chunk in response.iter_bytes(chunk_size=8192):
|
for chunk in response.iter_bytes(chunk_size=8192):
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
else:
|
else:
|
||||||
if show_progress:
|
if show_progress:
|
||||||
# Show progress bar
|
|
||||||
with Progress(
|
with Progress(
|
||||||
SpinnerColumn(),
|
SpinnerColumn(),
|
||||||
TextColumn("[progress.description]{task.description}"),
|
TextColumn("[progress.description]{task.description}"),
|
||||||
@@ -467,11 +463,9 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
downloaded += len(chunk)
|
downloaded += len(chunk)
|
||||||
progress.update(task, completed=downloaded)
|
progress.update(task, completed=downloaded)
|
||||||
else:
|
else:
|
||||||
# Silent download loop
|
|
||||||
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 client.RequestError as e:
|
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print(f"[red]Error downloading template:[/red] {e}")
|
console.print(f"[red]Error downloading template:[/red] {e}")
|
||||||
if zip_path.exists():
|
if zip_path.exists():
|
||||||
@@ -488,7 +482,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
return zip_path, metadata
|
return zip_path, metadata
|
||||||
|
|
||||||
|
|
||||||
def download_and_extract_template(project_path: Path, ai_assistant: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None) -> Path:
|
def download_and_extract_template(project_path: Path, ai_assistant: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None) -> 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)
|
||||||
"""
|
"""
|
||||||
@@ -502,12 +496,13 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
|||||||
ai_assistant,
|
ai_assistant,
|
||||||
current_dir,
|
current_dir,
|
||||||
verbose=verbose and tracker is None,
|
verbose=verbose and tracker is None,
|
||||||
show_progress=(tracker is None)
|
show_progress=(tracker is None),
|
||||||
|
client=client
|
||||||
)
|
)
|
||||||
if tracker:
|
if tracker:
|
||||||
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
|
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
|
||||||
tracker.add("download", "Download template")
|
tracker.add("download", "Download template")
|
||||||
tracker.complete("download", meta['filename']) # already downloaded inside helper
|
tracker.complete("download", meta['filename'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if tracker:
|
if tracker:
|
||||||
tracker.error("fetch", str(e))
|
tracker.error("fetch", str(e))
|
||||||
@@ -647,6 +642,7 @@ def init(
|
|||||||
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
||||||
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)"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize a new Specify project from the latest template.
|
Initialize a new Specify project from the latest template.
|
||||||
@@ -775,7 +771,12 @@ def init(
|
|||||||
with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live:
|
with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live:
|
||||||
tracker.attach_refresh(lambda: live.update(tracker.render()))
|
tracker.attach_refresh(lambda: live.update(tracker.render()))
|
||||||
try:
|
try:
|
||||||
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker)
|
# Create a httpx client with verify based on skip_tls
|
||||||
|
verify = not skip_tls
|
||||||
|
local_ssl_context = ssl_context if verify else False
|
||||||
|
local_client = httpx.Client(verify=local_ssl_context)
|
||||||
|
|
||||||
|
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
|
||||||
|
|
||||||
# Git step
|
# Git step
|
||||||
if not no_git:
|
if not no_git:
|
||||||
@@ -839,18 +840,22 @@ def init(
|
|||||||
# Removed farewell line per user request
|
# Removed farewell line per user request
|
||||||
|
|
||||||
|
|
||||||
|
# Add skip_tls option to check
|
||||||
@app.command()
|
@app.command()
|
||||||
def check():
|
def check(skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)")):
|
||||||
"""Check that all required tools are installed."""
|
"""Check that all required tools are installed."""
|
||||||
show_banner()
|
show_banner()
|
||||||
console.print("[bold]Checking Specify requirements...[/bold]\n")
|
console.print("[bold]Checking Specify requirements...[/bold]\n")
|
||||||
|
|
||||||
# Check if we have internet connectivity by trying to reach GitHub API
|
# Check if we have internet connectivity by trying to reach GitHub API
|
||||||
console.print("[cyan]Checking internet connectivity...[/cyan]")
|
console.print("[cyan]Checking internet connectivity...[/cyan]")
|
||||||
|
verify = not skip_tls
|
||||||
|
local_ssl_context = ssl_context if verify else False
|
||||||
|
local_client = httpx.Client(verify=local_ssl_context)
|
||||||
try:
|
try:
|
||||||
response = client.get("https://api.github.com", timeout=5, follow_redirects=True)
|
response = local_client.get("https://api.github.com", timeout=5, follow_redirects=True)
|
||||||
console.print("[green]✓[/green] Internet connection available")
|
console.print("[green]✓[/green] Internet connection available")
|
||||||
except client.RequestError:
|
except httpx.RequestError:
|
||||||
console.print("[red]✗[/red] No internet connection - required for downloading templates")
|
console.print("[red]✗[/red] No internet connection - required for downloading templates")
|
||||||
console.print("[yellow]Please check your internet connection[/yellow]")
|
console.print("[yellow]Please check your internet connection[/yellow]")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user