Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
Expand All @@ -39,6 +38,22 @@ jobs:
- uses: extractions/setup-just@v4
- run: just test ${{ matrix.python-version }}

integration:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.13"
- run: uv sync --python 3.13 --extra test
- name: Test publish setup and deployment against Posit Connect
uses: posit-dev/with-connect@main
with:
version: release
license: ${{ secrets.CONNECT_LICENSE_FILE }}
command: uv run --no-sync pytest -m integration -vv tests/integration

build:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ venv/
.ruff_cache/
.mypy_cache/
.coverage

# Local Posit Connect integration-test license
/.connect-license.lic

# roborev snapshots
/.roborev/
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Repository Instructions

## Local Connect integration tests

The live integration suite requires Docker and a valid Posit Connect license copied to
`.connect-license.lic` at the repository root. This file is intentionally gitignored and must
never be committed.

For Posit developers with a local Connect checkout, copy the standard test license:

```bash
cp /path/to/connect/test/licenses/legacy-enterprise.lic \
.connect-license.lic
```

Run the suite with:

```bash
just integration
```

This uses `posit-dev/with-connect` to start a temporary Connect container, provides
`CONNECT_SERVER` and `CONNECT_API_KEY` to pytest, and stops the container afterward.
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# posit-cli

@AGENTS.md

A single, friendly CLI — `posit` — for working with Posit Connect, in the spirit of
[`gh`](https://cli.github.com/). Distribution name `posit-cli`, import package `posit_cli`,
executable `posit`.
Expand Down
12 changes: 12 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
test py="3.13":
uv run --python {{py}} --extra test pytest tests

# Run tests that publish to a live Connect instance.
integration:
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -f .connect-license.lic ]]; then
echo "Missing .connect-license.lic; see AGENTS.md." >&2
exit 1
fi
uvx --from git+https://github.com/posit-dev/with-connect.git \
with-connect --license .connect-license.lic -- \
uv run --extra test pytest -m integration -vv tests/integration

# Check formatting and lint
lint:
uv run --extra lint ruff format --check
Expand Down
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ A friendly command-line interface for Posit products, in the spirit of [`gh`](ht
```console
$ posit connect login https://connect.example.com # OAuth, tokens in your OS keyring
$ posit connect api v1/user -q .username # gh-api-style raw request
$ posit connect deploy streamlit ./my-app # everything rsconnect can do
$ posit connect publish . --server https://connect.example.com
$ posit connect publish . --init # configure without publishing
```

This project is in early-stage development and so far only supports Posit Connect's APIs.

## Installation

`posit-cli` requires Python 3.9 or newer.

Install [`posit-cli` from PyPI](https://pypi.org/project/posit-cli/) with
[`uv`](https://docs.astral.sh/uv/):

Expand Down Expand Up @@ -53,13 +56,26 @@ $ posit connect login https://connect.example.com
$ posit connect api v1/user -q .username
```

**3. Deploy something.** Anything `rsconnect` can deploy, `posit` can too:
**3. Publish.** Run the command from your project directory:

```console
$ cd my-app
$ posit connect publish . --server https://connect.example.com
```

If the project has not been configured yet, an interactive terminal opens the
setup wizard before continuing with the publish. The generated
`.posit/publish` configuration is reused on later runs.

The server is needed only for the first publish. Later publishes reuse the
saved deployment record:

```console
$ posit connect deploy streamlit ./my-app
$ posit connect publish .
```

That's it — from here, explore `posit connect --help` for the full command set.
Anything `rsconnect` can deploy remains available under `posit connect deploy`.
Explore `posit connect --help` for the full command set.

## Authentication

Expand Down Expand Up @@ -114,6 +130,40 @@ $ posit connect api v1/users -X GET -f page_size=5 # or force GET
$ posit connect api v1/content -f name=my-app # POST body (creates content)
```

## `posit connect publish`

`posit connect publish` is the main workflow. On a fresh project it opens a
Questionary-powered setup wizard in an interactive terminal and then publishes
the content:

```console
$ posit connect publish . --server https://connect.example.com
```

Use `--init` when you want to configure the project without publishing it:

```console
$ posit connect publish . --init
$ posit connect publish . --init --type python-fastapi --entrypoint app.py --title "Sales API"
```

Fresh projects do not prompt when stdin is non-interactive. Automation should
run `publish --init` with `--type` and `--entrypoint` first; Quarto content also
requires `--quarto-version`.

Configured projects can publish to a URL or saved server name:

```console
$ posit connect publish . --server https://connect.example.com
$ posit connect publish . --name production
$ posit connect publish . --config sales-api
$ posit connect publish . --deployment production
```

`--config` selects an exact `.posit/publish` configuration and `--deployment`
selects an exact deployment record. Successful publishes print the content URL
to stdout.

## `posit connect *`

`posit` wraps [`rsconnect-python`](https://github.com/posit-dev/rsconnect-python):
Expand Down
15 changes: 13 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ name = "posit-cli"
dynamic = ["version"]
description = "A single, friendly command-line interface for Posit Connect, in the spirit of gh."
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.9"
license = "MIT"
license-files = ["LICENSE"]
authors = [{ name = "Posit Software, PBC" }]
dependencies = [
# posit-cli reuses rsconnect-python's *internal* API (RSConnectExecutor,
# RSConnectClient). These have no stability contract, so re-verify the internals
# on bumps. See tests/test_rsconnect_contract.py.
"rsconnect-python>=1.30,<2",
# Publisher APIs used by `connect init` and `connect publish`. Pin the exact
# PR 830 commit until the API is available in an rsconnect-python release.
"rsconnect-python @ git+https://github.com/posit-dev/rsconnect-python.git@c92d8f4ac033a5b1bb4f67bbaae33bf8e0b143e5",
# rsconnect imports `keyring` optionally; we depend on it directly so OAuth
# tokens land in the OS keyring.
"keyring>=23.0",
"click>=8.0",
"questionary>=2.1.1,<3",
# jq bindings power `posit connect api --jq`, mirroring `gh api --jq`.
"jq>=1.4",
]
Expand All @@ -35,6 +38,9 @@ build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs"

[tool.hatch.metadata]
allow-direct-references = true

# Write the resolved version into the package. An sdist has no git metadata, so a
# wheel built from an sdist reads this file instead of asking git.
[tool.hatch.build.hooks.vcs]
Expand All @@ -48,3 +54,8 @@ line-length = 99

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F"]

[tool.pytest.ini_options]
markers = [
"integration: requires a live Posit Connect server and API key",
]
9 changes: 3 additions & 6 deletions src/posit_cli/connect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
"""The ``posit connect`` command group.

Mounts the entire ``rsconnect`` CLI (login, deploy, add, list, ...) under
``posit connect`` so those commands come for free and track rsconnect-python
upstream, then layers on a ``gh api``-style ``posit connect api`` command.
"""
"""The ``posit connect`` command group."""

import click
from rsconnect.main import cli as rsconnect_cli

from .api import api as api_cmd
from .publish import publish as publish_cmd


_epilog = (
Expand All @@ -29,3 +25,4 @@ def connect() -> None:
connect.add_command(_cmd, name=_name)

connect.add_command(api_cmd, name="api")
connect.add_command(publish_cmd, name="publish")
Loading