diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..568b2cd --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @AuthPlane/authplane-eng diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..1a15cbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,68 @@ +--- +name: Bug Report +about: Report a bug in the Authplane .NET SDK or an adapter +title: "[Bug] " +labels: bug +assignees: "" +--- + +## Description + +A clear description of the bug. + +## Affected Package + +- [ ] `Authplane.Sdk` (core) +- [ ] `Authplane.Mcp` + +## Steps to Reproduce + +1. Add the package reference to your `.csproj` ... +2. Configure / call ... +3. Observe ... + +Minimal reproducible code snippet: + +```csharp +// paste here +``` + +## Expected Behavior + +What you expected to happen. + +## Actual Behavior + +What actually happened. Include stack trace, HTTP status codes, and relevant log output. + +## Environment + +- **Package version:** (e.g., `Authplane.Sdk 1.2.3`) +- **Target framework:** (e.g., `net8.0`, `net10.0`) +- **.NET SDK version:** (`dotnet --info`) +- **OS:** (e.g., Ubuntu 22.04, macOS 14, Windows 11) +- **Framework (if adapter):** (e.g., ASP.NET Core 10, ModelContextProtocol.AspNetCore 1.0.0) +- **Authplane `authserver` version / issuer:** (if relevant) + +## Configuration + +Relevant SDK configuration (redact secrets): + +```csharp +// paste here +``` + +## Logs + +
+Relevant logs + +``` +(paste relevant logs here — redact any sensitive data, especially tokens) +``` + +
+ +## Additional Context + +Any other relevant information. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..6171b32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,37 @@ +--- +name: Feature Request +about: Suggest a new feature for the Authplane .NET SDK or an adapter +title: "[Feature] " +labels: enhancement +assignees: "" +--- + +## Affected Package + +- [ ] `Authplane.Sdk` (core) +- [ ] `Authplane.Mcp` +- [ ] New package + +## Problem + +Describe the problem this feature would solve. What are you trying to do that you can't do today? + +## Proposed Solution + +Describe your proposed solution. How should this work? Include a rough API sketch if possible: + +```csharp +// example usage +``` + +## Alternatives Considered + +What alternatives have you considered? Why is this approach better? + +## Use Case + +Describe the use case. Who benefits from this feature? Is this blocking integration with a specific framework or service? + +## Additional Context + +Any other relevant information, links to RFCs or specs, or examples from other projects. diff --git a/.github/ISSUE_TEMPLATE/mcp-compatibility.md b/.github/ISSUE_TEMPLATE/mcp-compatibility.md new file mode 100644 index 0000000..7f945a6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mcp-compatibility.md @@ -0,0 +1,76 @@ +--- +name: MCP Compatibility Report +about: Report compatibility of an MCP client or server with the Authplane .NET adapter +title: "[Compat] " +labels: compatibility, mcp +assignees: "" +--- + +## Adapter + +- [ ] `Authplane.Mcp` (MCP .NET adapter) + +## MCP Library Version + +- **Library:** (e.g., `ModelContextProtocol.AspNetCore`) +- **Version:** +- **Transport:** (e.g., streamable-http, stdio) + +## MCP Client (if reporting a client-side issue) + +- **Client:** (e.g., Claude Code, MCP Inspector, Cursor) +- **Version:** +- **Platform:** (macOS / Linux / Windows) + +## Authplane SDK Version + +- `Authplane.Sdk`: +- `Authplane.Mcp`: +- `authserver` (issuer): +- **Target framework / .NET SDK version:** (`dotnet --info`) + +## Description + +Brief summary of the compatibility observation. + +## Compatibility Scenarios + +Check each that was tested. Mark pass / fail / skip. + +- [ ] **JWT validation** — protected tool accepts valid bearer token +- [ ] **Scope enforcement** — tool-specific scope required and checked +- [ ] **DPoP-bound tokens** — proof-of-possession verified end-to-end (if applicable) +- [ ] **Token refresh** — client refreshes without losing session +- [ ] **Metadata discovery** — adapter surfaces `WWW-Authenticate` / protected-resource metadata correctly +- [ ] **Error handling** — expired, revoked, malformed tokens produce the expected error shape + +## Reproduction Steps + +1. Install adapter ... +2. Configure MCP server with Authplane middleware ... +3. Connect client ... +4. Observe results + +## Logs + +
+Server logs (adapter) + +``` +(paste relevant logs here) +``` + +
+ +
+Client logs + +``` +(paste relevant logs here) +``` + +
+ +## Additional Context + +Screenshots, network traces, spec references, or upstream issues. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9c06d7e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,48 @@ +version: 2 + +updates: + # NuGet — the solution at the root resolves both packable projects and the + # test projects together, so a single ecosystem block covers the tree. + - package-ecosystem: nuget + directory: / + schedule: + interval: weekly + day: monday + labels: + - dependencies + - dotnet + commit-message: + prefix: "deps" + open-pull-requests-limit: 10 + groups: + # Only minor + patch updates are bundled. Majors fall out of the + # group and each gets its own PR so breaking changes are reviewed + # one at a time. + nuget-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + # GitHub Actions + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + labels: + - dependencies + - ci + commit-message: + prefix: "ci" + open-pull-requests-limit: 5 + groups: + # Same approach as the nuget group: bundle minor + patch, let + # majors open as individual PRs. + actions-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ea7c587 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +## Summary + +Brief description of what this PR does and why. + +## Linked Issue + + + +## Changes + +- + +## Affected Projects + +- [ ] `Authplane.Sdk` (core) +- [ ] `Authplane.Mcp` +- [ ] None (infra / docs / CI only) + +## Test Plan + +How was this tested? Include relevant test names or manual verification steps. + +## Checklist + +- [ ] `dotnet build Authplane.slnx --configuration Release` passes +- [ ] `dotnet format Authplane.slnx --verify-no-changes` is clean +- [ ] `dotnet test` passes for affected projects +- [ ] Coverage thresholds met (80 line / 70 branch) +- [ ] Tests added for new functionality +- [ ] Documentation updated (if applicable) +- [ ] `CHANGELOG.md` entry added under `[Unreleased]` (if user-facing) +- [ ] New workflow actions are SHA-pinned (`pinact run` after changes) +- [ ] No token values, secrets, or key material in logs or test fixtures diff --git a/.github/workflows/backport-fixes.yml b/.github/workflows/backport-fixes.yml new file mode 100644 index 0000000..55cf10c --- /dev/null +++ b/.github/workflows/backport-fixes.yml @@ -0,0 +1,131 @@ +name: Backport fixes to default branch + +# Thin wrapper around scripts/backport-fixes.sh for maintainers who +# prefer dispatching from the Actions UI. Behavior: +# +# - Happy path (no conflicts): runs the script, pushes the backport +# branch, opens a PR against the target. +# - Any conflict: cherry-pick fails, the workflow fails loudly. Re-run +# the script locally to finish: +# scripts/backport-fixes.sh --from --to +# and resolve conflicts via git's native cherry-pick state machine. +# +# fromBranch can be a branch (release/v*, hotfix/v*) or a tag (vX.Y.Z). +# After the release workflow runs, the release branch is deleted — use +# the tag as the fromBranch instead. + +on: + workflow_dispatch: + inputs: + fromBranch: + description: 'Source ref on origin (branch or tag, e.g. release/v0.6.0, hotfix/v0.5.1, or v0.6.0). Do not include origin/.' + required: true + type: string + toBranch: + description: 'Target branch on origin. Leave empty to use the repository default branch.' + required: false + type: string + default: '' + +concurrency: + group: backport-${{ inputs.fromBranch }}-to-${{ inputs.toBranch }} + cancel-in-progress: false + +# Least-privilege default; the job re-grants the write scopes it needs. +permissions: + contents: read + +jobs: + backport: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Resolve target branch + id: target + run: | + to="${{ inputs.toBranch }}" + if [[ -z "$to" ]]; then + to="${{ github.event.repository.default_branch }}" + fi + echo "to=$to" >> "$GITHUB_OUTPUT" + + - name: Check out repo with full history + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Configure git author + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # The script exits non-zero on any cherry-pick conflict. That failure + # propagates here and halts the workflow before push/PR. + - name: Run backport script + id: run + run: | + ./scripts/backport-fixes.sh \ + --from "${{ inputs.fromBranch }}" \ + --to "${{ steps.target.outputs.to }}" + echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" + + - name: Push backport branch + if: startsWith(steps.run.outputs.branch, 'backport/') + run: | + git push origin "${{ steps.run.outputs.branch }}" + + - name: Open pull request + if: startsWith(steps.run.outputs.branch, 'backport/') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + from="${{ inputs.fromBranch }}" + to="${{ steps.target.outputs.to }}" + branch="${{ steps.run.outputs.branch }}" + title="chore: backport from $from to $to" + body=$(cat <> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80c4190..c0bd70d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - develop + - main jobs: quality: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..7fe8634 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,73 @@ +name: CodeQL + +# Static application security testing (SAST) for the C# sources across both +# packable projects (Authplane.Sdk, Authplane.Mcp). Runs CodeQL's +# security-extended query suite on pull requests, pushes to main, and a weekly +# schedule, uploading results to the GitHub Security tab. +# +# Closes the OpenSSF Scorecard "SAST" gap: CodeQL statically analyzes the +# first-party source, complementing the dependency-vulnerability scanning +# (the security workflow) that runs separately. + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Wednesdays 06:00 UTC + - cron: "0 6 * * 3" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (csharp) + # Code scanning (SARIF upload) requires GitHub Code Security, which is + # only available on public repos. Gate on visibility so the job only + # runs when the repo is public. + if: ${{ github.event.repository.visibility == 'public' }} + runs-on: ubuntu-latest + permissions: + # Required for CodeQL to upload its SARIF results to code scanning. + security-events: write + contents: read + actions: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup .NET + uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + # 8.0.x alongside 10.0.x: the projects multi-target `net8.0;net10.0`, + # so the extractor needs the 8.0 reference packs to compile that leg. + dotnet-version: | + 8.0.x + 10.0.x + + # build-mode: manual — C# is compiled, so the extractor must observe a + # real build. Autobuild guesses at the solution layout; we drive the + # compile explicitly below instead. + - name: Initialize CodeQL + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + with: + languages: csharp + build-mode: manual + queries: security-extended + + - name: Build solution + # Compile every project so the extractor sees all types. Tests are + # excluded from the analysis target on purpose: they would also pull + # the external conformance catalog, which this workflow does not clone. + run: dotnet build Authplane.slnx --configuration Release + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + with: + category: "/language:csharp" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 81260b0..e6113ff 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -12,7 +12,7 @@ on: - cron: "0 6 * * 2" push: branches: - - develop + - main workflow_dispatch: permissions: read-all diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 9df42c0..7100d79 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -15,7 +15,7 @@ on: - ".github/workflows/security.yml" push: branches: - - develop + - main paths: - "**/*.csproj" - "**/Directory.Build.props" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3aad59a..20348bc 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -30,7 +30,7 @@ jobs: This issue has been automatically marked as stale because it has not had activity in 60 days. It will be closed in 30 days if no further activity occurs. Add a comment or the `pinned` label to - keep it open. See [CONTRIBUTING.md](../blob/develop/CONTRIBUTING.md). + keep it open. See [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md). stale-pr-message: | This PR has been automatically marked as stale because it has not had activity in 30 days. It will be closed in 30 days if no diff --git a/.github/workflows/workflows-lint.yml b/.github/workflows/workflows-lint.yml new file mode 100644 index 0000000..dfadf36 --- /dev/null +++ b/.github/workflows/workflows-lint.yml @@ -0,0 +1,64 @@ +name: Lint workflows + +# Catches workflow YAML / shell-in-`run:` regressions at PR time so a +# typo can't reach a release tag and surface only when a publish run +# fails. Scoped to changes under `.github/workflows/**` to keep CI +# overhead off unrelated PRs. + +on: + pull_request: + paths: + - ".github/workflows/**" + push: + branches: + - main + paths: + - ".github/workflows/**" + +permissions: + contents: read + +jobs: + actionlint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Pulls the matching actionlint binary release from GitHub Releases + # via the upstream download script. The script is fetched by commit + # SHA (not a mutable tag) and sha256-verified before it runs — this + # closes Scorecard's "downloadThenRun not pinned by hash" gap. The + # script then checksum-verifies the actionlint binary it pulls from + # the matching release. + # + # To bump: change ACTIONLINT_VERSION, set ACTIONLINT_SCRIPT_SHA to the + # commit the new tag points at (`gh api repos/rhysd/actionlint/commits/vX.Y.Z -q .sha`), + # and update ACTIONLINT_SCRIPT_SHA256 to that file's sha256. + # + # Install dir is passed explicitly as the script's second positional + # arg so the workflow doesn't couple to the script's internal default + # of $PWD (which happens to be $GITHUB_WORKSPACE after checkout — + # a coincidence, not a contract). + - name: Install actionlint + env: + ACTIONLINT_VERSION: "1.7.7" + ACTIONLINT_SCRIPT_SHA: "03d0035246f3e81f36aed592ffb4bebf33a03106" + ACTIONLINT_SCRIPT_SHA256: "221d1d16c03e4e4fcd867de34104e8d479bdce20ccdfa553b9a5c0dc29bf6af2" + ACTIONLINT_INSTALL_DIR: ${{ runner.temp }}/actionlint + run: | + mkdir -p "${ACTIONLINT_INSTALL_DIR}" + script="${ACTIONLINT_INSTALL_DIR}/download-actionlint.bash" + curl -fsSL -o "${script}" \ + "https://raw.githubusercontent.com/rhysd/actionlint/${ACTIONLINT_SCRIPT_SHA}/scripts/download-actionlint.bash" + echo "${ACTIONLINT_SCRIPT_SHA256} ${script}" | sha256sum -c - + bash "${script}" "${ACTIONLINT_VERSION}" "${ACTIONLINT_INSTALL_DIR}" + echo "${ACTIONLINT_INSTALL_DIR}" >> "${GITHUB_PATH}" + "${ACTIONLINT_INSTALL_DIR}/actionlint" -version + + # `-shellcheck=shellcheck` makes the shellcheck dependency explicit + # rather than relying on actionlint's implicit lookup against the + # runner image's $PATH; if the Ubuntu image ever drops shellcheck the + # job fails loudly instead of silently degrading. + - name: Run actionlint + run: actionlint -color -shellcheck=shellcheck diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4dd3695..5f18b16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,7 +100,7 @@ See `demo/README.md` for the demo prerequisites. ## Pull Request Guidelines -- **Branching:** branch off `develop`. Use `ISSUE-ID-short-description` style; the issue tracker auto-detects the prefix. +- **Branching:** branch off `main`. Use `ISSUE-ID-short-description` style; the issue tracker auto-detects the prefix. - **Commits:** [Conventional Commits](https://www.conventionalcommits.org/) format. Reference the tracked issue in the commit body or footer (e.g. `(ISSUE-ID)`). - **PR template:** describe the change, link the issue, list the test plan. - **CI:** every PR must pass `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes`, and the conformance suite. Coverage thresholds (80 line / 70 branch) are enforced. @@ -109,7 +109,7 @@ See `demo/README.md` for the demo prerequisites. ## CI / Workflow Expectations - GitHub Actions are SHA-pinned via [pinact](https://github.com/suzuki-shunsuke/pinact); the manifest is `.pinact.yaml`. To upgrade an action: bump the tag, then `pinact run` to refresh the SHA. -- The workflow file is `.github/workflows/ci.yml`. Changes to it require a PR (no direct pushes to `develop`). +- The workflow file is `.github/workflows/ci.yml`. Changes to it require a PR (no direct pushes to `main`). ## Code of Conduct diff --git a/README.md b/README.md index f3302a7..11125ef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Authplane .NET SDK -[![CI](https://img.shields.io/github/actions/workflow/status/AuthPlane/cs-sdk/ci.yml?branch=develop&style=flat-square&label=CI)](https://github.com/AuthPlane/cs-sdk/actions/workflows/ci.yml) +[![CI](https://img.shields.io/github/actions/workflow/status/AuthPlane/cs-sdk/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/AuthPlane/cs-sdk/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/License-Apache_2.0-blue?style=flat-square)](LICENSE) OAuth 2.1 JWT validation and token operations for .NET resource servers, with a first-class adapter for [Model Context Protocol](https://modelcontextprotocol.io/) servers. diff --git a/RELEASE_SETUP.md b/RELEASE_SETUP.md index 493ceef..9fff315 100644 --- a/RELEASE_SETUP.md +++ b/RELEASE_SETUP.md @@ -70,17 +70,17 @@ For patches to an older minor line (not the current default-branch line): - the line is strictly older than the default branch's current line. 2. **Land the fix on `hotfix/vX.Y.Z`** — cherry-pick or commit directly. Add a `## [X.Y.Z]` CHANGELOG entry. 3. **Dispatch the release** — same as above, from the `hotfix/v*` branch. -4. **Backport to the default branch** — after publication, port the fix back so the default branch line carries it too. *The dedicated Backport workflow has not been added yet — use the manual cherry-pick fallback below until it lands. Tracked separately.* +4. **Backport to the default branch** — after publication, port the fix back so the default branch line carries it too. Dispatch **Backport fixes to default branch** from the Actions tab with `fromBranch=vX.Y.Z`; the release branch is deleted by `release.yml`, so use the tag. The workflow wraps `scripts/backport-fixes.sh`, pushes the backport branch and opens the PR. On a cherry-pick conflict it fails loudly — finish locally with the same script. Manual cherry-pick fallback: ```bash git fetch --tags origin - git checkout -b backport/ origin/develop + git checkout -b backport/ origin/main git cherry-pick vX.Y.Z~..vX.Y.Z # adjust the range to the hotfix commits # resolve any conflicts, then: git push origin backport/ - gh pr create --base develop --title 'chore(backport): port vX.Y.Z fixes to develop' \ + gh pr create --base main --title 'chore(backport): port vX.Y.Z fixes to main' \ --body 'Cherry-picked from tag vX.Y.Z. See RELEASE_SETUP.md §6.' ``` @@ -113,6 +113,6 @@ NuGet does not support atomic multi-package uploads. If `publish-nuget.yml` publ No `--target` — the tag already points at the correct commit on the (now deleted) source branch. -4. If any commits on the source branch need to reach the default branch, port them back via the manual cherry-pick fallback documented in §6.4 (the dedicated **Backport fixes** workflow has not been added yet). +4. If any commits on the source branch need to reach the default branch, port them back with the **Backport fixes** workflow (§6.4), or the manual cherry-pick fallback documented there. The git tag is already live, so re-running `release.yml` is not an option (the tag-exists pre-flight refuses). diff --git a/scripts/backport-fixes.sh b/scripts/backport-fixes.sh new file mode 100755 index 0000000..5e82a47 --- /dev/null +++ b/scripts/backport-fixes.sh @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Cherry-pick commits from a release/hotfix branch (or a release tag) to a +# local backport branch off the target. Does NOT push, create PRs, or +# touch remotes beyond `git fetch`. +# +# Conflicts use git's native cherry-pick state machine — resolve, then +# `git cherry-pick --continue` (or --skip / --abort). Re-running this +# script is not needed after a conflict; git's sequencer handles it. +# +# Uses `git cherry` for patch-ID-based matching, so commits already +# cherry-picked to the target (under different SHAs) are correctly +# detected and excluded. + +usage() { + cat <<'EOF' +Usage: + backport-fixes.sh --from --to [--branch ] [--no-filter] + +Options: + --from Source ref on origin: a branch (e.g. release/v0.6.0, + hotfix/v0.5.1) or a tag (e.g. v0.6.0). Do not include + 'origin/'. Required. Use the tag after the release + workflow has deleted the source branch. + --to Target branch on origin. Required. + --branch Name for the local backport branch (default: + `backport/vX.Y.Z` derived from --from when it + matches release/vX.Y.Z, hotfix/vX.Y.Z, or vX.Y.Z; + otherwise `backport/`). + --no-filter Include commits whose subject starts with + 'release:' or 'release-prep:' (automated version + bumps). Default behavior excludes them to keep the + target branch's own version string intact. + -h, --help Show this help. + +Behavior: + 1. Fetches origin (both branch and tag refs). + 2. Lists commits on the source ref that aren't already on origin/, + and commits that are already there (skipped). + 3. Creates the backport branch off origin/. + 4. Runs `git cherry-pick -x` with the candidates, oldest-first. + 5. On conflict: stops. Resolve, then `git cherry-pick --continue`. + + By default, commits with subjects matching ^(release|release-prep): + are excluded from the candidate list — in this repo these are always + automated version-file edits that would conflict with the target's + own version string. Pass --no-filter to include them. + +If the backport branch already exists locally, the script fails — delete +it (`git branch -D `) or pass `--branch ` to override. + +No push. No PR. The branch stays local; you decide what to do next. + +Examples: + backport-fixes.sh --from release/v0.6.0 --to main + backport-fixes.sh --from v0.6.0 --to main # branch deleted post-release +EOF +} + +FROM="" +TO="" +BRANCH_OVERRIDE="" +NO_FILTER="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --from) FROM="${2-}"; shift 2 ;; + --to) TO="${2-}"; shift 2 ;; + --branch) BRANCH_OVERRIDE="${2-}"; shift 2 ;; + --no-filter) NO_FILTER=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "error: unknown argument: $1" >&2; usage >&2; exit 2 ;; + esac +done + +if [[ -z "$FROM" ]]; then + echo "error: --from is required" >&2 + usage >&2 + exit 2 +fi +if [[ -z "$TO" ]]; then + echo "error: --to is required" >&2 + usage >&2 + exit 2 +fi +if [[ "$FROM" == origin/* || "$TO" == origin/* ]]; then + echo "error: refs must not include 'origin/'" >&2 + exit 2 +fi +if [[ "$FROM" == "$TO" ]]; then + echo "error: --from and --to must differ" >&2 + exit 2 +fi + +# Must be in a git repo +if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "error: not inside a git repository" >&2 + exit 1 +fi + +# Detect in-progress cherry-pick first — gives a more actionable error +# than the generic dirty-tree check, which also trips during a conflict. +if [[ -f "$(git rev-parse --git-dir)/CHERRY_PICK_HEAD" ]]; then + echo "error: a cherry-pick is already in progress. Finish or abort it first:" >&2 + echo " git cherry-pick --continue | --skip | --abort" >&2 + exit 1 +fi + +# Require clean working tree — cherry-picks onto a dirty tree are unsafe. +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "error: working tree has uncommitted changes. Commit or stash first." >&2 + exit 1 +fi + +echo "Fetching origin..." +# Fetch the target branch and try to fetch FROM as both a branch and a tag. +# Branches go to refs/remotes/origin/; tags go to refs/tags/. +# The two explicit refspecs make either accepted; whichever doesn't exist +# is silently ignored. +git fetch origin "$TO" --no-tags +git fetch origin --no-tags \ + "+refs/heads/$FROM:refs/remotes/origin/$FROM" 2>/dev/null \ + || true +git fetch origin --no-tags \ + "+refs/tags/$FROM:refs/tags/$FROM" 2>/dev/null \ + || true + +# Resolve FROM to a ref that exists locally after fetch. +if git rev-parse --verify "refs/remotes/origin/$FROM" >/dev/null 2>&1; then + from_ref="refs/remotes/origin/$FROM" + from_pretty="origin/$FROM" +elif git rev-parse --verify "refs/tags/$FROM" >/dev/null 2>&1; then + from_ref="refs/tags/$FROM" + from_pretty="$FROM (tag)" +else + echo "error: $FROM not found on origin (tried both branches and tags)" >&2 + exit 1 +fi + +if ! git rev-parse --verify "refs/remotes/origin/$TO" >/dev/null 2>&1; then + echo "error: origin/$TO not found on remote" >&2 + exit 1 +fi + +# `git cherry -v ` prints one line per commit: +# + -> not on upstream (candidate for backport) +# - -> already on upstream via patch-ID match +cherry_out="$(git cherry -v "refs/remotes/origin/$TO" "$from_ref" || true)" + +all_candidates="$(echo "$cherry_out" | awk '$1 == "+" { sub(/^\+ /, ""); print }')" +already_pretty="$(echo "$cherry_out" | awk '$1 == "-" { sub(/^- /, ""); print }')" + +if [[ -n "$NO_FILTER" ]]; then + candidates_pretty="$all_candidates" + filtered_pretty="" +else + filtered_pretty="$(echo "$all_candidates" | grep -E '^[0-9a-f]+ (release|release-prep):' || true)" + candidates_pretty="$(echo "$all_candidates" | grep -Ev '^[0-9a-f]+ (release|release-prep):' || true)" +fi + +shas="$(echo "$candidates_pretty" | awk 'NF { print $1 }')" + +n_candidates=0 +[[ -n "$candidates_pretty" ]] && n_candidates=$(echo "$candidates_pretty" | wc -l | tr -d ' ') +n_already=0 +[[ -n "$already_pretty" ]] && n_already=$(echo "$already_pretty" | wc -l | tr -d ' ') +n_filtered=0 +[[ -n "$filtered_pretty" ]] && n_filtered=$(echo "$filtered_pretty" | wc -l | tr -d ' ') + +echo +echo "=== Commits on $from_pretty not yet on origin/$TO ($n_candidates) ===" +if [[ "$n_candidates" -gt 0 ]]; then + echo "$candidates_pretty" +else + echo "(none)" +fi + +if [[ "$n_already" -gt 0 ]]; then + echo + echo "=== Already on origin/$TO, excluded ($n_already) ===" + echo "$already_pretty" +fi + +if [[ "$n_filtered" -gt 0 ]]; then + echo + echo "=== Skipped (version-bump commits; pass --no-filter to include) ($n_filtered) ===" + echo "$filtered_pretty" +fi + +if [[ "$n_candidates" -eq 0 ]]; then + echo + echo "Nothing to backport." + exit 0 +fi + +if [[ -n "$BRANCH_OVERRIDE" ]]; then + branch="$BRANCH_OVERRIDE" +elif [[ "$FROM" =~ ^(release|hotfix)/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + branch="backport/v${BASH_REMATCH[2]}" +elif [[ "$FROM" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + branch="backport/v${BASH_REMATCH[1]}" +else + flat="$(echo "$FROM" | sed -E 's|/|-|g; s/[^a-zA-Z0-9._-]+/-/g')" + branch="backport/${flat}" +fi + +if git show-ref --verify --quiet "refs/heads/$branch"; then + echo "error: local branch '$branch' already exists." >&2 + echo " Delete it (git branch -D $branch) or pass --branch ." >&2 + exit 1 +fi + +echo +echo "Creating branch $branch off origin/$TO..." +git checkout -b "$branch" "origin/$TO" + +echo +echo "Cherry-picking $n_candidates commit(s) with -x, oldest first..." +echo "If git stops on a conflict:" +echo " - Resolve, 'git add ', then 'git cherry-pick --continue'." +echo " - To drop the conflicting commit: 'git cherry-pick --skip'." +echo " - To bail out entirely: 'git cherry-pick --abort'." +echo + +# shellcheck disable=SC2086 # intentional word-split: $shas is a hex-only list +exec git cherry-pick -x $shas diff --git a/src/Authplane.Mcp/Authplane.Mcp.csproj b/src/Authplane.Mcp/Authplane.Mcp.csproj index f74fc57..488d2ed 100644 --- a/src/Authplane.Mcp/Authplane.Mcp.csproj +++ b/src/Authplane.Mcp/Authplane.Mcp.csproj @@ -14,13 +14,13 @@ https://github.com/AuthPlane/cs-sdk.git git true - https://github.com/AuthPlane/cs-sdk/tree/develop/src/Authplane.Mcp + https://github.com/AuthPlane/cs-sdk/tree/main/src/Authplane.Mcp Apache-2.0 README.md icon.png Authplane MCP adapter for .NET Copyright © Authplane Team - https://github.com/AuthPlane/cs-sdk/blob/develop/CHANGELOG.md + https://github.com/AuthPlane/cs-sdk/blob/main/CHANGELOG.md diff --git a/src/Authplane/Authplane.csproj b/src/Authplane/Authplane.csproj index d24ad7d..d799759 100644 --- a/src/Authplane/Authplane.csproj +++ b/src/Authplane/Authplane.csproj @@ -14,13 +14,13 @@ https://github.com/AuthPlane/cs-sdk.git git true - https://github.com/AuthPlane/cs-sdk/tree/develop/src/Authplane + https://github.com/AuthPlane/cs-sdk/tree/main/src/Authplane Apache-2.0 README.md icon.png Authplane SDK for .NET Copyright © Authplane Team - https://github.com/AuthPlane/cs-sdk/blob/develop/CHANGELOG.md + https://github.com/AuthPlane/cs-sdk/blob/main/CHANGELOG.md