Skip to content

Commit 7360023

Browse files
committed
Require platform link for push/pull, fix pull config merge
- cf push and cf pull now fail fast if project is not linked or user is not logged in, preventing silent sync failures - Pull config merge preserves platform_project_id when updating local project.json from SFTP results - Add explicit warnings when platform sync is skipped Made-with: Cursor
1 parent 72d6a61 commit 7360023

1 file changed

Lines changed: 88 additions & 60 deletions

File tree

chipfoundry_cli/main.py

Lines changed: 88 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,8 +1296,21 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
12961296
if not project_root:
12971297
console.print("[bold red]No project root specified and no .cf/project.json found in current directory. Please provide --project-root.[/bold red]")
12981298
raise click.Abort()
1299+
1300+
# Require platform link and login before pushing
1301+
platform_id = _load_project_platform_id(project_root)
1302+
if not platform_id:
1303+
console.print("[bold red]Project is not linked to the platform.[/bold red]")
1304+
console.print("Run [bold]cf link[/bold] to connect this project, or [bold]cf init[/bold] to create a new one.")
1305+
raise click.Abort()
1306+
12991307
# Load user config for defaults
13001308
config = load_user_config()
1309+
api_key = config.get("api_key")
1310+
if not api_key:
1311+
console.print("[bold red]Not logged in.[/bold red]")
1312+
console.print("Run [bold]cf login[/bold] to authenticate before pushing.")
1313+
raise click.Abort()
13011314
if not sftp_username:
13021315
sftp_username = config.get("sftp_username")
13031316
if not sftp_username:
@@ -1430,32 +1443,25 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
14301443
sftp.close()
14311444
transport.close()
14321445

1433-
# --- Platform sync ---
1434-
platform_id = _load_project_platform_id(project_root or ".")
1435-
config = load_user_config()
1436-
api_key = config.get("api_key")
1437-
1438-
if platform_id and api_key:
1446+
# --- Platform sync (platform_id and api_key verified at top of push) ---
1447+
try:
1448+
import json as _json
1449+
with open(project_json_path, "r") as f:
1450+
pj = _json.load(f)
14391451
try:
1440-
import json as _json
1441-
with open(project_json_path, "r") as f:
1442-
pj = _json.load(f)
1443-
try:
1444-
_api_put(f"/projects/{platform_id}", {"cli_project_json": pj, "cli_sync_source": "push"})
1445-
console.print("[green]✓ Platform project synced[/green]")
1446-
except SystemExit:
1447-
console.print("[yellow]⚠ SFTP upload succeeded but platform sync failed[/yellow]")
1448-
except Exception:
1449-
console.print("[yellow]⚠ Could not read project.json for platform sync[/yellow]")
1452+
_api_put(f"/projects/{platform_id}", {"cli_project_json": pj, "cli_sync_source": "push"})
1453+
console.print("[green]✓ Platform project synced[/green]")
1454+
except SystemExit:
1455+
console.print("[yellow]⚠ SFTP upload succeeded but platform sync failed[/yellow]")
1456+
except Exception:
1457+
console.print("[yellow]⚠ Could not read project.json for platform sync[/yellow]")
14501458

1451-
if submit:
1452-
try:
1453-
_api_post(f"/projects/{platform_id}/submit", {})
1454-
console.print("[green]✓ Project submitted for review[/green]")
1455-
except SystemExit:
1456-
console.print("[yellow]⚠ Submit failed — ensure the project has a name and description[/yellow]")
1457-
elif submit:
1458-
console.print("[yellow]⚠ --submit requires a linked platform project and active login[/yellow]")
1459+
if submit:
1460+
try:
1461+
_api_post(f"/projects/{platform_id}/submit", {})
1462+
console.print("[green]✓ Project submitted for review[/green]")
1463+
except SystemExit:
1464+
console.print("[yellow]⚠ Submit failed — ensure the project has a name[/yellow]")
14591465

14601466
@main.command('pull')
14611467
@click.option('--project-name', required=False, help='Project name to pull results for (defaults to value in .cf/project.json if present).')
@@ -1472,9 +1478,21 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
14721478
if not project_name:
14731479
console.print("[bold red]No project name specified and no .cf/project.json found in current directory. Please provide --project-name.[/bold red]")
14741480
raise click.Abort()
1475-
1481+
1482+
# Require platform link and login before pulling
1483+
platform_id = _load_project_platform_id(".")
1484+
if not platform_id:
1485+
console.print("[bold red]Project is not linked to the platform.[/bold red]")
1486+
console.print("Run [bold]cf link[/bold] to connect this project, or [bold]cf init[/bold] to create a new one.")
1487+
raise click.Abort()
1488+
14761489
# Load user config for defaults
14771490
config = load_user_config()
1491+
api_key = config.get("api_key")
1492+
if not api_key:
1493+
console.print("[bold red]Not logged in.[/bold red]")
1494+
console.print("Run [bold]cf login[/bold] to authenticate before pulling.")
1495+
raise click.Abort()
14781496
if not sftp_username:
14791497
sftp_username = config.get("sftp_username")
14801498
if not sftp_username:
@@ -1530,15 +1548,29 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
15301548
sftp_download_recursive(sftp, remote_dir, output_dir, console=console)
15311549
console.print(f"[green]✓ All files downloaded to {output_dir}[/green]")
15321550

