Initial reusable pipeline (ci, security-scan, ai-review)
This commit is contained in:
commit
9adebedf02
2 changed files with 175 additions and 0 deletions
151
.github/workflows/repo-pipeline.yml
vendored
Normal file
151
.github/workflows/repo-pipeline.yml
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
name: repo-pipeline
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
lint_command:
|
||||
description: Command that runs lint checks
|
||||
required: false
|
||||
type: string
|
||||
default: |
|
||||
if which swiftlint > /dev/null; then
|
||||
swiftlint --strict
|
||||
else
|
||||
brew install swiftlint
|
||||
swiftlint --strict
|
||||
fi
|
||||
build_command:
|
||||
description: Command that builds the project
|
||||
required: false
|
||||
type: string
|
||||
default: |
|
||||
xcodebuild build \
|
||||
-project CamperLogBook.xcodeproj \
|
||||
-scheme CamperLogBook \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
test_command:
|
||||
description: Command that executes tests
|
||||
required: false
|
||||
type: string
|
||||
default: |
|
||||
xcodebuild test \
|
||||
-project CamperLogBook.xcodeproj \
|
||||
-scheme CamperLogBook \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: ci
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: Checkout caller repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Lint
|
||||
shell: bash
|
||||
run: ${{ inputs.lint_command }}
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: ${{ inputs.build_command }}
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: ${{ inputs.test_command }}
|
||||
|
||||
security-scan:
|
||||
name: security-scan
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Checkout caller repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Semgrep
|
||||
uses: returntocorp/semgrep-action@v1
|
||||
with:
|
||||
config: p/default
|
||||
|
||||
- name: Dependency Review
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/dependency-review-action@v4
|
||||
|
||||
ai-review:
|
||||
name: ai-review
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
steps:
|
||||
- name: Validate ChatGPT and Claude review status
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pull_number = context.payload.pull_request
|
||||
? context.payload.pull_request.number
|
||||
: context.payload.issue?.number;
|
||||
|
||||
if (!pull_number) {
|
||||
core.setFailed("No pull request context found for ai-review validation.");
|
||||
return;
|
||||
}
|
||||
|
||||
const pr = await github.rest.pulls.get({ owner, repo, pull_number });
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pull_number,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const bodyParts = [pr.data.body || "", ...comments.map(c => c.body || "")];
|
||||
const allText = bodyParts.join("\n\n").toLowerCase();
|
||||
|
||||
const hasSection = (name) => allText.includes(`### ${name}`.toLowerCase());
|
||||
const hasPass = (name) => {
|
||||
const regex = new RegExp(`###\\s*${name}[\\s\\S]*?dod status\\s*:\\s*pass`, "i");
|
||||
return bodyParts.some(part => regex.test(part || ""));
|
||||
};
|
||||
const hasZeroBlocker = (name) => {
|
||||
const regex = new RegExp(`###\\s*${name}[\\s\\S]*?blocker\\s*:\\s*0`, "i");
|
||||
return bodyParts.some(part => regex.test(part || ""));
|
||||
};
|
||||
const hasZeroMajor = (name) => {
|
||||
const regex = new RegExp(`###\\s*${name}[\\s\\S]*?major\\s*:\\s*0`, "i");
|
||||
return bodyParts.some(part => regex.test(part || ""));
|
||||
};
|
||||
|
||||
const checks = [
|
||||
{ name: "ChatGPT", section: hasSection("ChatGPT"), pass: hasPass("ChatGPT"), blocker: hasZeroBlocker("ChatGPT"), major: hasZeroMajor("ChatGPT") },
|
||||
{ name: "Claude", section: hasSection("Claude"), pass: hasPass("Claude"), blocker: hasZeroBlocker("Claude"), major: hasZeroMajor("Claude") }
|
||||
];
|
||||
|
||||
const failures = checks.flatMap(c => {
|
||||
const missing = [];
|
||||
if (!c.section) missing.push("missing section");
|
||||
if (!c.pass) missing.push("missing `DoD status: PASS`");
|
||||
if (!c.blocker) missing.push("missing `Blocker: 0`");
|
||||
if (!c.major) missing.push("missing `Major: 0`");
|
||||
return missing.length ? [`${c.name}: ${missing.join(", ")}`] : [];
|
||||
});
|
||||
|
||||
if (failures.length) {
|
||||
core.setFailed(`AI review gate failed:\n- ${failures.join("\n- ")}`);
|
||||
} else {
|
||||
core.info("AI review gate passed.");
|
||||
}
|
||||
24
README.md
Normal file
24
README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# vanity-dev-engine
|
||||
|
||||
Shared CI/Security/AI reusable workflows for Vanity ecosystem repositories.
|
||||
|
||||
## Reusable Workflow
|
||||
|
||||
Use from another repository:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
use-vanity-dev-engine:
|
||||
uses: OliverGiertz/vanity-dev-engine/.github/workflows/repo-pipeline.yml@v1
|
||||
secrets: inherit
|
||||
```
|
||||
|
||||
Optional inputs:
|
||||
|
||||
- `lint_command`
|
||||
- `build_command`
|
||||
- `test_command`
|
||||
|
||||
Optional control:
|
||||
|
||||
- set repository variable `USE_VANITY_DEV_ENGINE=true` in consumer repos.
|
||||
Loading…
Add table
Add a link
Reference in a new issue