mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-10-09 03:17:54 +00:00
- #805: insert missing skills/ segment in all per-skill install commands across 5 domain READMEs (engineering-team, project-management, marketing-skill, c-level-advisor, ra-qm-team); every path now resolves to a real directory - #806: pr-review-expert SKILL.md curl examples now pass Jira/Linear credentials via stdin curl config (-K -) instead of argv, with a netrc note, so tokens never reach the process list or shell history - #807: implement the documented interfaces for the three senior-devops scripts (terraform_scaffolder: aws/gcp/azure module skeletons with optional terraform fmt/validate; pipeline_generator: GitHub Actions / CircleCI configs with build,test,security,deploy stages and runtime detection; deployment_manager: blue-green/rolling manifests + kubectl runbooks with deploy/rollback/analyze subcommands); align SKILL.md - #807: ci-cd-pipeline-builder stack_detector now detects Terraform and Docker stacks and emits their lint/test/build commands; downstream pipeline_generator gains a generic job for non-node/python/go stacks - #748: sync-vibe-skills.py defaults to a flat layout one level below ~/.vibe/skills (the only depth Vibe discovers), with collision-safe naming and a --nested flag for the legacy namespaced layout - #785: new scripts/sync-codebuff-skills.py syncs all skills into Codebuff's ~/.agents/skills using the same flat-layout machinery https://claude.ai/code/session_01CUWsrUNZP9jpxvAwq67UiT
238 lines
7.6 KiB
Python
Executable file
238 lines
7.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Pipeline Generator
|
|
Scaffolds CI/CD pipeline configurations for GitHub Actions or CircleCI with
|
|
build, test, security, and deploy stages. Detects node/python/go projects to
|
|
pick sensible default commands.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, List
|
|
|
|
VALID_STAGES = ["build", "test", "security", "deploy"]
|
|
|
|
|
|
def detect_runtime(project: Path) -> str:
|
|
if (project / "package.json").exists():
|
|
return "node"
|
|
if (project / "pyproject.toml").exists() or (project / "requirements.txt").exists():
|
|
return "python"
|
|
if (project / "go.mod").exists():
|
|
return "go"
|
|
return "generic"
|
|
|
|
|
|
RUNTIME_COMMANDS: Dict[str, Dict[str, List[str]]] = {
|
|
"node": {
|
|
"setup": ["npm ci"],
|
|
"build": ["npm run build --if-present"],
|
|
"test": ["npm run lint --if-present", "npm test"],
|
|
},
|
|
"python": {
|
|
"setup": ["pip install -r requirements.txt"],
|
|
"build": ["python -m compileall ."],
|
|
"test": ["python -m ruff check .", "python -m pytest"],
|
|
},
|
|
"go": {
|
|
"setup": ["go mod download"],
|
|
"build": ["go build ./..."],
|
|
"test": ["go vet ./...", "go test ./..."],
|
|
},
|
|
"generic": {
|
|
"setup": ["echo 'add setup commands here'"],
|
|
"build": ["echo 'add build commands here'"],
|
|
"test": ["echo 'add test commands here'"],
|
|
},
|
|
}
|
|
|
|
GITHUB_SETUP_STEPS = {
|
|
"node": """ - uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'""",
|
|
"python": """ - uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
cache: 'pip'""",
|
|
"go": """ - uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.22'""",
|
|
"generic": "",
|
|
}
|
|
|
|
CIRCLECI_IMAGES = {
|
|
"node": "cimg/node:20.11",
|
|
"python": "cimg/python:3.12",
|
|
"go": "cimg/go:1.22",
|
|
"generic": "cimg/base:current",
|
|
}
|
|
|
|
|
|
def github_job(name: str, runtime: str, commands: List[str], needs: List[str],
|
|
extra: str = "") -> str:
|
|
lines = [f" {name}:"]
|
|
if needs:
|
|
lines.append(f" needs: [{', '.join(needs)}]")
|
|
if name == "deploy":
|
|
lines.append(" if: github.ref == 'refs/heads/main'")
|
|
lines.append(" runs-on: ubuntu-latest")
|
|
lines.append(" steps:")
|
|
lines.append(" - uses: actions/checkout@v4")
|
|
setup = GITHUB_SETUP_STEPS[runtime]
|
|
if setup and name in ("build", "test"):
|
|
lines.append(setup)
|
|
for cmd in RUNTIME_COMMANDS[runtime]["setup"]:
|
|
lines.append(f" - run: {cmd}")
|
|
for cmd in commands:
|
|
lines.append(f" - run: {cmd}")
|
|
if extra:
|
|
lines.append(extra)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def generate_github(stages: List[str], runtime: str) -> str:
|
|
jobs = []
|
|
prev: List[str] = []
|
|
for stage in stages:
|
|
if stage == "build":
|
|
jobs.append(github_job("build", runtime, RUNTIME_COMMANDS[runtime]["build"], prev))
|
|
elif stage == "test":
|
|
jobs.append(github_job("test", runtime, RUNTIME_COMMANDS[runtime]["test"], prev))
|
|
elif stage == "security":
|
|
extra = """ - name: Run Trivy filesystem scan
|
|
uses: aquasecurity/trivy-action@master
|
|
with:
|
|
scan-type: 'fs'
|
|
scan-ref: '.'
|
|
severity: 'CRITICAL,HIGH'
|
|
exit-code: '1'"""
|
|
jobs.append(github_job("security", runtime, [], prev, extra=extra))
|
|
elif stage == "deploy":
|
|
extra = """ - name: Build and push image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
push: true
|
|
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
|
|
- name: Deploy
|
|
run: echo 'replace with your deploy command (e.g. aws ecs update-service / kubectl apply)'"""
|
|
jobs.append(github_job("deploy", runtime, [], prev, extra=extra))
|
|
prev = [stage]
|
|
|
|
return f"""name: CI/CD Pipeline
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
{chr(10).join(jobs)}
|
|
"""
|
|
|
|
|
|
def generate_circleci(stages: List[str], runtime: str) -> str:
|
|
image = CIRCLECI_IMAGES[runtime]
|
|
job_blocks = []
|
|
workflow_jobs = []
|
|
prev = None
|
|
for stage in stages:
|
|
if stage == "security":
|
|
commands = ["echo 'add security scanner here (e.g. trivy fs .)'"]
|
|
elif stage == "deploy":
|
|
commands = ["echo 'replace with your deploy command'"]
|
|
else:
|
|
commands = RUNTIME_COMMANDS[runtime]["setup"] + RUNTIME_COMMANDS[runtime][stage]
|
|
steps = "\n".join(f" - run: {cmd}" for cmd in commands)
|
|
job_blocks.append(f""" {stage}:
|
|
docker:
|
|
- image: {image}
|
|
steps:
|
|
- checkout
|
|
{steps}""")
|
|
if prev:
|
|
workflow_jobs.append(f""" - {stage}:
|
|
requires: [{prev}]""")
|
|
else:
|
|
workflow_jobs.append(f" - {stage}")
|
|
prev = stage
|
|
|
|
return f"""version: 2.1
|
|
|
|
jobs:
|
|
{chr(10).join(job_blocks)}
|
|
|
|
workflows:
|
|
ci:
|
|
jobs:
|
|
{chr(10).join(workflow_jobs)}
|
|
"""
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate a CI/CD pipeline config for GitHub Actions or CircleCI."
|
|
)
|
|
parser.add_argument("target", help="Project path to scaffold the pipeline into")
|
|
parser.add_argument("--platform", default="github", choices=["github", "circleci"],
|
|
help="CI platform (default: github)")
|
|
parser.add_argument("--stages", default="build,test,deploy",
|
|
help=f"Comma-separated stages from: {','.join(VALID_STAGES)}")
|
|
parser.add_argument("--force", action="store_true", help="Overwrite an existing config")
|
|
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
|
|
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
|
parser.add_argument("--output", "-o", help="Write JSON results to this file")
|
|
args = parser.parse_args()
|
|
|
|
project = Path(args.target)
|
|
if not project.is_dir():
|
|
print(f"❌ Error: target path is not a directory: {project}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
stages = [s.strip() for s in args.stages.split(",") if s.strip()]
|
|
invalid = [s for s in stages if s not in VALID_STAGES]
|
|
if invalid or not stages:
|
|
print(f"❌ Error: invalid stages {invalid or '(none)'}; "
|
|
f"choose from {','.join(VALID_STAGES)}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
runtime = detect_runtime(project)
|
|
if args.verbose:
|
|
print(f"📊 Detected runtime: {runtime}")
|
|
|
|
if args.platform == "github":
|
|
config = generate_github(stages, runtime)
|
|
config_path = project / ".github" / "workflows" / "ci.yml"
|
|
else:
|
|
config = generate_circleci(stages, runtime)
|
|
config_path = project / ".circleci" / "config.yml"
|
|
|
|
if config_path.exists() and not args.force:
|
|
print(f"❌ Error: {config_path} already exists (use --force to overwrite)", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
config_path.write_text(config, encoding="utf-8")
|
|
print(f"✅ Pipeline written: {config_path} (platform={args.platform}, "
|
|
f"stages={','.join(stages)}, runtime={runtime})")
|
|
|
|
results = {
|
|
"status": "success",
|
|
"platform": args.platform,
|
|
"stages": stages,
|
|
"runtime": runtime,
|
|
"config_path": str(config_path),
|
|
}
|
|
if args.json or args.output:
|
|
output = json.dumps(results, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(output, encoding="utf-8")
|
|
print(f"Results written to {args.output}")
|
|
else:
|
|
print(output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|