esysrepo-ctl 0.2.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.
Files changed (48) hide show
  1. esysrepo_ctl-0.2.0/PKG-INFO +165 -0
  2. esysrepo_ctl-0.2.0/README.md +152 -0
  3. esysrepo_ctl-0.2.0/pyproject.toml +46 -0
  4. esysrepo_ctl-0.2.0/setup.cfg +4 -0
  5. esysrepo_ctl-0.2.0/src/esysrepo_ctl/__init__.py +3 -0
  6. esysrepo_ctl-0.2.0/src/esysrepo_ctl/__main__.py +6 -0
  7. esysrepo_ctl-0.2.0/src/esysrepo_ctl/bootstrap.py +156 -0
  8. esysrepo_ctl-0.2.0/src/esysrepo_ctl/cli.py +182 -0
  9. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/__init__.py +1 -0
  10. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/doctor.py +28 -0
  11. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/install.py +44 -0
  12. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/preflight.py +67 -0
  13. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/remove.py +44 -0
  14. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/reporting.py +26 -0
  15. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/update.py +38 -0
  16. esysrepo_ctl-0.2.0/src/esysrepo_ctl/commands/version.py +29 -0
  17. esysrepo_ctl-0.2.0/src/esysrepo_ctl/compat.py +157 -0
  18. esysrepo_ctl-0.2.0/src/esysrepo_ctl/conan.py +261 -0
  19. esysrepo_ctl-0.2.0/src/esysrepo_ctl/diagnostics.py +167 -0
  20. esysrepo_ctl-0.2.0/src/esysrepo_ctl/discovery.py +182 -0
  21. esysrepo_ctl-0.2.0/src/esysrepo_ctl/exit_codes.py +17 -0
  22. esysrepo_ctl-0.2.0/src/esysrepo_ctl/output.py +34 -0
  23. esysrepo_ctl-0.2.0/src/esysrepo_ctl/paths.py +48 -0
  24. esysrepo_ctl-0.2.0/src/esysrepo_ctl/process.py +110 -0
  25. esysrepo_ctl-0.2.0/src/esysrepo_ctl/python_env.py +421 -0
  26. esysrepo_ctl-0.2.0/src/esysrepo_ctl/remote.py +72 -0
  27. esysrepo_ctl-0.2.0/src/esysrepo_ctl/toolchain.py +42 -0
  28. esysrepo_ctl-0.2.0/src/esysrepo_ctl/wrappers.py +394 -0
  29. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/PKG-INFO +165 -0
  30. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/SOURCES.txt +46 -0
  31. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/dependency_links.txt +1 -0
  32. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/entry_points.txt +3 -0
  33. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/requires.txt +5 -0
  34. esysrepo_ctl-0.2.0/src/esysrepo_ctl.egg-info/top_level.txt +1 -0
  35. esysrepo_ctl-0.2.0/tests/test_adr0008_modes.py +139 -0
  36. esysrepo_ctl-0.2.0/tests/test_bootstrap.py +284 -0
  37. esysrepo_ctl-0.2.0/tests/test_cli_help.py +29 -0
  38. esysrepo_ctl-0.2.0/tests/test_commands_lifecycle.py +186 -0
  39. esysrepo_ctl-0.2.0/tests/test_conan.py +272 -0
  40. esysrepo_ctl-0.2.0/tests/test_discovery.py +152 -0
  41. esysrepo_ctl-0.2.0/tests/test_discovery_version.py +23 -0
  42. esysrepo_ctl-0.2.0/tests/test_doctor.py +114 -0
  43. esysrepo_ctl-0.2.0/tests/test_exit_codes.py +136 -0
  44. esysrepo_ctl-0.2.0/tests/test_integration_artifactory.py +220 -0
  45. esysrepo_ctl-0.2.0/tests/test_process.py +76 -0
  46. esysrepo_ctl-0.2.0/tests/test_python_env.py +199 -0
  47. esysrepo_ctl-0.2.0/tests/test_smoke.py +8 -0
  48. esysrepo_ctl-0.2.0/tests/test_wrappers.py +64 -0
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: esysrepo-ctl
3
+ Version: 0.2.0
4
+ Summary: Control-plane tool for managing the native esysrepo CLI
5
+ Author: Project maintainers
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: typer>=0.12
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Requires-Dist: ruff>=0.8; extra == "dev"
13
+
14
+ # esysrepo-ctl
15
+
16
+ Control-plane tool for installing, updating, diagnosing, and managing the native
17
+ `esysrepo` CLI (distributed via Conan). Distributed as a Python package on PyPI.
18
+
19
+ Native repository operations use `esysrepo` / `erepo` (from the Conan package).
20
+ This tool only manages the lifecycle of that native CLI.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pipx install esysrepo-ctl
26
+ ```
27
+
28
+ Or from a development checkout:
29
+
30
+ ```bash
31
+ python -m pip install -e ".[dev]"
32
+ ```
33
+
34
+ Both `esysrepo-ctl` and `erepo-ctl` are equivalent entry points.
35
+
36
+ Then install the native CLI (first run bootstraps a Python env + Conan 2 per
37
+ [ADR-0008](docs/adr/0008-dual-mode-python-venv-isolation.md)):
38
+
39
+ ```bash
40
+ # Interactive: detect mode, confirm, persist
41
+ esysrepo-ctl install
42
+
43
+ # Non-interactive installer-like (managed home venv + always-on wrappers)
44
+ esysrepo-ctl install --mode managed --yes
45
+
46
+ # Project-private: activate your venv first, then:
47
+ esysrepo-ctl install --mode project --yes
48
+ ```
49
+
50
+ | Mode | Conan lives in | Native `esysrepo` / `erepo` wrappers |
51
+ |------|----------------|--------------------------------------|
52
+ | `project` | Active project venv | That venv’s `bin` / `Scripts` (while activated) |
53
+ | `pipx` | Managed home venv | Managed `…/esysrepo-ctl/bin` |
54
+ | `managed` | Managed home venv | Managed `…/esysrepo-ctl/bin` |
55
+
56
+ Overrides: `ESYSREPO_CTL_MODE`, `ESYSREPO_CTL_YES=1`, `--reconfigure` (ignore
57
+ persisted mode). Choice is persisted under `ESYSREPO_CTL_HOME` / platform data
58
+ dir (`config.json`).
59
+
60
+ ## Commands
61
+
62
+ | Command | Purpose |
63
+ |---------|---------|
64
+ | `install` | Install the native `esysrepo` CLI via Conan |
65
+ | `update` | Update the native CLI (auto-bootstraps when allowed) |
66
+ | `remove` | Remove the native CLI from the Conan cache |
67
+ | `doctor` | Diagnose environment, PATH, and compatibility |
68
+ | `version` | Print control-plane and native CLI versions |
69
+
70
+ ```bash
71
+ esysrepo-ctl --help
72
+ esysrepo-ctl version
73
+ esysrepo-ctl doctor
74
+ esysrepo-ctl install
75
+ esysrepo-ctl install --mode managed --yes
76
+ esysrepo-ctl install --mode project --yes --reconfigure
77
+ esysrepo-ctl install --version 0.2.4
78
+ esysrepo-ctl --channel center install # unnamed Conan Center-style refs
79
+ esysrepo-ctl update
80
+ esysrepo-ctl remove
81
+ ```
82
+
83
+ ## Conan channel (ADR-0007)
84
+
85
+ By default, package refs use LibESys Artifactory identity:
86
+
87
+ `esysrepo-cli/<version>@libesys/stable`
88
+
89
+ | Mode | How | Package ref shape |
90
+ |------|-----|-------------------|
91
+ | `libesys` (default) | `--channel libesys` or unset | `…@libesys/stable` |
92
+ | `center` | `--channel center` or `ESYSREPO_CTL_CHANNEL=center` | unnamed (`esysrepo-cli/<ver>`) |
93
+
94
+ Configure the LibESys Conan remote yourself (or via CI vars `CONAN_REMOTE_URL` /
95
+ `CONAN_LOGIN_USERNAME` / `CONAN_PASSWORD`). Install writes VirtualBuildEnv +
96
+ VirtualRunEnv under the managed data dir and places wrappers per mode (above);
97
+ `doctor` / discovery look at PATH, project `Scripts`/`bin`, and the managed bin.
98
+
99
+ ## Strict mode / no auto-bootstrap
100
+
101
+ By default, eligible commands (currently `update`) may auto-install the native CLI when
102
+ it is missing (ADR-0005). Disable that with:
103
+
104
+ ```bash
105
+ esysrepo-ctl --no-bootstrap update
106
+ # or
107
+ esysrepo-ctl --strict update
108
+ ```
109
+
110
+ Auto-bootstrap is also disabled when any of these are set:
111
+
112
+ - `CI=true` (typical CI environments)
113
+ - `ESYSREPO_CTL_NO_BOOTSTRAP=1`
114
+ - `ESYSREPO_CTL_STRICT=1`
115
+
116
+ Explicit `install` / `remove` / `doctor` / `version` never trigger implicit install.
117
+
118
+ ## Exit codes
119
+
120
+ Stable codes for scripting (ADR-0006):
121
+
122
+ | Code | Meaning |
123
+ |------|---------|
124
+ | 0 | Success |
125
+ | 1 | General error |
126
+ | 2 | Usage error (bad arguments / unknown command) |
127
+ | 3 | Native `esysrepo` / `erepo` missing |
128
+ | 4 | Auto-bootstrap failure |
129
+ | 5 | Conan failure |
130
+ | 6 | Environment / compatibility error |
131
+
132
+ Errors print to stderr as `error: …` with optional `hint: …` remediation lines.
133
+
134
+ ## Troubleshooting
135
+
136
+ | Symptom | What to try |
137
+ |---------|-------------|
138
+ | `doctor` reports Conan missing | Install Conan 2 and ensure `conan` is on `PATH` (or re-run `esysrepo-ctl install --mode … --yes` so ctl bootstraps Conan into the chosen venv) |
139
+ | `doctor` reports esysrepo missing | `esysrepo-ctl install` then re-check `doctor` / `version` |
140
+ | Install/update fails talking to remotes | `conan remote list`, fix auth/network, retry |
141
+ | Package installed but CLI not found | Check managed bin (`esysrepo-ctl doctor`) and Conan remotes |
142
+ | Wrong package identity / not found | Use default `--channel libesys` for Artifactory; ensure remote is configured |
143
+ | CI must not mutate the environment | Use `--no-bootstrap` or rely on `CI=true` |
144
+ | `where esysrepo` lists `~/.conan2/…/bin` before the venv wrapper | Shell PATH is polluted (Conan cache bins first), so Windows never runs the `.bat`. **cmd.exe:** `set "PATH=%VIRTUAL_ENV%\\Scripts;%PATH%"` then `where esysrepo` — or `call "%VIRTUAL_ENV%\\Scripts\\_esysrepo_ctl_fix_path.bat"`. **PowerShell:** `$env:PATH = (@("$env:VIRTUAL_ENV\\Scripts") + ($env:PATH -split ';' \| ? { $_ -notmatch '\\.conan2' })) -join ';'`. Or deactivate/reactivate the venv after `esysrepo-ctl install --mode project --yes`. |
145
+
146
+ ```bash
147
+ esysrepo-ctl doctor
148
+ esysrepo-ctl version
149
+ ```
150
+
151
+ ## Documentation
152
+
153
+ - [Architecture overview](docs/architecture/overview.md)
154
+ - [CLI specification](docs/architecture/cli-spec.md)
155
+ - [Quality requirements](docs/architecture/quality-requirements.md)
156
+ - [ADRs](docs/adr/README.md)
157
+ - [MVP implementation plan](plans/0001-mvp-implementation.md)
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ python scripts/setup-dev.sh # or scripts/setup-dev.ps1 on Windows
163
+ pytest
164
+ pre-commit run --all-files
165
+ ```
@@ -0,0 +1,152 @@
1
+ # esysrepo-ctl
2
+
3
+ Control-plane tool for installing, updating, diagnosing, and managing the native
4
+ `esysrepo` CLI (distributed via Conan). Distributed as a Python package on PyPI.
5
+
6
+ Native repository operations use `esysrepo` / `erepo` (from the Conan package).
7
+ This tool only manages the lifecycle of that native CLI.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pipx install esysrepo-ctl
13
+ ```
14
+
15
+ Or from a development checkout:
16
+
17
+ ```bash
18
+ python -m pip install -e ".[dev]"
19
+ ```
20
+
21
+ Both `esysrepo-ctl` and `erepo-ctl` are equivalent entry points.
22
+
23
+ Then install the native CLI (first run bootstraps a Python env + Conan 2 per
24
+ [ADR-0008](docs/adr/0008-dual-mode-python-venv-isolation.md)):
25
+
26
+ ```bash
27
+ # Interactive: detect mode, confirm, persist
28
+ esysrepo-ctl install
29
+
30
+ # Non-interactive installer-like (managed home venv + always-on wrappers)
31
+ esysrepo-ctl install --mode managed --yes
32
+
33
+ # Project-private: activate your venv first, then:
34
+ esysrepo-ctl install --mode project --yes
35
+ ```
36
+
37
+ | Mode | Conan lives in | Native `esysrepo` / `erepo` wrappers |
38
+ |------|----------------|--------------------------------------|
39
+ | `project` | Active project venv | That venv’s `bin` / `Scripts` (while activated) |
40
+ | `pipx` | Managed home venv | Managed `…/esysrepo-ctl/bin` |
41
+ | `managed` | Managed home venv | Managed `…/esysrepo-ctl/bin` |
42
+
43
+ Overrides: `ESYSREPO_CTL_MODE`, `ESYSREPO_CTL_YES=1`, `--reconfigure` (ignore
44
+ persisted mode). Choice is persisted under `ESYSREPO_CTL_HOME` / platform data
45
+ dir (`config.json`).
46
+
47
+ ## Commands
48
+
49
+ | Command | Purpose |
50
+ |---------|---------|
51
+ | `install` | Install the native `esysrepo` CLI via Conan |
52
+ | `update` | Update the native CLI (auto-bootstraps when allowed) |
53
+ | `remove` | Remove the native CLI from the Conan cache |
54
+ | `doctor` | Diagnose environment, PATH, and compatibility |
55
+ | `version` | Print control-plane and native CLI versions |
56
+
57
+ ```bash
58
+ esysrepo-ctl --help
59
+ esysrepo-ctl version
60
+ esysrepo-ctl doctor
61
+ esysrepo-ctl install
62
+ esysrepo-ctl install --mode managed --yes
63
+ esysrepo-ctl install --mode project --yes --reconfigure
64
+ esysrepo-ctl install --version 0.2.4
65
+ esysrepo-ctl --channel center install # unnamed Conan Center-style refs
66
+ esysrepo-ctl update
67
+ esysrepo-ctl remove
68
+ ```
69
+
70
+ ## Conan channel (ADR-0007)
71
+
72
+ By default, package refs use LibESys Artifactory identity:
73
+
74
+ `esysrepo-cli/<version>@libesys/stable`
75
+
76
+ | Mode | How | Package ref shape |
77
+ |------|-----|-------------------|
78
+ | `libesys` (default) | `--channel libesys` or unset | `…@libesys/stable` |
79
+ | `center` | `--channel center` or `ESYSREPO_CTL_CHANNEL=center` | unnamed (`esysrepo-cli/<ver>`) |
80
+
81
+ Configure the LibESys Conan remote yourself (or via CI vars `CONAN_REMOTE_URL` /
82
+ `CONAN_LOGIN_USERNAME` / `CONAN_PASSWORD`). Install writes VirtualBuildEnv +
83
+ VirtualRunEnv under the managed data dir and places wrappers per mode (above);
84
+ `doctor` / discovery look at PATH, project `Scripts`/`bin`, and the managed bin.
85
+
86
+ ## Strict mode / no auto-bootstrap
87
+
88
+ By default, eligible commands (currently `update`) may auto-install the native CLI when
89
+ it is missing (ADR-0005). Disable that with:
90
+
91
+ ```bash
92
+ esysrepo-ctl --no-bootstrap update
93
+ # or
94
+ esysrepo-ctl --strict update
95
+ ```
96
+
97
+ Auto-bootstrap is also disabled when any of these are set:
98
+
99
+ - `CI=true` (typical CI environments)
100
+ - `ESYSREPO_CTL_NO_BOOTSTRAP=1`
101
+ - `ESYSREPO_CTL_STRICT=1`
102
+
103
+ Explicit `install` / `remove` / `doctor` / `version` never trigger implicit install.
104
+
105
+ ## Exit codes
106
+
107
+ Stable codes for scripting (ADR-0006):
108
+
109
+ | Code | Meaning |
110
+ |------|---------|
111
+ | 0 | Success |
112
+ | 1 | General error |
113
+ | 2 | Usage error (bad arguments / unknown command) |
114
+ | 3 | Native `esysrepo` / `erepo` missing |
115
+ | 4 | Auto-bootstrap failure |
116
+ | 5 | Conan failure |
117
+ | 6 | Environment / compatibility error |
118
+
119
+ Errors print to stderr as `error: …` with optional `hint: …` remediation lines.
120
+
121
+ ## Troubleshooting
122
+
123
+ | Symptom | What to try |
124
+ |---------|-------------|
125
+ | `doctor` reports Conan missing | Install Conan 2 and ensure `conan` is on `PATH` (or re-run `esysrepo-ctl install --mode … --yes` so ctl bootstraps Conan into the chosen venv) |
126
+ | `doctor` reports esysrepo missing | `esysrepo-ctl install` then re-check `doctor` / `version` |
127
+ | Install/update fails talking to remotes | `conan remote list`, fix auth/network, retry |
128
+ | Package installed but CLI not found | Check managed bin (`esysrepo-ctl doctor`) and Conan remotes |
129
+ | Wrong package identity / not found | Use default `--channel libesys` for Artifactory; ensure remote is configured |
130
+ | CI must not mutate the environment | Use `--no-bootstrap` or rely on `CI=true` |
131
+ | `where esysrepo` lists `~/.conan2/…/bin` before the venv wrapper | Shell PATH is polluted (Conan cache bins first), so Windows never runs the `.bat`. **cmd.exe:** `set "PATH=%VIRTUAL_ENV%\\Scripts;%PATH%"` then `where esysrepo` — or `call "%VIRTUAL_ENV%\\Scripts\\_esysrepo_ctl_fix_path.bat"`. **PowerShell:** `$env:PATH = (@("$env:VIRTUAL_ENV\\Scripts") + ($env:PATH -split ';' \| ? { $_ -notmatch '\\.conan2' })) -join ';'`. Or deactivate/reactivate the venv after `esysrepo-ctl install --mode project --yes`. |
132
+
133
+ ```bash
134
+ esysrepo-ctl doctor
135
+ esysrepo-ctl version
136
+ ```
137
+
138
+ ## Documentation
139
+
140
+ - [Architecture overview](docs/architecture/overview.md)
141
+ - [CLI specification](docs/architecture/cli-spec.md)
142
+ - [Quality requirements](docs/architecture/quality-requirements.md)
143
+ - [ADRs](docs/adr/README.md)
144
+ - [MVP implementation plan](plans/0001-mvp-implementation.md)
145
+
146
+ ## Development
147
+
148
+ ```bash
149
+ python scripts/setup-dev.sh # or scripts/setup-dev.ps1 on Windows
150
+ pytest
151
+ pre-commit run --all-files
152
+ ```
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "esysrepo-ctl"
7
+ version = "0.2.0"
8
+ description = "Control-plane tool for managing the native esysrepo CLI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Project maintainers" }]
13
+ dependencies = [
14
+ "typer>=0.12",
15
+ ]
16
+
17
+ [project.scripts]
18
+ esysrepo-ctl = "esysrepo_ctl.cli:app"
19
+ erepo-ctl = "esysrepo_ctl.cli:app"
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "pytest>=8.0",
24
+ "ruff>=0.8",
25
+ ]
26
+
27
+ [tool.setuptools]
28
+ package-dir = { "" = "src" }
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+ pythonpath = ["src"]
36
+ markers = [
37
+ "integration: live Conan/Artifactory tests (require ESYSREPO_CTL_INTEGRATION=1)",
38
+ ]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py311"
43
+ src = ["src", "tests"]
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """esysrepo-ctl — installable package."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m esysrepo_ctl``."""
2
+
3
+ from esysrepo_ctl.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,156 @@
1
+ """Auto-bootstrap of the native esysrepo CLI (ADR-0005, ADR-0008)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+
9
+ from esysrepo_ctl import conan as conan_ops
10
+ from esysrepo_ctl.compat import CompatPolicy
11
+ from esysrepo_ctl.conan import ConanOpResult
12
+ from esysrepo_ctl.discovery import ToolInfo, find_native_cli
13
+ from esysrepo_ctl.exit_codes import ExitCode
14
+ from esysrepo_ctl.python_env import wrapper_bin_dir_for_mode
15
+ from esysrepo_ctl.toolchain import bootstrap_for_lifecycle
16
+
17
+ # Subcommands that may auto-bootstrap when the native CLI is missing.
18
+ AUTO_BOOTSTRAP_COMMANDS = frozenset({"update"})
19
+
20
+ # Subcommands that never trigger implicit install.
21
+ EXEMPT_FROM_AUTO_BOOTSTRAP = frozenset({"install", "remove", "version", "doctor"})
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class EnsureResult:
26
+ """Outcome of ensuring the native CLI is available."""
27
+
28
+ native: ToolInfo | None
29
+ bootstrapped: bool
30
+ exit_code: ExitCode
31
+ message: str | None = None
32
+ remediation: str | None = None
33
+ bootstrap_outcome: ConanOpResult | None = None
34
+
35
+ @property
36
+ def ok(self) -> bool:
37
+ return self.exit_code is ExitCode.SUCCESS and self.native is not None
38
+
39
+
40
+ def bootstrap_permitted(
41
+ *,
42
+ cli_no_bootstrap: bool = False,
43
+ environ: Mapping[str, str] | None = None,
44
+ ) -> bool:
45
+ """Return whether auto-bootstrap is allowed for this invocation."""
46
+ env = environ if environ is not None else os.environ
47
+ if cli_no_bootstrap:
48
+ return False
49
+ if _truthy(env.get("ESYSREPO_CTL_NO_BOOTSTRAP")):
50
+ return False
51
+ if _truthy(env.get("ESYSREPO_CTL_STRICT")):
52
+ return False
53
+ # CI and similar non-interactive contexts default to strict (no implicit install).
54
+ if _truthy(env.get("CI")):
55
+ return False
56
+ return True
57
+
58
+
59
+ def should_auto_bootstrap(command: str | None) -> bool:
60
+ """Return whether ``command`` is eligible for first-use auto-bootstrap."""
61
+ if command is None:
62
+ return False
63
+ if command in EXEMPT_FROM_AUTO_BOOTSTRAP:
64
+ return False
65
+ return command in AUTO_BOOTSTRAP_COMMANDS
66
+
67
+
68
+ def ensure_native_cli(
69
+ *,
70
+ allow_bootstrap: bool,
71
+ policy: CompatPolicy | None = None,
72
+ ) -> EnsureResult:
73
+ """Detect the native CLI and optionally install it when missing.
74
+
75
+ When ``allow_bootstrap`` is false (strict / ``--no-bootstrap`` / CI), a missing
76
+ native CLI yields :attr:`ExitCode.MISSING_NATIVE_CLI` without changing the
77
+ environment.
78
+ """
79
+ native = find_native_cli()
80
+ if native is not None:
81
+ return EnsureResult(
82
+ native=native,
83
+ bootstrapped=False,
84
+ exit_code=ExitCode.SUCCESS,
85
+ )
86
+
87
+ if not allow_bootstrap:
88
+ return EnsureResult(
89
+ native=None,
90
+ bootstrapped=False,
91
+ exit_code=ExitCode.MISSING_NATIVE_CLI,
92
+ message="native esysrepo CLI is not installed",
93
+ remediation=(
94
+ "Install explicitly with `esysrepo-ctl install`, or re-run without "
95
+ "`--no-bootstrap` / strict mode to allow auto-bootstrap."
96
+ ),
97
+ )
98
+
99
+ try:
100
+ # Auto-bootstrap already implies consent; skip interactive mode confirm.
101
+ info, conan = bootstrap_for_lifecycle(yes=True)
102
+ except RuntimeError as exc:
103
+ return EnsureResult(
104
+ native=None,
105
+ bootstrapped=False,
106
+ exit_code=ExitCode.ENVIRONMENT_ERROR,
107
+ message=str(exc),
108
+ remediation=(
109
+ "Set `--mode project|pipx|managed --yes` or ESYSREPO_CTL_MODE / "
110
+ "ESYSREPO_CTL_YES, then re-run."
111
+ ),
112
+ )
113
+
114
+ wrapper_bin = wrapper_bin_dir_for_mode(info.mode)
115
+ outcome = conan_ops.install(policy=policy, conan=conan, wrapper_bin=wrapper_bin)
116
+ if not outcome.ok:
117
+ return EnsureResult(
118
+ native=None,
119
+ bootstrapped=False,
120
+ exit_code=ExitCode.BOOTSTRAP_FAILURE
121
+ if outcome.exit_code is ExitCode.CONAN_FAILURE
122
+ else outcome.exit_code,
123
+ message=f"auto-bootstrap failed for {outcome.package_ref}",
124
+ remediation=outcome.remediation
125
+ or "Fix the Conan error above, then re-run or use `esysrepo-ctl install`.",
126
+ bootstrap_outcome=outcome,
127
+ )
128
+
129
+ native = find_native_cli()
130
+ if native is None:
131
+ return EnsureResult(
132
+ native=None,
133
+ bootstrapped=True,
134
+ exit_code=ExitCode.BOOTSTRAP_FAILURE,
135
+ message=("auto-bootstrap finished but esysrepo/erepo is still not discoverable"),
136
+ remediation=(
137
+ "Conan installed the package, but the native CLI was not found in PATH "
138
+ "or the mode-local wrapper directory. Re-run `esysrepo-ctl install` or "
139
+ "check doctor output."
140
+ ),
141
+ bootstrap_outcome=outcome,
142
+ )
143
+
144
+ return EnsureResult(
145
+ native=native,
146
+ bootstrapped=True,
147
+ exit_code=ExitCode.SUCCESS,
148
+ message=f"auto-bootstrapped native CLI ({outcome.package_ref})",
149
+ bootstrap_outcome=outcome,
150
+ )
151
+
152
+
153
+ def _truthy(value: str | None) -> bool:
154
+ if value is None:
155
+ return False
156
+ return value.strip().lower() in {"1", "true", "yes", "on"}