From 7803d3c232227e0247f533a4398666ce33796605 Mon Sep 17 00:00:00 2001 From: devlooped-bot Date: Sat, 11 Jul 2026 00:38:31 +0000 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20files=20with=20dotn?= =?UTF-8?q?et-file=20sync=20=EF=BB=BF#=20devlooped/oss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhance documentation instructions for project work https://github.com/devlooped/oss/commit/e616d89 - Refactor GitHub Actions workflows to use unified PR_TOKEN for authentication and add skip PR creation warning https://github.com/devlooped/oss/commit/7c1c6e6 - Ignore .env files recursively https://github.com/devlooped/oss/commit/3776526 - Improve default Product metadata, remove .git from user-facing URLs https://github.com/devlooped/oss/commit/4339749 - Ignore *.local recursively https://github.com/devlooped/oss/commit/a225b7a - If the OSMF eula is present, switch to its license file https://github.com/devlooped/oss/commit/4b84c54 - Consider either None or Content for OSMF license patching https://github.com/devlooped/oss/commit/083a37b - Add AzuriteConfig to .gitignore https://github.com/devlooped/oss/commit/12c18e7 - Add new entries to .gitignore file https://github.com/devlooped/oss/commit/c76f0cc - Fix PackageId default from Pack SDK https://github.com/devlooped/oss/commit/6e24389 - Improve PackageId/IsPackable defaulting https://github.com/devlooped/oss/commit/e8b0802 - Add '/mcps' to .gitignore https://github.com/devlooped/oss/commit/c1d29fb - Fix empty default path for NuGet SDK targets https://github.com/devlooped/oss/commit/c679525 - Add '/temp' directory to .gitignore https://github.com/devlooped/oss/commit/79631d3 - Update .gitignore to ignore 'mcps' folder recursively https://github.com/devlooped/oss/commit/11680ae - Add 'terminals' to .gitignore https://github.com/devlooped/oss/commit/5e7a79e - Add 'agent-tools' to .gitignore https://github.com/devlooped/oss/commit/ff61659 - fix: support robust NuGetize pack delegation for PackFolder=build projects (#39) https://github.com/devlooped/oss/commit/dcd5f0a - Set severity of IDE1100 to none https://github.com/devlooped/oss/commit/1ed9afe - Set explicit tab size and eol for code files https://github.com/devlooped/oss/commit/5dba0d0 - Revert EOL change in editorconfig for C# files https://github.com/devlooped/oss/commit/2d0e5a5 - Revert indent size for project files https://github.com/devlooped/oss/commit/a62c459 - Group Spectre.Console updates https://github.com/devlooped/oss/commit/917ff54 - Enhance include workflow with OSMF EULA support https://github.com/devlooped/oss/commit/f2050db - Trim whitespace in file content replacement https://github.com/devlooped/oss/commit/e53557f - Fix path pattern for markdown files in workflow https://github.com/devlooped/oss/commit/6a6de05 - Group MEAI packages together https://github.com/devlooped/oss/commit/e733294 - Fix error message quotes in includes.yml https://github.com/devlooped/oss/commit/26e8cb7 - Change label from 'docs' to 'dependencies' https://github.com/devlooped/oss/commit/2d1fb4e - Avoid failure on PR creation if osmfeula.txt doesn't exist in repo https://github.com/devlooped/oss/commit/8ff5178 - Update create-pull-request action to version 8 https://github.com/devlooped/oss/commit/0662872 - Ensure lf for Scriban templates always https://github.com/devlooped/oss/commit/4a9aa32 - Add OpenTelemetry patterns to dependabot config https://github.com/devlooped/oss/commit/387f061 - Update actions-includes version to v2 https://github.com/devlooped/oss/commit/d571412 - Add .NET 9.0 so format can succeed https://github.com/devlooped/oss/commit/ec9d7cd - Make sure the feed url is available to msbuild https://github.com/devlooped/oss/commit/931da83 - Move dotnet setup to composite action https://github.com/devlooped/oss/commit/08c7077 - Switch to dotnet-env for .NET SDK setup https://github.com/devlooped/oss/commit/56c2b85 - Switch to dnx retest shorthand https://github.com/devlooped/oss/commit/fddfd89 - Ensure quiet confirmation of tool download/install https://github.com/devlooped/oss/commit/e11b002 - Ensure dnx run succeeds even on Windows https://github.com/devlooped/oss/commit/7f5f9ee - Update branches for push event in build.yml https://github.com/devlooped/oss/commit/5da103c - Allow workflow to work cross-org https://github.com/devlooped/oss/commit/af171b7 - Add explicit write permissions from caller workflow https://github.com/devlooped/oss/commit/8fa147d - Publish builds should point to nuget.org not CI feed https://github.com/devlooped/oss/commit/db918f0 - Cap rate limit wait at 5 minutes, abort if longer https://github.com/devlooped/oss/commit/61a602f - Update cron schedule in triage workflow https://github.com/devlooped/oss/commit/5ee8c91 - Update VersionLabel to use release tag name https://github.com/devlooped/oss/commit/b62978b - feat: add validation step to check for disallowed package versions in publish workflow https://github.com/devlooped/oss/commit/0ca5fd1 - Ignore slnx file format too https://github.com/devlooped/oss/commit/68b409c - Support using current Version from CVM https://github.com/devlooped/oss/commit/2fff747 - Update Directory.Build.props with support for underscores in branch name https://github.com/devlooped/oss/commit/81d972f - Add Company MSBuild property by default https://github.com/devlooped/oss/commit/c509be4 - Disable warnings for non-packable test projects https://github.com/devlooped/oss/commit/95b338b - Update versioning scheme in Directory.Build.props https://github.com/devlooped/oss/commit/f2400ef - Enable package pruning in Directory.Build.props https://github.com/devlooped/oss/commit/0ff8b7b - Change file type from None to Content for osmfeula.txt https://github.com/devlooped/oss/commit/fd03672 - Add Pack attribute to OSMFEULA content item https://github.com/devlooped/oss/commit/dd13ed3 - Hide osmfeula.txt from package visibility https://github.com/devlooped/oss/commit/a1f5d11 # devlooped/SponsorLink - Introduce standalone SponsorManifest for read/validate https://github.com/devlooped/SponsorLink/commit/a755e4b - Bump dependency in analyzer targets too https://github.com/devlooped/SponsorLink/commit/1454a8f - Bump Microsoft.IdentityModel.JsonWebTokens https://github.com/devlooped/SponsorLink/commit/2de6c98 - Bump Microsoft.IdentityModel.JsonWebTokens in the identity group https://github.com/devlooped/SponsorLink/commit/d641245 - Unify JWT version from tests with analyzer targets https://github.com/devlooped/SponsorLink/commit/869f74b - Bump JWT also in samples https://github.com/devlooped/SponsorLink/commit/e8ec200 - Update SponsorLink.Analyzer.Tests.targets to match JWT version of core https://github.com/devlooped/SponsorLink/commit/8ddf000 - Fix central package version for ILRepack in analyzer sample https://github.com/devlooped/SponsorLink/commit/8bc0a69 - Fix version/override for CPV https://github.com/devlooped/SponsorLink/commit/8a40822 - Improve wording on editor usage requiring sponsorship https://github.com/devlooped/SponsorLink/commit/21d8dac --- .editorconfig | 5 + .gitattributes | 1 + .github/copilot-instructions.md | 94 ++++++++++ .github/dependabot.yml | 11 ++ .github/workflows/build.yml | 20 +-- .github/workflows/dotnet-env.yml | 52 ++++++ .github/workflows/dotnet-file.yml | 7 +- .github/workflows/includes.yml | 44 ++++- .github/workflows/publish.yml | 29 ++-- .github/workflows/triage.yml | 10 +- .gitignore | 9 + .netconfig | 117 +++++++------ _config.yml | 2 +- readme.md | 63 +++---- src/Directory.Build.props | 37 +++- src/Directory.Build.targets | 35 +++- .../SponsorLink.Analyzer.Tests.targets | 4 +- src/SponsorLink/SponsorLink.Analyzer.targets | 8 +- src/SponsorLink/SponsorLink/ManifestStatus.cs | 25 --- src/SponsorLink/SponsorLink/Resources.es.resx | 3 + src/SponsorLink/SponsorLink/Resources.resx | 2 +- src/SponsorLink/SponsorLink/SponsorLink.cs | 78 --------- .../SponsorLink/SponsorLink.csproj | 2 +- .../SponsorLink/SponsorManifest.cs | 160 ++++++++++++++++++ ...orLinkTests.cs => SponsorManifestTests.cs} | 59 ++++--- src/SponsorLink/Tests/Tests.csproj | 4 +- 26 files changed, 613 insertions(+), 268 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/dotnet-env.yml delete mode 100644 src/SponsorLink/SponsorLink/ManifestStatus.cs create mode 100644 src/SponsorLink/SponsorLink/SponsorManifest.cs rename src/SponsorLink/Tests/{SponsorLinkTests.cs => SponsorManifestTests.cs} (63%) diff --git a/.editorconfig b/.editorconfig index 4cab270..3ba6837 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,6 +30,8 @@ indent_size = 2 # Dotnet code style settings: [*.{cs,vb}] +tab_width = 4 + # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary @@ -57,6 +59,9 @@ dotnet_style_require_accessibility_modifiers = omit_if_default:error # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = error +# IDE1100: Error reading content of source file 'Project.TargetFrameworkMoniker' (i.e. from ThisAssembly) +dotnet_diagnostic.IDE1100.severity = none + [*.cs] # Top-level files are definitely OK csharp_using_directive_placement = outside_namespace:silent diff --git a/.gitattributes b/.gitattributes index 4f89148..3095556 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ # normalize by default * text=auto encoding=UTF-8 *.sh text eol=lf +*.sbn eol=lf # These are windows specific files which we may as well ensure are # always crlf on checkout diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..514ebd5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,94 @@ +# .NET Repository + +**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** + +## Working Effectively + +### Essential Build Commands +- **Restore dependencies**: `dotnet restore` + +- **Build the entire solution**: `dotnet build` + +- **Run tests**: `dnx --yes retest` + - Runs all unit tests across the solution + - If tests fail due to Azure Storage, run the following commands and retry: `npm install azurite` and `npx azurite &` + +### Build Validation and CI Requirements +- **Always run before committing**: + * `dnx --yes retest` + * `dotnet format whitespace -v:diag --exclude ~/.nuget` + * `dotnet format style -v:diag --exclude ~/.nuget` + +### Project Structure and Navigation + +| Directory | Description | +|-----------|-------------| +| `src/` | Contains the repo source code. | +| `bin/` | Contains built packages (if any) | + +### Code Style and Formatting + +#### EditorConfig Rules +The repository uses `.editorconfig` at the repo root for consistent code style. + +- **Indentation**: 4 spaces for C# files, 2 spaces for XML/YAML/JSON +- **Line endings**: LF (Unix-style) +- **Sort using directives**: System.* namespaces first (`dotnet_sort_system_directives_first = true`) +- **Type references**: Prefer language keywords over framework type names (`int` vs `Int32`) +- **Modern C# features**: Use object/collection initializers, coalesce expressions when possible, use var when the type is apparent from the right-hand side of the assignment +- **Visibility modifiers**: only explicitly specify visibility when different from the default (e.g. `public` for classes, no `internal` for classes or `private` for fields, etc.) + +#### Formatting Validation +- CI enforces formatting with `dotnet format whitespace` and `dotnet format style` +- Run locally: `dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget` +- Fix formatting: `dotnet format` (without `--verify-no-changes`) + +### Testing Practices + +#### Test Framework +- **xUnit** for all unit and integration tests +- **Moq** for mocking dependencies +- Located in `src/*.Tests/` + +#### Test Attributes +Custom xUnit attributes are sometimes used for conditional test execution: +- `[SecretsFact("XAI_API_KEY")]` - Skips test if required secrets are missing from user secrets or environment variables +- `[LocalFact("SECRET")]` - Runs only locally (skips in CI), requires specified secrets +- `[CIFact]` - Runs only in CI environment + +### Dependency Management + +#### Adding Dependencies +- Add to appropriate `.csproj` file +- Run `dotnet restore` to update dependencies +- Ensure version consistency across projects where applicable + +#### CI/CD Pipeline +- **Build workflow**: `.github/workflows/build.yml` - runs on PR and push to main/rel/feature branches +- **Publish workflow**: Publishes to Sleet feed when `SLEET_CONNECTION` secret is available +- **OS matrix**: Configured in `.github/workflows/os-matrix.json` (defaults to ubuntu-latest) + +### Special Files and Tools + +#### dnx Command +- **Purpose**: built-in tool for running arbitrary dotnet tools that are published on nuget.org. `--yes` auto-confirms install before run. +- **Example**: `dnx --yes retest` - runs tests with automatic retry on transient failures (retest being a tool package published at https://www.nuget.org/packages/retest) +- **In CI**: `dnx --yes retest -- --no-build` (skips build, runs tests only) + +#### Directory.Build.rsp +- MSBuild response file with default build arguments +- `-nr:false` - disables node reuse +- `-m:1` - single-threaded build (for stability) +- `-v:m` - minimal verbosity + +#### Code Quality +- All PRs must pass format validation +- Tests must pass on all target frameworks +- Follow existing patterns and conventions in the codebase + +## Documenting Work + +Project implemention details, design and key decisions should be documented in a top-level AGENTS.md file at the repo root. +Keep this file updated whenever you make change significant changes for future reference. + +User-facing features and APIs should be documented to highlight (not extensively, as an overview) key project features and capabilities, in the readme.md file at the repo root. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c95eb73..17ca3e2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,9 +24,17 @@ updates: Extensions: patterns: - "Microsoft.Extensions*" + exclude-patterns: + - "Microsoft.Extensions.AI*" + ExtensionsAI: + patterns: + - "Microsoft.Extensions.AI*" Web: patterns: - "Microsoft.AspNetCore*" + OpenTelemetry: + patterns: + - "OpenTelemetry*" Tests: patterns: - "Microsoft.NET.Test*" @@ -38,3 +46,6 @@ updates: ProtoBuf: patterns: - "protobuf-*" + Spectre: + patterns: + - "Spectre.Console*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36f49e2..94db3ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ on: - Release - Debug push: - branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ] + branches: [ main, 'feature/*', 'rel/*' ] paths-ignore: - changelog.md - readme.md @@ -28,6 +28,7 @@ env: GH_TOKEN: ${{ secrets.GH_TOKEN }} MSBUILDTERMINALLOGGER: auto Configuration: ${{ github.event.inputs.configuration || 'Release' }} + SLEET_FEED_URL: ${{ vars.SLEET_FEED_URL }} defaults: run: @@ -65,22 +66,14 @@ jobs: fetch-depth: 0 - name: ⚙ dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.x - 8.x - 9.x + uses: devlooped/actions-dotnet-env@v1 - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - name: 🧪 test - env: - AZURE_WEBPUBSUB: ${{ secrets.AZURE_WEBPUBSUB }} - run: | - dotnet tool update -g dotnet-retest - dotnet retest -- --no-build + shell: pwsh + run: dnx --yes retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v4 @@ -106,6 +99,9 @@ jobs: submodules: recursive fetch-depth: 0 + - name: ⚙ dotnet + uses: devlooped/actions-dotnet-env@v1 + - name: ✓ ensure format run: | dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget diff --git a/.github/workflows/dotnet-env.yml b/.github/workflows/dotnet-env.yml new file mode 100644 index 0000000..1206d10 --- /dev/null +++ b/.github/workflows/dotnet-env.yml @@ -0,0 +1,52 @@ +name: dotnet-env +on: + workflow_dispatch: + push: + branches: + - main + paths: + - '**/*.*proj' + +jobs: + which-dotnet: + runs-on: ubuntu-latest + env: + PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }} + permissions: + contents: write + pull-requests: write + + steps: + - name: 🤖 defaults + uses: devlooped/actions-bot@v1 + with: + name: ${{ secrets.BOT_NAME }} + email: ${{ secrets.BOT_EMAIL }} + gh_token: ${{ env.PR_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: 🤘 checkout + uses: actions/checkout@v4 + with: + token: ${{ env.PR_TOKEN || github.token }} + + - name: 🤌 dotnet + uses: devlooped/actions-which-dotnet@v1 + + - name: ✍ pull request + if: env.PR_TOKEN != '' + uses: peter-evans/create-pull-request@v7 + with: + base: main + branch: which-dotnet + delete-branch: true + labels: dependencies + title: "⚙ Update dotnet versions" + body: "Update dotnet versions" + commit-message: "Update dotnet versions" + token: ${{ env.PR_TOKEN }} + + - name: ⚠️ skip pull request + if: env.PR_TOKEN == '' + shell: bash + run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository." diff --git a/.github/workflows/dotnet-file.yml b/.github/workflows/dotnet-file.yml index 083f24d..402f6ba 100644 --- a/.github/workflows/dotnet-file.yml +++ b/.github/workflows/dotnet-file.yml @@ -12,5 +12,10 @@ env: jobs: run: + permissions: + contents: write uses: devlooped/oss/.github/workflows/dotnet-file-core.yml@main - secrets: inherit \ No newline at end of file + secrets: + BOT_NAME: ${{ secrets.BOT_NAME }} + BOT_EMAIL: ${{ secrets.BOT_EMAIL }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/includes.yml b/.github/workflows/includes.yml index d787ccb..ee446be 100644 --- a/.github/workflows/includes.yml +++ b/.github/workflows/includes.yml @@ -5,12 +5,15 @@ on: branches: - 'main' paths: - - '**.md' + - '**.md' - '!changelog.md' + - 'osmfeula.txt' jobs: includes: runs-on: ubuntu-latest + env: + PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }} permissions: contents: write pull-requests: write @@ -20,28 +23,53 @@ jobs: with: name: ${{ secrets.BOT_NAME }} email: ${{ secrets.BOT_EMAIL }} - gh_token: ${{ secrets.GH_TOKEN }} + gh_token: ${{ env.PR_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: 🤘 checkout uses: actions/checkout@v4 with: - token: ${{ env.GH_TOKEN }} + token: ${{ env.PR_TOKEN || github.token }} - name: +Mᐁ includes - uses: devlooped/actions-includes@v1 + uses: devlooped/actions-includes@v2 + + - name: 📝 OSMF EULA + shell: pwsh + run: | + $file = "osmfeula.txt" + $props = "src/Directory.Build.props" + if (-not (test-path $file) -or -not (test-path $props)) { + exit 0 + } + + $product = dotnet msbuild $props -getproperty:Product + if (-not $product) { + write-error 'To use OSMF EULA, ensure the $(Product) property is set in Directory.props' + exit 1 + } + + ((get-content -raw $file) -replace '\$product\$',$product).trim() | set-content $file - name: ✍ pull request - uses: peter-evans/create-pull-request@v6 + if: env.PR_TOKEN != '' + uses: peter-evans/create-pull-request@v8 with: - add-paths: '**.md' + add-paths: | + **.md + *.txt base: main branch: markdown-includes delete-branch: true - labels: docs + labels: dependencies author: ${{ env.BOT_AUTHOR }} committer: ${{ env.BOT_AUTHOR }} commit-message: +Mᐁ includes title: +Mᐁ includes body: +Mᐁ includes - token: ${{ env.GH_TOKEN }} + token: ${{ env.PR_TOKEN }} + + - name: ⚠️ skip pull request + if: env.PR_TOKEN == '' + shell: bash + run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository." diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5cb9fd7..4fe29c5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,10 +12,11 @@ env: Configuration: Release PackOnBuild: true GeneratePackageOnBuild: true - VersionLabel: ${{ github.ref }} + VersionLabel: refs/tags/${{ github.event.release.tag_name }} GH_TOKEN: ${{ secrets.GH_TOKEN }} MSBUILDTERMINALLOGGER: auto - + SLEET_FEED_URL: https://api.nuget.org/v3/index.json + jobs: publish: runs-on: ${{ vars.PUBLISH_AGENT || 'ubuntu-latest' }} @@ -27,22 +28,14 @@ jobs: fetch-depth: 0 - name: ⚙ dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.x - 8.x - 9.x + uses: devlooped/actions-dotnet-env@v1 - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - name: 🧪 test - env: - AZURE_WEBPUBSUB: ${{ secrets.AZURE_WEBPUBSUB }} - run: | - dotnet tool update -g dotnet-retest - dotnet retest -- --no-build + shell: pwsh + run: dnx --yes retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v4 @@ -51,6 +44,16 @@ jobs: name: logs path: '*.binlog' + - name: ✅ validate + shell: pwsh + working-directory: bin + run: | + $invalid = Get-ChildItem -File -Filter "*.42.42*.nupkg" + if ($invalid) { + Write-Error "Found dev/local packages with disallowed 42.42* version prefix:`n$($invalid.Name -join "`n")" + exit 1 + } + - name: 🚀 nuget env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 56ff299..bd30f7c 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -1,7 +1,7 @@ name: 'triage' on: schedule: - - cron: '42 0 * * *' + - cron: '42 0 1,15 * *' workflow_dispatch: # Manual triggering through the GitHub UI, API, or CLI @@ -49,7 +49,11 @@ jobs: # if we don't have at least 100 requests left, wait until reset if ($rate.remaining -lt 100) { $wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s)) - echo "Rate limit remaining is $($rate.remaining), waiting for $($wait / 1000) seconds to reset" + if ($wait -gt 300) { + echo "Rate limit remaining is $($rate.remaining), reset in $wait seconds (more than 5'). Aborting." + exit 1 + } + echo "Rate limit remaining is $($rate.remaining), waiting $wait seconds to reset" sleep $wait $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate echo "Rate limit has reset to $($rate.remaining) requests" @@ -100,4 +104,4 @@ jobs: closeDays: ${{ fromJson(inputs.daysBeforeClose || 30) }} closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity.\n\nHappy Coding!" pingDays: 80 - pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." \ No newline at end of file + pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." diff --git a/.gitignore b/.gitignore index 2ac54a7..25e70dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,24 @@ bin obj +out artifacts pack +agent-tools +terminals TestResults results BenchmarkDotNet.Artifacts +mcps /app +/temp .vs .vscode .genaiscript .idea local.settings.json +.env +.next +*.local *.suo *.sdf @@ -24,6 +32,7 @@ local.settings.json *.binlog *.zip __azurite*.* +AzuriteConfig __*__ .nuget diff --git a/.netconfig b/.netconfig index 442c577..375f2ef 100644 --- a/.netconfig +++ b/.netconfig @@ -23,23 +23,23 @@ skip [file ".editorconfig"] url = https://github.com/devlooped/oss/blob/main/.editorconfig - sha = e81ab754b366d52d92bd69b24bef1d5b1c610634 - etag = 7298c6450967975a8782b5c74f3071e1910fc59686e48f9c9d5cd7c68213cf59 + sha = a62c45934ac2952f2f5d54d8aea4a7ebc1babaff + etag = b5e919b472a52d4b522f86494f0f2c0ba74a6d9601454e20e4cbaf744317ff62 weak [file ".gitattributes"] url = https://github.com/devlooped/oss/blob/main/.gitattributes - sha = 5f92a68e302bae675b394ef343114139c075993e - etag = 338ba6d92c8d1774363396739c2be4257bfc58026f4b0fe92cb0ae4460e1eff7 + sha = 4a9aa321c4982b83c185cf8dffed181ff84667d5 + etag = 09cad18280ed04b67f7f87591e5481510df04d44c3403231b8af885664d8fd58 weak [file ".github/dependabot.yml"] url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml - sha = 49661dbf0720cde93eb5569be7523b5912351560 - etag = c147ea2f3431ca0338c315c4a45b56ee233c4d30f8d6ab698d0e1980a257fd6a + sha = 387f0616b2c56adc4f33f2858da818f7e0d92ef3 + etag = a1aaba1440e5ee2a7b7f93fc0fb004d4e1d4d9780350c753f7c429e37241345a weak [file ".github/workflows/build.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml - sha = 06e898ccba692566ebf845fa7c8833ac6c318c0a - etag = 0a4b3f0a875cd8c9434742b4046558aecf610d3fa3d490cfd2099266e95e9195 + sha = 5da103cfbc1c4f9b5f59cfa698d2afbd744a7525 + etag = 851af098748f7cfa5bc3cfd4cc404a6de930532b59ceb2b3b535282c41226f3a weak [file ".github/workflows/changelog.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml @@ -48,18 +48,18 @@ weak [file ".github/workflows/dotnet-file.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file.yml - sha = 59aaf432369b5ea597831d4feec5a6ac4024c2e3 - etag = 1374e3f8c9b7af69c443605c03f7262300dcb7d783738d9eb9fe84268ed2d10c + sha = 8fa147d4799d73819040736c399d0b1db2c2d86c + etag = 1ca805a23656e99c03f9d478dba8ccef6e571f5de2ac0e9bb7e3c5216c99a694 weak [file ".github/workflows/publish.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml - sha = 06e898ccba692566ebf845fa7c8833ac6c318c0a - etag = 2f64f75ad01f735fd05290370fb8a826111ac8dd7e74ce04226bb627a54a62ba + sha = 0ca5fd11125cd10c51b4433a86917c06ff1ae839 + etag = ac46ffdcef820bbc6bd32378aef25ff79713196e0a5696791abb3a804e06e4d8 weak [file ".gitignore"] url = https://github.com/devlooped/oss/blob/main/.gitignore - sha = e0be248fff1d39133345283b8227372b36574b75 - etag = c449ec6f76803e1891357ca2b8b4fcb5b2e5deeff8311622fd92ca9fbf1e6575 + sha = ff61659751374b95c7a8a0477c908a8119f756f0 + etag = e5865f083db45081a7b4eaa518018971b34e6ef93f917ac510dea96d27f792b3 weak [file "Directory.Build.rsp"] url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp @@ -68,8 +68,8 @@ weak [file "_config.yml"] url = https://github.com/devlooped/oss/blob/main/_config.yml - sha = fa83a5161ba52bc5d510ce0ba75ee0b1f8d4bc63 - etag = 9139148f845adf503fd3c3c140eb64421fc476a1f9c027fc50825c0efb05f557 + sha = 68b409c486842062e0de0e5b11e6fdb7cd12d6e2 + etag = d608aa0ddaedc2d8a87260f50756e8d8314964ad4671b76bd085bcb458757010 weak [file "assets/css/style.scss"] url = https://github.com/devlooped/oss/blob/main/assets/css/style.scss @@ -83,13 +83,13 @@ weak [file "src/Directory.Build.props"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props - sha = b76de49afb376aa48eb172963ed70663b59b31d3 - etag = c8b56f3860cc7ccb8773b7bd6189f5c7a6e3a2c27e9104c1ee201fbdc5af9873 + sha = 6e2438919e108aeb75106dc0737c45f5e55d5f42 + etag = f1d6384abf18d8d891ce5e835a10c73fe029c42151374be96d7e4af43d189c65 weak [file "src/Directory.Build.targets"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets - sha = a8b208093599263b7f2d1fe3854634c588ea5199 - etag = 19087699f05396205e6b050d999a43b175bd242f6e8fac86f6df936310178b03 + sha = dcd5f0a9a00a5b0c23d41a71dcc66ea41dc5f75d + etag = 2cca66d8a1adbae24dc2efee53a55fa09949242eb35b86c5fd66425da18c6107 weak [file "src/kzu.snk"] url = https://github.com/devlooped/oss/blob/main/src/kzu.snk @@ -98,8 +98,8 @@ weak [file ".github/workflows/includes.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml - sha = 85829f2510f335f4a411867f3dbaaa116c3ab3de - etag = 086f6b6316cc6ea7089c0dcc6980be519e6ed6e6201e65042ef41b82634ec0ee + sha = d571412f07b0fb3c40023dc906f64906d7ed2b46 + etag = ab627107749577bb619b024aff93027359b66b4c3f5a85a36b235e428387cd17 weak [file ".github/release.yml"] url = https://github.com/devlooped/oss/blob/main/.github/release.yml @@ -119,8 +119,8 @@ skip [file ".github/workflows/triage.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml - sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c - etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a + sha = 5ee8c91c8d9e8c0ee739fe1ec877f36c15a4aff0 + etag = b19dc36058fc7d385cb5795c9e0860c11b9d027c57cb2a5e6a112e692c6884c1 weak [file "src/SponsorLink"] url = https://github.com/devlooped/SponsorLink/tree/main/samples/dotnet/ @@ -198,15 +198,15 @@ weak [file "src/SponsorLink/SponsorLink.Analyzer.Tests.targets"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.Analyzer.Tests.targets - sha = df44ccc14cc11b5674c55aca9ba8596bdbcf8438 + sha = 8a4082211918b604ad95ef0f3da3cd414747c46a - etag = a3e9cbcc227dd56a7bed236eaded136f1b80f9f36a4fabce8be695ee844bf881 + etag = ac4e82c24d5a812eb7a1ad20d2d076b7aeedddd90c8196eaea0c227693a2ede6 weak [file "src/SponsorLink/SponsorLink.Analyzer.targets"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.Analyzer.targets - sha = 0f551e3be564625ee4d078649c55363bf35954ba + sha = 8a4082211918b604ad95ef0f3da3cd414747c46a - etag = 7276d81830e29f8d5f3e27ee62cadaf7aef02a0162b9a05c88e1daef9cc4875e + etag = b75dd01945453c3ccd9eb96f65959ff1607a2cf11226fac5014b01b7cb6314d7 weak [file "src/SponsorLink/SponsorLink/AnalyzerOptionsExtensions.cs"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/AnalyzerOptionsExtensions.cs @@ -226,12 +226,6 @@ etag = a5d79dbc0ed9fac4fb1879fb3790b9ebab18e47c14c454554ce9f53f21487bb5 weak -[file "src/SponsorLink/SponsorLink/ManifestStatus.cs"] - url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/ManifestStatus.cs - sha = f47528874a6d9192b5546f84b455f5ccc474a707 - - etag = e46848f83c0436ba33a1c09a4060ad627a74db41bab66bb37ca40fce8a6532a7 - weak [file "src/SponsorLink/SponsorLink/Resources.es-AR.resx"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es-AR.resx sha = 586398c3e650495f36601ecc8983a14ed745e058 @@ -240,27 +234,27 @@ weak [file "src/SponsorLink/SponsorLink/Resources.es.resx"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es.resx - sha = 29921560c73bb91c2a21a21800daf0b250773598 + sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834 - etag = feb9dc86e4d9c0c4a294cd6e03c5b914943e8d206b88a125abd1b0f882ddb247 + etag = 89a7bb797aeacca43e043196a00eea91f282df4caf9bbe937749026a03f707ad weak [file "src/SponsorLink/SponsorLink/Resources.resx"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.resx - sha = 29921560c73bb91c2a21a21800daf0b250773598 + sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834 - etag = 7665a3be17cd224b1c413ade6a9c1c5a822dace1e7f9daae33a2e52d8bca15bb + etag = 8902652b8907de2fbccf73f3738d0fce503fc667a084171d6b88bf3373e559e7 weak [file "src/SponsorLink/SponsorLink/SponsorLink.cs"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.cs - sha = 3f72a9fd35274a659dd380a7d5b747d71b9732a1 + sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3 - etag = 616598e0ecb6d2ce97660aa6ac049e2a31a1c953669743b7b612b61d40c37706 + etag = 402e2beb11cf64c07be3d0fc3e89115fd09fc24133c08a8951bf0e784909c510 weak [file "src/SponsorLink/SponsorLink/SponsorLink.csproj"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.csproj - sha = 0f551e3be564625ee4d078649c55363bf35954ba + sha = e8ec200934a3b3788c2e31d7022c717f5fd152fa - etag = 27db7c8288b721804b52a719a9218ab1198f4db5b7a7d06bce4e1770def2d4a0 + etag = 1a58baf82b1813f68610272aa6161a18a70d5c619154734039a0d48fce6d735a weak [file "src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLinkAnalyzer.cs @@ -346,12 +340,6 @@ etag = 1875555adb7eab21acf1e730b6baeb8c095d9f6f9f07303a87ad9c16e0f6490d weak -[file "src/SponsorLink/Tests/SponsorLinkTests.cs"] - url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorLinkTests.cs - sha = f47528874a6d9192b5546f84b455f5ccc474a707 - - etag = 1fa41250bd984e8aa840a966d34ce0e94f2111d1422d7f50b864c38364fcf4a4 - weak [file "src/SponsorLink/Tests/SponsorableManifest.cs"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorableManifest.cs sha = f47528874a6d9192b5546f84b455f5ccc474a707 @@ -360,9 +348,9 @@ weak [file "src/SponsorLink/Tests/Tests.csproj"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Tests.csproj - sha = 0f551e3be564625ee4d078649c55363bf35954ba + sha = e8ec200934a3b3788c2e31d7022c717f5fd152fa - etag = 7d27c17944c61da196f11f904383b25b3f40579fbeb0cacb367bf05ec184ad7f + etag = eb34fc9fe25b0169f069ff692379a19c59673727d8abb6f45816012661329df5 weak [file "src/SponsorLink/Tests/keys/kzu.key"] url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key @@ -423,3 +411,32 @@ sha = 032439dbf180fca0539a5bd3a019f18ab3484b76 etag = da7c0104131bd474b52fc9bc9f9bda6470e24ae38d4fb9f5c4f719bc01370ab5 weak +[file "readme.tmp.md"] + url = https://github.com/devlooped/oss/blob/main/readme.tmp.md + skip +[file "oss.cs"] + url = https://github.com/devlooped/oss/blob/main/oss.cs + skip +[file ".github/actions/dotnet/action.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/actions/dotnet/action.yml + skip +[file ".github/copilot-instructions.md"] + url = https://github.com/devlooped/oss/blob/main/.github/copilot-instructions.md + sha = e616d89d9537c4b8ccf1c20dd277ab82104167c4 + etag = 6ee650d118a57494d3545d54718ccaa5257b09d54504e9c21514fe596edd9678 + weak +[file ".github/workflows/dotnet-env.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-env.yml + sha = 7c1c6e615b5785e0ac9db33cb17343d6c1de16ff + etag = 68c1e28f475ff9d05f985bf53a51fce6e64b24a8b75bd8e47119ff57309281f1 + weak +[file "src/SponsorLink/SponsorLink/SponsorManifest.cs"] + url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorManifest.cs + sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3 + etag = 55ef89e8441156541c1c74a50675b7f56633b56493031f0ffa877460839e3536 + weak +[file "src/SponsorLink/Tests/SponsorManifestTests.cs"] + url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorManifestTests.cs + sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3 + etag = 82ae1c417265f2e136544980b4f687a1cc2c1bfb24df93d354c259053550f4a3 + weak diff --git a/_config.yml b/_config.yml index a61f7e0..04e6093 100644 --- a/_config.yml +++ b/_config.yml @@ -1,3 +1,3 @@ theme: jekyll-theme-slate -exclude: [ 'src/', '*.sln', 'Gemfile*', '*.rsp' ] \ No newline at end of file +exclude: [ 'src/', '*.sln', '*.slnx', 'Gemfile*', '*.rsp' ] diff --git a/readme.md b/readme.md index ec0102d..6d98337 100644 --- a/readme.md +++ b/readme.md @@ -207,44 +207,37 @@ await serverChannel.SendAsync(request); # Sponsors -[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png "Clarius Org")](https://github.com/clarius) -[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) -[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png "Torutek")](https://github.com/torutek-gh) -[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png "DRIVE.NET, Inc.")](https://github.com/drivenet) -[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png "Keith Pickford")](https://github.com/Keflon) -[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png "Thomas Bolon")](https://github.com/tbolon) -[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png "Kori Francis")](https://github.com/kfrancis) -[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png "Toni Wenzel")](https://github.com/twenzel) -[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png "Uno Platform")](https://github.com/unoplatform) -[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png "Dan Siegel")](https://github.com/dansiegel) -[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png "Reuben Swartz")](https://github.com/rbnswartz) -[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png "Jacob Foshee")](https://github.com/jfoshee) -[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png "")](https://github.com/Mrxx99) -[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png "Eric Johnson")](https://github.com/eajhnsn1) -[![Ix Technologies B.V.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/IxTechnologies.png "Ix Technologies B.V.")](https://github.com/IxTechnologies) -[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png "David JENNI")](https://github.com/davidjenni) -[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png "Jonathan ")](https://github.com/Jonathan-Hickey) -[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png "Charley Wu")](https://github.com/akunzai) -[![Jakob Tikjøb Andersen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jakobt.png "Jakob Tikjøb Andersen")](https://github.com/jakobt) -[![Tino Hager](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tinohager.png "Tino Hager")](https://github.com/tinohager) -[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png "Ken Bonny")](https://github.com/KenBonny) -[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png "Simon Cropp")](https://github.com/SimonCropp) -[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png "agileworks-eu")](https://github.com/agileworks-eu) -[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png "sorahex")](https://github.com/sorahex) -[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png "Zheyu Shen")](https://github.com/arsdragonfly) -[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png "Vezel")](https://github.com/vezel-dev) -[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png "ChilliCream")](https://github.com/ChilliCream) -[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png "4OTC")](https://github.com/4OTC) -[![Vincent Limo](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/v-limo.png "Vincent Limo")](https://github.com/v-limo) -[![Jordan S. Jones](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jordansjones.png "Jordan S. Jones")](https://github.com/jordansjones) -[![domischell](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/DominicSchell.png "domischell")](https://github.com/DominicSchell) -[![Joseph Kingry](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jkingry.png "Joseph Kingry")](https://github.com/jkingry) +[![Clarius Org](https://avatars.githubusercontent.com/u/71888636?v=4&s=39 "Clarius Org")](https://github.com/clarius) +[![MFB Technologies, Inc.](https://avatars.githubusercontent.com/u/87181630?v=4&s=39 "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) +[![SandRock](https://avatars.githubusercontent.com/u/321868?u=99e50a714276c43ae820632f1da88cb71632ec97&v=4&s=39 "SandRock")](https://github.com/sandrock) +[![DRIVE.NET, Inc.](https://avatars.githubusercontent.com/u/15047123?v=4&s=39 "DRIVE.NET, Inc.")](https://github.com/drivenet) +[![Keith Pickford](https://avatars.githubusercontent.com/u/16598898?u=64416b80caf7092a885f60bb31612270bffc9598&v=4&s=39 "Keith Pickford")](https://github.com/Keflon) +[![Thomas Bolon](https://avatars.githubusercontent.com/u/127185?u=7f50babfc888675e37feb80851a4e9708f573386&v=4&s=39 "Thomas Bolon")](https://github.com/tbolon) +[![Kori Francis](https://avatars.githubusercontent.com/u/67574?u=3991fb983e1c399edf39aebc00a9f9cd425703bd&v=4&s=39 "Kori Francis")](https://github.com/kfrancis) +[![Reuben Swartz](https://avatars.githubusercontent.com/u/724704?u=2076fe336f9f6ad678009f1595cbea434b0c5a41&v=4&s=39 "Reuben Swartz")](https://github.com/rbnswartz) +[![Jacob Foshee](https://avatars.githubusercontent.com/u/480334?v=4&s=39 "Jacob Foshee")](https://github.com/jfoshee) +[![](https://avatars.githubusercontent.com/u/33566379?u=bf62e2b46435a267fa246a64537870fd2449410f&v=4&s=39 "")](https://github.com/Mrxx99) +[![Eric Johnson](https://avatars.githubusercontent.com/u/26369281?u=41b560c2bc493149b32d384b960e0948c78767ab&v=4&s=39 "Eric Johnson")](https://github.com/eajhnsn1) +[![Jonathan ](https://avatars.githubusercontent.com/u/5510103?u=98dcfbef3f32de629d30f1f418a095bf09e14891&v=4&s=39 "Jonathan ")](https://github.com/Jonathan-Hickey) +[![Ken Bonny](https://avatars.githubusercontent.com/u/6417376?u=569af445b6f387917029ffb5129e9cf9f6f68421&v=4&s=39 "Ken Bonny")](https://github.com/KenBonny) +[![Simon Cropp](https://avatars.githubusercontent.com/u/122666?v=4&s=39 "Simon Cropp")](https://github.com/SimonCropp) +[![agileworks-eu](https://avatars.githubusercontent.com/u/5989304?v=4&s=39 "agileworks-eu")](https://github.com/agileworks-eu) +[![Zheyu Shen](https://avatars.githubusercontent.com/u/4067473?v=4&s=39 "Zheyu Shen")](https://github.com/arsdragonfly) +[![Vezel](https://avatars.githubusercontent.com/u/87844133?v=4&s=39 "Vezel")](https://github.com/vezel-dev) +[![ChilliCream](https://avatars.githubusercontent.com/u/16239022?v=4&s=39 "ChilliCream")](https://github.com/ChilliCream) +[![4OTC](https://avatars.githubusercontent.com/u/68428092?v=4&s=39 "4OTC")](https://github.com/4OTC) +[![domischell](https://avatars.githubusercontent.com/u/66068846?u=0a5c5e2e7d90f15ea657bc660f175605935c5bea&v=4&s=39 "domischell")](https://github.com/DominicSchell) +[![Adrian Alonso](https://avatars.githubusercontent.com/u/2027083?u=129cf516d99f5cb2fd0f4a0787a069f3446b7522&v=4&s=39 "Adrian Alonso")](https://github.com/adalon) +[![torutek](https://avatars.githubusercontent.com/u/33917059?v=4&s=39 "torutek")](https://github.com/torutek) +[![Ryan McCaffery](https://avatars.githubusercontent.com/u/16667079?u=c0daa64bb5c1b572130e05ae2b6f609ecc912d4d&v=4&s=39 "Ryan McCaffery")](https://github.com/mccaffers) +[![Seika Logiciel](https://avatars.githubusercontent.com/u/2564602?v=4&s=39 "Seika Logiciel")](https://github.com/SeikaLogiciel) +[![Andrew Grant](https://avatars.githubusercontent.com/devlooped-user?s=39 "Andrew Grant")](https://github.com/wizardness) +[![eska-gmbh](https://avatars.githubusercontent.com/devlooped-team?s=39 "eska-gmbh")](https://github.com/eska-gmbh) +[![Geodata AS](https://avatars.githubusercontent.com/u/5946299?v=4&s=39 "Geodata AS")](https://github.com/geodata-no) - -[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png "Sponsor this project")](https://github.com/sponsors/devlooped) -  +[![Sponsor this project](https://avatars.githubusercontent.com/devlooped-sponsor?s=118 "Sponsor this project")](https://github.com/sponsors/devlooped) [Learn more about GitHub Sponsors](https://github.com/sponsors) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 381c383..93a0b1e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -20,6 +20,7 @@ Daniel Cazzulino + Devlooped Copyright (C) Daniel Cazzulino and Contributors. All rights reserved. false MIT @@ -42,6 +43,13 @@ true + + false + + true + + + false @@ -126,11 +134,22 @@ <_VersionLabel>$(_VersionLabel.Replace('/merge', '')) <_VersionLabel>$(_VersionLabel.Replace('/', '-')) + + <_VersionLabel>$(_VersionLabel.Replace('_', '-')) $(_VersionLabel) $(_VersionLabel) + + + true + 42.42.0 + $(VersionSuffix).$(GITHUB_RUN_NUMBER) @@ -150,9 +169,25 @@ + + + + + + + 1.0.0 + $(VersionPrefix)-$(VersionSuffix) + $(VersionPrefix) + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 6232750..bd2b7ca 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -32,6 +32,22 @@ true + + + <_PackageId>$(PackageId) + + $(MSBuildSDKsPath)\..\NuGet.Build.Tasks.Pack.targets + + + + + + + $(_PackageId) + + false + + @@ -165,22 +181,35 @@ @(_GitSourceRoot) + + $([System.IO.Path]::GetFileNameWithoutExtension($(PrivateRepositoryUrl))) + $(ProductFromUrl) - $(RepositoryUrl) + $(RepositoryUrl.Replace('.git', '')) $(Description) - $(RepositoryUrl)/blob/main/changelog.md + $(RepositoryUrl.Replace('.git', ''))/blob/main/changelog.md + + + + + OSMFEULA.txt + true + + + + diff --git a/src/SponsorLink/SponsorLink.Analyzer.Tests.targets b/src/SponsorLink/SponsorLink.Analyzer.Tests.targets index 1ce67f6..4687e1e 100644 --- a/src/SponsorLink/SponsorLink.Analyzer.Tests.targets +++ b/src/SponsorLink/SponsorLink.Analyzer.Tests.targets @@ -7,12 +7,12 @@ - + - + diff --git a/src/SponsorLink/SponsorLink.Analyzer.targets b/src/SponsorLink/SponsorLink.Analyzer.targets index 0f58764..6a78464 100644 --- a/src/SponsorLink/SponsorLink.Analyzer.targets +++ b/src/SponsorLink/SponsorLink.Analyzer.targets @@ -84,15 +84,15 @@ - - + + - - + + diff --git a/src/SponsorLink/SponsorLink/ManifestStatus.cs b/src/SponsorLink/SponsorLink/ManifestStatus.cs deleted file mode 100644 index 0960e5a..0000000 --- a/src/SponsorLink/SponsorLink/ManifestStatus.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -namespace Devlooped.Sponsors; - -/// -/// The resulting status from validation. -/// -public enum ManifestStatus -{ - /// - /// The manifest couldn't be read at all. - /// - Unknown, - /// - /// The manifest was read and is valid (not expired and properly signed). - /// - Valid, - /// - /// The manifest was read but has expired. - /// - Expired, - /// - /// The manifest was read, but its signature is invalid. - /// - Invalid, -} diff --git a/src/SponsorLink/SponsorLink/Resources.es.resx b/src/SponsorLink/SponsorLink/Resources.es.resx index b9ff562..17a7de0 100644 --- a/src/SponsorLink/SponsorLink/Resources.es.resx +++ b/src/SponsorLink/SponsorLink/Resources.es.resx @@ -169,6 +169,9 @@ Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posterio Eres un contribuidor al proyecto, eres lo máximo 💟! + + El uso de {0} sin warnings en el editor requiere un patrocinio activo. Ver mas en {1}. + Patrocinar los proyectos en que dependes asegura que se mantengan activos, y que recibas el apoyo que necesitas. También es muy económico y está disponible en todo el mundo! Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'. diff --git a/src/SponsorLink/SponsorLink/Resources.resx b/src/SponsorLink/SponsorLink/Resources.resx index 7b1dfa9..93b75bc 100644 --- a/src/SponsorLink/SponsorLink/Resources.resx +++ b/src/SponsorLink/SponsorLink/Resources.resx @@ -171,7 +171,7 @@ Please consider supporting the project by sponsoring at {0} and running 'sponsor You are a contributor to the project, you rock 💟! - Editor usage of {0} requires an active sponsorship. Learn more at {1}. + Editor usage of {0} without warnings requires an active sponsorship. Learn more at {1}. Sponsoring projects you depend on ensures they remain active, and that you get the support you need. It's also super affordable and available worldwide! diff --git a/src/SponsorLink/SponsorLink/SponsorLink.cs b/src/SponsorLink/SponsorLink/SponsorLink.cs index eec50c8..2c1e6db 100644 --- a/src/SponsorLink/SponsorLink/SponsorLink.cs +++ b/src/SponsorLink/SponsorLink/SponsorLink.cs @@ -165,82 +165,4 @@ public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, I return principal != null; } - - /// - /// Validates the manifest signature and optional expiration. - /// - /// The JWT to validate. - /// The key to validate the manifest signature with. - /// Except when returning , returns the security token read from the JWT, even if signature check failed. - /// The associated claims, only when return value is not . - /// Whether to check for expiration. - /// The status of the validation. - public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsIdentity? identity, bool validateExpiration) - { - token = default; - identity = default; - - SecurityKey key; - try - { - key = JsonWebKey.Create(jwk); - } - catch (ArgumentException) - { - return ManifestStatus.Unknown; - } - - var handler = new JsonWebTokenHandler { MapInboundClaims = false }; - - if (!handler.CanReadToken(jwt)) - return ManifestStatus.Unknown; - - var validation = new TokenValidationParameters - { - RequireExpirationTime = false, - ValidateLifetime = false, - ValidateAudience = false, - ValidateIssuer = false, - ValidateIssuerSigningKey = true, - IssuerSigningKey = key, - RoleClaimType = "roles", - NameClaimType = "sub", - }; - - var result = handler.ValidateTokenAsync(jwt, validation).Result; - if (result.Exception != null) - { - if (result.Exception is SecurityTokenInvalidSignatureException) - { - var jwtToken = handler.ReadJsonWebToken(jwt); - token = jwtToken; - identity = new ClaimsIdentity(jwtToken.Claims); - return ManifestStatus.Invalid; - } - else - { - var jwtToken = handler.ReadJsonWebToken(jwt); - token = jwtToken; - identity = new ClaimsIdentity(jwtToken.Claims); - return ManifestStatus.Invalid; - } - } - - token = result.SecurityToken; - identity = new ClaimsIdentity(result.ClaimsIdentity.Claims, "JWT"); - - if (validateExpiration && token.ValidTo == DateTime.MinValue) - return ManifestStatus.Invalid; - - // The sponsorable manifest does not have an expiration time. - if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow) - return ManifestStatus.Expired; - - return ManifestStatus.Valid; - } - - class JwtRolesPrincipal(ClaimsIdentity identity) : ClaimsPrincipal([identity]) - { - public override bool IsInRole(string role) => HasClaim("roles", role) || base.IsInRole(role); - } } diff --git a/src/SponsorLink/SponsorLink/SponsorLink.csproj b/src/SponsorLink/SponsorLink/SponsorLink.csproj index 46691cc..25a474c 100644 --- a/src/SponsorLink/SponsorLink/SponsorLink.csproj +++ b/src/SponsorLink/SponsorLink/SponsorLink.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/SponsorLink/SponsorLink/SponsorManifest.cs b/src/SponsorLink/SponsorLink/SponsorManifest.cs new file mode 100644 index 0000000..b4aa9d7 --- /dev/null +++ b/src/SponsorLink/SponsorLink/SponsorManifest.cs @@ -0,0 +1,160 @@ +// +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Security.Claims; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Tokens; + +namespace Devlooped.Sponsors; + +/// +/// The resulting status from validation. +/// +public enum ManifestStatus +{ + /// + /// The manifest couldn't be read at all. + /// + Unknown, + /// + /// The manifest was read and is valid (not expired and properly signed). + /// + Valid, + /// + /// The manifest was read but has expired. + /// + Expired, + /// + /// The manifest was read, but its signature is invalid. + /// + Invalid, +} + +/// +/// Represents the sponsorship status of a user. +/// +/// The status. +/// The principal potentially containing roles validated from the manifest. +/// The security token from the validated manifest. +public record SponsorManifest(ManifestStatus Status, ClaimsPrincipal Principal, SecurityToken? SecurityToken) +{ + /// + /// Whether the manifest is . + /// + public bool IsValid => Status == ManifestStatus.Valid; +} + +static partial class SponsorLink +{ + /// + /// Reads the local manifest (if present) for the specified sponsorable account and validates it + /// against the given JWK key. + /// + /// The sponsorable account to read. + /// The public key to validate the signature on the manifest JWT if found. + /// Whether to validate the manifest expiration. If , + /// an expired manifest will be reported as . The expiration date + /// can be checked in that case via the . + /// A manifest that represents the user status. + public static SponsorManifest GetManifest(string sponsorable, string jwk, bool validateExpiration = true) + { + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".sponsorlink", "github", sponsorable + ".jwt"); + + if (!File.Exists(path)) + return new SponsorManifest(ManifestStatus.Unknown, new ClaimsPrincipal(), null); + + return ParseManifest(File.ReadAllText(path), jwk, validateExpiration); + } + + internal static SponsorManifest ParseManifest(string jwt, string jwk, bool validateExpiration) + { + var status = Validate(jwt, jwk, out var token, out var identity, validateExpiration); + + if (status == ManifestStatus.Unknown || identity == null) + return new SponsorManifest(status, new ClaimsPrincipal(), token); + + return new SponsorManifest(status, new JwtRolesPrincipal(identity), token); + } + + /// + /// Validates the manifest signature and optional expiration. + /// + /// The JWT to validate. + /// The key to validate the manifest signature with. + /// Except when returning , returns the security token read from the JWT, even if signature check failed. + /// The associated claims, only when return value is not . + /// Whether to check for expiration. + /// The status of the validation. + public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsIdentity? identity, bool validateExpiration) + { + token = default; + identity = default; + + SecurityKey key; + try + { + key = JsonWebKey.Create(jwk); + } + catch (ArgumentException) + { + return ManifestStatus.Unknown; + } + + var handler = new JsonWebTokenHandler { MapInboundClaims = false }; + + if (!handler.CanReadToken(jwt)) + return ManifestStatus.Unknown; + + var validation = new TokenValidationParameters + { + RequireExpirationTime = false, + ValidateLifetime = false, + ValidateAudience = false, + ValidateIssuer = false, + ValidateIssuerSigningKey = true, + IssuerSigningKey = key, + RoleClaimType = "roles", + NameClaimType = "sub", + }; + + var result = handler.ValidateTokenAsync(jwt, validation).Result; + if (!result.IsValid || result.Exception != null) + { + if (result.Exception is SecurityTokenInvalidSignatureException) + { + var jwtToken = handler.ReadJsonWebToken(jwt); + token = jwtToken; + identity = new ClaimsIdentity(jwtToken.Claims); + return ManifestStatus.Invalid; + } + else + { + var jwtToken = handler.ReadJsonWebToken(jwt); + token = jwtToken; + identity = new ClaimsIdentity(jwtToken.Claims); + return ManifestStatus.Invalid; + } + } + + token = result.SecurityToken; + identity = new ClaimsIdentity(result.ClaimsIdentity.Claims, "JWT"); + + if (validateExpiration && token.ValidTo == DateTime.MinValue) + return ManifestStatus.Invalid; + + // The sponsorable manifest does not have an expiration time. + if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow) + return ManifestStatus.Expired; + + return ManifestStatus.Valid; + } + + class JwtRolesPrincipal(ClaimsIdentity identity) : ClaimsPrincipal([identity]) + { + public override bool IsInRole(string role) => HasClaim("roles", role) || base.IsInRole(role); + } +} diff --git a/src/SponsorLink/Tests/SponsorLinkTests.cs b/src/SponsorLink/Tests/SponsorManifestTests.cs similarity index 63% rename from src/SponsorLink/Tests/SponsorLinkTests.cs rename to src/SponsorLink/Tests/SponsorManifestTests.cs index 7625e2c..7895a81 100644 --- a/src/SponsorLink/Tests/SponsorLinkTests.cs +++ b/src/SponsorLink/Tests/SponsorManifestTests.cs @@ -8,7 +8,7 @@ namespace Devlooped.Tests; -public class SponsorLinkTests +public class SponsorManifestTests { // We need to convert to jwk string since the analyzer project has merged the JWT assembly and types. public static string ToJwk(SecurityKey key) @@ -19,64 +19,67 @@ public static string ToJwk(SecurityKey key) [Fact] public void ValidateSponsorable() { - var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); - var jwt = manifest.ToJwt(); - var jwk = ToJwk(manifest.SecurityKey); + var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); + var jwt = sponsorable.ToJwt(); + var jwk = ToJwk(sponsorable.SecurityKey); // NOTE: sponsorable manifest doesn't have expiration date. - var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false); + var manifest = SponsorLink.ParseManifest(jwt, jwk, false); - Assert.Equal(ManifestStatus.Valid, status); + Assert.True(manifest.IsValid); + Assert.Equal(ManifestStatus.Valid, manifest.Status); } [Fact] public void ValidateWrongKey() { - var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); - var jwt = manifest.ToJwt(); + var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); + var jwt = sponsorable.ToJwt(); var jwk = ToJwk(new RsaSecurityKey(RSA.Create())); - var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false); + var manifest = SponsorLink.ParseManifest(jwt, jwk, false); - Assert.Equal(ManifestStatus.Invalid, status); + Assert.Equal(ManifestStatus.Invalid, manifest.Status); // We should still be a able to read the data, knowing it may have been tampered with. - Assert.NotNull(principal); - Assert.NotNull(token); + Assert.NotNull(manifest.Principal); + Assert.NotNull(manifest.SecurityToken); } [Fact] public void ValidateExpiredSponsor() { - var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); - var jwk = ToJwk(manifest.SecurityKey); - var sponsor = manifest.Sign([], expiration: TimeSpan.Zero); + var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); + var jwk = ToJwk(sponsorable.SecurityKey); + var sponsor = sponsorable.Sign([], expiration: TimeSpan.Zero); // Will be expired after this. Thread.Sleep(1000); - var status = SponsorLink.Validate(sponsor, jwk, out var token, out var principal, true); + var manifest = SponsorLink.ParseManifest(sponsor, jwk, true); - Assert.Equal(ManifestStatus.Expired, status); + Assert.Equal(ManifestStatus.Expired, manifest.Status); // We should still be a able to read the data, even if expired (but not tampered with). - Assert.NotNull(principal); - Assert.NotNull(token); + Assert.NotNull(manifest.Principal); + Assert.NotNull(manifest.SecurityToken); } [Fact] public void ValidateUnknownFormat() { - var manifest = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); - var jwk = ToJwk(manifest.SecurityKey); + var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234"); + var jwk = ToJwk(sponsorable.SecurityKey); - var status = SponsorLink.Validate("asdfasdf", jwk, out var token, out var principal, false); + var manifest = SponsorLink.ParseManifest("asdfasdf", jwk, false); - Assert.Equal(ManifestStatus.Unknown, status); + Assert.Equal(ManifestStatus.Unknown, manifest.Status); // Nothing could be read at all. - Assert.Null(principal); - Assert.Null(token); + Assert.False(manifest.IsValid); + Assert.NotNull(manifest.Principal); + Assert.Null(manifest.Principal.Identity); + Assert.Null(manifest.SecurityToken); } [Fact] @@ -111,7 +114,7 @@ public void ValidateCachedManifest() var jwt = File.ReadAllText(path); - var status = SponsorLink.Validate(jwt, + var manifest = SponsorLink.ParseManifest(jwt, """ { "e": "AQAB", @@ -119,8 +122,8 @@ public void ValidateCachedManifest() "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V" } """ - , out var token, out var principal, false); + , false); - Assert.Equal(ManifestStatus.Valid, status); + Assert.Equal(ManifestStatus.Valid, manifest.Status); } } diff --git a/src/SponsorLink/Tests/Tests.csproj b/src/SponsorLink/Tests/Tests.csproj index b14b666..df8bc07 100644 --- a/src/SponsorLink/Tests/Tests.csproj +++ b/src/SponsorLink/Tests/Tests.csproj @@ -10,8 +10,8 @@ - - + +