Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa2b6b7b4a | ||
|
|
de75e57c5e | ||
|
|
967788e045 | ||
|
|
ec14acb46e | ||
| 431a1d2543 | |||
| e96501e937 | |||
| 4628673afd | |||
| 460883dfcb | |||
| ea50777645 |
2 changed files with 249 additions and 45 deletions
264
.github/workflows/repo-pipeline.yml
vendored
264
.github/workflows/repo-pipeline.yml
vendored
|
|
@ -3,57 +3,131 @@ name: repo-pipeline
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
|
repo_type:
|
||||||
|
description: Repository type (ios, node, python, custom)
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
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: Command that runs lint checks
|
description: Optional lint command override
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: |
|
default: ""
|
||||||
if which swiftlint > /dev/null; then
|
|
||||||
swiftlint --strict
|
|
||||||
else
|
|
||||||
brew install swiftlint
|
|
||||||
swiftlint --strict
|
|
||||||
fi
|
|
||||||
build_command:
|
build_command:
|
||||||
description: Command that builds the project
|
description: Optional build command override
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: |
|
default: ""
|
||||||
xcodebuild build \
|
|
||||||
-project CamperLogBook.xcodeproj \
|
|
||||||
-scheme CamperLogBook \
|
|
||||||
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
|
|
||||||
CODE_SIGNING_ALLOWED=NO
|
|
||||||
test_command:
|
test_command:
|
||||||
description: Command that executes tests
|
description: Optional test command override
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: |
|
default: ""
|
||||||
xcodebuild test \
|
|
||||||
-project CamperLogBook.xcodeproj \
|
|
||||||
-scheme CamperLogBook \
|
|
||||||
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
|
|
||||||
CODE_SIGNING_ALLOWED=NO
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
name: ci
|
name: ci
|
||||||
runs-on: macos-15
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout caller repository
|
- name: Checkout caller repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Resolve commands
|
||||||
|
id: resolve
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
repo_type='${{ inputs.repo_type }}'
|
||||||
|
lint='${{ inputs.lint_command }}'
|
||||||
|
build='${{ inputs.build_command }}'
|
||||||
|
test='${{ inputs.test_command }}'
|
||||||
|
xcode_project='${{ inputs.xcode_project }}'
|
||||||
|
xcode_scheme='${{ inputs.xcode_scheme }}'
|
||||||
|
|
||||||
|
if [ -z "$lint" ]; then
|
||||||
|
case "$repo_type" in
|
||||||
|
ios)
|
||||||
|
lint="echo 'No default ios lint command on ubuntu runner. Set lint_command override if needed.'"
|
||||||
|
;;
|
||||||
|
node)
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$build" ]; then
|
||||||
|
case "$repo_type" in
|
||||||
|
ios)
|
||||||
|
build="echo 'No default ios build command on ubuntu runner. Use Xcode Cloud or set build_command override.'"
|
||||||
|
;;
|
||||||
|
node)
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$test" ]; then
|
||||||
|
case "$repo_type" in
|
||||||
|
ios)
|
||||||
|
test="echo 'No default ios test command on ubuntu runner. Use Xcode Cloud or set test_command override.'"
|
||||||
|
;;
|
||||||
|
node)
|
||||||
|
test="npm test --if-present"
|
||||||
|
;;
|
||||||
|
python)
|
||||||
|
test="pytest -q"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
test="echo 'No test default for repo_type=$repo_type'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "lint=$lint"
|
||||||
|
echo "build=$build"
|
||||||
|
echo "test=$test"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ${{ inputs.lint_command }}
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
eval "${{ steps.resolve.outputs.lint }}"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ${{ inputs.build_command }}
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
eval "${{ steps.resolve.outputs.build }}"
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ${{ inputs.test_command }}
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
eval "${{ steps.resolve.outputs.test }}"
|
||||||
|
|
||||||
security-scan:
|
security-scan:
|
||||||
name: security-scan
|
name: security-scan
|
||||||
|
|
@ -68,28 +142,144 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Gitleaks
|
- name: Install gitleaks
|
||||||
uses: gitleaks/gitleaks-action@v2
|
shell: bash
|
||||||
env:
|
run: |
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
set -euo pipefail
|
||||||
|
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: Semgrep
|
- name: Gitleaks scan
|
||||||
uses: returntocorp/semgrep-action@v1
|
shell: bash
|
||||||
with:
|
run: |
|
||||||
config: p/default
|
set -euo pipefail
|
||||||
|
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: read
|
pull-requests: write
|
||||||
issues: read
|
issues: write
|
||||||
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:
|
||||||
|
|
|
||||||
30
README.md
30
README.md
|
|
@ -9,16 +9,30 @@ 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
|
uses: OliverGiertz/vanity-dev-engine/.github/workflows/repo-pipeline.yml@v1.5
|
||||||
secrets: inherit
|
with:
|
||||||
|
repo_type: ios
|
||||||
|
xcode_project: CamperLogBook.xcodeproj
|
||||||
|
xcode_scheme: CamperLogBook
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional inputs:
|
## Inputs
|
||||||
|
|
||||||
- `lint_command`
|
- `repo_type`: `ios`, `node`, `python`, `custom`
|
||||||
- `build_command`
|
- `xcode_project`: Xcode project path for iOS repos
|
||||||
- `test_command`
|
- `xcode_scheme`: Xcode scheme for iOS repos
|
||||||
|
- `lint_command`: optional override
|
||||||
|
- `build_command`: optional override
|
||||||
|
- `test_command`: optional override
|
||||||
|
|
||||||
Optional control:
|
## Produced checks
|
||||||
|
|
||||||
- set repository variable `USE_VANITY_DEV_ENGINE=true` in consumer repos.
|
- `use-vanity-dev-engine / ci`
|
||||||
|
- `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).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue