Skip to content

Commit bf8ebc8

Browse files
authored
feat(platform): add Windows support, migrate to new Zenable CLI (#90)
1 parent b200cc3 commit bf8ebc8

12 files changed

Lines changed: 454 additions & 41 deletions

File tree

.github/actions/bootstrap/action.yml

Lines changed: 38 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: |
@@ -67,19 +76,45 @@ runs:
6776
fi
6877
working-directory: ${{ inputs.working-directory }}
6978

70-
- name: Set Python hash for caching
79+
- name: Install trufflehog
80+
if: runner.os != 'Windows'
81+
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
82+
run: |
83+
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
84+
85+
- name: Set Python hash for caching (Unix)
86+
if: runner.os != 'Windows'
7187
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
7288
run: |
7389
# Create a hash of the Python version for better cache keys
7490
echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" | tee -a "${GITHUB_ENV}"
7591
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+
76104
- name: Cache pre-commit environments
77105
uses: actions/cache@v4
78106
with:
79107
path: ~/.cache/pre-commit
80108
key: pre-commit|${{ env.PY }}|${{ hashFiles(format('{0}/.pre-commit-config.yaml', inputs.working-directory)) }}
81109

82-
- name: Initialize the repository
110+
- name: Initialize the repository (Unix)
111+
if: runner.os != 'Windows'
83112
working-directory: ${{ inputs.working-directory }}
84113
shell: 'bash --noprofile --norc -Eeuo pipefail {0}'
85114
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/etc/dictionary.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
allstar
22
anchore
33
buildx
4+
conftest
45
cookiecutter
56
dependabot
67
digestabot
8+
docstrings
79
dockerhub
810
htmlcov
911
pylance
1012
pythonpath
1113
refurb
1214
skopeo
1315
syft
16+
taskfile
1417
zenable
1518
zizmor

.github/workflows/ci.yml

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,147 @@ 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+
# Note: no checkout step. The cookiecutter template directory contains
110+
# characters (pipe, quotes) that are illegal on NTFS, so we cannot check
111+
# out the repo on Windows. Instead, cookiecutter fetches the template
112+
# directly from the remote branch.
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: bash
123+
env:
124+
RUN_POST_HOOK: 'true'
125+
SKIP_GIT_PUSH: 'true'
126+
TEMPLATE_REF: ${{ github.event.pull_request.head.sha || github.sha }}
127+
run: |
128+
git config --global user.name "CI Automation"
129+
git config --global user.email "ci@zenable.io"
130+
131+
# The template directory name contains NTFS-illegal characters
132+
# (double quotes, pipe). Use extract_template_zip.py to safely
133+
# extract and rename only the top-level template dir.
134+
zipUrl="https://github.com/${{ github.repository }}/archive/${TEMPLATE_REF}.zip"
135+
scriptUrl="https://raw.githubusercontent.com/${{ github.repository }}/${TEMPLATE_REF}/scripts/extract_template_zip.py"
136+
tmpdir=$(mktemp -d)
137+
curl -fsSL "$zipUrl" -o "$tmpdir/template.zip"
138+
curl -fsSL "$scriptUrl" -o "$tmpdir/extract_template_zip.py"
139+
repoDir=$(python3 "$tmpdir/extract_template_zip.py" "$tmpdir/template.zip" "$tmpdir/src")
140+
141+
uvx --with gitpython cookiecutter "$repoDir" --no-input --output-dir "$RUNNER_TEMP"
142+
- name: Verify generated project
143+
shell: pwsh
144+
run: |
145+
$project = Join-Path $env:RUNNER_TEMP "replace-me"
146+
147+
# Verify the project directory was created
148+
if (-not (Test-Path $project)) {
149+
Write-Error "Project directory not found at $project"
150+
exit 1
151+
}
152+
153+
# Verify key files exist
154+
$requiredFiles = @(
155+
"pyproject.toml",
156+
"Taskfile.yml",
157+
"Dockerfile",
158+
"CLAUDE.md",
159+
".github/project.yml",
160+
".github/workflows/ci.yml"
161+
)
162+
foreach ($file in $requiredFiles) {
163+
$filePath = Join-Path $project $file
164+
if (-not (Test-Path $filePath)) {
165+
Write-Error "Required file missing: $file"
166+
exit 1
167+
}
168+
}
169+
170+
# Verify no unrendered cookiecutter variables remain
171+
$pattern = '\{\{\s*cookiecutter\.'
172+
$matches = Get-ChildItem -Path $project -Recurse -File -Exclude '.git' |
173+
Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } |
174+
Select-String -Pattern $pattern
175+
if ($matches) {
176+
Write-Error "Unrendered cookiecutter variables found:"
177+
$matches | ForEach-Object { Write-Error $_.ToString() }
178+
exit 1
179+
}
180+
181+
# Verify git repo was initialized and has a commit
182+
$gitDir = Join-Path $project ".git"
183+
if (-not (Test-Path $gitDir)) {
184+
Write-Error "Git repository not initialized"
185+
exit 1
186+
}
187+
188+
Push-Location $project
189+
$commitCount = git rev-list --count HEAD 2>$null
190+
Pop-Location
191+
if ($commitCount -lt 1) {
192+
Write-Error "No commits found in generated project"
193+
exit 1
194+
}
195+
196+
Write-Host "Windows smoke test passed: project generated and verified successfully"
197+
- name: Setup WSL with Docker
198+
shell: bash
199+
run: |
200+
wsl --install -d Ubuntu-24.04 --no-launch
201+
wsl -d Ubuntu-24.04 -u root -- bash -ec "
202+
apt-get update -qq
203+
apt-get install -y -qq curl ca-certificates >/dev/null
204+
curl -fsSL https://get.docker.com | sh -s -- --quiet
205+
service docker start
206+
"
207+
208+
# Create docker wrappers that route to WSL's Docker.
209+
# - .bat for Task's mvdan/sh (Go's exec.LookPath needs a Windows extension)
210+
# - bash script for Git Bash steps
211+
mkdir -p "$HOME/bin"
212+
printf '@wsl -d Ubuntu-24.04 -u root -- docker %%*\r\n' > "$HOME/bin/docker.bat"
213+
cat > "$HOME/bin/docker" << 'WRAPPER'
214+
#!/bin/bash
215+
exec wsl -d Ubuntu-24.04 -u root -- docker "$@"
216+
WRAPPER
217+
chmod +x "$HOME/bin/docker"
218+
echo "$HOME/bin" >> "$GITHUB_PATH"
219+
- name: Initialize generated project
220+
shell: bash
221+
run: |
222+
cd "$RUNNER_TEMP/replace-me"
223+
task -v init
224+
- name: Run unit tests
225+
shell: bash
226+
# Integration tests require Docker (Linux images) which is not
227+
# available on Windows runners; those are covered by the Linux CI job.
228+
run: |
229+
cd "$RUNNER_TEMP/replace-me"
230+
task -v unit-test
231+
- name: Build Docker image
232+
shell: bash
233+
run: |
234+
cd "$RUNNER_TEMP/replace-me"
235+
task -v build
236+
- name: Verify Docker image
237+
shell: bash
238+
run: |
239+
docker run --rm zenable-io/replace-me:latest --version
240+
docker run --rm zenable-io/replace-me:latest --help
241+
- name: Verify zenable CLI
242+
shell: bash
243+
run: |
244+
export PATH="$HOME/.zenable/bin:$PATH"
245+
zenable version
105246
finalizer:
106247
# 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
107248
# 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 +251,7 @@ jobs:
110251
name: Finalize the pipeline
111252
runs-on: ubuntu-24.04
112253
# Keep this aligned with the above jobs
113-
needs: [lint, test]
254+
needs: [lint, test, windows-smoke-test]
114255
if: always() # Ensure it runs even if "needs" fails or is cancelled
115256
steps:
116257
- name: Check for failed or cancelled jobs

