pyvolt-cli 0.3.2__tar.gz → 0.4.0__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.
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/PKG-INFO +1 -1
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyproject.toml +1 -1
- pyvolt_cli-0.4.0/pyvolt_cli/__init__.py +9 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyvolt_cli/app.py +108 -4
- pyvolt_cli-0.3.2/pyvolt_cli/__init__.py +0 -4
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/.gitignore +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/LICENSE +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/README.md +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyvolt_cli/__main__.py +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyvolt_cli/client.py +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyvolt_cli/config.py +0 -0
- {pyvolt_cli-0.3.2 → pyvolt_cli-0.4.0}/pyvolt_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvolt-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: The command-line companion to Pyvolt: managed Python deployments on infrastructure you actually own.
|
|
5
5
|
Project-URL: Homepage, https://pyvolt.com
|
|
6
6
|
Project-URL: Repository, https://github.com/pyvolt-hq/pyvolt-cli
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyvolt-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "The command-line companion to Pyvolt: managed Python deployments on infrastructure you actually own."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""pyvolt-cli: the terminal companion to Pyvolt — managed Python deployments
|
|
2
|
+
on infrastructure you actually own. Docs: https://pyvolt.com/docs/cli/"""
|
|
3
|
+
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
__version__ = version("pyvolt-cli")
|
|
8
|
+
except PackageNotFoundError: # running from a source tree without an install
|
|
9
|
+
__version__ = "0.0.0.dev0"
|
|
@@ -36,7 +36,7 @@ def _emit(data) -> bool:
|
|
|
36
36
|
return False
|
|
37
37
|
|
|
38
38
|
STATUS_STYLE = {
|
|
39
|
-
"ready": "green", "live": "green", "
|
|
39
|
+
"ready": "green", "live": "green", "success": "green", "running": "cyan",
|
|
40
40
|
"active": "green", "queued": "yellow", "deploying": "cyan",
|
|
41
41
|
"failed": "red", "error": "red", "inactive": "red",
|
|
42
42
|
}
|
|
@@ -171,9 +171,18 @@ def ssh(
|
|
|
171
171
|
os.execvp("ssh", args)
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
apps_app = typer.Typer(help="List your apps; `pyvolt apps create` makes a new one.")
|
|
175
|
+
app.add_typer(apps_app, name="apps")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@apps_app.callback(invoke_without_command=True)
|
|
179
|
+
def apps(
|
|
180
|
+
ctx: typer.Context,
|
|
181
|
+
server: str = typer.Option("", "--server", help="Filter by server name."),
|
|
182
|
+
):
|
|
176
183
|
"""List your apps."""
|
|
184
|
+
if ctx.invoked_subcommand is not None:
|
|
185
|
+
return
|
|
177
186
|
data = Api().get("/v1/apps", params={"server": server} if server else None)
|
|
178
187
|
if _emit(data):
|
|
179
188
|
return
|
|
@@ -183,6 +192,101 @@ def apps(server: str = typer.Option("", "--server", help="Filter by server name.
|
|
|
183
192
|
console.print(t)
|
|
184
193
|
|
|
185
194
|
|
|
195
|
+
@apps_app.command("create")
|
|
196
|
+
def apps_create(
|
|
197
|
+
server: str = typer.Option(..., "--server", "-s", metavar="SERVER", help="Server to create the app on."),
|
|
198
|
+
domain: str = typer.Option(..., "--domain", "-d", help="Subdomain prefix (no dots — becomes <prefix>.preview.pyvolt.com) or a full custom domain."),
|
|
199
|
+
repo: str = typer.Option("", "--repo", "-r", metavar="OWNER/REPO", help="GitHub repository, resolved through your connection."),
|
|
200
|
+
repo_url: str = typer.Option("", "--repo-url", help="Raw git URL for repos outside a GitHub connection."),
|
|
201
|
+
branch: str = typer.Option("main", "--branch", "-b", help="Branch deploys ship from."),
|
|
202
|
+
path: str = typer.Option("", "--path", help="Project directory inside the repo (monorepos)."),
|
|
203
|
+
env: list[str] = typer.Option([], "--env", "-e", metavar="KEY=VALUE", help="Create-time env var (repeatable)."),
|
|
204
|
+
serve_command: str = typer.Option("", "--serve-command", help="Override the detected start command."),
|
|
205
|
+
release_command: str = typer.Option("", "--release-command", help="Override the detected release steps."),
|
|
206
|
+
build_command: str = typer.Option("", "--build-command", help="Override the detected build command."),
|
|
207
|
+
python_version: str = typer.Option("", "--python", help="Override the detected Python version."),
|
|
208
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show the resolved plan without creating anything."),
|
|
209
|
+
):
|
|
210
|
+
"""Create an app on one of your servers.
|
|
211
|
+
|
|
212
|
+
Repo autodiscovery prefills the framework, serve and release commands
|
|
213
|
+
(same as the dashboard form); every flag above overrides it. Use
|
|
214
|
+
--dry-run to inspect the plan first, then create and follow up with
|
|
215
|
+
`pyvolt deploy DOMAIN --follow`."""
|
|
216
|
+
api = Api()
|
|
217
|
+
srv = api.resolve_server(server)
|
|
218
|
+
env_map: dict[str, str] = {}
|
|
219
|
+
for pair in env:
|
|
220
|
+
if "=" not in pair:
|
|
221
|
+
raise fail(f"Expected KEY=VALUE, got [bold]{pair}[/bold]")
|
|
222
|
+
key, _, value = pair.partition("=")
|
|
223
|
+
env_map[key] = value
|
|
224
|
+
payload = {
|
|
225
|
+
"server": srv["name"],
|
|
226
|
+
"repo": repo,
|
|
227
|
+
"repo_url": repo_url,
|
|
228
|
+
"branch": branch,
|
|
229
|
+
"project_path": path,
|
|
230
|
+
"env": env_map,
|
|
231
|
+
"serve_command": serve_command,
|
|
232
|
+
"release_command": release_command,
|
|
233
|
+
"build_command": build_command,
|
|
234
|
+
"python_version": python_version,
|
|
235
|
+
"dry_run": dry_run,
|
|
236
|
+
}
|
|
237
|
+
# A dot means "that's the whole domain"; a bare word is a pyvolt
|
|
238
|
+
# subdomain prefix. Prefixes can't contain dots, so this never guesses.
|
|
239
|
+
payload["custom_domain" if "." in domain else "domain_prefix"] = domain
|
|
240
|
+
r = api.post("/v1/apps", ok=(200, 201, 400, 402, 409), json=payload)
|
|
241
|
+
if r.status_code in (400, 402, 409):
|
|
242
|
+
raise fail(r.json()["detail"])
|
|
243
|
+
body = r.json()
|
|
244
|
+
if _emit(body):
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
plan = body["plan"]
|
|
248
|
+
t = _table("FIELD", "VALUE")
|
|
249
|
+
t.add_row("domain", plan["domain"])
|
|
250
|
+
t.add_row("repo", f"{plan['repo_url']} @ {plan['branch']}")
|
|
251
|
+
if plan["framework"]:
|
|
252
|
+
t.add_row("framework", plan["framework"])
|
|
253
|
+
t.add_row("protocol", plan["app_protocol"].upper())
|
|
254
|
+
t.add_row("package manager", plan["package_manager"])
|
|
255
|
+
if plan["python_version"]:
|
|
256
|
+
t.add_row("python", plan["python_version"])
|
|
257
|
+
if plan["serve_command"]:
|
|
258
|
+
t.add_row("serve", plan["serve_command"])
|
|
259
|
+
if plan["release_command"]:
|
|
260
|
+
t.add_row("release", plan["release_command"])
|
|
261
|
+
if plan["build_command"]:
|
|
262
|
+
t.add_row("build", plan["build_command"])
|
|
263
|
+
if plan["frontend_dir"]:
|
|
264
|
+
t.add_row("frontend", f"{plan['frontend_dir']}/ (passthrough: {plan['frontend_passthrough'] or 'none'})")
|
|
265
|
+
console.print(t)
|
|
266
|
+
if plan["evidence"]:
|
|
267
|
+
console.print(f"[dim]Found: {', '.join(plan['evidence'])}[/dim]")
|
|
268
|
+
for w in plan["warnings"]:
|
|
269
|
+
console.print(f"[yellow]⚠[/yellow] {w}")
|
|
270
|
+
|
|
271
|
+
if dry_run:
|
|
272
|
+
console.print("[dim]Dry run — nothing was created.[/dim]")
|
|
273
|
+
return
|
|
274
|
+
created = body["app"]
|
|
275
|
+
if not body["scaffolded"]:
|
|
276
|
+
err_detail = body["scaffold_error"] or "unknown error"
|
|
277
|
+
console.print(f"[red]✗[/red] App row created but scaffolding failed: {err_detail}")
|
|
278
|
+
console.print(f" Retry from the dashboard: {created['dashboard_url']}")
|
|
279
|
+
raise typer.Exit(1)
|
|
280
|
+
if body["deploy_key_ok"] is False:
|
|
281
|
+
console.print(
|
|
282
|
+
"[yellow]⚠[/yellow] Deploy key push failed — deploys will fail at clone. "
|
|
283
|
+
"Grant the Pyvolt GitHub App access to the repo, then redeploy."
|
|
284
|
+
)
|
|
285
|
+
console.print(f"[green]✓[/green] Created [bold]{created['domain']}[/bold]")
|
|
286
|
+
console.print(f" Deploy: [bold]pyvolt deploy {created['domain']} --follow[/bold]")
|
|
287
|
+
console.print(f" Dashboard: {created['dashboard_url']}")
|
|
288
|
+
|
|
289
|
+
|
|
186
290
|
@app.command()
|
|
187
291
|
def status(app_name: str = typer.Argument(..., metavar="DOMAIN")):
|
|
188
292
|
"""Show one app's details: status, URLs, repo and branch."""
|
|
@@ -202,7 +306,7 @@ def status(app_name: str = typer.Argument(..., metavar="DOMAIN")):
|
|
|
202
306
|
|
|
203
307
|
# ---- deployments --------------------------------------------------------------
|
|
204
308
|
|
|
205
|
-
TERMINAL = ("
|
|
309
|
+
TERMINAL = ("success", "failed")
|
|
206
310
|
|
|
207
311
|
|
|
208
312
|
def _follow(api: Api, deployment_id: int) -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|