Skip to content

Commit fe5bca9

Browse files
committed
The 0.1.0 release.
This is the initial functionality with commands, aliases and defaults. Tets coverage is minimal, although `dev-cmd` does dogfood itself for CI.
1 parent f1c7f88 commit fe5bca9

17 files changed

+994
-0
lines changed

.github/FUNDING.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github: jsirois
2+

.github/workflows/ci.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
on: [push, pull_request]
3+
defaults:
4+
run:
5+
shell: bash
6+
concurrency:
7+
group: CI-${{ github.ref }}
8+
# Queue on all branches and tags, but only cancel overlapping PR burns.
9+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/') }}
10+
jobs:
11+
org-check:
12+
name: Check GitHub Organization
13+
if: github.repository_owner == 'jsirois'
14+
runs-on: ubuntu-24.04
15+
steps:
16+
- name: Noop
17+
run: "true"
18+
checks:
19+
name: uv run dev-cmd ci
20+
needs: org-check
21+
runs-on: ubuntu-24.04
22+
strategy:
23+
matrix:
24+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
25+
steps:
26+
- name: Checkout dev-cmd
27+
uses: actions/checkout@v4
28+
- name: Install the latest version of uv
29+
uses: astral-sh/setup-uv@v4
30+
with:
31+
python-version: ${{ matrix.python-version }}
32+
- name: Run CI checks
33+
run: uv run dev-cmd ci -- -vvs
34+