.pre-commit-config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ repos:
4343
rev: ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # frozen: v3.90.11
4444
hooks:
4545
- id: trufflehog
46-
# Check the past 2 commits; it's useful to make this go further back than main when running this where main and HEAD are equal
47-
entry: trufflehog git file://. --since-commit main~1 --no-verification --fail
46+
# Resolve the repo root via git-common-dir so this works in both normal repos and worktrees
47+
# (trufflehog doesn't support .git files used by worktrees).
48+
# Guard against detached HEAD (e.g. CI) by falling back to the commit SHA.
49+
language: system
50+
entry: bash -c 'BRANCH=$(git rev-parse --abbrev-ref HEAD); [ "$BRANCH" = "HEAD" ] && BRANCH=$(git rev-parse HEAD); trufflehog git "file://$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" --branch "$BRANCH" --since-commit HEAD~1 --no-verification --fail'
4851
- repo: https://github.com/python-openapi/openapi-spec-validator
4952
rev: a76da2ffdaf698a7fdbd755f89b051fef4c790fd # frozen: 0.8.0b1
5053
hooks:

.pre-commit-hooks.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
- id: zenable-check
33
name: Run a Zenable check on all changed files
44
language: system
5-
entry: uvx zenable-mcp@latest check
5+
entry: zenable check
66
pass_filenames: true

docs/ai-ide-support.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@ The AI-Native Python template automatically configures AI-powered development to
77

88
## Automatic Configuration
99

10-
When you generate a new project, the post-generation hook automatically detects which IDEs and AI assistants you have installed and creates appropriate configuration files:
10+
When you generate a new project, the post-generation hook automatically installs the [Zenable CLI](https://cli.zenable.app) and configures your IDE integrations:
1111

12-
- Model Context Protocol (MCP) configuration for [Zenable](https://zenable.io) and other MCP servers (if supported tools are detected)
13-
- IDE-specific configuration files based on what's installed (Claude, GitHub Copilot, Cursor, etc.)
14-
- Project-specific context and guidelines tailored to your project
12+
**Installation (if the Zenable CLI is not already installed):**
13+
14+
macOS/Linux:
15+
```bash
16+
curl -fsSL https://cli.zenable.app/install.sh | bash
17+
```
18+
19+
Windows:
20+
```powershell
21+
powershell -ExecutionPolicy Bypass -Command "irm https://cli.zenable.app/install.ps1 | iex"
22+
```
23+
24+
**IDE Configuration:**
25+
26+
Once installed, `zenable install` detects which IDEs and AI assistants you have installed and creates appropriate configuration files for 15+ supported IDEs including Claude Code, Cursor, Windsurf, VS Code, GitHub Copilot, and more.
1527

1628
These configurations are dynamically generated based on your installed IDEs and project settings, and include:
1729

0 commit comments

Comments
 (0)