usecli 0.1.79__tar.gz → 0.1.81__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.
- {usecli-0.1.79 → usecli-0.1.81}/PKG-INFO +30 -1
- {usecli-0.1.79 → usecli-0.1.81}/README.md +29 -0
- {usecli-0.1.79 → usecli-0.1.81}/pyproject.toml +1 -1
- {usecli-0.1.79 → usecli-0.1.81}/pyproject.toml.orig +1 -1
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/ui/list.py +6 -2
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/templates/usecli.config.toml.j2 +7 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/config/manager.py +10 -2
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/discovery.py +107 -24
- usecli-0.1.81/src/usecli/shared/upgrades/settings.py +129 -0
- {usecli-0.1.79 → usecli-0.1.81}/LICENSE +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/bundler/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/bundler/__main__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/README.md +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/custom/README.md +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/custom/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/upgrade_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/make/make_bundle_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/init_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/config/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/config/colors.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/base_command.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/error/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/error/handler.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/error/utils.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/exceptions/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/exceptions/base.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/exceptions/config.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/exceptions/usage.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/exceptions/validation.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/runtime.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/ui/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/ui/title.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/ui/title.txt +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/validators/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/validators/network.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/validators/numeric.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/validators/path.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/core/validators/string.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/services/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/services/command_service.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/templates/command.py.j2 +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/templates/theme.toml.j2 +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/ayu_dark.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/default.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/dracula.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/nord.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/themes/tokyo_night.toml +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/utils/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/utils/interactive/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/entry.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/menu.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/params.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/progress.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/config/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/config/globals.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/__init__.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/checker.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/installer.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/pyproject.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/shared/upgrades/service.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/ui.py +0 -0
- {usecli-0.1.79 → usecli-0.1.81}/src/usecli/usecli.config.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usecli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.81
|
|
4
4
|
Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
|
|
5
5
|
Author: Edward Boswell
|
|
6
6
|
Author-email: Edward Boswell <thememium@gmail.com>
|
|
@@ -251,6 +251,35 @@ make:theme Create new theme (usecli only)
|
|
|
251
251
|
make:bundle Build a standalone executable with PyInstaller (usecli only)
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
### Upgrades
|
|
255
|
+
|
|
256
|
+
Every CLI built on usecli ships `upgrade`, which detects how the running
|
|
257
|
+
package was installed and delegates to the same package manager — `uv tool`,
|
|
258
|
+
`pipx`, or `pip` — instead of creating a second installation:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
mycli upgrade --check # show installed/latest versions and the source
|
|
262
|
+
mycli upgrade # confirm and install the latest release
|
|
263
|
+
mycli upgrade --force # skip the prompt and the up-to-date check
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The source comes from packaging metadata: PyPI for index installs, and the
|
|
267
|
+
Git remote for PEP 610 `direct_url.json` installs. Override it in
|
|
268
|
+
`usecli.config.toml` when metadata is not enough (private mirrors, missing
|
|
269
|
+
`direct_url.json`, a package name that differs from the command name):
|
|
270
|
+
|
|
271
|
+
```toml
|
|
272
|
+
[usecli.upgrade]
|
|
273
|
+
enabled = true
|
|
274
|
+
source = "git" # "auto" (default) | "pypi" | "git"
|
|
275
|
+
package = "mycli" # optional
|
|
276
|
+
url = "https://github.com/owner/mycli.git" # required for "git" unless detected
|
|
277
|
+
ref = "main" # branch, tag, or commit
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Frozen bundles, editable installs, and commit-pinned installs are reported
|
|
281
|
+
as non-upgradeable with an explanation and manual next step.
|
|
282
|
+
|
|
254
283
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
255
284
|
|
|
256
285
|
<!-- ENTRY POINTS & PACKAGING -->
|
|
@@ -221,6 +221,35 @@ make:theme Create new theme (usecli only)
|
|
|
221
221
|
make:bundle Build a standalone executable with PyInstaller (usecli only)
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
### Upgrades
|
|
225
|
+
|
|
226
|
+
Every CLI built on usecli ships `upgrade`, which detects how the running
|
|
227
|
+
package was installed and delegates to the same package manager — `uv tool`,
|
|
228
|
+
`pipx`, or `pip` — instead of creating a second installation:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
mycli upgrade --check # show installed/latest versions and the source
|
|
232
|
+
mycli upgrade # confirm and install the latest release
|
|
233
|
+
mycli upgrade --force # skip the prompt and the up-to-date check
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The source comes from packaging metadata: PyPI for index installs, and the
|
|
237
|
+
Git remote for PEP 610 `direct_url.json` installs. Override it in
|
|
238
|
+
`usecli.config.toml` when metadata is not enough (private mirrors, missing
|
|
239
|
+
`direct_url.json`, a package name that differs from the command name):
|
|
240
|
+
|
|
241
|
+
```toml
|
|
242
|
+
[usecli.upgrade]
|
|
243
|
+
enabled = true
|
|
244
|
+
source = "git" # "auto" (default) | "pypi" | "git"
|
|
245
|
+
package = "mycli" # optional
|
|
246
|
+
url = "https://github.com/owner/mycli.git" # required for "git" unless detected
|
|
247
|
+
ref = "main" # branch, tag, or commit
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Frozen bundles, editable installs, and commit-pinned installs are reported
|
|
251
|
+
as non-upgradeable with an explanation and manual next step.
|
|
252
|
+
|
|
224
253
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
225
254
|
|
|
226
255
|
<!-- ENTRY POINTS & PACKAGING -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usecli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.81"
|
|
4
4
|
description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
|
|
@@ -150,8 +150,12 @@ def collect_commands_data(
|
|
|
150
150
|
or any(alias.startswith(prefix_filter) for alias in g["aliases"])
|
|
151
151
|
]
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
app_name =
|
|
153
|
+
info = getattr(app, "info", None)
|
|
154
|
+
app_name = (
|
|
155
|
+
getattr(info, "name", None)
|
|
156
|
+
or get_script_command_name(default="usecli")
|
|
157
|
+
or "usecli"
|
|
158
|
+
)
|
|
155
159
|
|
|
156
160
|
return {
|
|
157
161
|
"usage": f"{app_name} [COMMAND] [OPTIONS] [ARGUMENTS]",
|
|
@@ -12,3 +12,10 @@ hide_inspire = {{ hide_inspire | default(false) | lower }}
|
|
|
12
12
|
|
|
13
13
|
[usecli.upgrade]
|
|
14
14
|
enabled = {{ upgrade_enabled | default(true) | lower }}
|
|
15
|
+
# Where upgrades come from: "auto" (detect from install metadata), "pypi", or "git".
|
|
16
|
+
source = "{{ upgrade_source | default('auto') }}"
|
|
17
|
+
# Git only: repository URL. Falls back to the URL in the install metadata.
|
|
18
|
+
# url = "https://github.com/owner/repo.git"
|
|
19
|
+
# Optional: distribution name and Git ref (branch, tag, or commit) overrides.
|
|
20
|
+
# package = "my-cli"
|
|
21
|
+
# ref = "main"
|
|
@@ -284,8 +284,16 @@ class ConfigManager:
|
|
|
284
284
|
"environment": "prod",
|
|
285
285
|
"command_name": "usecli",
|
|
286
286
|
"hide_inspire": False,
|
|
287
|
-
#
|
|
288
|
-
|
|
287
|
+
# The built-in `upgrade` command reads [usecli.upgrade]. `source`
|
|
288
|
+
# overrides install-source detection: "auto" (packaging metadata),
|
|
289
|
+
# "pypi", or "git" (with `package`/`url`/`ref` overrides).
|
|
290
|
+
"upgrade": {
|
|
291
|
+
"enabled": True,
|
|
292
|
+
"source": "auto",
|
|
293
|
+
"package": None,
|
|
294
|
+
"url": None,
|
|
295
|
+
"ref": None,
|
|
296
|
+
},
|
|
289
297
|
}
|
|
290
298
|
|
|
291
299
|
def __init__(
|
|
@@ -13,9 +13,13 @@ import logging
|
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
15
|
from dataclasses import dataclass
|
|
16
|
-
from typing import Any, Literal
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
17
17
|
|
|
18
18
|
from usecli.shared.config.manager import _find_distribution_for_console_script
|
|
19
|
+
from usecli.shared.upgrades.settings import config_error, from_config
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from usecli.shared.upgrades.settings import UpgradeSettings
|
|
19
23
|
|
|
20
24
|
logger = logging.getLogger(__name__)
|
|
21
25
|
|
|
@@ -170,29 +174,8 @@ def _revision_kind(revision: str | None) -> Literal["commit", "tag", "branch"] |
|
|
|
170
174
|
return "branch"
|
|
171
175
|
|
|
172
176
|
|
|
173
|
-
def
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
config: Optional ConfigManager used for the version fallback when no
|
|
178
|
-
distribution can be identified. Defaults to the global config.
|
|
179
|
-
|
|
180
|
-
Returns:
|
|
181
|
-
The discovered installation details. ``source`` is ``"unknown"`` and
|
|
182
|
-
``pinned`` is true when the installation cannot be classified (frozen
|
|
183
|
-
bundles, unidentified distributions).
|
|
184
|
-
"""
|
|
185
|
-
if getattr(sys, "frozen", False):
|
|
186
|
-
return InstallInfo(
|
|
187
|
-
package=_running_command_name() or "application",
|
|
188
|
-
version=_fallback_version(config),
|
|
189
|
-
source="unknown",
|
|
190
|
-
pinned=True,
|
|
191
|
-
pinned_reason="frozen bundle",
|
|
192
|
-
frozen=True,
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
dist = _find_running_distribution()
|
|
177
|
+
def _from_distribution(dist: Any | None, config: Any | None) -> InstallInfo:
|
|
178
|
+
"""Classify an installation from its distribution metadata."""
|
|
196
179
|
if dist is None:
|
|
197
180
|
logger.debug("No distribution found for the running console script")
|
|
198
181
|
return InstallInfo(
|
|
@@ -285,3 +268,103 @@ def discover(config: Any | None = None) -> InstallInfo:
|
|
|
285
268
|
pinned=True,
|
|
286
269
|
pinned_reason="direct URL install",
|
|
287
270
|
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _override_package(
|
|
274
|
+
install: InstallInfo,
|
|
275
|
+
settings: UpgradeSettings,
|
|
276
|
+
config: Any | None,
|
|
277
|
+
) -> str:
|
|
278
|
+
"""Resolve the distribution name for a configured source."""
|
|
279
|
+
if settings.package:
|
|
280
|
+
return settings.package
|
|
281
|
+
if install.source != "unknown" and install.package:
|
|
282
|
+
return install.package
|
|
283
|
+
raise config_error(
|
|
284
|
+
"upgrade.source is set but the package name could not be discovered.",
|
|
285
|
+
'Set \\[usecli.upgrade] package = "<distribution name>".',
|
|
286
|
+
config,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _apply_settings(
|
|
291
|
+
install: InstallInfo,
|
|
292
|
+
settings: UpgradeSettings,
|
|
293
|
+
config: Any | None,
|
|
294
|
+
) -> InstallInfo:
|
|
295
|
+
"""Replace the discovered source with the configured one."""
|
|
296
|
+
package = _override_package(install, settings, config)
|
|
297
|
+
|
|
298
|
+
if settings.source == "pypi":
|
|
299
|
+
return InstallInfo(
|
|
300
|
+
package=package,
|
|
301
|
+
version=install.version,
|
|
302
|
+
source="index",
|
|
303
|
+
installer=install.installer,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# settings.source == "git": prefer the configured remote, then the
|
|
307
|
+
# discovered Git URL. Direct-URL and editable records are not remotes.
|
|
308
|
+
url = settings.url or (install.url if install.source == "git" else None)
|
|
309
|
+
if not url:
|
|
310
|
+
raise config_error(
|
|
311
|
+
'upgrade.source = "git" requires a repository URL.',
|
|
312
|
+
'Set \\[usecli.upgrade] url = "https://github.com/owner/repo.git".',
|
|
313
|
+
config,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# The installed revision and commit only describe ``url`` when the
|
|
317
|
+
# configured remote is the one the package was installed from.
|
|
318
|
+
same_remote = install.source == "git" and (
|
|
319
|
+
settings.url is None or settings.url == install.url
|
|
320
|
+
)
|
|
321
|
+
revision = settings.ref or (install.revision if same_remote else None)
|
|
322
|
+
pinned = _revision_kind(revision) == "commit"
|
|
323
|
+
return InstallInfo(
|
|
324
|
+
package=package,
|
|
325
|
+
version=install.version,
|
|
326
|
+
source="git",
|
|
327
|
+
installer=install.installer,
|
|
328
|
+
url=url,
|
|
329
|
+
revision=revision,
|
|
330
|
+
commit=install.commit if same_remote else None,
|
|
331
|
+
pinned=pinned,
|
|
332
|
+
pinned_reason=f"Git commit ({revision})" if pinned else None,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def discover(config: Any | None = None) -> InstallInfo:
|
|
337
|
+
"""Build an :class:`InstallInfo` for the running application.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
config: Optional ConfigManager used for ``[usecli.upgrade]`` source
|
|
341
|
+
overrides and for the version fallback when no distribution can
|
|
342
|
+
be identified. Defaults to the global config.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The discovered installation details. ``source`` is ``"unknown"`` and
|
|
346
|
+
``pinned`` is true when the installation cannot be classified (frozen
|
|
347
|
+
bundles, unidentified distributions).
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
UsecliConfigError: When ``[usecli.upgrade]`` configures an invalid
|
|
351
|
+
source, or omits a value that source requires.
|
|
352
|
+
"""
|
|
353
|
+
if getattr(sys, "frozen", False):
|
|
354
|
+
return InstallInfo(
|
|
355
|
+
package=_running_command_name() or "application",
|
|
356
|
+
version=_fallback_version(config),
|
|
357
|
+
source="unknown",
|
|
358
|
+
pinned=True,
|
|
359
|
+
pinned_reason="frozen bundle",
|
|
360
|
+
frozen=True,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
install = _from_distribution(_find_running_distribution(), config)
|
|
364
|
+
settings = from_config(config)
|
|
365
|
+
if not settings.overrides_source:
|
|
366
|
+
return install
|
|
367
|
+
return _apply_settings(install, settings, config)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
__all__ = ["InstallInfo", "InstallSource", "discover"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Upgrade source configuration from ``[usecli.upgrade]``.
|
|
2
|
+
|
|
3
|
+
The ``upgrade`` command discovers the install source from packaging
|
|
4
|
+
metadata by default. CLI authors distribute builds that metadata cannot
|
|
5
|
+
describe (private indexes, mirrors, Git remotes installed with a tool that
|
|
6
|
+
strips ``direct_url.json``), so the source can be pinned in
|
|
7
|
+
``usecli.config.toml``:
|
|
8
|
+
|
|
9
|
+
.. code-block:: toml
|
|
10
|
+
|
|
11
|
+
[usecli.upgrade]
|
|
12
|
+
enabled = true
|
|
13
|
+
source = "auto" # auto | pypi | git
|
|
14
|
+
package = "my-cli"
|
|
15
|
+
url = "https://github.com/owner/my-cli.git" # source = "git"
|
|
16
|
+
ref = "main" # branch, tag, or commit
|
|
17
|
+
|
|
18
|
+
``auto`` keeps the metadata-based behaviour. Any other value is
|
|
19
|
+
authoritative: discovery, checks, and installation all use it.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import Any, Literal
|
|
26
|
+
|
|
27
|
+
UpgradeSource = Literal["auto", "pypi", "git"]
|
|
28
|
+
|
|
29
|
+
_SOURCES: dict[str, UpgradeSource] = {
|
|
30
|
+
"auto": "auto",
|
|
31
|
+
"pypi": "pypi",
|
|
32
|
+
"git": "git",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class UpgradeSettings:
|
|
38
|
+
"""Upgrade source overrides configured by the CLI author.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
source: Where releases come from. ``auto`` derives it from the
|
|
42
|
+
running installation's packaging metadata.
|
|
43
|
+
package: Distribution name to upgrade. Defaults to the discovered
|
|
44
|
+
distribution name.
|
|
45
|
+
url: Git remote to upgrade from. Defaults to the discovered Git URL.
|
|
46
|
+
ref: Git branch, tag, or commit to install. Defaults to the
|
|
47
|
+
discovered revision.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
source: UpgradeSource = "auto"
|
|
51
|
+
package: str | None = None
|
|
52
|
+
url: str | None = None
|
|
53
|
+
ref: str | None = None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def overrides_source(self) -> bool:
|
|
57
|
+
"""Whether the configured source replaces metadata discovery."""
|
|
58
|
+
return self.source != "auto"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _config_file(config: Any) -> str | None:
|
|
62
|
+
path = getattr(config, "usecli_config_path", None)
|
|
63
|
+
return str(path) if path else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def config_error(message: str, suggestion: str, config: Any) -> Exception:
|
|
67
|
+
# Imported lazily: exceptions live in the CLI layer and import Rich.
|
|
68
|
+
from usecli.cli.core.exceptions.config import UsecliConfigError
|
|
69
|
+
|
|
70
|
+
return UsecliConfigError(
|
|
71
|
+
message,
|
|
72
|
+
config_file=_config_file(config),
|
|
73
|
+
suggestion=suggestion,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _text(value: Any) -> str | None:
|
|
78
|
+
"""Normalize an optional string setting; blank values mean unset."""
|
|
79
|
+
if not isinstance(value, str):
|
|
80
|
+
return None
|
|
81
|
+
stripped = value.strip()
|
|
82
|
+
return stripped or None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def from_config(config: Any | None) -> UpgradeSettings:
|
|
86
|
+
"""Read ``[usecli.upgrade]`` settings from a ``ConfigManager``-like object.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
config: Config manager exposing ``get(key, default)``. Objects
|
|
90
|
+
without a ``get`` method (test doubles, ``None``) yield the
|
|
91
|
+
defaults, preserving metadata-based discovery.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The resolved settings.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
UsecliConfigError: When ``upgrade.source`` is not one of
|
|
98
|
+
``auto``, ``pypi``, or ``git``.
|
|
99
|
+
"""
|
|
100
|
+
get = getattr(config, "get", None)
|
|
101
|
+
if get is None:
|
|
102
|
+
return UpgradeSettings()
|
|
103
|
+
|
|
104
|
+
raw_source = get("upgrade.source", "auto")
|
|
105
|
+
if raw_source is None:
|
|
106
|
+
raw_source = "auto"
|
|
107
|
+
if not isinstance(raw_source, str):
|
|
108
|
+
raise config_error(
|
|
109
|
+
f"upgrade.source must be a string, got {type(raw_source).__name__}.",
|
|
110
|
+
'Set \\[usecli.upgrade] source = "auto", "pypi", or "git".',
|
|
111
|
+
config,
|
|
112
|
+
)
|
|
113
|
+
source = _SOURCES.get(raw_source.strip().lower())
|
|
114
|
+
if source is None:
|
|
115
|
+
raise config_error(
|
|
116
|
+
f"Invalid upgrade.source '{raw_source}'.",
|
|
117
|
+
'Set \\[usecli.upgrade] source to "auto", "pypi", or "git".',
|
|
118
|
+
config,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return UpgradeSettings(
|
|
122
|
+
source=source,
|
|
123
|
+
package=_text(get("upgrade.package")),
|
|
124
|
+
url=_text(get("upgrade.url")),
|
|
125
|
+
ref=_text(get("upgrade.ref")),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = ["UpgradeSettings", "UpgradeSource", "config_error", "from_config"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{usecli-0.1.79 → usecli-0.1.81}/src/usecli/cli/commands/defaults/make/make_bundle_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|