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
8 changes: 3 additions & 5 deletions .envrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/use/bin/env bash

if command -v nix-shell >/dev/null 2>&1; then
use flake
fi
#!/usr/bin/env bash
source_env_if_exists .envrc.local
command -v nix >/dev/null 2>&1 && use flake
7 changes: 6 additions & 1 deletion .github/workflows/template-render-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ jobs:
with:
python-version: '3.11'

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install copier
run: pip install copier
run: uv tool install copier

- name: Run render tests
env:
UV_SYSTEM_PYTHON: "1"
run: make test-template-render
94 changes: 0 additions & 94 deletions .idea/workspace.xml

This file was deleted.

73 changes: 44 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,63 @@
<img src="scaf-logo.png" width="250px">
</p>

**template-starter** is a generic Scaf base for creating new Scaf templates.
**django-template** is a Scaf template for creating production-ready Django web applications with modern tooling and best practices.

## What This Starter Provides
## What This Template Provides

Use this starter to generate new Scaf templates with validated Copier prompts,
GitHub/GitLab CI templates, optional semantic-release wiring (GitHub token or
GitHub App), optional gitleaks scanning, and generated `make`/`task`/`just`
`init`/`check` commands.
Generate Django projects with Docker Compose development environment, ASGI support, authentication, GraphQL API, optional Celery task queue, optional Sentry error tracking, and full CI/CD configuration for GitHub or GitLab.

## Features

- Copier prompts with input validation and conditional logic
- CI scaffolding for GitHub or GitLab
- Optional semantic-release setup and config
- Optional secret scanning in CI
- Template correctness CI (Copier render checks)
- Choice of local task runner (`make`, `task`, or `just`) with `init`/`check`
- Generated project docs for usage and upgrade flow
- Apache-2.0 license by default (license selection via Copier option planned)
- **Django 6.0** with ASGI support via Daphne
- **Docker Compose** development environment with PostgreSQL, Redis, and Mailpit
- **Authentication** via django-allauth with email verification
- **GraphQL API** with Strawberry and DataLoader
- **Optional Celery** for distributed task processing with beat scheduler
- **Optional Sentry** integration for error tracking
- **CI/CD** scaffolding for GitHub Actions or GitLab CI
- **Semantic Release** automation for versioning and changelogs
- **Secret Scanning** with gitleaks in CI
- **PyCharm & VS Code** debug configurations included
- **Nix** development environment with direnv integration
- **1Password CLI** integration for secrets management
- **Choice of task runner** (`make`, `task`, or `just`)
- Comprehensive documentation for development, debugging, and upgrades

## Copier Options

- `copier__ci_provider`: `github` or `gitlab` (only asked when repo setup is disabled; otherwise inferred from `copier__repo_provider`)
- `copier__enable_semantic_release`: include release automation
- `copier__github_semantic_release_auth`: `github_token` or `github_app` (GitHub + semantic-release only)
- `copier__enable_secret_scanning`: include gitleaks CI
- `copier__task_runner`: `make`, `task`, or `just`
- `copier__configure_repo`: enable repository remote setup
- `copier__project_name`: Human-readable project name
- `copier__project_slug`: Python identifier-style slug
- `copier__description`: Short project description
- `copier__author_name`: Author name
- `copier__domain_name`: Project domain
- `copier__email`: Author email
- `copier__timezone`: Default timezone (e.g., `America/New_York`)
- `copier__use_celery`: Enable Celery for task management
- `copier__use_sentry`: Enable Sentry error tracking
- `copier__task_runner`: Choose `make`, `task`, or `just`
- `copier__configure_repo`: Enable repository remote setup
- `copier__repo_provider`: `github` or `gitlab`
- `copier__repo_org`: organization/group name
- `copier__repo_name`: repository name
- `copier__create_repo`: create provider repo automatically when missing (`gh` for GitHub, `glab` for GitLab)
- `copier__repo_visibility`: `private` or `public` for automatic repo creation
- `copier__repo_org`: Organization/group name
- `copier__repo_name`: Repository name
- `copier__create_repo`: Auto-create repository if missing
- `copier__repo_visibility`: `private` or `public`
- `copier__ci_provider`: CI provider (inferred from repo provider)
- `copier__enable_semantic_release`: Include semantic-release automation
- `copier__github_semantic_release_auth`: `github_token` or `github_app`
- `copier__enable_secret_scanning`: Include gitleaks secret scanning

