Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# hatch-vcs reads the version from the tags, so fetch them.
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@v9.0.0
- uses: extractions/setup-just@v4
- run: just build
Expand Down
29 changes: 27 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ jobs:
fetch-depth: 0
- uses: astral-sh/setup-uv@v9.0.0
- uses: extractions/setup-just@v4
- name: assert tag matches pyproject version
# The tag is the only source of the version, so this only guards against a
# build that did not pick the tag up (shallow clone, dirty tree, two tags on
# one commit).
- name: assert build version matches tag
env:
TAG: ${{ github.ref_name }}
run: |
ver="$(just version)"
if [ "$TAG" != "v$ver" ]; then
echo "::error::tag '${TAG}' does not match pyproject version 'v${ver}'"
echo "::error::tag '${TAG}' does not match build version 'v${ver}'"
exit 1
fi
- name: assert tag is on main
Expand All @@ -34,4 +37,26 @@ jobs:
fi
- run: just build
- run: just smoke
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1

# Runs only after PyPI accepts the upload, so a failed publish leaves no release.
github-release:
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: create GitHub release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: gh release create "$TAG" dist/* --title "$TAG" --generate-notes
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ dist/
.venv/
venv/

# Written at build time by hatch-vcs
/src/posit_cli/_version.py

# Tooling
.pytest_cache/
.ruff_cache/
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ posit-sdk) and the OAuth-release gotcha.
`RSConnectClient`), which has no stability contract. `tests/test_rsconnect_contract.py` guards
the surface we depend on; pin the rsconnect version and re-verify on bumps.

- The git tag sets the version, through `hatch-vcs`. `pyproject.toml` has no `version` field.
Do not add one.

See `RELEASE.md` for how to cut a release to PyPI.
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ smoke:
install: build
uv pip install "$(ls -t dist/*.whl | head -1)"

# Print the current version
# Print the version that a build gets from the current git state
version:
@uv version --short
@uvx hatch version

# Remove build/test artifacts
clean:
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,27 @@ This project is in early-stage development and so far only supports Posit Connec

## Installation

`posit-cli` isn't on PyPI yet. Install the latest version straight from GitHub
with [`uv`](https://docs.astral.sh/uv/):
Install [`posit-cli` from PyPI](https://pypi.org/project/posit-cli/) with
[`uv`](https://docs.astral.sh/uv/):

```console
uv tool install posit-cli
```

To get unreleased changes, install from GitHub instead:

```console
uv tool install git+https://github.com/posit-dev/posit-cli.git
```

If you authenticate to GitHub over SSH, use the `git+ssh://` form instead (uv
requires the `git@` username):
If you authenticate to GitHub over SSH, use the `git+ssh://` form (uv requires
the `git@` username):

```console
uv tool install git+ssh://git@github.com/posit-dev/posit-cli.git
```

Either way this puts the `posit` executable on your `PATH`. To upgrade later, run
Each of these puts the `posit` executable on your `PATH`. To upgrade later, run
`uv tool upgrade posit-cli`. See uv's
[Git authentication docs](https://docs.astral.sh/uv/concepts/authentication/git/)
for tokens and other hosts.
Expand Down
30 changes: 21 additions & 9 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
# Releasing

The git tag is the only source of the version. `pyproject.toml` has no `version` field —
`hatch-vcs` reads the tag at build time. Do not add one.

Follow these steps to publish a new version to PyPI:

1. Bump `version` in `pyproject.toml`.
2. Run `uv lock` if the bump changed any dependency. CI fails the build if `uv.lock` has drifted.
3. Commit the change and merge it to `main`.
4. Tag the merged commit on `main` as `vX.Y.Z`. The tag must match the `pyproject.toml` version
exactly.
5. Push the tag. This triggers `.github/workflows/release.yaml`.
1. Make sure `main` holds everything you want in the release.
2. Tag the commit on `main` as `vX.Y.Z`.
3. Push the tag. This triggers `.github/workflows/release.yaml`.

```console
git checkout main && git pull
git tag v0.2.0
git push origin v0.2.0
```

The release workflow confirms that the build picked the tag up and that the tag is on `main`,
builds the wheel and sdist, smoke-tests the wheel, then publishes to PyPI through GitHub's OIDC
trusted-publisher flow. No PyPI token is stored in this repo. After PyPI accepts the upload, a
second job creates a GitHub release for the tag, with generated notes and the built artifacts
attached.

The release workflow checks the tag against `pyproject.toml` and against `main`, builds the
wheel and sdist, smoke-tests the wheel, then publishes to PyPI through GitHub's OIDC
trusted-publisher flow. No PyPI token is stored in this repo.
Run `just version` to see the version that a build gets from the current git state. A commit
after the last tag gets a development version (for example `0.2.1.dev3+g1a2b3c4`), which PyPI
rejects. Only a clean, tagged commit produces a release version.

## One-time setup for a new repo

Expand Down
13 changes: 11 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[project]
name = "posit-cli"
version = "0.1.0"
# The version comes from the git tag, through hatch-vcs. Never hard-code it here.
dynamic = ["version"]
description = "A single, friendly command-line interface for Posit Connect, in the spirit of gh."
readme = "README.md"
requires-python = ">=3.8"
Expand Down Expand Up @@ -28,9 +29,17 @@ test = ["pytest>=7"]
lint = ["ruff>=0.6"]

[build-system]
requires = ["hatchling"]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"

# 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]
version-file = "src/posit_cli/_version.py"

[tool.hatch.build.targets.wheel]
packages = ["src/posit_cli"]

Expand Down
16 changes: 11 additions & 5 deletions skills/posit-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,21 @@ These are rsconnect commands — run `posit connect <command> --help` for detail

## Installing the CLI

`posit-cli` isn't on PyPI yet; install from GitHub with [`uv`](https://docs.astral.sh/uv/):
Install from PyPI with [`uv`](https://docs.astral.sh/uv/):

```console
uv tool install git+https://github.com/posit-dev/posit-cli.git # `posit` onto your PATH
uv tool upgrade posit-cli # later, to update
uv tool install posit-cli # `posit` onto your PATH
uv tool upgrade posit-cli # later, to update
```

If GitHub is set up for SSH auth, use the `git+ssh://` form instead (uv requires
the `git@` username):
For unreleased changes, install from GitHub instead:

```console
uv tool install git+https://github.com/posit-dev/posit-cli.git
```

If GitHub is set up for SSH auth, use the `git+ssh://` form (uv requires the
`git@` username):

```console
uv tool install git+ssh://git@github.com/posit-dev/posit-cli.git
Expand Down
3 changes: 1 addition & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.