posit-cli 0.1.1__tar.gz → 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 (25) hide show
  1. posit_cli-0.2.0/.github/dependabot.yml +14 -0
  2. {posit_cli-0.1.1 → posit_cli-0.2.0}/.github/workflows/ci.yaml +3 -3
  3. {posit_cli-0.1.1 → posit_cli-0.2.0}/.github/workflows/release.yaml +3 -3
  4. posit_cli-0.2.0/CONTRIBUTING.md +91 -0
  5. {posit_cli-0.1.1 → posit_cli-0.2.0}/Justfile +14 -2
  6. posit_cli-0.1.1/README.md → posit_cli-0.2.0/PKG-INFO +53 -0
  7. posit_cli-0.1.1/PKG-INFO → posit_cli-0.2.0/README.md +33 -18
  8. {posit_cli-0.1.1 → posit_cli-0.2.0}/pyproject.toml +2 -1
  9. {posit_cli-0.1.1 → posit_cli-0.2.0}/skills/posit-cli/SKILL.md +6 -0
  10. {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/_version.py +2 -2
  11. {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/connect/__init__.py +3 -0
  12. posit_cli-0.2.0/src/posit_cli/connect/version.py +68 -0
  13. {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_rsconnect_contract.py +1 -0
  14. posit_cli-0.2.0/tests/test_version.py +97 -0
  15. posit_cli-0.2.0/uv.lock +3186 -0
  16. posit_cli-0.1.1/uv.lock +0 -1142
  17. {posit_cli-0.1.1 → posit_cli-0.2.0}/.gitignore +0 -0
  18. {posit_cli-0.1.1 → posit_cli-0.2.0}/CLAUDE.md +0 -0
  19. {posit_cli-0.1.1 → posit_cli-0.2.0}/LICENSE +0 -0
  20. {posit_cli-0.1.1 → posit_cli-0.2.0}/RELEASE.md +0 -0
  21. {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/__init__.py +0 -0
  22. {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/__main__.py +0 -0
  23. {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/connect/api.py +0 -0
  24. {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_api.py +0 -0
  25. {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_cli.py +0 -0
@@ -0,0 +1,14 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: uv
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ commit-message:
8
+ prefix: chore(deps)
9
+ - package-ecosystem: github-actions
10
+ directory: /
11
+ schedule:
12
+ interval: weekly
13
+ commit-message:
14
+ prefix: chore(deps)
@@ -12,7 +12,7 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
14
  - uses: actions/checkout@v7
15
- - uses: astral-sh/setup-uv@v9.0.0
15
+ - uses: astral-sh/setup-uv@v10.3.3
16
16
  - uses: extractions/setup-just@v4
17
17
  # Fail fast if uv.lock has drifted from pyproject.toml.
18
18
  - run: uv lock --locked
@@ -33,7 +33,7 @@ jobs:
33
33
  - "3.14"
34
34
  steps:
35
35
  - uses: actions/checkout@v7
36
- - uses: astral-sh/setup-uv@v9.0.0
36
+ - uses: astral-sh/setup-uv@v10.3.3
37
37
  with:
38
38
  python-version: ${{ matrix.python-version }}
39
39
  - uses: extractions/setup-just@v4
@@ -46,7 +46,7 @@ jobs:
46
46
  # hatch-vcs reads the version from the tags, so fetch them.
47
47
  with:
48
48
  fetch-depth: 0
49
- - uses: astral-sh/setup-uv@v9.0.0
49
+ - uses: astral-sh/setup-uv@v10.3.3
50
50
  - uses: extractions/setup-just@v4
51
51
  - run: just build
52
52
  - run: just smoke
@@ -13,7 +13,7 @@ jobs:
13
13
  - uses: actions/checkout@v7
14
14
  with:
15
15
  fetch-depth: 0
16
- - uses: astral-sh/setup-uv@v9.0.0
16
+ - uses: astral-sh/setup-uv@v10.3.3
17
17
  - uses: extractions/setup-just@v4
18
18
  # The tag is the only source of the version, so this only guards against a
19
19
  # build that did not pick the tag up (shallow clone, dirty tree, two tags on
@@ -37,7 +37,7 @@ jobs:
37
37
  fi
38
38
  - run: just build
39
39
  - run: just smoke
40
- - uses: actions/upload-artifact@v4
40
+ - uses: actions/upload-artifact@v7
41
41
  with:
42
42
  name: dist
43
43
  path: dist/
@@ -51,7 +51,7 @@ jobs:
51
51
  contents: write
52
52
  steps:
53
53
  - uses: actions/checkout@v7
54
- - uses: actions/download-artifact@v4
54
+ - uses: actions/download-artifact@v8
55
55
  with:
56
56
  name: dist
57
57
  path: dist/
@@ -0,0 +1,91 @@
1
+ # Contributing
2
+
3
+ This guide covers local development for `posit-cli`. End-user installation and CLI usage are documented in [README.md](README.md).
4
+
5
+ ## Prerequisites
6
+
7
+ Install these tools before starting:
8
+
9
+ - Git
10
+ - Python 3.8 or newer
11
+ - [uv](https://docs.astral.sh/uv/)
12
+ - [just](https://github.com/casey/just)
13
+
14
+ The project uses uv for dependency management and virtual environments. The supported Python versions are 3.8 through 3.14, which are tested in CI.
15
+
16
+ ## Set Up
17
+
18
+ Clone the repository and change into its directory:
19
+
20
+ ```console
21
+ git clone https://github.com/posit-dev/posit-cli.git
22
+ cd posit-cli
23
+ ```
24
+
25
+ Sync the project, test, and lint dependencies:
26
+
27
+ ```console
28
+ just deps
29
+ ```
30
+
31
+ This runs `uv sync --all-extras`, using `uv.lock` to create or update the project environment in `.venv`. You do not need to activate `.venv` when using the `just` recipes.
32
+
33
+ To put an editable `posit` command on your `PATH`, run:
34
+
35
+ ```console
36
+ just dev
37
+ posit --help
38
+ ```
39
+
40
+ `just dev` installs the checkout as an editable uv tool. Changes to the source are therefore available through `posit` without rebuilding or reinstalling the package. If the command is not found, add the directory printed by `uv tool dir --bin` to your `PATH`, or run `uv tool update-shell`.
41
+
42
+ ## Common Commands
43
+
44
+ Run these commands from the repository root:
45
+
46
+ | Command | Purpose |
47
+ | --- | --- |
48
+ | `just deps` | Sync runtime, test, and lint dependencies from `uv.lock`. |
49
+ | `just dev` | Install the checkout as an editable uv tool. |
50
+ | `just test` | Run tests against Python 3.13. |
51
+ | `just test 3.8` | Run tests against a specific Python version. |
52
+ | `just lint` | Check formatting and run Ruff. |
53
+ | `just fmt` | Format files and apply Ruff fixes. |
54
+ | `just build` | Build the wheel and source distribution in `dist/`. |
55
+ | `just smoke` | Run `posit --help` from the latest built wheel. |
56
+ | `just install` | Install the latest built wheel as a non-editable uv tool. |
57
+ | `just uninstall` | Remove the uv-managed `posit-cli` tool. |
58
+ | `just clean` | Remove build, test, and lint artifacts. |
59
+
60
+ `just smoke` expects a wheel to exist in `dist/`, so run `just build` first unless another command has already built one.
61
+
62
+ ## Dependency Changes
63
+
64
+ Runtime dependencies and optional test/lint dependencies are declared in `pyproject.toml`. When changing them:
65
+
66
+ 1. Update `pyproject.toml`.
67
+ 2. Run `uv lock` to update `uv.lock`.
68
+ 3. Run `just deps` to sync the local environment.
69
+ 4. Include both `pyproject.toml` and `uv.lock` in the change.
70
+
71
+ CI checks that the lockfile is current with `uv lock --locked`.
72
+
73
+ ## Before Opening A Pull Request
74
+
75
+ Run the checks relevant to your change. The normal local validation sequence is:
76
+
77
+ ```console
78
+ just deps
79
+ just lint
80
+ just test
81
+ just build
82
+ just smoke
83
+ ```
84
+
85
+ CI also runs the test suite against Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14. Add or update tests when changing behavior, and update user-facing documentation in `README.md` when the CLI changes.
86
+
87
+ Keep commits focused and use Conventional Commit messages, such as `fix: handle expired Connect tokens` or `docs: clarify local setup`.
88
+
89
+ ## Releases
90
+
91
+ Release preparation and publishing are documented in [RELEASE.md](RELEASE.md). In particular, do not add a hard-coded version to `pyproject.toml`; release versions come from Git tags through `hatch-vcs`.
@@ -1,5 +1,9 @@
1
1
  # posit-cli task runner. Run `just --list` to see recipes.
2
2
 
3
+ # Sync project and development dependencies into the project environment
4
+ deps:
5
+ uv sync --all-extras
6
+
3
7
  # Run the test suite against a single Python version (default 3.13)
4
8
  test py="3.13":
5
9
  uv run --python {{py}} --extra test pytest tests
@@ -25,9 +29,17 @@ smoke:
25
29
  WHL=$(ls -t dist/*.whl | head -1)
26
30
  uv run --no-project --with "$WHL" posit --help
27
31
 
28
- # Install the most recently built wheel into the active environment
32
+ # Install the most recently built wheel as a standalone uv tool
29
33
  install: build
30
- uv pip install "$(ls -t dist/*.whl | head -1)"
34
+ uv tool install --force "$(ls -t dist/*.whl | head -1)"
35
+
36
+ # Install the project as an editable standalone uv tool
37
+ dev:
38
+ uv tool install --editable --force .
39
+
40
+ # Remove the installed standalone uv tool
41
+ uninstall:
42
+ uv tool uninstall posit-cli
31
43
 
32
44
  # Print the version that a build gets from the current git state
33
45
  version:
@@ -1,3 +1,23 @@
1
+ Metadata-Version: 2.5
2
+ Name: posit-cli
3
+ Version: 0.2.0
4
+ Summary: A single, friendly command-line interface for Posit Connect, in the spirit of gh.
5
+ Author: Posit Software, PBC
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.8
9
+ Requires-Dist: click>=8.0
10
+ Requires-Dist: jq>=1.4
11
+ Requires-Dist: keyring>=23.0
12
+ Requires-Dist: rsconnect-python<2,>=1.33
13
+ Provides-Extra: lint
14
+ Requires-Dist: ruff>=0.6; extra == 'lint'
15
+ Provides-Extra: snowflake
16
+ Requires-Dist: rsconnect-python[snowflake]; extra == 'snowflake'
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=7; extra == 'test'
19
+ Description-Content-Type: text/markdown
20
+
1
21
  # posit-cli
2
22
 
3
23
  A friendly command-line interface for Posit products, in the spirit of [`gh`](https://cli.github.com/).
@@ -37,6 +57,22 @@ Each of these puts the `posit` executable on your `PATH`. To upgrade later, run
37
57
  [Git authentication docs](https://docs.astral.sh/uv/concepts/authentication/git/)
38
58
  for tokens and other hosts.
39
59
 
60
+ ### Connect on Snowflake
61
+
62
+ To deploy to Posit Connect on Snowflake, install the Snowflake extra instead:
63
+
64
+ ```console
65
+ uv tool install 'posit-cli[snowflake]'
66
+ # or: pip install 'posit-cli[snowflake]'
67
+ ```
68
+
69
+ This includes the Snowflake CLI. Before deploying, configure a
70
+ [Snowflake CLI connection](https://docs.snowflake.com/en/developer-guide/snowflake-cli/connecting/configure-connections)
71
+ with access to your Connect instance and `authenticator` set to `snowflake_jwt`
72
+ (key-pair authentication) or `oauth`. Select it with
73
+ `--snowflake-connection-name` when adding the Connect server with
74
+ `posit connect add`; see `posit connect add --help` for options.
75
+
40
76
  ## Getting started
41
77
 
42
78
  **1. Log in.** Point `posit` at your Connect server and authenticate. This runs
@@ -114,10 +150,27 @@ $ posit connect api v1/users -X GET -f page_size=5 # or force GET
114
150
  $ posit connect api v1/content -f name=my-app # POST body (creates content)
115
151
  ```
116
152
 
153
+ ## `posit connect version`
154
+
155
+ Print the Connect server version using your saved credentials:
156
+
157
+ ```console
158
+ $ posit connect version
159
+ $ posit connect version --name production
160
+ $ posit connect version --server https://connect.example.com --api-key "$CONNECT_API_KEY"
161
+ ```
162
+
163
+ The command supports `-n`/`--name`, `-s`/`--server`, `-k`/`--api-key`,
164
+ `--no-tls-verify`, and `-c`/`--cacert`, with the same connection environment
165
+ variables as `posit connect api`. It prints an empty line if the server hides
166
+ its version. Rsconnect's `CONNECT_SERVER_VERSION` environment variable overrides
167
+ the reported version.
168
+
117
169
  ## `posit connect *`
118
170
 
119
171
  `posit` wraps [`rsconnect-python`](https://github.com/posit-dev/rsconnect-python):
120
172
  it re-exposes the full `rsconnect` command set under `posit connect`, in addition to the `api` utility.
173
+ The `version` command is overridden to report the Connect server version.
121
174
 
122
175
  ## Using `posit` with Claude Code
123
176
 
@@ -1,21 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: posit-cli
3
- Version: 0.1.1
4
- Summary: A single, friendly command-line interface for Posit Connect, in the spirit of gh.
5
- Author: Posit Software, PBC
6
- License-Expression: MIT
7
- License-File: LICENSE
8
- Requires-Python: >=3.8
9
- Requires-Dist: click>=8.0
10
- Requires-Dist: jq>=1.4
11
- Requires-Dist: keyring>=23.0
12
- Requires-Dist: rsconnect-python<2,>=1.30
13
- Provides-Extra: lint
14
- Requires-Dist: ruff>=0.6; extra == 'lint'
15
- Provides-Extra: test
16
- Requires-Dist: pytest>=7; extra == 'test'
17
- Description-Content-Type: text/markdown
18
-
19
1
  # posit-cli
20
2
 
21
3
  A friendly command-line interface for Posit products, in the spirit of [`gh`](https://cli.github.com/).
@@ -55,6 +37,22 @@ Each of these puts the `posit` executable on your `PATH`. To upgrade later, run
55
37
  [Git authentication docs](https://docs.astral.sh/uv/concepts/authentication/git/)
56
38
  for tokens and other hosts.
57
39
 
40
+ ### Connect on Snowflake
41
+
42
+ To deploy to Posit Connect on Snowflake, install the Snowflake extra instead:
43
+
44
+ ```console
45
+ uv tool install 'posit-cli[snowflake]'
46
+ # or: pip install 'posit-cli[snowflake]'
47
+ ```
48
+
49
+ This includes the Snowflake CLI. Before deploying, configure a
50
+ [Snowflake CLI connection](https://docs.snowflake.com/en/developer-guide/snowflake-cli/connecting/configure-connections)
51
+ with access to your Connect instance and `authenticator` set to `snowflake_jwt`
52
+ (key-pair authentication) or `oauth`. Select it with
53
+ `--snowflake-connection-name` when adding the Connect server with
54
+ `posit connect add`; see `posit connect add --help` for options.
55
+
58
56
  ## Getting started
59
57
 
60
58
  **1. Log in.** Point `posit` at your Connect server and authenticate. This runs
@@ -132,10 +130,27 @@ $ posit connect api v1/users -X GET -f page_size=5 # or force GET
132
130
  $ posit connect api v1/content -f name=my-app # POST body (creates content)
133
131
  ```
134
132
 
133
+ ## `posit connect version`
134
+
135
+ Print the Connect server version using your saved credentials:
136
+
137
+ ```console
138
+ $ posit connect version
139
+ $ posit connect version --name production
140
+ $ posit connect version --server https://connect.example.com --api-key "$CONNECT_API_KEY"
141
+ ```
142
+
143
+ The command supports `-n`/`--name`, `-s`/`--server`, `-k`/`--api-key`,
144
+ `--no-tls-verify`, and `-c`/`--cacert`, with the same connection environment
145
+ variables as `posit connect api`. It prints an empty line if the server hides
146
+ its version. Rsconnect's `CONNECT_SERVER_VERSION` environment variable overrides
147
+ the reported version.
148
+
135
149
  ## `posit connect *`
136
150
 
137
151
  `posit` wraps [`rsconnect-python`](https://github.com/posit-dev/rsconnect-python):
138
152
  it re-exposes the full `rsconnect` command set under `posit connect`, in addition to the `api` utility.
153
+ The `version` command is overridden to report the Connect server version.
139
154
 
140
155
  ## Using `posit` with Claude Code
141
156
 
@@ -12,7 +12,7 @@ dependencies = [
12
12
  # posit-cli reuses rsconnect-python's *internal* API (RSConnectExecutor,
13
13
  # RSConnectClient). These have no stability contract, so re-verify the internals
14
14
  # on bumps. See tests/test_rsconnect_contract.py.
15
- "rsconnect-python>=1.30,<2",
15
+ "rsconnect-python>=1.33,<2",
16
16
  # rsconnect imports `keyring` optionally; we depend on it directly so OAuth
17
17
  # tokens land in the OS keyring.
18
18
  "keyring>=23.0",
@@ -25,6 +25,7 @@ dependencies = [
25
25
  posit = "posit_cli.__main__:cli"
26
26
 
27
27
  [project.optional-dependencies]
28
+ snowflake = ["rsconnect-python[snowflake]"]
28
29
  test = ["pytest>=7"]
29
30
  lint = ["ruff>=0.6"]
30
31
 
@@ -175,6 +175,10 @@ posit connect deploy quarto ./report
175
175
  posit connect deploy manifest ./manifest.json # deploy a prepared bundle
176
176
  ```
177
177
 
178
+ To update existing Connect content instead of creating new content, pass its
179
+ GUID with `--guid <guid>`. Find the GUID with
180
+ `posit connect api v1/content -q '.[] | "\(.guid) \(.title)"'`.
181
+
178
182
  Available deploy targets include `api`, `bokeh`, `bundle`, `dash`, `fastapi`,
179
183
  `flask`, `gradio`, `html`, `manifest`, `nodejs`, `notebook`, `panel`, `pyproject`,
180
184
  `quarto`, `shiny`, `streamlit`, `tensorflow`, `voila`. Use `quickstart` to
@@ -191,6 +195,8 @@ These are rsconnect commands — run `posit connect <command> --help` for detail
191
195
  - `add` / `list` / `remove` / `details` / `info` — manage saved servers and
192
196
  deployment metadata.
193
197
  - `login` / `logout` — OAuth credential management.
198
+ - `token` — print the OAuth access token from `login` (for the Connect SDKs or
199
+ `curl`). `--refresh` gets a new one after a 401; exit status 4 means log in again.
194
200
  - `bootstrap` — create an initial admin user on a fresh Connect instance.
195
201
 
196
202
  ## When something fails
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.1'
22
- __version_tuple__ = version_tuple = (0, 1, 1)
21
+ __version__ = version = '0.2.0'
22
+ __version_tuple__ = version_tuple = (0, 2, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -9,6 +9,7 @@ import click
9
9
  from rsconnect.main import cli as rsconnect_cli
10
10
 
11
11
  from .api import api as api_cmd
12
+ from .version import version as version_cmd
12
13
 
13
14
 
14
15
  _epilog = (
@@ -29,3 +30,5 @@ for _name, _cmd in rsconnect_cli.commands.items():
29
30
  connect.add_command(_cmd, name=_name)
30
31
 
31
32
  connect.add_command(api_cmd, name="api")
33
+ # The mounted command reports the rsconnect package version.
34
+ connect.add_command(version_cmd, name="version")
@@ -0,0 +1,68 @@
1
+ """Print the selected Connect server's version."""
2
+
3
+ from typing import Optional
4
+
5
+ import click
6
+ from rsconnect.api import RSConnectException, RSConnectExecutor
7
+
8
+
9
+ @click.command(
10
+ context_settings={"help_option_names": ["-h", "--help"]},
11
+ short_help="Show the Connect server version.",
12
+ )
13
+ @click.option("--name", "-n", default=None, help="Nickname of a saved server.")
14
+ @click.option(
15
+ "--server",
16
+ "-s",
17
+ default=None,
18
+ envvar="CONNECT_SERVER",
19
+ help="Connect server URL [env: CONNECT_SERVER].",
20
+ )
21
+ @click.option(
22
+ "--api-key",
23
+ "-k",
24
+ default=None,
25
+ envvar="CONNECT_API_KEY",
26
+ help="Connect API key [env: CONNECT_API_KEY].",
27
+ )
28
+ @click.option(
29
+ "--no-tls-verify",
30
+ "insecure",
31
+ is_flag=True,
32
+ default=False,
33
+ envvar="CONNECT_INSECURE",
34
+ help="Skip TLS certificate verification (still uses TLS) [env: CONNECT_INSECURE].",
35
+ )
36
+ @click.option(
37
+ "--cacert",
38
+ "-c",
39
+ type=click.Path(exists=True, dir_okay=False),
40
+ default=None,
41
+ envvar="CONNECT_CA_CERTIFICATE",
42
+ help="Path to trusted TLS CA certificate.",
43
+ )
44
+ def version(
45
+ name: Optional[str],
46
+ server: Optional[str],
47
+ api_key: Optional[str],
48
+ insecure: bool,
49
+ cacert: Optional[str],
50
+ ) -> None:
51
+ """Print the Connect server version using saved credentials by default.
52
+
53
+ CONNECT_SERVER_VERSION overrides the reported version. If the server hides
54
+ its version, print an empty line.
55
+ """
56
+ try:
57
+ executor = RSConnectExecutor(
58
+ ctx=None,
59
+ name=name,
60
+ url=server,
61
+ api_key=api_key,
62
+ insecure=insecure,
63
+ cacert=cacert,
64
+ )
65
+ server_version = executor.client.server_version()
66
+ except RSConnectException as exc:
67
+ raise click.ClickException(str(exc)) from exc
68
+ click.echo(server_version)
@@ -17,6 +17,7 @@ def test_executor_surface():
17
17
  assert expected in params, f"RSConnectExecutor lost __init__ param {expected!r}"
18
18
 
19
19
  assert hasattr(RSConnectClient, "request")
20
+ assert callable(getattr(RSConnectClient, "server_version", None))
20
21
  req_params = inspect.signature(RSConnectClient.request).parameters
21
22
  for expected in ("method", "path", "query_params", "body", "headers"):
22
23
  assert expected in req_params, f"RSConnectClient.request lost {expected!r}"
@@ -0,0 +1,97 @@
1
+ """Tests for Connect server version output and connection selection."""
2
+
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+ from click.testing import CliRunner
7
+ from rsconnect.api import RSConnectClient, RSConnectException, RSConnectServer
8
+
9
+ from posit_cli.__main__ import cli
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ "settings, output", [({"version": "2026.10.0"}, "2026.10.0\n"), ({}, "\n")]
14
+ )
15
+ def test_server_version(settings, output, monkeypatch):
16
+ monkeypatch.delenv("CONNECT_SERVER_VERSION", raising=False)
17
+ client = RSConnectClient(RSConnectServer("https://connect.example.com", api_key="key"))
18
+ with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
19
+ with patch.object(client, "get", return_value=settings) as get:
20
+ executor.return_value.client = client
21
+ result = CliRunner().invoke(cli, ["connect", "version"])
22
+ assert result.exit_code == 0, result.output
23
+ assert result.output == output
24
+ get.assert_called_once_with("server_settings")
25
+ executor.assert_called_once_with(
26
+ ctx=None, name=None, url=None, api_key=None, insecure=False, cacert=None
27
+ )
28
+
29
+
30
+ def test_server_version_override(monkeypatch):
31
+ monkeypatch.setenv("CONNECT_SERVER_VERSION", "2026.09.0")
32
+ client = RSConnectClient(RSConnectServer("https://connect.example.com", api_key="key"))
33
+ with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
34
+ with patch.object(client, "get") as get:
35
+ executor.return_value.client = client
36
+ result = CliRunner().invoke(cli, ["connect", "version"])
37
+ assert result.exit_code == 0, result.output
38
+ assert result.output == "2026.09.0\n"
39
+ get.assert_not_called()
40
+
41
+
42
+ @pytest.mark.parametrize("use_env", [False, True])
43
+ def test_connection_options(tmp_path, use_env):
44
+ cacert = tmp_path / "ca.pem"
45
+ cacert.write_text("certificate")
46
+ args = ["connect", "version", "--name", "production"]
47
+ env = {}
48
+ if use_env:
49
+ env = {
50
+ "CONNECT_SERVER": "https://connect.example.com",
51
+ "CONNECT_API_KEY": "key",
52
+ "CONNECT_INSECURE": "true",
53
+ "CONNECT_CA_CERTIFICATE": str(cacert),
54
+ }
55
+ else:
56
+ args += [
57
+ "-s",
58
+ "https://connect.example.com",
59
+ "-k",
60
+ "key",
61
+ "--no-tls-verify",
62
+ "-c",
63
+ str(cacert),
64
+ ]
65
+ with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
66
+ executor.return_value.client.server_version.return_value = "2026.10.0"
67
+ result = CliRunner().invoke(cli, args, env=env)
68
+ assert result.exit_code == 0, result.output
69
+ executor.assert_called_once_with(
70
+ ctx=None,
71
+ name="production",
72
+ url="https://connect.example.com",
73
+ api_key="key",
74
+ insecure=True,
75
+ cacert=str(cacert),
76
+ )
77
+
78
+
79
+ @pytest.mark.parametrize("stage", ["selection", "request"])
80
+ def test_errors(stage):
81
+ with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
82
+ error = RSConnectException("unable to retrieve server version")
83
+ if stage == "selection":
84
+ executor.side_effect = error
85
+ else:
86
+ executor.return_value.client.server_version.side_effect = error
87
+ result = CliRunner().invoke(cli, ["connect", "version"])
88
+ assert result.exit_code == 1
89
+ assert result.output == "Error: unable to retrieve server version\n"
90
+ assert isinstance(result.exception, SystemExit)
91
+
92
+
93
+ def test_help():
94
+ result = CliRunner().invoke(cli, ["connect", "version", "-h"])
95
+ assert result.exit_code == 0
96
+ assert "Connect server version" in result.output
97
+ assert "--server" in result.output