diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ea28949 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,40 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Asia/Kolkata + open-pull-requests-limit: 10 + labels: [dependencies, python] + groups: + python-runtime: + dependency-type: production + python-development: + dependency-type: development + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: "04:15" + timezone: Asia/Kolkata + open-pull-requests-limit: 10 + labels: [dependencies, javascript] + groups: + playwright: + patterns: ["@playwright/*"] + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "04:30" + timezone: Asia/Kolkata + labels: [dependencies, github-actions] + groups: + actions: + patterns: ["*"] + diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..526319c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,16 @@ +area/api: + - changed-files: + - any-glob-to-any-file: "src/llm_router/**" +area/tests: + - changed-files: + - any-glob-to-any-file: ["tests/**", "playwright.config.ts"] +area/ci-cd: + - changed-files: + - any-glob-to-any-file: [".github/**", "Dockerfile", ".dockerignore"] +documentation: + - changed-files: + - any-glob-to-any-file: "**/*.md" +dependencies: + - changed-files: + - any-glob-to-any-file: ["pyproject.toml", "package.json", "package-lock.json"] + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8af031a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Summary + + + +## Verification + +- [ ] Unit tests added or updated +- [ ] Integration tests added or updated +- [ ] Playwright end-to-end coverage added or updated +- [ ] Security, privacy, and fallback behavior reviewed +- [ ] Documentation updated + diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000..f6f182d --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,43 @@ +name: Merge trusted PR after CI + +on: + workflow_run: + workflows: [CI] + types: [completed] + +permissions: + contents: write + pull-requests: write + +jobs: + merge: + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.pull_requests[0] != null + runs-on: ubuntu-latest + steps: + - name: Merge trusted same-repository pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + REPOSITORY: ${{ github.repository }} + run: | + pr="$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER")" + head_repo="$(jq -r '.head.repo.full_name' <<<"$pr")" + login="$(jq -r '.user.login' <<<"$pr")" + association="$(jq -r '.author_association' <<<"$pr")" + draft="$(jq -r '.draft' <<<"$pr")" + + if [[ "$head_repo" != "$REPOSITORY" || "$draft" == "true" ]]; then + echo "Skipping forked or draft pull request." + exit 0 + fi + + if [[ "$login" != "dependabot[bot]" && ! "$association" =~ ^(OWNER|MEMBER|COLLABORATOR)$ ]]; then + echo "Skipping untrusted author association: $association" + exit 0 + fi + + gh pr merge "$PR_NUMBER" --repo "$REPOSITORY" --squash --delete-branch + diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..4802002 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,33 @@ +name: CD + +on: + workflow_run: + workflows: [CI] + types: [completed] + +permissions: + contents: read + +jobs: + release-artifact: + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.event == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + - uses: docker/setup-buildx-action@v3 + - uses: docker/build-push-action@v6 + with: + context: . + outputs: type=oci,dest=${{ runner.temp }}/local-llm-router.tar + tags: local-llm-router:${{ github.event.workflow_run.head_sha }} + - uses: actions/upload-artifact@v4 + with: + name: local-llm-router-${{ github.event.workflow_run.head_sha }} + path: ${{ runner.temp }}/local-llm-router.tar + retention-days: 14 + diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..c0d8574 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,29 @@ +name: PR labeler + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + label: + runs-on: ubuntu-latest + steps: + - name: Ensure managed labels exist + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPOSITORY: ${{ github.repository }} + run: | + gh label create "area/api" --color "1D76DB" --force --repo "$REPOSITORY" + gh label create "area/tests" --color "5319E7" --force --repo "$REPOSITORY" + gh label create "area/ci-cd" --color "0052CC" --force --repo "$REPOSITORY" + gh label create "documentation" --color "0075CA" --force --repo "$REPOSITORY" + gh label create "dependencies" --color "0366D6" --force --repo "$REPOSITORY" + gh label create "python" --color "3572A5" --force --repo "$REPOSITORY" + gh label create "javascript" --color "F1E05A" --force --repo "$REPOSITORY" + gh label create "github-actions" --color "2088FF" --force --repo "$REPOSITORY" + - uses: actions/labeler@v6 diff --git a/README.md b/README.md index 7bbe0b2..6af6b3b 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,15 @@ docker build -t local-llm-router:dev . CI reports unit/static analysis, integration, and Playwright end-to-end tests separately. The release-image build starts only after all three test layers pass. +After a successful CI run on `main`, CD creates a versioned OCI image artifact. Registry +or cluster publication remains disabled until an explicit deployment destination is +configured. Dependabot maintains Python, npm, and GitHub Actions dependencies, while the +PR labeler classifies API, test, CI/CD, documentation, and dependency changes. + +Successful PR CI runs are merged automatically only for trusted same-repository authors +and Dependabot. Forks, drafts, and untrusted author associations are deliberately skipped; +repository branch-protection and review requirements continue to apply. + ## Runtime settings All settings use the `ROUTER_` prefix.