## Quick Start

```bash
# Local path
scaf my-template ./template-starter
scaf my-django-project ./django-template

# Git URL
scaf my-template https://github.com/getscaf/template-starter.git
scaf my-django-project https://github.com/getscaf/django-template.git
```

## Testing This Starter Template
## Testing This Template

Run local render tests:

Expand All @@ -60,8 +72,11 @@ CI runs the same command in `.github/workflows/template-render-tests.yaml`.

Each generated project includes:

- `docs/using-template.md`
- `docs/upgrading.md`
- `docs/development.md` - Local development setup and workflows
- `docs/debug.md` - Debugging with PyCharm and VS Code
- `docs/project-overview.md` - Architecture and technology stack
- `docs/using-template.md` - Day-to-day template usage
- `docs/upgrading.md` - Copier update workflow
- `docs/semantic-release-github.md` (when GitHub + semantic-release is enabled)
- `docs/semantic-release-gitlab.md` (when GitLab + semantic-release is enabled)

Expand Down
1 change: 1 addition & 0 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ _exclude:

_tasks:
- python tasks.py
- bash scripts/update-python-version.sh

copier__project_name_raw:
type: str
Expand Down
19 changes: 10 additions & 9 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
in {
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
jq
pre-commit
python313 # matches Dockerfile, due for upgrade?
python313.pkgs.black
python313.pkgs.boto3
python313.pkgs.botocore
python313.pkgs.copier
python313.pkgs.isort
python313.pkgs.pip-tools
python313.pkgs.psycopg2
python314 # matches Dockerfile, due for upgrade?
python314.pkgs.black
python314.pkgs.boto3
python314.pkgs.botocore
python314.pkgs.copier
python314.pkgs.isort
python314.pkgs.pip-tools
python314.pkgs.psycopg2
uv
];
shellHook = ''
Expand All @@ -32,7 +33,7 @@
pre-commit install
fi
if [ ! -d .venv ]; then
uv venv .venv --python python3.13
uv venv .venv --python python3.14
. .venv/bin/activate
# uv pip install -r requirements.txt -r requirements-dev.txt
else
Expand Down
8 changes: 3 additions & 5 deletions template/.envrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail

# Source personal .envrc.local if it exists (loaded first, can override everything)
[ -f .envrc.local ] && source .envrc.local

command -v nix-shell >/dev/null 2>&1 && use flake
source_env_if_exists .envrc.local
command -v nix >/dev/null 2>&1 && use flake

