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
4 changes: 0 additions & 4 deletions copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ project_description:
type: str
help: Short summary of the project.
default: Brief description of the project
version:
type: str
help: Initial package version.
default: 0.1.0
user_name:
type: str
help: Primary project author's name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
setup-uv: true
task: dist
github-token: ${{"{{"}} secrets.GITHUB_TOKEN {{"}}"}}
env:
RELEASE_VERSION: ${{"{{"}} github.event.release.tag_name {{"}}"}}
- name: pypi
uses: pypa/gh-action-pypi-publish@release/v1{% endif %}
{% if publish_to_docker_hub == True %}
Expand All @@ -40,11 +42,9 @@ jobs:
username: ${{"{{"}} secrets.DOCKER_USERNAME {{"}}"}}
password: ${{"{{"}} secrets.DOCKER_TOKEN {{"}}"}}
- name: version
run: |
TAG_NAME="${{"{{"}} github.event.release.tag_name {{"}}"}}"
RELEASE_VERSION="${TAG_NAME#v}"
echo "RELEASE_VERSION=${RELEASE_VERSION}"
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_ENV}
env:
TAG_NAME: ${{"{{"}} github.event.release.tag_name {{"}}"}}
run: echo "RELEASE_VERSION=${TAG_NAME#v}" >> "$GITHUB_ENV"
- name: docker
uses: juftin/actions/taskfile@v1
with:
Expand Down
9 changes: 1 addition & 8 deletions template/.releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@
[
"@semantic-release/exec",
{
"prepareCmd": "uv version ${nextRelease.version} && uv build"
}
],
[
"@semantic-release/git",
{
"assets": ["pyproject.toml", "uv.lock"],
"message": "🔖 {{ package_name }} ${nextRelease.version}\n\n${nextRelease.notes}\n[skip ci]"
"prepareCmd": "UV_LOCKED=0 uv version --frozen ${nextRelease.version} && uv build && UV_LOCKED=0 uv version --frozen 0.0.0"
}
],
[
Expand Down
13 changes: 5 additions & 8 deletions template/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,20 @@ tasks:
desc: Build Distribution Artifacts
cmds:
- cmd: rm -rf {{"{{"}}.ROOT_DIR{{"}}"}}/dist
- cmd: UV_LOCKED=0 uv version --frozen "{{"{{"}}.RELEASE_VERSION | default "0.0.0"{{"}}"}}"
- cmd: uv build
- cmd: UV_LOCKED=0 uv version --frozen 0.0.0
#######################################
docker:
desc: Build Docker Image
cmd: |
docker buildx build \
--tag {{"{{"}}.DOCKER_REPO{{"}}"}}:{{"{{"}}.RELEASE_VERSION{{"}}"}} \
--build-arg RELEASE_VERSION={{"{{"}}.RELEASE_VERSION | default "0.0.0"{{"}}"}} \
--tag {{"{{"}}.DOCKER_REPO{{"}}"}}:{{"{{"}}.RELEASE_VERSION | default "local"{{"}}"}} \
--tag {{"{{"}}.DOCKER_REPO{{"}}"}}:latest \
--file {{"{{"}}.ROOT_DIR{{"}}"}}/Dockerfile \
{{"{{"}}.CLI_ARGS | default ""{{"}}"}} \
{{"{{"}}.ROOT_DIR{{"}}"}}
vars:
RELEASE_VERSION:
ref: .RELEASE_VERSION | default "local"
requires:
vars: [DOCKER_REPO]
status:
Expand Down Expand Up @@ -164,12 +164,9 @@ tasks:
- |
docker run \
--rm -it \
{{"{{"}}.DOCKER_REPO{{"}}"}}:{{"{{"}}.DOCKER_TAG_PREFIX{{"}}"}}{{"{{"}}.RELEASE_VERSION{{"}}"}} \
{{"{{"}}.DOCKER_REPO{{"}}"}}:{{"{{"}}.DOCKER_TAG_PREFIX{{"}}"}}{{"{{"}}.RELEASE_VERSION | default "local"{{"}}"}} \
{{"{{"}}.CLI_ARGS{{"}}"}}
deps: ["docker"]
vars:
RELEASE_VERSION:
ref: .RELEASE_VERSION | default "local"
status:
- test ! -f {{"{{"}}.ROOT_DIR{{"}}"}}/{{"{{"}}.DOCKERFILE | default "Dockerfile"{{"}}"}}
requires:
Expand Down
8 changes: 8 additions & 0 deletions template/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ checks on the code before it is committed. You can install the pre-commit
hooks manually, or they will be set up when you run `task install`.

This project uses [semantic-versioning] standards, managed by [semantic-release].

The source version stays at `0.0.0`. Releases create tags and artifacts without
committing version changes. Package and Docker builds use `RELEASE_VERSION` from
the release tag (with an optional `v` prefix); local builds default to `0.0.0`.
For example, `RELEASE_VERSION=v1.2.3 task build` builds version `1.2.3`.
Package builds reset the source version to `0.0.0` after building and leave
`uv.lock` unchanged. Docker applies the version inside the image.

Releases for this project are handled entirely by CI/CD via pull requests being
merged into the `main` branch. Contributions follow the [gitmoji] standards
with [conventional commits].
Expand Down
2 changes: 1 addition & 1 deletion template/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "{{ package_name }}"
version = "{{ version }}"
version = "0.0.0"
description = "{{ project_description }}"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
8 changes: 4 additions & 4 deletions template/{% if build_docker_image %}Dockerfile{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ COPY pyproject.toml /app/pyproject.toml
COPY uv.lock /app/uv.lock
COPY src/{{ project_slug }} /app/src/{{ project_slug }}

# Install project
# Install project with the release version, retaining locked dependencies.
ARG RELEASE_VERSION=0.0.0
RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --no-dev --all-extras
UV_LOCKED=0 uv version --frozen "${RELEASE_VERSION#v}" && \
UV_LOCKED=0 uv sync --frozen --no-dev --all-extras

# Environment Variables
ENV PATH="/app/.venv/bin:${PATH}" \
Expand Down
1 change: 0 additions & 1 deletion tests/answers.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
project_name: example-project
package_name: example-project
project_description: Brief description of the project
version: 0.1.0
user_name: Justin Flannery
user_email: juftin@juftin.com
github_user: juftin
Expand Down
10 changes: 5 additions & 5 deletions tests/example-project/.github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
setup-uv: true
task: dist
github-token: ${{ secrets.GITHUB_TOKEN }}
env:
RELEASE_VERSION: ${{ github.event.release.tag_name }}
- name: pypi
uses: pypa/gh-action-pypi-publish@release/v1

Expand All @@ -39,11 +41,9 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: version
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
RELEASE_VERSION="${TAG_NAME#v}"
echo "RELEASE_VERSION=${RELEASE_VERSION}"
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_ENV}
env:
TAG_NAME: ${{ github.event.release.tag_name }}
run: echo "RELEASE_VERSION=${TAG_NAME#v}" >> "$GITHUB_ENV"
- name: docker
uses: juftin/actions/taskfile@v1
with:
Expand Down
9 changes: 1 addition & 8 deletions tests/example-project/.releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@
[
"@semantic-release/exec",
{
"prepareCmd": "uv version ${nextRelease.version} && uv build"
}
],
[
"@semantic-release/git",
{
"assets": ["pyproject.toml", "uv.lock"],
"message": "🔖 example-project ${nextRelease.version}\n\n${nextRelease.notes}\n[skip ci]"
"prepareCmd": "UV_LOCKED=0 uv version --frozen ${nextRelease.version} && uv build && UV_LOCKED=0 uv version --frozen 0.0.0"
}
],
[
Expand Down
8 changes: 4 additions & 4 deletions tests/example-project/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ COPY pyproject.toml /app/pyproject.toml
COPY uv.lock /app/uv.lock
COPY src/example_project /app/src/example_project

# Install project
# Install project with the release version, retaining locked dependencies.
ARG RELEASE_VERSION=0.0.0
RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --no-dev --all-extras
UV_LOCKED=0 uv version --frozen "${RELEASE_VERSION#v}" && \
UV_LOCKED=0 uv sync --frozen --no-dev --all-extras

# Environment Variables
ENV PATH="/app/.venv/bin:${PATH}" \
Expand Down
13 changes: 5 additions & 8 deletions tests/example-project/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,20 @@ tasks:
desc: Build Distribution Artifacts
cmds:
- cmd: rm -rf {{.ROOT_DIR}}/dist
- cmd: UV_LOCKED=0 uv version --frozen "{{.RELEASE_VERSION | default "0.0.0"}}"
- cmd: uv build
- cmd: UV_LOCKED=0 uv version --frozen 0.0.0
#######################################
docker:
desc: Build Docker Image
cmd: |
docker buildx build \
--tag {{.DOCKER_REPO}}:{{.RELEASE_VERSION}} \
--build-arg RELEASE_VERSION={{.RELEASE_VERSION | default "0.0.0"}} \
--tag {{.DOCKER_REPO}}:{{.RELEASE_VERSION | default "local"}} \
--tag {{.DOCKER_REPO}}:latest \
--file {{.ROOT_DIR}}/Dockerfile \
{{.CLI_ARGS | default ""}} \
{{.ROOT_DIR}}
vars:
RELEASE_VERSION:
ref: .RELEASE_VERSION | default "local"
requires:
vars: [DOCKER_REPO]
status:
Expand Down Expand Up @@ -164,12 +164,9 @@ tasks:
- |
docker run \
--rm -it \
{{.DOCKER_REPO}}:{{.DOCKER_TAG_PREFIX}}{{.RELEASE_VERSION}} \
{{.DOCKER_REPO}}:{{.DOCKER_TAG_PREFIX}}{{.RELEASE_VERSION | default "local"}} \
{{.CLI_ARGS}}
deps: ["docker"]
vars:
RELEASE_VERSION:
ref: .RELEASE_VERSION | default "local"
status:
- test ! -f {{.ROOT_DIR}}/{{.DOCKERFILE | default "Dockerfile"}}
requires:
Expand Down
8 changes: 8 additions & 0 deletions tests/example-project/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ checks on the code before it is committed. You can install the pre-commit
hooks manually, or they will be set up when you run `task install`.

This project uses [semantic-versioning] standards, managed by [semantic-release].

The source version stays at `0.0.0`. Releases create tags and artifacts without
committing version changes. Package and Docker builds use `RELEASE_VERSION` from
the release tag (with an optional `v` prefix); local builds default to `0.0.0`.
For example, `RELEASE_VERSION=v1.2.3 task build` builds version `1.2.3`.
Package builds reset the source version to `0.0.0` after building and leave
`uv.lock` unchanged. Docker applies the version inside the image.

Releases for this project are handled entirely by CI/CD via pull requests being
merged into the `main` branch. Contributions follow the [gitmoji] standards
with [conventional commits].
Expand Down
2 changes: 1 addition & 1 deletion tests/example-project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "example-project"
version = "0.1.0"
version = "0.0.0"
description = "Brief description of the project"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
62 changes: 62 additions & 0 deletions tests/test_copier.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Verify that Copier renders and updates the expected Python project."""

import json
import os
import shutil
import subprocess
import sys
import tarfile
import zipfile
from pathlib import Path

import pytest
Expand Down Expand Up @@ -355,3 +359,61 @@ def test_copier_removes_consolidated_ci_workflows(tmp_path: Path) -> None:
assert (project / ".github/workflows/ci.yaml").is_file()
for filename in legacy_workflows:
assert not (project / ".github/workflows" / filename).exists()


@pytest.mark.parametrize(
("release_ref", "expected_version"),
[("", "0.0.0"), ("v1.2.3", "1.2.3"), ("2.0.0-beta.1", "2.0.0b1")],
)
def test_distribution_uses_release_version_without_changing_source(
rendered_project: Path,
release_ref: str,
expected_version: str,
) -> None:
"""Stamp wheel and sdist metadata while preserving source and lock bytes."""
pyproject = rendered_project / "pyproject.toml"
assert 'version = "0.0.0"' in pyproject.read_text()
original_pyproject = pyproject.read_bytes()
lockfile = rendered_project / "uv.lock"
lockfile.write_text("version = 1\n")
original_lockfile = lockfile.read_bytes()

task_executable = shutil.which("task")
assert task_executable is not None
result = subprocess.run(
[task_executable, "dist"],
cwd=rendered_project,
env={**os.environ, "RELEASE_VERSION": release_ref},
check=False,
capture_output=True,
text=True,
)

assert result.returncode == 0, result.stderr
assert pyproject.read_bytes() == original_pyproject
assert lockfile.read_bytes() == original_lockfile
wheel = next((rendered_project / "dist").glob("*.whl"))
with zipfile.ZipFile(wheel) as archive:
metadata_path = next(
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
)
assert f"Version: {expected_version}\n" in archive.read(metadata_path).decode()
sdist = next((rendered_project / "dist").glob("*.tar.gz"))
with tarfile.open(sdist) as archive:
metadata = next(
member
for member in archive.getmembers()
if member.name.endswith("/PKG-INFO")
)
metadata_file = archive.extractfile(metadata)
assert metadata_file is not None
assert f"Version: {expected_version}\n" in metadata_file.read().decode()


def test_releases_do_not_commit_version_changes(rendered_project: Path) -> None:
"""Create release artifacts without configuring release process commits."""
release_config = json.loads((rendered_project / ".releaserc.json").read_text())
plugins = release_config["plugins"]
assert "@semantic-release/git" not in [
plugin[0] if isinstance(plugin, list) else plugin for plugin in plugins
]
Loading