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
1 change: 1 addition & 0 deletions .github/etc/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ refurb
skopeo
syft
taskfile
winget
zenable
zizmor
39 changes: 20 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,10 @@ jobs:
name: Windows Smoke Test
runs-on: windows-latest
steps:
# Note: no checkout step. The cookiecutter template directory contains
# characters (pipe, quotes) that are illegal on NTFS, so we cannot check
# out the repo on Windows. Instead, cookiecutter fetches the template
# directly from the remote branch.
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ env.python_version }}
# No checkout in this job (NTFS-illegal chars in template dir), so disable cache
enable-cache: false
ignore-empty-workdir: true
- name: Install Task
Expand All @@ -126,24 +121,30 @@ jobs:
env:
RUN_POST_HOOK: 'true'
SKIP_GIT_PUSH: 'true'
TEMPLATE_REF: ${{ github.event.pull_request.head.sha || github.sha }}
TEMPLATE_REPO: ${{ github.repository }}
TEMPLATE_REF: ${{ github.head_ref || github.ref_name }}
run: |
git config --global user.name "CI Automation"
git config --global user.email "ci@zenable.io"

# The template directory name contains NTFS-illegal characters
# (double quotes, pipe). Use extract_template_zip.py to safely
# extract and rename only the top-level template dir.
zipUrl="https://github.com/${{ github.repository }}/archive/${TEMPLATE_REF}.zip"
scriptUrl="https://raw.githubusercontent.com/${{ github.repository }}/${TEMPLATE_REF}/scripts/extract_template_zip.py"
tmpdir=$(mktemp -d)
curl -fsSL "$zipUrl" -o "$tmpdir/template.zip"
curl -fsSL "$scriptUrl" -o "$tmpdir/extract_template_zip.py"
repoDir=$(python3 "$tmpdir/extract_template_zip.py" "$tmpdir/template.zip" "$tmpdir/src")

uvx --with gitpython cookiecutter "$repoDir" --no-input \
project_name="ci-test-project" \
--output-dir "$RUNNER_TEMP"
# Use the same command as README.md (gh: syntax). On Windows, if the
# default branch still has NTFS-illegal paths (pre-merge), the gh: clone
# fails because cookiecutter clones main first. Fall back to a local
# clone in that case. After merge, the gh: command succeeds directly.
uvx --with gitpython cookiecutter \
"gh:${TEMPLATE_REPO}" \
--checkout "${TEMPLATE_REF}" \
--no-input project_name="ci-test-project" \
--output-dir "$RUNNER_TEMP" \
|| {
echo "::warning::gh: clone failed (expected pre-merge on Windows). Using local clone."
template_dir="$RUNNER_TEMP/template-repo"
git clone --no-checkout "https://github.com/${TEMPLATE_REPO}.git" "$template_dir"
git -C "$template_dir" checkout "${TEMPLATE_REF}"
uvx --with gitpython cookiecutter "$template_dir" \
--no-input project_name="ci-test-project" \
--output-dir "$RUNNER_TEMP"
}
- name: Verify generated project
shell: pwsh
run: |
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
exclude: '^{{cookiecutter\.project_name\|replace\(" ", ""\)}}/.*'
exclude: '^{{cookiecutter\.project_name}}/.*'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ For more details, see our [documentation](docs/index.md).
## Getting Started