# Read secret from 1Password if not already set (returns 0 if set, 1 if failed)
read_secret() {
Expand All @@ -22,7 +20,7 @@ read_secret DJANGO_SECRET_KEY "op://{{ copier__project_name }}/DJANGO_SECRET_KEY
{%- if copier__mail_service == 'Mailgun' %}
read_secret MAILGUN_API_KEY "op://{{ copier__project_name }}/MAILGUN_API_KEY/token" || failed="${failed}MAILGUN_API_KEY "
{%- endif %}
[ -z "$failed" ] || { echo "Error: Failed to load secrets from 1Password: $failed" >&2; echo "Tip: Start 1Password or set them in .envrc.local to bypass 1Password" >&2; exit 1; }
[ -z "$failed" ] || { echo "Error: Failed to load secrets from 1Password: $failed" >&2; echo "Tip: Start 1Password and ensure the key is there, or set them in .envrc.local to bypass 1Password" >&2; exit 1; }

# Environment configuration
export USE_DOCKER=yes
Expand Down
4 changes: 2 additions & 2 deletions template/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build all of the dependencies then we will build the actual application image
FROM python:3.13-slim AS build
FROM python:__PYTHON_FULL_VERSION__-slim AS build

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
Expand Down Expand Up @@ -54,7 +54,7 @@ RUN set -x \
-r /tmp/requirements/tests.txt; fi

# Now we can build the application image
FROM python:3.13-slim
FROM python:__PYTHON_FULL_VERSION__-slim

ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH="/app"
Expand Down
2 changes: 1 addition & 1 deletion template/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ To work with this project, you need to install:
* [Docker Compose](https://docs.docker.com/compose/install/) - Multi-container orchestration (usually included with Docker Desktop)
* [direnv](https://direnv.net/) - Environment variable management (recommended)
* [1Password CLI](https://developer.1password.com/docs/cli/) - For secrets management (or configure alternative in `.envrc.local`)
* [Python 3.13+](https://www.python.org/downloads/) - For local development and dependency management
* [Python __PYTHON_VERSION__+](https://www.python.org/downloads/) - For local development and dependency management
* [uv](https://github.com/astral-sh/uv) - Fast Python package installer

## Setup your environment
Expand Down
2 changes: 1 addition & 1 deletion template/docs/project-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ For more details, see the [Development Documentation](./development.md).

### Backend Features
- **Django 6.0** - Latest stable Django framework
- **Python 3.13** - Modern Python runtime
- **Python __PYTHON_VERSION__** - Modern Python runtime
- **PostgreSQL 16** - Robust relational database
- **Django Allauth** - Complete authentication system
- **Daphne ASGI** - Async-capable application server
Expand Down
12 changes: 6 additions & 6 deletions template/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
pre-commit
python313 # matches Dockerfile, due for upgrade?
python313.pkgs.black
python313.pkgs.copier
python313.pkgs.isort
python313.pkgs.pip-tools
__PYTHON_NIX_ATTR__
__PYTHON_NIX_ATTR__.pkgs.black
__PYTHON_NIX_ATTR__.pkgs.copier
__PYTHON_NIX_ATTR__.pkgs.isort
__PYTHON_NIX_ATTR__.pkgs.pip-tools
uv
];
shellHook = ''
Expand All @@ -32,7 +32,7 @@
fi

if [ ! -d .venv ]; then
uv venv .venv --python python3.13
uv venv .venv --python python__PYTHON_VERSION__
. .venv/bin/activate
# uv pip install -r requirements.txt -r requirements-dev.txt
else
Expand Down
34 changes: 34 additions & 0 deletions template/scripts/update-python-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail

# Kept as a single script so the Docker Hub API is called once and the resolved
# version is shared across all file updates without needing a temp file.

tags=$(curl -fsSL "https://registry.hub.docker.com/v2/repositories/library/python/tags?page_size=100")

minor=$(echo "$tags" | jq -r '
[.results[].name | select(test("^[0-9]+\\.[0-9]+-slim$")) | rtrimstr("-slim")]
| map(split(".") | map(tonumber))
| max_by(.[0] * 1000 + .[1])
| join(".")
')

full=$(echo "$tags" | jq -r --arg minor "$minor" '
[.results[].name | select(test("^" + $minor + "\\.[0-9]+-slim$")) | rtrimstr("-slim")]
| map(split(".") | map(tonumber))
| if length > 0 then max_by(.[2]) | join(".") else $minor end
')

nix_attr="python${minor//.}"

echo "Updating Python version to ${minor} (Docker: ${full})"

update() { if [ -f "$1" ]; then sed -i "${@:2}" "$1"; fi; }

update backend/Dockerfile "s|__PYTHON_FULL_VERSION__|${full}|g"
update flake.nix "s|__PYTHON_NIX_ATTR__|${nix_attr}|g"
update flake.nix "s|python__PYTHON_VERSION__|python${minor}|g"
update docs/development.md "s|Python __PYTHON_VERSION__+|Python ${minor}+|g"
update docs/project-overview.md "s|\*\*Python __PYTHON_VERSION__\*\*|**Python ${minor}**|g"
update .gitlab-ci.yml "s|image: python:__PYTHON_VERSION__|image: python:${minor}|g"
update .github/workflows/template-correctness.yaml "s|python-version: '__PYTHON_VERSION__'|python-version: '${minor}'|g"
Loading