Compare commits

..

No commits in common. "main" and "v1.1" have entirely different histories.
main ... v1.1

2 changed files with 105 additions and 215 deletions

View file

@ -8,35 +8,75 @@ on:
required: false required: false
type: string type: string
default: ios default: ios
xcode_project:
description: Xcode project path for ios repos
required: false
type: string
default: CamperLogBook.xcodeproj
xcode_scheme:
description: Xcode scheme for ios repos
required: false
type: string
default: CamperLogBook
lint_command: lint_command:
description: Optional lint command override description: Command that runs lint checks
required: false required: false
type: string type: string
default: "" default: ""
build_command: build_command:
description: Optional build command override description: Command that builds the project
required: false required: false
type: string type: string
default: "" default: ""
test_command: test_command:
description: Optional test command override description: Command that executes tests
required: false required: false
type: string type: string
default: "" default: ""
jobs: jobs:
ci: ci-ios:
name: ci name: ci
if: ${{ inputs.repo_type == 'ios' }}
runs-on: macos-15
steps:
- name: Checkout caller repository
uses: actions/checkout@v4
- name: Resolve commands
id: resolve
shell: bash
run: |
set -euo pipefail
lint="${{ inputs.lint_command }}"
build="${{ inputs.build_command }}"
test="${{ inputs.test_command }}"
if [ -z "$lint" ]; then
lint="if which swiftlint > /dev/null; then swiftlint --strict; else brew install swiftlint && swiftlint --strict; fi"
fi
if [ -z "$build" ]; then
build="xcodebuild build -project CamperLogBook.xcodeproj -scheme CamperLogBook -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' CODE_SIGNING_ALLOWED=NO"
fi
if [ -z "$test" ]; then
test="xcodebuild test -project CamperLogBook.xcodeproj -scheme CamperLogBook -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' CODE_SIGNING_ALLOWED=NO"
fi
{
echo "lint<<EOF"
echo "$lint"
echo "EOF"
echo "build<<EOF"
echo "$build"
echo "EOF"
echo "test<<EOF"
echo "$test"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Lint
shell: bash
run: ${{ steps.resolve.outputs.lint }}
- name: Build
shell: bash
run: ${{ steps.resolve.outputs.build }}
- name: Test
shell: bash
run: ${{ steps.resolve.outputs.test }}
ci-generic:
name: ci
if: ${{ inputs.repo_type != 'ios' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout caller repository - name: Checkout caller repository
@ -47,87 +87,64 @@ jobs:
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
repo_type='${{ inputs.repo_type }}' repo_type="${{ inputs.repo_type }}"
lint='${{ inputs.lint_command }}' lint="${{ inputs.lint_command }}"
build='${{ inputs.build_command }}' build="${{ inputs.build_command }}"
test='${{ inputs.test_command }}' test="${{ inputs.test_command }}"
xcode_project='${{ inputs.xcode_project }}'
xcode_scheme='${{ inputs.xcode_scheme }}'
if [ -z "$lint" ]; then if [ -z "$lint" ]; then
case "$repo_type" in case "$repo_type" in
ios) node) lint="npm run lint --if-present" ;;
lint="echo 'No default ios lint command on ubuntu runner. Set lint_command override if needed.'" python) lint="python -m pip install -U pip && pip install ruff && ruff check ." ;;
;; custom) lint="" ;;
node) *) lint="" ;;
lint="npm run lint --if-present"
;;
python)
lint="python3 -m pip install -U pip && python3 -m pip install ruff && ruff check ."
;;
*)
lint="echo 'No lint default for repo_type=$repo_type'"
;;
esac esac
fi fi
if [ -z "$build" ]; then if [ -z "$build" ]; then
case "$repo_type" in case "$repo_type" in
ios) node) build="npm run build --if-present" ;;
build="echo 'No default ios build command on ubuntu runner. Use Xcode Cloud or set build_command override.'" python) build="" ;;
;; custom) build="" ;;
node) *) build="" ;;
build="npm run build --if-present"
;;
python)
build="echo 'No default build step for python'"
;;
*)
build="echo 'No build default for repo_type=$repo_type'"
;;
esac esac
fi fi
if [ -z "$test" ]; then if [ -z "$test" ]; then
case "$repo_type" in case "$repo_type" in
ios) node) test="npm test --if-present" ;;
test="echo 'No default ios test command on ubuntu runner. Use Xcode Cloud or set test_command override.'" python) test="pytest -q" ;;
;; custom) test="" ;;
node) *) test="" ;;
test="npm test --if-present"
;;
python)
test="pytest -q"
;;
*)
test="echo 'No test default for repo_type=$repo_type'"
;;
esac esac
fi fi
{ {
echo "lint=$lint" echo "lint<<EOF"
echo "build=$build" echo "$lint"
echo "test=$test" echo "EOF"
echo "build<<EOF"
echo "$build"
echo "EOF"
echo "test<<EOF"
echo "$test"
echo "EOF"
} >> "$GITHUB_OUTPUT" } >> "$GITHUB_OUTPUT"
- name: Lint - name: Lint
if: ${{ steps.resolve.outputs.lint != '' }}
shell: bash shell: bash
run: | run: ${{ steps.resolve.outputs.lint }}
set -euo pipefail
eval "${{ steps.resolve.outputs.lint }}"
- name: Build - name: Build
if: ${{ steps.resolve.outputs.build != '' }}
shell: bash shell: bash
run: | run: ${{ steps.resolve.outputs.build }}
set -euo pipefail
eval "${{ steps.resolve.outputs.build }}"
- name: Test - name: Test
if: ${{ steps.resolve.outputs.test != '' }}
shell: bash shell: bash
run: | run: ${{ steps.resolve.outputs.test }}
set -euo pipefail
eval "${{ steps.resolve.outputs.test }}"
security-scan: security-scan:
name: security-scan name: security-scan
@ -142,144 +159,28 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install gitleaks - name: Gitleaks
shell: bash uses: gitleaks/gitleaks-action@v2
run: | env:
set -euo pipefail GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_VERSION=$(curl -s https://api.github.com/repos/gitleaks/gitleaks/releases/latest | grep '"tag_name"' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar -xz
sudo mv gitleaks /usr/local/bin/gitleaks
gitleaks version
- name: Gitleaks scan - name: Semgrep
shell: bash uses: returntocorp/semgrep-action@v1
run: | with:
set -euo pipefail config: p/default
gitleaks detect --source . --no-git --verbose
- name: Install Semgrep
shell: bash
run: |
set -euo pipefail
python3 -m pip install --upgrade pip
python3 -m pip install semgrep
semgrep --version
- name: Semgrep scan
shell: bash
run: |
set -euo pipefail
semgrep --config p/default --error
- name: Dependency Review - name: Dependency Review
if: ${{ github.event_name == 'pull_request' }} if: github.event_name == 'pull_request'
continue-on-error: true
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v4
ai-review: ai-review:
name: ai-review name: ai-review
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: read
issues: write issues: read
steps: steps:
# Claude review is performed locally by Claude Code before the PR is merged.
# See CLAUDE.md in the repository for the process.
- name: Generate ChatGPT review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
if [ -z "${OPENAI_API_KEY:-}" ]; then
echo "::notice::OPENAI_API_KEY not set ChatGPT review skipped"
exit 0
fi
python3 << 'PYEOF'
import os, json, urllib.request
token = os.environ["GITHUB_TOKEN"]
repo = os.environ["REPO"]
pr_num = os.environ["PR_NUMBER"]
headers_gh = {"Authorization": f"Bearer {token}", "User-Agent": "vanity-dev-engine",
"Accept": "application/vnd.github.v3+json"}
# Skip if ChatGPT review already exists
req = urllib.request.Request(
f"https://api.github.com/repos/{repo}/issues/{pr_num}/comments?per_page=100",
headers=headers_gh)
with urllib.request.urlopen(req) as r:
comments = json.loads(r.read())
if any("### ChatGPT" in (c.get("body") or "") for c in comments):
print("ChatGPT review already present skipping generation.")
raise SystemExit(0)
# Fetch PR diff
req_diff = urllib.request.Request(
f"https://api.github.com/repos/{repo}/pulls/{pr_num}",
headers={**headers_gh, "Accept": "application/vnd.github.v3.diff"})
with urllib.request.urlopen(req_diff) as r:
diff = r.read().decode("utf-8", errors="replace")[:12000]
# Fetch PR body
req_pr = urllib.request.Request(
f"https://api.github.com/repos/{repo}/pulls/{pr_num}", headers=headers_gh)
with urllib.request.urlopen(req_pr) as r:
pr_data = json.loads(r.read())
pr_body = (pr_data.get("body") or "")[:800]
prompt = f"""You are a senior iOS Swift developer reviewing a pull request.
Analyse the changes carefully and write a concise code review.
PR title: {os.environ["PR_TITLE"]}
PR description: {pr_body}
Git diff (may be truncated):
{diff}
Reply with EXACTLY this structure no deviations:
### ChatGPT
DoD status: PASS
Blocker: 0
Major: 0
<your review here cover code quality, correctness, Swift best practices,
potential bugs, and suggestions. Be specific and constructive.>
Only set DoD status to FAIL or raise Blocker/Major above 0 when you find
real defects that must be fixed before merging."""
payload = json.dumps({
"model": "gpt-4o",
"max_tokens": 1500,
"messages": [{"role": "user", "content": prompt}]
}).encode()
req_ai = urllib.request.Request(
"https://api.openai.com/v1/chat/completions", data=payload,
headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
"content-type": "application/json"})
with urllib.request.urlopen(req_ai) as r:
review = json.loads(r.read())["choices"][0]["message"]["content"]
# Post comment
body_payload = json.dumps({"body": review}).encode()
req_post = urllib.request.Request(
f"https://api.github.com/repos/{repo}/issues/{pr_num}/comments",
data=body_payload,
headers={**headers_gh, "Content-Type": "application/json"})
with urllib.request.urlopen(req_post) as r:
result = json.loads(r.read())
print(f"ChatGPT review posted: {result['html_url']}")
PYEOF
- name: Validate ChatGPT and Claude review status - name: Validate ChatGPT and Claude review status
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:

View file

@ -9,30 +9,19 @@ Use from another repository:
```yaml ```yaml
jobs: jobs:
use-vanity-dev-engine: use-vanity-dev-engine:
uses: OliverGiertz/vanity-dev-engine/.github/workflows/repo-pipeline.yml@v1.5 uses: OliverGiertz/vanity-dev-engine/.github/workflows/repo-pipeline.yml@v1.1
secrets: inherit
with: with:
repo_type: ios repo_type: ios
xcode_project: CamperLogBook.xcodeproj
xcode_scheme: CamperLogBook
``` ```
## Inputs Optional inputs:
- `repo_type`: `ios`, `node`, `python`, `custom` - `repo_type` (`ios`, `node`, `python`, `custom`)
- `xcode_project`: Xcode project path for iOS repos - `lint_command`
- `xcode_scheme`: Xcode scheme for iOS repos - `build_command`
- `lint_command`: optional override - `test_command`
- `build_command`: optional override
- `test_command`: optional override
## Produced checks Optional control:
- `use-vanity-dev-engine / ci` - set repository variable `USE_VANITY_DEV_ENGINE=true` in consumer repos.
- `use-vanity-dev-engine / security-scan`
- `use-vanity-dev-engine / ai-review`
## Consumer toggle
Set repository variable `USE_VANITY_DEV_ENGINE=true` in consumer repos to activate central execution.
Note: The default CI runner is `ubuntu-latest`. For iOS repositories, provide explicit `build_command` and `test_command` overrides (or use Xcode Cloud for build/test).