diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..d60f452 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + target: 70% + threshold: 2% + patch: + default: + target: 60% + +comment: + layout: "reach,diff,flags,files" + behavior: default + require_changes: true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..dc4a6c4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global owner — auto-requested as reviewer on every PR +* @ANSCoder diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 610048a..e712a96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,24 @@ jobs: - name: Build run: swift build -c release - - name: Test - run: swift test + - name: Test with coverage + run: swift test --enable-code-coverage + + - name: Generate coverage report + run: | + xcrun llvm-cov export \ + .build/debug/NexioPackageTests.xctest/Contents/MacOS/NexioPackageTests \ + -instr-profile .build/debug/codecov/default.profdata \ + -ignore-filename-regex ".build|Tests" \ + -format lcov > coverage.lcov + + - name: Upload to Codecov + uses: codecov/codecov-action@v4 + with: + files: coverage.lcov + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # ── Swift 6 strict concurrency ──────────────────────────────────────────── swift6: diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 0000000..17e46b8 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,29 @@ +name: Danger + +on: + pull_request: + branches: [main] + +jobs: + danger: + name: Danger Swift + runs-on: macos-15 + if: github.event_name == 'pull_request' + permissions: + pull-requests: write + issues: write + statuses: write + + steps: + - uses: actions/checkout@v4 + + - name: Select Xcode 16.3 + run: sudo xcode-select -s /Applications/Xcode_16.3.app + + - name: Install Danger Swift + run: brew install danger/tap/danger-swift + + - name: Run Danger + run: danger-swift ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dangerfile.swift b/Dangerfile.swift new file mode 100644 index 0000000..2123a0a --- /dev/null +++ b/Dangerfile.swift @@ -0,0 +1,38 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + +import Danger + +let danger = Danger() + +// Warn on large PRs +let bigPRThreshold = 400 +let additions = danger.github.pullRequest.additions ?? 0 +let deletions = danger.github.pullRequest.deletions ?? 0 +if additions + deletions > bigPRThreshold { + warn("PR is large (\(additions + deletions) lines). Consider splitting.") +} + +// Require CHANGELOG update for non-trivial PRs +let hasChangelogUpdate = danger.git.modifiedFiles.contains("CHANGELOG.md") + || danger.git.createdFiles.contains("CHANGELOG.md") +let isTrivial = danger.github.pullRequest.title.contains("[trivial]") + || danger.github.pullRequest.title.contains("docs:") + || danger.github.pullRequest.title.contains("chore:") +if !hasChangelogUpdate && !isTrivial { + warn("No `CHANGELOG.md` update. Add an entry under `[Unreleased]` or prefix title with `chore:`/`docs:` to skip.") +} + +// Require PR description +let bodyLength = danger.github.pullRequest.body?.count ?? 0 +if bodyLength < 20 { + fail("PR description is too short. Explain what changed and why.") +} + +// Warn if tests were not touched when source was modified +let sourceChanged = danger.git.modifiedFiles.contains { $0.hasPrefix("Sources/") } +let testsChanged = danger.git.modifiedFiles.contains { $0.hasPrefix("Tests/") } + || danger.git.createdFiles.contains { $0.hasPrefix("Tests/") } +if sourceChanged && !testsChanged { + warn("Source files changed but no test files modified. Add or update tests where appropriate.") +} diff --git a/README.md b/README.md index 0480d5d..68a3b93 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ [![Platforms](https://img.shields.io/badge/Platforms-iOS%2016%20·%20macOS%2013%20·%20watchOS%209%20·%20tvOS%2016-blue.svg)](https://developer.apple.com) [![SPM](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://swift.org/package-manager) [![License](https://img.shields.io/badge/License-MIT-lightgrey.svg)](LICENSE) +[![CI](https://github.com/ANSCoder/Nexio/actions/workflows/ci.yml/badge.svg)](https://github.com/ANSCoder/Nexio/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/ANSCoder/Nexio/branch/main/graph/badge.svg)](https://codecov.io/gh/ANSCoder/Nexio) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FANSCoder%2FNexio%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/ANSCoder/Nexio) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FANSCoder%2FNexio%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/ANSCoder/Nexio) Typed JSON requests · Auth strategies · Retry with backoff · Interceptor pipeline · SwiftUI image loading · Zero dependencies