Skip to content

Commit 1c341f1

Browse files
JonZeollaclaude
andcommitted
feat: add Windows smoke test to CI pipeline
Add a windows-latest job that generates a project from the template and verifies it renders correctly with all defaults. The bootstrap action is updated to handle Windows by skipping Unix-specific steps (Homebrew, trufflehog, sha256sum) and using PowerShell alternatives. The smoke test verifies: - Project directory is created - Key files exist (pyproject.toml, Taskfile.yml, Dockerfile, etc.) - No unrendered cookiecutter variables remain - Git repo is initialized with at least one commit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dab3cd9 commit 1c341f1

2 files changed

Lines changed: 117 additions & 4 deletions

File tree

.github/actions/bootstrap/action.yml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@ inputs:
1616
runs:
1717
using: 'composite'
1818
steps:
19-
- name: Create run_script for running scripts downstream
19+
- name: Create run_script for running scripts downstream (Unix)
20+
if: runner.os != 'Windows'
2021
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
2122
working-directory: ${{ inputs.working-directory }}
2223
run: |
2324
run_script="uv run --frozen"
2425
echo "run_script=${run_script}" | tee -a "${GITHUB_ENV}"
2526
27+
- name: Create run_script for running scripts downstream (Windows)
28+
if: runner.os == 'Windows'
29+
shell: pwsh
30+
working-directory: ${{ inputs.working-directory }}
31+
run: |
32+
echo "run_script=uv run --frozen" | Tee-Object -Append $env:GITHUB_ENV
33+
2634
- name: Setup uv
2735
uses: astral-sh/setup-uv@v4
2836
with:
@@ -37,6 +45,7 @@ runs:
3745
repo-token: ${{ inputs.token }}
3846

3947
- name: Add Homebrew to the path
48+
if: runner.os != 'Windows'
4049
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
4150
# This ensures compatibility with macOS runners and Linux runners with Homebrew
4251
run: |
@@ -68,23 +77,44 @@ runs:
6877
working-directory: ${{ inputs.working-directory }}
6978

7079
- name: Install trufflehog
80+
if: runner.os != 'Windows'
7181
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
7282
run: |
7383
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
7484
75-
- name: Set Python hash for caching
85+
- name: Set Python hash for caching (Unix)
86+
if: runner.os != 'Windows'
7687
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
7788
run: |
7889
# Create a hash of the Python version for better cache keys
7990
echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" | tee -a "${GITHUB_ENV}"
8091
92+
- name: Set Python hash for caching (Windows)
93+
if: runner.os == 'Windows'
94+
shell: pwsh
95+
run: |
96+
$pyVersion = python -VV 2>&1
97+
$hash = [System.BitConverter]::ToString(
98+
[System.Security.Cryptography.SHA256]::Create().ComputeHash(
99+
[System.Text.Encoding]::UTF8.GetBytes($pyVersion)
100+
)
101+
).Replace("-", "").ToLower()
102+
echo "PY=$hash" | Tee-Object -Append $env:GITHUB_ENV
103+
81104
- name: Cache pre-commit environments
82105
uses: actions/cache@v4
83106
with:
84107
path: ~/.cache/pre-commit
85108
key: pre-commit|${{ env.PY }}|${{ hashFiles(format('{0}/.pre-commit-config.yaml', inputs.working-directory)) }}
86109

87-
- name: Initialize the repository
110+
- name: Initialize the repository (Unix)
111+
if: runner.os != 'Windows'
88112
working-directory: ${{ inputs.working-directory }}
89113
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
90114
run: task -v init
115+
116+
- name: Initialize the repository (Windows)
117+
if: runner.os == 'Windows'
118+
working-directory: ${{ inputs.working-directory }}
119+
shell: pwsh
120+
run: task -v init

.github/workflows/ci.yml

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,89 @@ jobs:
102102
name: vuln-scan-results
103103
path: vulns.json
104104
if-no-files-found: error
105+
windows-smoke-test:
106+
name: Windows Smoke Test
107+
runs-on: windows-latest
108+
steps:
109+
- name: Checkout the repository
110+
uses: actions/checkout@v6
111+
with:
112+
persist-credentials: 'false'
113+
- name: Setup uv
114+
uses: astral-sh/setup-uv@v4
115+
with:
116+
python-version: ${{ env.python_version }}
117+
- name: Install Task
118+
uses: go-task/setup-task@v1
119+
with:
120+
repo-token: ${{ secrets.GITHUB_TOKEN }}
121+
- name: Generate project from template
122+
shell: pwsh
123+
env:
124+
RUN_POST_HOOK: 'true'
125+
SKIP_GIT_PUSH: 'true'
126+
run: |
127+
# Configure git identity for the post-gen commit
128+
git config --global user.name "CI Automation"
129+
git config --global user.email "ci@zenable.io"
130+
131+
# Generate a project with all defaults using cookiecutter
132+
uvx --with gitpython cookiecutter . --no-input --output-dir "$env:RUNNER_TEMP"
133+
- name: Verify generated project
134+
shell: pwsh
135+
run: |
136+
$project = Join-Path $env:RUNNER_TEMP "replace-me"
137+
138+
# Verify the project directory was created
139+
if (-not (Test-Path $project)) {
140+
Write-Error "Project directory not found at $project"
141+
exit 1
142+
}
143+
144+
# Verify key files exist
145+
$requiredFiles = @(
146+
"pyproject.toml",
147+
"Taskfile.yml",
148+
"Dockerfile",
149+
"CLAUDE.md",
150+
".github/project.yml",
151+
".github/workflows/ci.yml"
152+
)
153+
foreach ($file in $requiredFiles) {
154+
$filePath = Join-Path $project $file
155+
if (-not (Test-Path $filePath)) {
156+
Write-Error "Required file missing: $file"
157+
exit 1
158+
}
159+
}
160+
161+
# Verify no unrendered cookiecutter variables remain
162+
$pattern = '\{\{\s*cookiecutter\.'
163+
$matches = Get-ChildItem -Path $project -Recurse -File -Exclude '.git' |
164+
Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } |
165+
Select-String -Pattern $pattern
166+
if ($matches) {
167+
Write-Error "Unrendered cookiecutter variables found:"
168+
$matches | ForEach-Object { Write-Error $_.ToString() }
169+
exit 1
170+
}
171+
172+
# Verify git repo was initialized and has a commit
173+
$gitDir = Join-Path $project ".git"
174+
if (-not (Test-Path $gitDir)) {
175+
Write-Error "Git repository not initialized"
176+
exit 1
177+
}
178+
179+
Push-Location $project
180+
$commitCount = git rev-list --count HEAD 2>$null
181+
Pop-Location
182+
if ($commitCount -lt 1) {
183+
Write-Error "No commits found in generated project"
184+
exit 1
185+
}
186+
187+
Write-Host "Windows smoke test passed: project generated and verified successfully"
105188
finalizer:
106189
# This gives us something to set as required in the repo settings. Some projects use dynamic fan-outs using matrix strategies and the fromJSON function, so
107190
# you can't hard-code what _should_ run vs not. Having a finalizer simplifies that so you can just check that the finalizer succeeded, and if so, your
@@ -110,7 +193,7 @@ jobs:
110193
name: Finalize the pipeline
111194
runs-on: ubuntu-24.04
112195
# Keep this aligned with the above jobs
113-
needs: [lint, test]
196+
needs: [lint, test, windows-smoke-test]
114197
if: always() # Ensure it runs even if "needs" fails or is cancelled
115198
steps:
116199
- name: Check for failed or cancelled jobs

0 commit comments

Comments
 (0)