1. Create an [empty GitHub repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository)
1. Ensure you have [`brew`](https://brew.sh/) installed
1. Generate a project with the same name as the repo you just made:
1. Install the prerequisites and generate your project:

**macOS / Linux (Homebrew)**

```bash
# Install the prerequisites
brew install uv go-task
uvx --with gitpython cookiecutter gh:zenable-io/ai-native-python
```

**Windows (PowerShell)**

# Initialize your project
```powershell
winget install astral-sh.uv Task.Task
uvx --with gitpython cookiecutter gh:zenable-io/ai-native-python
```

Expand Down
6 changes: 3 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ tasks:
update:
desc: Update the project dev and runtime dependencies
cmds:
# This currently assumes uv was installed via uv (locally); we will want to make it more flexible in the future
- '{{if ne .GITHUB_ACTIONS "true"}}brew upgrade uv{{end}}'
# Upgrade uv via brew on macOS/Linux when running locally (not CI)
- '{{if and (ne .GITHUB_ACTIONS "true") (ne OS "windows")}}brew upgrade uv{{end}}'
- uv tool upgrade --all
- pre-commit autoupdate --freeze --jobs 4
# Copy the newly updated config into the project template, excluding the exclude line
- cat .pre-commit-config.yaml | grep -v ^exclude > '{{`{{cookiecutter.project_name|replace(" ", "")}}`}}/.pre-commit-config.yaml'
- cat .pre-commit-config.yaml | grep -v ^exclude > '{{`{{cookiecutter.project_name}}`}}/.pre-commit-config.yaml'
- uv lock --upgrade
# This can take a while but it's required for the following step to update BuildKit in the docker driver
- '{{if eq .CLI_ARGS "all"}}docker buildx rm multiplatform || true{{end}}'
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ task init # Installs pre-commit and sets up hooks, alongside setting up other p
6. **GitHub Actions** - Actionlint validation
7. **OpenAPI** - Schema validation

For the full list of hooks, see `{{cookiecutter.project_name|replace(" ", "")}}/.pre-commit-config.yaml`
For the full list of hooks, see `{{cookiecutter.project_name}}/.pre-commit-config.yaml`

### Configuration

Expand Down
11 changes: 8 additions & 3 deletions hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@


def validate_project_name() -> None:
"""Validate that project_name starts with an alphabetical character."""
# Check if project_name starts with an alphabetical character
"""Validate that project_name starts with an alphabetical character and contains no spaces."""
if not re.match(r"^[a-zA-Z]", PROJECT_NAME):
LOG.error(
"Invalid project name '%s': Python project names must start with an alphabetical character (a-z or A-Z).",
"Invalid project name '%s': must start with an alphabetical character (a-z or A-Z).",
PROJECT_NAME,
)
sys.exit(1)
if " " in PROJECT_NAME:
LOG.error(
"Invalid project name '%s': must not contain spaces. Use hyphens instead (e.g. 'my-project').",
PROJECT_NAME,
)
sys.exit(1)
Expand Down
53 changes: 0 additions & 53 deletions scripts/extract_template_zip.py

This file was deleted.

6 changes: 4 additions & 2 deletions tests/test_cookiecutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,13 @@ def test_autofix_hook(cookies, context):
"-invalid", # starts with dash
"9project", # starts with number
"!invalid", # starts with special character
"My Project", # contains space
"has space", # contains space
],
)
def test_invalid_project_name_validation(cookies, invalid_name):
"""
Test that project names starting with non-alphabetical characters are rejected
Test that project names with invalid characters are rejected
"""
result = cookies.bake(extra_context={"project_name": invalid_name})

Expand All @@ -238,7 +240,7 @@ def test_invalid_project_name_validation(cookies, invalid_name):
[
"ValidProject", # starts with uppercase
"validproject", # starts with lowercase
"My Project", # starts with uppercase, has space
"My-Project", # starts with uppercase, has hyphen
"a1234", # starts with lowercase, has numbers
"Z_project", # starts with uppercase, has underscore
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
exclude: '^{{ cookiecutter.project_name|replace(" ", "") }}/.*'
exclude: '^{{ cookiecutter.project_name }}/.*'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ tasks:
update:
desc: Update the project dev and runtime dependencies
cmds:
# This currently assumes uv was installed via uv (locally); we will want to make it more flexible in the future
- '{{ '{{if ne .GITHUB_ACTIONS "true"}}' }}brew upgrade uv{{ '{{end}}' }}'
# Upgrade uv via brew on macOS/Linux when running locally (not CI)
- '{{ '{{if and (ne .GITHUB_ACTIONS "true") (ne OS "windows")}}' }}brew upgrade uv{{ '{{end}}' }}'
- uv tool upgrade --all
- pre-commit autoupdate --freeze --jobs 4
- uv lock --upgrade
Expand Down
Loading