.github/workflows/release.yml

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Release
2+
on:
3+
push:
4+
tags:
5+
- v[0-9]+.[0-9]+.[0-9]+
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: The tag to manually run a deploy for.
10+
required: true
11+
defaults:
12+
run:
13+
shell: bash
14+
jobs:
15+
org-check:
16+
name: Check GitHub Organization
17+
if: ${{ github.repository_owner == 'jsirois' }}
18+
runs-on: ubuntu-24.04
19+
steps:
20+
- name: Noop
21+
run: "true"
22+
determine-tag:
23+
name: Determine the release tag to operate against.
24+
needs: org-check
25+
runs-on: ubuntu-24.04
26+
outputs:
27+
release-tag: ${{ steps.determine-tag.outputs.release-tag }}
28+
release-version: ${{ steps.determine-tag.outputs.release-version }}
29+
steps:
30+
- name: Determine Tag
31+
id: determine-tag
32+
run: |
33+
if [[ -n "${{ github.event.inputs.tag }}" ]]; then
34+
RELEASE_TAG=${{ github.event.inputs.tag }}
35+
else
36+
RELEASE_TAG=${GITHUB_REF#refs/tags/}
37+
fi
38+
if [[ "${RELEASE_TAG}" =~ ^v[0-9]+.[0-9]+.[0-9]+$ ]]; then
39+
echo "release-tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
40+
echo "release-version=${RELEASE_TAG#v}" >> $GITHUB_OUTPUT
41+
else
42+
echo "::error::Release tag '${RELEASE_TAG}' must match 'v\d+.\d+.\d+'."
43+
exit 1
44+
fi
45+
pypi:
46+
name: Publish sdist and wheel to PyPI
47+
needs: determine-tag
48+
runs-on: ubuntu-24.04
49+
environment: Release
50+
permissions:
51+
id-token: write
52+
steps:
53+
- name: Checkout dev-cmd ${{ needs.determine-tag.outputs.release-tag }}
54+
uses: actions/checkout@v4
55+
with:
56+
ref: ${{ needs.determine-tag.outputs.release-tag }}
57+
- name: Install the latest version of uv
58+
uses: astral-sh/setup-uv@v4
59+
- name: Build sdist and wheel
60+
run: uv build
61+
- name: Publish dev-cmd ${{ needs.determine-tag.outputs.release-tag }}
62+
uses: pypa/gh-action-pypi-publish@release/v1
63+
with:
64+
print-hash: true
65+
verbose: true

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Build artifacts dirs.
2+
/*.egg-info/
3+
4+
# Python bytecode.
5+
__pycache__/
6+
7+
# UV outputs artifacts here.
8+
/dist/

CHANGES.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Release Notes
2+
3+
## 0.1.0
4+
5+
Initial release.

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# dev
2+
3+
The dev tool provides a simple way to define commands in `pyproject.toml` to develop your project
4+
with and then execute them.
5+
6+
This is a very new tool that can be expected to change rapidly and in breaking ways until the 1.0
7+
release. The current best documentation is the dogfooding this project uses for its own development
8+
described below. You can look at the `[tool.dev-cmd]` configuration in `pyproject.toml` to get a
9+
sense of how definition of commands, aliases and defaults works.
10+
11+
## Development
12+
13+
Development uses [`uv`](https://docs.astral.sh/uv/getting-started/installation/). Install as you
14+
best see fit.
15+
16+
With `uv` installed, running `uv run dev-cmd` is enough to get the tools run-dev uses installed and
17+
run against the codebase. This includes formatting code, linting code, performing type checks and
18+
then running tests.

RELEASE.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Release Process
2+
3+
## Preparation
4+
5+
### Version Bump and Changelog
6+
7+
1. Bump the version in [`dev_cmd/__init__.py`](dev_cmd/__init__.py).
8+
2. Run `uv run dev-cmd` as a sanity check on the state of the project.
9+
3. Update [`CHANGES.md`](CHANGES.md) with any changes that are likely to be useful to consumers.
10+
4. Open a PR with these changes and land it on https://github.com/jsirois/dev-cmd main.
11+
12+
## Release
13+
14+
### Push Release Tag
15+
16+
Sync a local branch with https://github.com/jsirois/dev-cmd main and confirm it has the version bump
17+
and changelog update as the tip commit:
18+
19+
```
20+
$ git log --stat -1 HEAD | grep -E "CHANGES|__init__"
21+
CHANGES.md | 5 +
22+
dev_cmd/__init__.py | 4 +```
23+
24+
Tag the release as `v<version>` and push the tag to https://github.com/jsirois/dev-cmd main:
25+
26+
```
27+
$ git tag --sign -am 'Release 0.1.0' v0.1.0
28+
$ git push --tags https://github.com/jsirois/dev-cmd HEAD:main
29+
```
30+
31+
The release is automated and will create a PyPI release at
32+
[https://pypi.org/project/dev-cmd/&lt;version&gt;/](https://pypi.org/project/dev-cmd/#history).

dev_cmd/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright 2024 John Sirois.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
__version__ = "0.1.0"

dev_cmd/__main__.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright 2024 John Sirois.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
import sys
5+
6+
from dev_cmd.run import main
7+
8+
if __name__ == "__main__":
9+
sys.exit(main())

dev_cmd/errors.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2024 John Sirois.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
5+
class DevError(Exception):
6+
"""Indicates an error processing dev commands."""
7+
8+
9+
class InvalidProjectError(DevError):
10+
"""Indicates the dev runner cannot locate or parse the `pyproject.toml` file."""
11+
12+
13+
class InvalidArgumentError(DevError):
14+
"""Indicates invalid argument were passed to the dev command."""
15+
16+
17+
class InvalidModelError(DevError):
18+
"""Indicates invalid dev command configuration."""

dev_cmd/model.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2024 John Sirois.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
from __future__ import annotations
5+
6+
from dataclasses import dataclass
7+
from typing import Any, Dict, Iterable, Mapping, Optional, Tuple
8+
9+
from dev_cmd.errors import InvalidModelError
10+
11+
12+
@dataclass(frozen=True)
13+
class Command:
14+
name: str
15+
env: Mapping[str, str]
16+
args: Tuple[str, ...]
17+
accepts_extra_args: bool
18+
19+
20+
@dataclass(frozen=True)
21+
class Dev:
22+
commands: Mapping[str, Command]
23+
aliases: Mapping[str, Tuple[Command, ...]]
24+
default: Optional[Tuple[str, Tuple[Command, ...]]] = None
25+
source: Any = "<code>"
26+
27+
28+
@dataclass(frozen=True)
29+
class Invocation:
30+
@classmethod
31+
def create(cls, *tasks: Tuple[str, Iterable[Command]]) -> Invocation:
32+
_tasks: Dict[str, Tuple[Command, ...]] = {}
33+
accepts_extra_args: Optional[Command] = None
34+
for task, commands in tasks:
35+
_tasks[task] = tuple(commands)
36+
for command in commands:
37+
if command.accepts_extra_args:
38+
if accepts_extra_args is not None:
39+
raise InvalidModelError(
40+
f"The command {command.name!r} accepts extra args, but only one "
41+
f"command can accept extra args per invocation and command "
42+
f"{accepts_extra_args.name!r} already does."
43+
)
44+
accepts_extra_args = command
45+
46+
return cls(tasks=_tasks, accepts_extra_args=accepts_extra_args is not None)
47+
48+
tasks: Mapping[str, Tuple[Command, ...]]
49+
accepts_extra_args: bool

0 commit comments

Comments
 (0)