sweatstack-cli 0.7.2__py3-none-any.whl → 0.9.0__py3-none-any.whl
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.
- sweatstack_cli/__init__.py +1 -1
- sweatstack_cli/commands/app.py +49 -3
- sweatstack_cli/commands/page.py +57 -4
- sweatstack_cli/project.py +34 -0
- {sweatstack_cli-0.7.2.dist-info → sweatstack_cli-0.9.0.dist-info}/METADATA +4 -4
- {sweatstack_cli-0.7.2.dist-info → sweatstack_cli-0.9.0.dist-info}/RECORD +8 -8
- {sweatstack_cli-0.7.2.dist-info → sweatstack_cli-0.9.0.dist-info}/WHEEL +0 -0
- {sweatstack_cli-0.7.2.dist-info → sweatstack_cli-0.9.0.dist-info}/entry_points.txt +0 -0
sweatstack_cli/__init__.py
CHANGED
sweatstack_cli/commands/app.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json as json_lib
|
|
6
|
+
import sys
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, cast
|
|
8
9
|
|
|
@@ -16,9 +17,14 @@ from sweatstack_cli.project import CONFIG_FILENAME, write_config
|
|
|
16
17
|
app = typer.Typer(name="app", help="Manage OAuth2 applications.")
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _is_interactive() -> bool:
|
|
21
|
+
"""Check if stdin is a TTY (interactive terminal)."""
|
|
22
|
+
return sys.stdin.isatty()
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
@app.command()
|
|
20
26
|
def create(
|
|
21
|
-
name: str = typer.Argument(
|
|
27
|
+
name: str | None = typer.Argument(None, help="Application name."),
|
|
22
28
|
description: str | None = typer.Option(
|
|
23
29
|
None,
|
|
24
30
|
"--description",
|
|
@@ -56,7 +62,33 @@ def create(
|
|
|
56
62
|
"""Create a new private OAuth2 application."""
|
|
57
63
|
if env and env_file:
|
|
58
64
|
console.print("[red]Error:[/red] --env and --env-file are mutually exclusive.")
|
|
59
|
-
raise typer.Exit(
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
|
|
67
|
+
interactive = _is_interactive()
|
|
68
|
+
|
|
69
|
+
# Resolve name
|
|
70
|
+
if name is None:
|
|
71
|
+
if not interactive:
|
|
72
|
+
console.print("[red]Error:[/red] App name is required.")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
name = typer.prompt("App name")
|
|
75
|
+
|
|
76
|
+
# Prompt for optional fields in interactive mode
|
|
77
|
+
if interactive:
|
|
78
|
+
if description is None:
|
|
79
|
+
description = typer.prompt(
|
|
80
|
+
"Description (optional)", default="", show_default=False
|
|
81
|
+
)
|
|
82
|
+
if not description:
|
|
83
|
+
description = None
|
|
84
|
+
if page is None:
|
|
85
|
+
page = typer.prompt(
|
|
86
|
+
"Page slug (optional)", default="", show_default=False
|
|
87
|
+
)
|
|
88
|
+
if not page:
|
|
89
|
+
page = None
|
|
90
|
+
if not secret:
|
|
91
|
+
secret = typer.confirm("Generate client secret?", default=False)
|
|
60
92
|
|
|
61
93
|
body: dict[str, str | bool] = {"name": name}
|
|
62
94
|
|
|
@@ -86,7 +118,18 @@ def create(
|
|
|
86
118
|
if env_path:
|
|
87
119
|
_write_env_file(env_path, result)
|
|
88
120
|
|
|
89
|
-
# Write sweatstack.toml
|
|
121
|
+
# Write sweatstack.toml (protect existing file)
|
|
122
|
+
config_path = Path(CONFIG_FILENAME)
|
|
123
|
+
if config_path.exists():
|
|
124
|
+
if not interactive:
|
|
125
|
+
console.print(
|
|
126
|
+
f"[red]Error:[/red] {CONFIG_FILENAME} already exists. "
|
|
127
|
+
"Remove it first or run interactively."
|
|
128
|
+
)
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
console.print(f"[yellow]Warning:[/yellow] {CONFIG_FILENAME} already exists.")
|
|
131
|
+
typer.confirm("Overwrite?", abort=True)
|
|
132
|
+
|
|
90
133
|
config_path = write_config(
|
|
91
134
|
app_name=result["name"],
|
|
92
135
|
client_id=result["id"],
|
|
@@ -206,6 +249,9 @@ def link(
|
|
|
206
249
|
console.print(f" [cyan][{i}][/cyan] {app_info['name']}{page_info}")
|
|
207
250
|
|
|
208
251
|
console.print()
|
|
252
|
+
if not sys.stdin.isatty():
|
|
253
|
+
console.print("[red]Error:[/red] Cannot select application in non-interactive mode.")
|
|
254
|
+
raise typer.Exit(1)
|
|
209
255
|
selection = typer.prompt("Select application", type=int)
|
|
210
256
|
|
|
211
257
|
if selection < 1 or selection > len(apps):
|
sweatstack_cli/commands/page.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import mimetypes
|
|
6
|
+
import sys
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
import typer
|
|
@@ -12,7 +13,7 @@ from sweatstack_cli.api import APIClient
|
|
|
12
13
|
from sweatstack_cli.config import get_settings
|
|
13
14
|
from sweatstack_cli.console import console
|
|
14
15
|
from sweatstack_cli.exceptions import APIError, AuthenticationError
|
|
15
|
-
from sweatstack_cli.project import CONFIG_FILENAME, ProjectConfig
|
|
16
|
+
from sweatstack_cli.project import CONFIG_FILENAME, ProjectConfig, update_config, write_config
|
|
16
17
|
|
|
17
18
|
# URL where users can create pages for their applications
|
|
18
19
|
SETTINGS_URL = "https://app.sweatstack.no/settings/api"
|
|
@@ -59,12 +60,14 @@ def deploy(
|
|
|
59
60
|
None,
|
|
60
61
|
help="Page slug. Reads from sweatstack.toml if not provided.",
|
|
61
62
|
),
|
|
62
|
-
directory: Path | None = typer.
|
|
63
|
+
directory: Path | None = typer.Option(
|
|
63
64
|
None,
|
|
64
|
-
|
|
65
|
+
"--dir",
|
|
66
|
+
help="Directory to deploy. Reads from sweatstack.toml if not provided.",
|
|
65
67
|
),
|
|
66
68
|
) -> None:
|
|
67
69
|
"""Deploy a static site to SweatStack Pages."""
|
|
70
|
+
|
|
68
71
|
# Load config for defaults
|
|
69
72
|
config = ProjectConfig.load()
|
|
70
73
|
|
|
@@ -76,9 +79,19 @@ def deploy(
|
|
|
76
79
|
raise typer.Exit(1)
|
|
77
80
|
|
|
78
81
|
# Resolve directory
|
|
82
|
+
directory_explicit = directory is not None
|
|
79
83
|
if directory is None:
|
|
80
84
|
dir_from_config = config.page.directory if config else None
|
|
81
|
-
|
|
85
|
+
if dir_from_config:
|
|
86
|
+
directory = Path(dir_from_config)
|
|
87
|
+
else:
|
|
88
|
+
if not sys.stdin.isatty():
|
|
89
|
+
console.print("[red]Error:[/red] No deploy directory specified.")
|
|
90
|
+
console.print("Use --dir or set it in sweatstack.toml.")
|
|
91
|
+
raise typer.Exit(1)
|
|
92
|
+
dir_input = typer.prompt("Deploy directory", default="public/")
|
|
93
|
+
directory = Path(dir_input)
|
|
94
|
+
directory_explicit = True
|
|
82
95
|
|
|
83
96
|
if not directory.exists():
|
|
84
97
|
console.print(f"[red]Error:[/red] Directory '{directory}' does not exist.")
|
|
@@ -90,6 +103,32 @@ def deploy(
|
|
|
90
103
|
|
|
91
104
|
directory = directory.resolve()
|
|
92
105
|
|
|
106
|
+
# Compute relative directory for config and warnings
|
|
107
|
+
try:
|
|
108
|
+
rel_dir = str(directory.relative_to(Path.cwd()))
|
|
109
|
+
except ValueError:
|
|
110
|
+
rel_dir = str(directory)
|
|
111
|
+
|
|
112
|
+
# Warn and confirm if deploying with different values than toml
|
|
113
|
+
if config:
|
|
114
|
+
mismatches: list[str] = []
|
|
115
|
+
if config.page.slug and slug and slug != config.page.slug:
|
|
116
|
+
mismatches.append(
|
|
117
|
+
f"Deploying to '{slug}' but "
|
|
118
|
+
f"{CONFIG_FILENAME} has slug '{config.page.slug}'"
|
|
119
|
+
)
|
|
120
|
+
if config.page.directory and rel_dir != config.page.directory:
|
|
121
|
+
mismatches.append(
|
|
122
|
+
f"Deploying '{rel_dir}' but "
|
|
123
|
+
f"{CONFIG_FILENAME} has directory '{config.page.directory}'"
|
|
124
|
+
)
|
|
125
|
+
if mismatches:
|
|
126
|
+
for msg in mismatches:
|
|
127
|
+
console.print(f"[yellow]Warning:[/yellow] {msg}")
|
|
128
|
+
if not sys.stdin.isatty():
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
typer.confirm("Continue?", abort=True)
|
|
131
|
+
|
|
93
132
|
# Collect files
|
|
94
133
|
files = _collect_files(directory)
|
|
95
134
|
file_count = len(files)
|
|
@@ -143,6 +182,20 @@ def deploy(
|
|
|
143
182
|
console.print(f"[red]Error:[/red] {e}")
|
|
144
183
|
raise typer.Exit(3)
|
|
145
184
|
|
|
185
|
+
# Save to config after successful deploy
|
|
186
|
+
if config:
|
|
187
|
+
update_kwargs: dict[str, str] = {}
|
|
188
|
+
if not config.page.slug:
|
|
189
|
+
update_kwargs["page_slug"] = page_slug
|
|
190
|
+
if not config.page.directory and directory_explicit:
|
|
191
|
+
update_kwargs["page_directory"] = rel_dir
|
|
192
|
+
if update_kwargs:
|
|
193
|
+
update_config(**update_kwargs)
|
|
194
|
+
console.print(f"[green]✓[/green] Saved to [bold]{CONFIG_FILENAME}[/bold]")
|
|
195
|
+
elif directory_explicit:
|
|
196
|
+
write_config(page_slug=page_slug, page_directory=rel_dir)
|
|
197
|
+
console.print(f"[green]✓[/green] Created [bold]{CONFIG_FILENAME}[/bold]")
|
|
198
|
+
|
|
146
199
|
console.print()
|
|
147
200
|
console.print(f"[green]✓[/green] Deployed [bold]{page_slug}[/bold] successfully!")
|
|
148
201
|
console.print(f" Files: {file_count}")
|
sweatstack_cli/project.py
CHANGED
|
@@ -109,3 +109,37 @@ def write_config(
|
|
|
109
109
|
config_path.write_text("\n".join(lines), encoding="utf-8")
|
|
110
110
|
|
|
111
111
|
return config_path
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def update_config(
|
|
115
|
+
path: Path | None = None,
|
|
116
|
+
*,
|
|
117
|
+
page_slug: str | None = None,
|
|
118
|
+
page_directory: str | None = None,
|
|
119
|
+
) -> Path | None:
|
|
120
|
+
"""Update specific fields in an existing sweatstack.toml without overwriting other values."""
|
|
121
|
+
config_path = path or Path(CONFIG_FILENAME)
|
|
122
|
+
|
|
123
|
+
if not config_path.exists():
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
128
|
+
except tomllib.TOMLDecodeError:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
app_data = data.get("app", {})
|
|
132
|
+
page_data = data.get("page", {})
|
|
133
|
+
|
|
134
|
+
if page_slug is not None:
|
|
135
|
+
page_data["slug"] = page_slug
|
|
136
|
+
if page_directory is not None:
|
|
137
|
+
page_data["directory"] = page_directory
|
|
138
|
+
|
|
139
|
+
return write_config(
|
|
140
|
+
path=config_path,
|
|
141
|
+
app_name=app_data.get("name"),
|
|
142
|
+
client_id=app_data.get("client_id"),
|
|
143
|
+
page_slug=page_data.get("slug"),
|
|
144
|
+
page_directory=page_data.get("directory"),
|
|
145
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sweatstack-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Command-line interface for SweatStack — the sports data platform for developers
|
|
5
5
|
Project-URL: Homepage, https://sweatstack.no
|
|
6
6
|
Project-URL: Documentation, https://docs.sweatstack.no
|
|
@@ -88,7 +88,7 @@ sweatstack page deploy
|
|
|
88
88
|
|
|
89
89
|
| Command | Description |
|
|
90
90
|
|---------|-------------|
|
|
91
|
-
| `sweatstack app create NAME` | Create a new private OAuth2 application |
|
|
91
|
+
| `sweatstack app create [NAME]` | Create a new private OAuth2 application |
|
|
92
92
|
| `sweatstack app link` | Link current directory to an existing application |
|
|
93
93
|
|
|
94
94
|
#### `app create` options
|
|
@@ -130,8 +130,8 @@ Deploy static sites to SweatStack Pages.
|
|
|
130
130
|
# Deploy using sweatstack.toml defaults (recommended)
|
|
131
131
|
sweatstack page deploy
|
|
132
132
|
|
|
133
|
-
# Or specify slug and directory
|
|
134
|
-
sweatstack page deploy my-page ./dist
|
|
133
|
+
# Or specify slug and directory explicitly
|
|
134
|
+
sweatstack page deploy my-page --dir ./dist
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
## License
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
sweatstack_cli/__init__.py,sha256=
|
|
1
|
+
sweatstack_cli/__init__.py,sha256=gqp4cHVRfTQZSb9iCu508gC8SFLnocm0gOfZLzXV934,100
|
|
2
2
|
sweatstack_cli/config.py,sha256=cD4OXnVAKVagYazyk9Cw-WWx62tJPqcjQt1cknYyymM,706
|
|
3
3
|
sweatstack_cli/console.py,sha256=69KWAM7ksnYRTONMYTXGXN4LWRAQhRxcLXO0LcWGLqo,300
|
|
4
4
|
sweatstack_cli/exceptions.py,sha256=qEUu8GydXGRKR4ndAdZ2wnkHekmFpBXDJiT5bXdl7Yk,613
|
|
5
5
|
sweatstack_cli/main.py,sha256=obcF1VGhwcE_pI1BwTJkY4J4BgIGac-y3yYGvY6ZOp8,2330
|
|
6
|
-
sweatstack_cli/project.py,sha256=
|
|
6
|
+
sweatstack_cli/project.py,sha256=yUpkrEbPD2c0lu8IKw88aQrkFtksLHMjmgHlaqKVQUo,3660
|
|
7
7
|
sweatstack_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
sweatstack_cli/version_check.py,sha256=yZ_qVe7tUBowUDAmMki-wyf5pSxlNDsoWKgTcSfesBA,2015
|
|
9
9
|
sweatstack_cli/api/__init__.py,sha256=l9s6BzF6Wx17K_ElRUX-8aAwwIIzqmGF1PQ3Mbc07mw,107
|
|
@@ -14,10 +14,10 @@ sweatstack_cli/auth/jwt.py,sha256=Ja6ccFyRKtX5MPjFdHNavVYf8KTz0MYT86StbnF-cwg,15
|
|
|
14
14
|
sweatstack_cli/auth/pkce.py,sha256=sO_xUeN1UIvXKX0yEIGgMTIIzvfCOCUwlyNAveEd16k,1542
|
|
15
15
|
sweatstack_cli/auth/tokens.py,sha256=kc8ytoO9PAyjLD6wUTIagVuP7_ia5ZjW5m_WjXIRIkA,4942
|
|
16
16
|
sweatstack_cli/commands/__init__.py,sha256=YCO8wYVvdGeoEI0p_oUm8ANLneuET4SHsKDjitMjAgY,35
|
|
17
|
-
sweatstack_cli/commands/app.py,sha256=
|
|
17
|
+
sweatstack_cli/commands/app.py,sha256=3kTnXBCNqu77VwFa-TL4AMtTtDFqvZ68q2CYalzJL_Q,8517
|
|
18
18
|
sweatstack_cli/commands/auth.py,sha256=lmlN2RcZIHdbZG-EEUVFwOSn6vPN-Ijgh_5E-cFQVtU,3478
|
|
19
|
-
sweatstack_cli/commands/page.py,sha256=
|
|
20
|
-
sweatstack_cli-0.
|
|
21
|
-
sweatstack_cli-0.
|
|
22
|
-
sweatstack_cli-0.
|
|
23
|
-
sweatstack_cli-0.
|
|
19
|
+
sweatstack_cli/commands/page.py,sha256=aiuKVYUBdV2AcAcDAISx8qzs0PKieXQKAoYq00fTDew,7470
|
|
20
|
+
sweatstack_cli-0.9.0.dist-info/METADATA,sha256=Y8EHoUGmIc4zTd87x-ZH7rziBQBmB7xMUjZyiTe98Fk,3674
|
|
21
|
+
sweatstack_cli-0.9.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
22
|
+
sweatstack_cli-0.9.0.dist-info/entry_points.txt,sha256=rnH0EV1U9CQlmKQcJ7RmxGpNRTa9J-YdiIJZDYrAXb4,56
|
|
23
|
+
sweatstack_cli-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|