1533-
# Automatically update local project config if available
1551+
# Merge pulled project config into local .cf/project.json, preserving platform_project_id
15341552
pulled_config_path = os.path.join(output_dir, "config", "project.json")
15351553
if os.path.exists(pulled_config_path):
15361554
local_config_path = os.path.join(".cf", "project.json")
15371555
os.makedirs(".cf", exist_ok=True)
1538-
1556+
15391557
try:
1540-
import shutil
1541-
shutil.copy2(pulled_config_path, local_config_path)
1558+
import json as _json
1559+
pulled_data = _json.loads(open(pulled_config_path).read())
1560+
1561+
existing_data = {}
1562+
if os.path.exists(local_config_path):
1563+
existing_data = _json.loads(open(local_config_path).read())
1564+
1565+
saved_platform_id = existing_data.get("project", {}).get("platform_project_id")
1566+
1567+
merged = pulled_data
1568+
if saved_platform_id:
1569+
merged.setdefault("project", {})["platform_project_id"] = saved_platform_id
1570+
1571+
with open(local_config_path, "w") as f:
1572+
_json.dump(merged, f, indent=2)
1573+
15421574
console.print(f"[green]✓ Project config automatically updated[/green]")
15431575
except Exception as e:
15441576
console.print(f"[yellow]Warning: Failed to update project config: {e}[/yellow]")
@@ -1555,39 +1587,35 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
15551587
transport.close()
15561588
console.print(f"[dim]Disconnected from {sftp_host}[/dim]")
15571589

1558-
# --- Platform sync and review notes ---
1559-
platform_id = _load_project_platform_id(".")
1560-
pull_config = load_user_config()
1561-
api_key = pull_config.get("api_key")
1562-
1563-
if platform_id and api_key:
1564-
# Sync local project.json to platform
1565-
local_pj = os.path.join(".", ".cf", "project.json")
1566-
if os.path.exists(local_pj):
1567-
try:
1568-
import json as _json
1569-
with open(local_pj, "r") as f:
1570-
pj = _json.load(f)
1571-
_api_put(f"/projects/{platform_id}", {"cli_project_json": pj, "cli_sync_source": "pull"})
1572-
console.print("[green]✓ Platform project synced[/green]")
1573-
except (SystemExit, Exception):
1574-
pass
1575-
1590+
# --- Platform sync and review notes (platform_id and api_key verified at top of pull) ---
1591+
local_pj = os.path.join(".", ".cf", "project.json")
1592+
if os.path.exists(local_pj):
15761593
try:
1577-
project = _api_get(f"/projects/{platform_id}")
1578-
status = project.get("status", "")
1579-
notes = project.get("admin_review_notes")
1580-
if notes:
1581-
from rich.panel import Panel
1582-
style = "bold red" if status == "CHANGES_REQUESTED" else "yellow"
1583-
console.print()
1584-
console.print(Panel(
1585-
notes,
1586-
title="Review Notes" if status != "CHANGES_REQUESTED" else "Changes Requested",
1587-
border_style=style,
1588-
))
1594+
import json as _json
1595+
with open(local_pj, "r") as f:
1596+
pj = _json.load(f)
1597+
_api_put(f"/projects/{platform_id}", {"cli_project_json": pj, "cli_sync_source": "pull"})
1598+
console.print("[green]✓ Platform project synced[/green]")
15891599
except SystemExit:
1590-
pass
1600+
console.print("[yellow]⚠ SFTP download succeeded but platform sync failed[/yellow]")
1601+
except Exception:
1602+
console.print("[yellow]⚠ Could not read project.json for platform sync[/yellow]")
1603+
1604+
try:
1605+
project = _api_get(f"/projects/{platform_id}")
1606+
status = project.get("status", "")
1607+
notes = project.get("admin_review_notes")
1608+
if notes:
1609+
from rich.panel import Panel
1610+
style = "bold red" if status == "CHANGES_REQUESTED" else "yellow"
1611+
console.print()
1612+
console.print(Panel(
1613+
notes,
1614+
title="Review Notes" if status != "CHANGES_REQUESTED" else "Changes Requested",
1615+
border_style=style,
1616+
))
1617+
except SystemExit:
1618+
pass
15911619

15921620
STATUS_COLORS = {
15931621
"DRAFT": "dim",

0 commit comments

Comments
 (0)