From db4a19fac19aa0afd69912e26f1f67e5fa38540b Mon Sep 17 00:00:00 2001 From: devlooped-bot <bot@devlooped.com> Date: Tue, 1 Oct 2024 01:01:05 +0000 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20files=20with=20dotn?= =?UTF-8?q?et-file=20sync=20#=20devlooped/oss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Improve triage actions on issues https://github.com/devlooped/oss/commit/33000c0 - SponsorLink code should be checked as regular code https://github.com/devlooped/oss/commit/e81ab75 - Set Version from VersionLabel if it's a refs/tags/ https://github.com/devlooped/oss/commit/57653a2 - Cleanup build and publish to use VersionLabel https://github.com/devlooped/oss/commit/14deaea - Improve default value for GenerateDocumentationFile https://github.com/devlooped/oss/commit/b76de49 # clarius/pages # inventaire/isbn3 --- .editorconfig | 3 - .gitattributes | 22 +- .github/release.yml | 2 +- .github/workflows/build.yml | 31 +- .github/workflows/changelog.config | 2 +- .github/workflows/includes.yml | 2 +- .github/workflows/publish.yml | 28 +- .github/workflows/sponsor.yml | 24 -- .github/workflows/test/action.yml | 36 -- .github/workflows/triage.yml | 103 ++++++ .netconfig | 236 ++----------- Directory.Build.rsp | 2 +- Gemfile | 2 +- readme.md | 3 +- src/Directory.Build.props | 8 +- src/Directory.Build.targets | 26 +- src/ISBN/groups.js | 167 +++++++++- src/SponsorLink/Analyzer/Analyzer.csproj | 32 -- .../Analyzer/Properties/launchSettings.json | 11 - .../Analyzer/StatusReportingAnalyzer.cs | 26 -- .../buildTransitive/SponsorableLib.targets | 3 - src/SponsorLink/Directory.Build.props | 43 --- src/SponsorLink/Directory.Build.targets | 8 - src/SponsorLink/Library/Library.csproj | 31 -- src/SponsorLink/Library/MyClass.cs | 5 - src/SponsorLink/Library/Resources.resx | 123 ------- src/SponsorLink/SponsorLink.targets | 141 -------- .../SponsorLink/AppDomainDictionary.cs | 36 -- .../SponsorLink/DiagnosticsManager.cs | 138 -------- src/SponsorLink/SponsorLink/ManifestStatus.cs | 25 -- src/SponsorLink/SponsorLink/SponsorLink.cs | 169 ---------- .../SponsorLink/SponsorLink.csproj | 46 --- .../SponsorLink/SponsorLink.es.resx | 163 --------- src/SponsorLink/SponsorLink/SponsorLink.resx | 164 ---------- .../SponsorLink/SponsorLinkAnalyzer.cs | 126 ------- src/SponsorLink/SponsorLink/SponsorStatus.cs | 25 -- .../SponsorLink/SponsorableLib.targets | 60 ---- src/SponsorLink/SponsorLink/ThisAssembly.cs | 31 -- src/SponsorLink/SponsorLink/Tracing.cs | 53 --- .../Devlooped.Sponsors.targets | 99 ------ src/SponsorLink/SponsorLink/devlooped.pub.jwk | 5 - src/SponsorLink/SponsorLink/sponsorable.md | 5 - src/SponsorLink/SponsorLinkAnalyzer.sln | 43 --- src/SponsorLink/Tests/.netconfig | 15 - src/SponsorLink/Tests/Attributes.cs | 59 ---- src/SponsorLink/Tests/Extensions.cs | 43 --- src/SponsorLink/Tests/JsonOptions.cs | 72 ---- src/SponsorLink/Tests/Sample.cs | 59 ---- src/SponsorLink/Tests/SponsorLinkTests.cs | 126 ------- src/SponsorLink/Tests/SponsorableManifest.cs | 309 ------------------ src/SponsorLink/Tests/Tests.csproj | 42 --- src/SponsorLink/readme.md | 34 -- 52 files changed, 351 insertions(+), 2716 deletions(-) delete mode 100644 .github/workflows/sponsor.yml delete mode 100644 .github/workflows/test/action.yml create mode 100644 .github/workflows/triage.yml delete mode 100644 src/SponsorLink/Analyzer/Analyzer.csproj delete mode 100644 src/SponsorLink/Analyzer/Properties/launchSettings.json delete mode 100644 src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs delete mode 100644 src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets delete mode 100644 src/SponsorLink/Directory.Build.props delete mode 100644 src/SponsorLink/Directory.Build.targets delete mode 100644 src/SponsorLink/Library/Library.csproj delete mode 100644 src/SponsorLink/Library/MyClass.cs delete mode 100644 src/SponsorLink/Library/Resources.resx delete mode 100644 src/SponsorLink/SponsorLink.targets delete mode 100644 src/SponsorLink/SponsorLink/AppDomainDictionary.cs delete mode 100644 src/SponsorLink/SponsorLink/DiagnosticsManager.cs delete mode 100644 src/SponsorLink/SponsorLink/ManifestStatus.cs delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.cs delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.csproj delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.es.resx delete mode 100644 src/SponsorLink/SponsorLink/SponsorLink.resx delete mode 100644 src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs delete mode 100644 src/SponsorLink/SponsorLink/SponsorStatus.cs delete mode 100644 src/SponsorLink/SponsorLink/SponsorableLib.targets delete mode 100644 src/SponsorLink/SponsorLink/ThisAssembly.cs delete mode 100644 src/SponsorLink/SponsorLink/Tracing.cs delete mode 100644 src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets delete mode 100644 src/SponsorLink/SponsorLink/devlooped.pub.jwk delete mode 100644 src/SponsorLink/SponsorLink/sponsorable.md delete mode 100644 src/SponsorLink/SponsorLinkAnalyzer.sln delete mode 100644 src/SponsorLink/Tests/.netconfig delete mode 100644 src/SponsorLink/Tests/Attributes.cs delete mode 100644 src/SponsorLink/Tests/Extensions.cs delete mode 100644 src/SponsorLink/Tests/JsonOptions.cs delete mode 100644 src/SponsorLink/Tests/Sample.cs delete mode 100644 src/SponsorLink/Tests/SponsorLinkTests.cs delete mode 100644 src/SponsorLink/Tests/SponsorableManifest.cs delete mode 100644 src/SponsorLink/Tests/Tests.csproj delete mode 100644 src/SponsorLink/readme.md diff --git a/.editorconfig b/.editorconfig index e17d14e..4cab270 100644 --- a/.editorconfig +++ b/.editorconfig @@ -107,6 +107,3 @@ dotnet_analyzer_diagnostic.category-Style.severity = none # VSTHRD200: Use "Async" suffix for async methods dotnet_diagnostic.VSTHRD200.severity = none - -[**/*SponsorLink*/**] -generated_code = true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 7c37579..4f89148 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,24 +1,8 @@ -# sln, csproj files (and friends) are always CRLF, even on linux -*.sln text eol=crlf -*.proj text eol=crlf -*.csproj text eol=crlf +# normalize by default +* text=auto encoding=UTF-8 +*.sh text eol=lf # These are windows specific files which we may as well ensure are # always crlf on checkout *.bat text eol=crlf *.cmd text eol=crlf - -# Opt in known filetypes to always normalize line endings on checkin -# and always use native endings on checkout -*.c text -*.config text -*.h text -*.cs text -*.md text -*.tt text -*.txt text - -# Some must always be checked out as lf so enforce that for those files -# If these are not lf then bash/cygwin on windows will not be able to -# excute the files -*.sh text eol=lf \ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml index 9a018cd..c178589 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -8,7 +8,6 @@ changelog: - invalid - wontfix - need info - - docs - techdebt authors: - devlooped-bot @@ -24,6 +23,7 @@ changelog: - title: 📝 Documentation updates labels: - docs + - documentation - title: 🔨 Other labels: - '*' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9a0364..a109549 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,25 +4,31 @@ name: build on: workflow_dispatch: + inputs: + configuration: + type: choice + description: Configuration + options: + - Release + - Debug push: branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ] paths-ignore: - changelog.md - - code-of-conduct.md - - security.md - - support.md - readme.md pull_request: types: [opened, synchronize, reopened] env: DOTNET_NOLOGO: true - VersionPrefix: 42.42.${{ github.run_number }} - VersionLabel: ${{ github.ref }} PackOnBuild: true GeneratePackageOnBuild: true + VersionPrefix: 42.42.${{ github.run_number }} + VersionLabel: ${{ github.ref }} GH_TOKEN: ${{ secrets.GH_TOKEN }} - + MSBUILDTERMINALLOGGER: auto + Configuration: ${{ github.event.inputs.configuration || 'Release' }} + defaults: run: shell: bash @@ -61,14 +67,10 @@ jobs: - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - - name: ⚙ GNU grep - if: matrix.os == 'macOS-latest' - run: | - brew install grep - echo 'export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"' >> .bash_profile - - name: 🧪 test - uses: ./.github/workflows/test + run: | + dotnet tool update -g dotnet-retest + dotnet retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v3 @@ -77,13 +79,12 @@ jobs: name: logs path: '*.binlog' - # Only push CI package to sleet feed if building on ubuntu (fastest) - name: 🚀 sleet env: SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }} if: env.SLEET_CONNECTION != '' run: | - dotnet tool install -g --version 4.0.18 sleet + dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r) sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found" dotnet-format: diff --git a/.github/workflows/changelog.config b/.github/workflows/changelog.config index cd34ee7..e47bccd 100644 --- a/.github/workflows/changelog.config +++ b/.github/workflows/changelog.config @@ -1,7 +1,7 @@ usernames-as-github-logins=true issues_wo_labels=true pr_wo_labels=true -exclude-labels=bydesign,dependencies,duplicate,question,invalid,wontfix,need info,docs +exclude-labels=bydesign,dependencies,duplicate,discussion,question,invalid,wontfix,need info,docs enhancement-label=:sparkles: Implemented enhancements: bugs-label=:bug: Fixed bugs: issues-label=:hammer: Other: diff --git a/.github/workflows/includes.yml b/.github/workflows/includes.yml index 9cdae21..15a781e 100644 --- a/.github/workflows/includes.yml +++ b/.github/workflows/includes.yml @@ -31,7 +31,7 @@ jobs: - name: ✍ pull request uses: peter-evans/create-pull-request@v6 with: - add-paths: '**/*.md' + add-paths: '**.md' base: main branch: markdown-includes delete-branch: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1c2833b..ae4240f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,18 +5,20 @@ name: publish on: release: - types: [released] + types: [prereleased, released] env: DOTNET_NOLOGO: true Configuration: Release PackOnBuild: true GeneratePackageOnBuild: true + VersionLabel: ${{ github.ref }} GH_TOKEN: ${{ secrets.GH_TOKEN }} - + MSBUILDTERMINALLOGGER: auto + jobs: publish: - runs-on: ubuntu-latest + runs-on: ${{ vars.PUBLISH_AGENT || 'ubuntu-latest' }} steps: - name: 🤘 checkout uses: actions/checkout@v4 @@ -25,10 +27,12 @@ jobs: fetch-depth: 0 - name: 🙏 build - run: dotnet build -m:1 -p:version=${GITHUB_REF#refs/*/v} -bl:build.binlog + run: dotnet build -m:1 -bl:build.binlog - name: 🧪 test - uses: ./.github/workflows/test + run: | + dotnet tool update -g dotnet-retest + dotnet retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v3 @@ -38,4 +42,16 @@ jobs: path: '*.binlog' - name: 🚀 nuget - run: dotnet nuget push ./bin/**/*.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + if: ${{ env.NUGET_API_KEY != '' && github.event.action != 'prereleased' }} + working-directory: bin + run: dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate + + - name: 🚀 sleet + env: + SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }} + if: env.SLEET_CONNECTION != '' + run: | + dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r) + sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found" diff --git a/.github/workflows/sponsor.yml b/.github/workflows/sponsor.yml deleted file mode 100644 index 1d484d3..0000000 --- a/.github/workflows/sponsor.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: sponsor 💜 -on: - issues: - types: [opened, edited, reopened] - pull_request: - types: [opened, edited, synchronize, reopened] - -jobs: - sponsor: - runs-on: ubuntu-latest - continue-on-error: true - env: - token: ${{ secrets.GH_TOKEN }} - if: ${{ !endsWith(github.event.sender.login, '[bot]') && !endsWith(github.event.sender.login, 'bot') }} - steps: - - name: 🤘 checkout - if: env.token != '' - uses: actions/checkout@v4 - - - name: 💜 sponsor - if: env.token != '' - uses: devlooped/actions-sponsor@main - with: - token: ${{ env.token }} diff --git a/.github/workflows/test/action.yml b/.github/workflows/test/action.yml deleted file mode 100644 index 4a7dbae..0000000 --- a/.github/workflows/test/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: test -description: runs dotnet tests with retry -runs: - using: "composite" - steps: - - name: 🧪 test - shell: bash --noprofile --norc {0} - env: - LC_ALL: en_US.utf8 - run: | - [ -f .bash_profile ] && source .bash_profile - counter=0 - exitcode=0 - reset="\e[0m" - warn="\e[0;33m" - while [ $counter -lt 6 ] - do - # run test and forward output also to a file in addition to stdout (tee command) - if [ $filter ] - then - echo -e "${warn}Retry $counter for $filter ${reset}" - dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m --filter=$filter | tee ./output.log - else - dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m | tee ./output.log - fi - # capture dotnet test exit status, different from tee - exitcode=${PIPESTATUS[0]} - if [ $exitcode == 0 ] - then - exit 0 - fi - # cat output, get failed test names, remove trailing whitespace, sort+dedupe, join as FQN~TEST with |, remove trailing |. - filter=$(cat ./output.log | grep -o -P '(?<=\sFailed\s)[\w\._]*' | sed 's/ *$//g' | sort -u | awk 'BEGIN { ORS="|" } { print("FullyQualifiedName~" $0) }' | grep -o -P '.*(?=\|$)') - ((counter++)) - done - exit $exitcode diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 0000000..56ff299 --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,103 @@ +name: 'triage' +on: + schedule: + - cron: '42 0 * * *' + + workflow_dispatch: + # Manual triggering through the GitHub UI, API, or CLI + inputs: + daysBeforeClose: + description: "Days before closing stale or need info issues" + required: true + default: "30" + daysBeforeStale: + description: "Days before labeling stale" + required: true + default: "180" + daysSinceClose: + description: "Days since close to lock" + required: true + default: "30" + daysSinceUpdate: + description: "Days since update to lock" + required: true + default: "30" + +permissions: + actions: write # For managing the operation state cache + issues: write + contents: read + +jobs: + stale: + # Do not run on forks + if: github.repository_owner == 'devlooped' + runs-on: ubuntu-latest + steps: + - name: ⌛ rate + shell: pwsh + if: github.event_name != 'workflow_dispatch' + env: + GH_TOKEN: ${{ secrets.DEVLOOPED_TOKEN }} + run: | + # add random sleep since we run on fixed schedule + $wait = get-random -max 180 + echo "Waiting random $wait seconds to start" + sleep $wait + # get currently authenticated user rate limit info + $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate + # 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" + sleep $wait + $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate + echo "Rate limit has reset to $($rate.remaining) requests" + } + + - name: ✏️ stale labeler + # pending merge: https://github.com/actions/stale/pull/1176 + uses: kzu/stale@c8450312ba97b204bf37545cb249742144d6ca69 + with: + ascending: true # Process the oldest issues first + stale-issue-label: 'stale' + stale-issue-message: | + Due to lack of recent activity, this issue has been labeled as 'stale'. + It will be closed if no further activity occurs within ${{ fromJson(inputs.daysBeforeClose || 30 ) }} more days. + Any new comment will remove the label. + close-issue-message: | + This issue will now be closed since it has been labeled 'stale' without activity for ${{ fromJson(inputs.daysBeforeClose || 30 ) }} days. + days-before-stale: ${{ fromJson(inputs.daysBeforeStale || 180) }} + days-before-close: ${{ fromJson(inputs.daysBeforeClose || 30 ) }} + days-before-pr-close: -1 # Do not close PRs labeled as 'stale' + exempt-all-milestones: true + exempt-all-assignees: true + exempt-issue-labels: priority,sponsor,backed + exempt-authors: kzu + + - name: 🤘 checkout actions + uses: actions/checkout@v4 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: v42 + + - name: ⚙ install actions + run: npm install --production + + - name: 🔒 issues locker + uses: ./locker + with: + token: ${{ secrets.DEVLOOPED_TOKEN }} + ignoredLabel: priority + daysSinceClose: ${{ fromJson(inputs.daysSinceClose || 30) }} + daysSinceUpdate: ${{ fromJson(inputs.daysSinceUpdate || 30) }} + + - name: 🔒 need info closer + uses: ./needs-more-info-closer + with: + token: ${{ secrets.DEVLOOPED_TOKEN }} + label: 'need info' + 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 diff --git a/.netconfig b/.netconfig index f84726c..17905a7 100644 --- a/.netconfig +++ b/.netconfig @@ -27,13 +27,13 @@ skip [file ".editorconfig"] url = https://github.com/devlooped/oss/blob/main/.editorconfig - sha = f571a42eac3cad554810dad15139ff390db5e1db - etag = ba2655b8b3ce5491b1c0eea5e0af201a085c48e07542bb9ec2c928084944ea86 + sha = e81ab754b366d52d92bd69b24bef1d5b1c610634 + etag = 7298c6450967975a8782b5c74f3071e1910fc59686e48f9c9d5cd7c68213cf59 weak [file ".gitattributes"] url = https://github.com/devlooped/oss/blob/main/.gitattributes - sha = 0683ee777d7d878d4bf013d7deea352685135a05 - etag = 7acb32f5fa6d4ccd9c824605a7c2b8538497f0068c165567807d393dcf4d6bb7 + sha = 5f92a68e302bae675b394ef343114139c075993e + etag = 338ba6d92c8d1774363396739c2be4257bfc58026f4b0fe92cb0ae4460e1eff7 weak [file ".github/FUNDING.yml"] url = https://github.com/devlooped/.github/blob/main/.github/FUNDING.yml @@ -47,8 +47,8 @@ weak [file ".github/workflows/build.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml - sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9 - etag = 6efc7d096b25bb4bbeffe7960a1194f1ceb5d21abeda85d28b55594b648ab44a + sha = 5e17ad62ebb5241555a7a4d29e3ab15e5ba120d2 + etag = f358acb1e45596bf0aad49996017da44939de30b805289c4ad205a7ccb6f99cb weak [file ".github/workflows/changelog.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml @@ -62,8 +62,8 @@ weak [file ".github/workflows/publish.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml - sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9 - etag = 9ea4083894308a610742488923d2a44778ebba6ca73fb13424647d9a82c918b4 + sha = 5e17ad62ebb5241555a7a4d29e3ab15e5ba120d2 + etag = 2cc96046d8f28e7cbcde89ed56d3d89e1a70fb0de7846ee1827bee66b7dfbcf1 weak [file ".gitignore"] url = https://github.com/devlooped/oss/blob/main/.gitignore @@ -72,8 +72,8 @@ weak [file "Directory.Build.rsp"] url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp - sha = ae25fae9d7daf0cb47d537ba870914aa3052f0c9 - etag = 6a6c6e1d3895df953abf14c82b0899e3eea75cdcd679f6212dcfea15183d73d6 + sha = 0f7f7f7e8a29de9b535676f75fe7c67e629a5e8c + etag = 0ccae83fc51f400bfd7058170bfec7aba11455e24a46a0d7e6a358da6486e255 weak [file "_config.yml"] url = https://github.com/devlooped/oss/blob/main/_config.yml @@ -92,13 +92,13 @@ weak [file "src/Directory.Build.props"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props - sha = 6e96c592c7b44bfda10404b9f90e4b8fab299249 - etag = a4925eb815bbcecc022de8d3245db069573d96ac5ecdf5f0e604f06b5577b01e + sha = b76de49afb376aa48eb172963ed70663b59b31d3 + etag = c8b56f3860cc7ccb8773b7bd6189f5c7a6e3a2c27e9104c1ee201fbdc5af9873 weak [file "src/Directory.Build.targets"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets - sha = c618ea86d94402a12c7d7d10fe2b5cb8a21c3eea - etag = 7cb1421f00d9f6f4c00f0ca98e485dcadb927cfa6b3f0b5d4fb212525d2ce9c0 + sha = a8b208093599263b7f2d1fe3854634c588ea5199 + etag = 19087699f05396205e6b050d999a43b175bd242f6e8fac86f6df936310178b03 weak [file "src/kzu.snk"] url = https://github.com/devlooped/oss/blob/main/src/kzu.snk @@ -107,18 +107,13 @@ weak [file "src/ISBN/groups.js"] url = https://github.com/inventaire/isbn3/blob/master/lib/groups.js - sha = fea7ae3df944d05c42838805b5fa71ee649a0c07 - etag = efa16bca3e0f5bfad3686f94bd5b1e114ff60a5619ff31f103fceea7b6306e08 + sha = 2bf73685ee06bded010fc8ca77c330dd2f9ed6e5 + etag = ae08ae9dc02c35c2eb8e568bdda6fd0a940395d922e0ed790e0ec97cd703eae6 weak [file ".github/workflows/includes.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml - sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9 - etag = e5ee22e115c925fb85ec931cda3ac811fcc453c03904554fa3f573935b221d34 - weak -[file ".github/workflows/test/action.yml"] - url = https://github.com/devlooped/oss/blob/main/.github/workflows/test/action.yml - sha = 9a1b07589b9bde93bc12528e9325712a32dec418 - etag = b54216ac431a83ce5477828d391f02046527e7f6fffd21da1d03324d352c3efb + sha = d152e7437fd0d6f6d9363d23cb3b78c07335ea49 + etag = ec40db34f379d0c6d83b2ec15624f330318a172cc4f85b5417c63e86eaf601df weak [file ".github/workflows/combine-prs.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml @@ -127,18 +122,13 @@ weak [file ".github/release.yml"] url = https://github.com/devlooped/oss/blob/main/.github/release.yml - sha = 1afd173fe8f81b510c597737b0d271218e81fa73 - etag = 482dc2c892fc7ce0cb3a01eb5d9401bee50ddfb067d8cb85873555ce63cf5438 + sha = 0c23e24704625cf75b2cb1fdc566cef7e20af313 + etag = 310df162242c95ed19ed12e3c96a65f77e558b46dced676ad5255eb12caafe75 weak [file ".github/workflows/changelog.config"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config - sha = 055a8b7c94b74ae139cce919d60b83976d2a9942 - etag = ddb17acb5872e9e69a76f9dec0ca590f25382caa2ccf750df058dcabb674db2b - weak -[file ".github/workflows/sponsor.yml"] - url = https://github.com/devlooped/oss/blob/main/.github/workflows/sponsor.yml - sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9 - etag = 0849ee61af6daee29615f9632173b4e82da5bfa9d78ff28907e9408bd5acde4d + sha = 08d83cb510732f861416760d37702f9f55bd7f9e + etag = 556a28914eeeae78ca924b1105726cdaa211af365671831887aec81f5f4301b4 weak [file ".github/workflows/pages.yml"] url = https://github.com/clarius/pages/blob/main/.github/workflows/pages.yml @@ -147,181 +137,11 @@ weak [file "Gemfile"] url = https://github.com/clarius/pages/blob/main/Gemfile - sha = 565a77f40db0863cb47ceb36f88790259a697c91 - etag = 24e482e91192e292b633e3c17c4f095286ffb5a041d299d761b2e6ef99ee7669 - weak -[file "src/SponsorLink/Analyzer/Analyzer.csproj"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Analyzer.csproj - sha = 7cda4a18313b0b38b26c0152e1007cdbb9b6ba3a - etag = d9444fa36daa8f4ff8f06fc2f9f600dbd8032f25ff58542d3b96676e0305677e - weak -[file "src/SponsorLink/Analyzer/Properties/launchSettings.json"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/Properties/launchSettings.json - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 6c59ab4d008e3221e316c9e3b6e0da155b892680d48cdc400a39d53cb9a12aac - weak -[file "src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 23d4cd16294974d85164fc26d6a7e2ae52698f23a62463db5025d69d9c166dc5 - weak -[file "src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 332060de0945590d7c41cd237c250b8186acd6fc2045cc85a890368c74fdf473 - weak -[file "src/SponsorLink/Directory.Build.props"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.props - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 6823e1e914ecedd174276e3d53517cc0b332bb47c56402a9512cfa6aeeeb067e - weak -[file "src/SponsorLink/Directory.Build.targets"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Directory.Build.targets - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 9938f29c3573bf8bdb9686e1d9884dee177256b1d5dd7ee41472dd64bfbdd92d - weak -[file "src/SponsorLink/Library/Library.csproj"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Library.csproj - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 3720f8ae0605aa64df8f6c1d9769969162175b79c93a21024653f210a42348e6 - weak -[file "src/SponsorLink/Library/MyClass.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/MyClass.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = b5b3ccd6cd14bb90dd9702b9d7e52cc22c11e601c039617738d688f9fd45d49b - weak -[file "src/SponsorLink/Library/Resources.resx"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Library/Resources.resx - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = aff6051733d22982e761f2b414173aafeab40e0a76a142e2b33025dced213eb2 - weak -[file "src/SponsorLink/SponsorLink.targets"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink.targets - sha = 7cda4a18313b0b38b26c0152e1007cdbb9b6ba3a - etag = d725bd9cfa33f35224e91748f64237e4dc66270f7e5ec7c835b78164531ae3db - weak -[file "src/SponsorLink/SponsorLink/AppDomainDictionary.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/AppDomainDictionary.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 4a70f86e73f951bca95618c221d821e38a31ef9092af4ac61447eab845671a28 - weak -[file "src/SponsorLink/SponsorLink/DiagnosticsManager.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/DiagnosticsManager.cs - sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953 - etag = 9f289f45169f35916fff1857840d4118ed134215639d6dae9016dc62004291a5 - weak -[file "src/SponsorLink/SponsorLink/ManifestStatus.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/ManifestStatus.cs - sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953 - etag = e46848f83c0436ba33a1c09a4060ad627a74db41bab66bb37ca40fce8a6532a7 - weak -[file "src/SponsorLink/SponsorLink/SponsorLink.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.cs - sha = 4fca946c3201d90d30e2183f699c850dcc1bf8d5 - etag = 96e1b1b28bfb2372bd5ffcc6bdef65ee926822b3489ce65be4e5a400884dce21 - weak -[file "src/SponsorLink/SponsorLink/SponsorLink.csproj"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.csproj - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = ffaea0b580d8dccd672e749a5efd11fda318c484ca4a34428ff81524ec80ec4b - weak -[file "src/SponsorLink/SponsorLink/SponsorLink.es.resx"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.es.resx - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = ded7de7a2624b335beb462763e3580413da21e80c8b40b4c773ca46c7af4e859 - weak -[file "src/SponsorLink/SponsorLink/SponsorLink.resx"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLink.resx - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 7d9e89ef2cf762a6119c9c6c2ed2517b71a546838151c005400301fde8def266 - weak -[file "src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs - sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953 - etag = fc96f7f5642cbf69b35b7e8de1756822580315f0cee61e47da3b2b1b03f68e1a - weak -[file "src/SponsorLink/SponsorLink/SponsorStatus.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorStatus.cs - sha = 4fca946c3201d90d30e2183f699c850dcc1bf8d5 - etag = 9a5f6f35c38c34b77796925d80addc998e204bc112fcd5fc124030060390e7c2 - weak -[file "src/SponsorLink/SponsorLink/SponsorableLib.targets"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/SponsorableLib.targets - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 2f923a97081481a6a264d63c8ff70ce5ba65c3dbaf7ea078cbe1388fb0868e1c - weak -[file "src/SponsorLink/SponsorLink/ThisAssembly.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/ThisAssembly.cs - sha = b2a11faac6c1c300bce8c1d45f95b585c19f2953 - etag = 978269025f58e2bae872af25fdfd94659e234e8365e3014c18b1b20fdcd155bf - weak -[file "src/SponsorLink/SponsorLink/Tracing.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/Tracing.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 22e32872cafd080bcd5ac9084355578ef70910c8e494602ead365139dcbf40c0 - weak -[file "src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 72ec691a085dc34f946627f7038a82569e44f0b63a9f4a7bd60f0f7b52fd198f - weak -[file "src/SponsorLink/SponsorLink/devlooped.pub.jwk"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/devlooped.pub.jwk - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = cf884781ff88b4d096841e3169282762a898b2050c9b5dac0013bc15bdbee267 - weak -[file "src/SponsorLink/SponsorLink/sponsorable.md"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLink/sponsorable.md - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 9c275d50705a2e661f0f86f1ae5e555c0033a05e86e12f936283a5b5ef47ae77 - weak -[file "src/SponsorLink/SponsorLinkAnalyzer.sln"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/SponsorLinkAnalyzer.sln - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = fc2928c9b303d81ff23891ee791a859b794d9f2d4b9f4e81b9ed15e5b74db487 - weak -[file "src/SponsorLink/Tests/.netconfig"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/.netconfig - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 089a26cdb722d57014c8b8104cc6f4e770868efdc49ae3119eebc873f00a316e - weak -[file "src/SponsorLink/Tests/Attributes.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Attributes.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 1d7c17a2c9424db73746112c338a39e0000134ac878b398e2aa88f7ea5c0c488 - weak -[file "src/SponsorLink/Tests/Extensions.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Extensions.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = f68e11894103f8748ce290c29927bf1e4f749e743ae33d5350e72ed22c15d245 - weak -[file "src/SponsorLink/Tests/JsonOptions.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/JsonOptions.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 6e9a1b12757a97491441b9534ced4e5dac6d9d6334008fa0cd20575650bbd935 - weak -[file "src/SponsorLink/Tests/Sample.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Sample.cs - sha = e732f6a2c44a2f7940a1868a92cd66523f74ed24 - etag = db968d1d665b77a17e13bc7ca3d43ea65ed05cbebc18669f1b607ebe0e38a59a - weak -[file "src/SponsorLink/Tests/SponsorLinkTests.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorLinkTests.cs - sha = d74f5111504a0fae6e5a1e68ca92bf7afddb3254 - etag = 1fa41250bd984e8aa840a966d34ce0e94f2111d1422d7f50b864c38364fcf4a4 - weak -[file "src/SponsorLink/Tests/SponsorableManifest.cs"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/SponsorableManifest.cs - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = e0c95e7fc6c0499dbc8c5cd28aa9a6a5a49c9d0ad41fe028a5a085aca7e00eaf - weak -[file "src/SponsorLink/Tests/Tests.csproj"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/Tests/Tests.csproj - sha = a0ae7272f31c766ebb129ea38c11c01df93b6b5d - etag = 237409e155202ec1b845593195d30057a949b2b18ae46a575e4cf480e4e2c8fe + sha = 90fa16ed0e7300a78a38ee1d23c34a7e875aab27 + etag = 3dd7febc8ae6760f19abfe787711f469c288cd803a6f1c545edec34264d48e71 weak -[file "src/SponsorLink/readme.md"] - url = https://github.com/devlooped/oss/blob/main/src/SponsorLink/readme.md - sha = 827a1d18bf0245978d81bcd3d52e9e6f1584d1ef - etag = 079b4aedba2aa9851e609b569f25c55db8d5922e3dbb1adc22611ce4d6cfe465 +[file ".github/workflows/triage.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml + sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c + etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a weak diff --git a/Directory.Build.rsp b/Directory.Build.rsp index 7c0dbc1..509cc66 100644 --- a/Directory.Build.rsp +++ b/Directory.Build.rsp @@ -2,4 +2,4 @@ -nr:false -m:1 -v:m --clp:Summary;ForceNoAlign \ No newline at end of file +-clp:Summary;ForceNoAlign diff --git a/Gemfile b/Gemfile index ed99566..fd95539 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' -gem 'github-pages', '~> 209', group: :jekyll_plugins +gem 'github-pages', '~> 231', group: :jekyll_plugins diff --git a/readme.md b/readme.md index 7ed0f35..a5e5f59 100644 --- a/readme.md +++ b/readme.md @@ -37,7 +37,6 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn [](https://github.com/clarius) [](https://github.com/KirillOsenkov) [](https://github.com/MFB-Technologies-Inc) -[](https://github.com/decriptor) [](https://github.com/torutek-gh) [](https://github.com/drivenet) [](https://github.com/AshleyMedway) @@ -55,7 +54,6 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn [](https://github.com/IxTechnologies) [](https://github.com/davidjenni) [](https://github.com/Jonathan-Hickey) -[](https://github.com/okyrylchuk) [](https://github.com/akunzai) [](https://github.com/jakobt) [](https://github.com/seanalexander) @@ -69,6 +67,7 @@ isbn3 repository which in turn fetches [isbn-international.org](https://www.isbn [](https://github.com/vezel-dev) [](https://github.com/ChilliCream) [](https://github.com/4OTC) +[](https://github.com/v-limo) <!-- sponsors.md --> diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 50fc169..381c383 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -46,8 +46,6 @@ <PropertyGroup Label="Build"> <Configuration Condition="'$(Configuration)' == '' and $(CI)">Release</Configuration> - <GenerateDocumentationFile>true</GenerateDocumentationFile> - <GenerateDocumentationFile Condition="$(MSBuildProjectName.Contains('Tests'))">false</GenerateDocumentationFile> <LangVersion>Latest</LangVersion> <!-- See https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies --> @@ -118,6 +116,8 @@ <PropertyGroup Label="Version" Condition="$(VersionLabel) != ''"> <_VersionLabel>$(VersionLabel.Replace('refs/heads/', ''))</_VersionLabel> + <_VersionLabel>$(_VersionLabel.Replace('refs/tags/v', ''))</_VersionLabel> + <!-- For PRs, we just need a fixed package version numbered after the PR # itself, so remove the commits # at the end --> <_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789'))</_VersionLabel> <!-- Next replace the prefix for simply 'pr', so we end up with 'pr99/merge' by default --> @@ -128,7 +128,9 @@ <_VersionLabel>$(_VersionLabel.Replace('/', '-'))</_VersionLabel> <!-- Set sanitized version to the actual version suffix used in build/pack --> - <VersionSuffix>$(_VersionLabel)</VersionSuffix> + <VersionSuffix Condition="!$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</VersionSuffix> + <!-- Special case for tags, the label is actually the version. Backs compat since passed-in value overrides MSBuild-set one --> + <Version Condition="$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</Version> </PropertyGroup> <ItemGroup Label="ThisAssembly.Project"> diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 0cb1e4e..6232750 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -4,6 +4,13 @@ <PropertyGroup Condition="'$(CI)' == 'true' and '$(Language)' == 'C#'"> <DefineConstants>CI;$(DefineConstants)</DefineConstants> </PropertyGroup> + + <PropertyGroup Label="Build"> + <!-- Tests projects don't need API docs, typically --> + <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(IsTestProject) == 'true'">false</GenerateDocumentationFile> + <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(MSBuildProjectName.Contains('Tests'))">false</GenerateDocumentationFile> + <GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == ''">true</GenerateDocumentationFile> + </PropertyGroup> <PropertyGroup Condition="'$(IsPackable)' == ''"> <IsPackable Condition="'$(PackAsTool)' == 'true'">true</IsPackable> @@ -27,23 +34,28 @@ <ItemGroup Condition="'$(IsPackable)' == 'true'" Label="NuGet"> <!-- This is compatible with nugetizer and SDK pack --> + <!-- Only difference is we don't copy either to output directory --> <!-- Project-level icon/readme will already be part of None items --> <None Update="@(None -> WithMetadataValue('Filename', 'icon'))" Pack="true" PackagePath="%(Filename)%(Extension)" + CopyToOutputDirectory="Never" Condition="'$(PackageIcon)' != ''" /> <None Update="@(None -> WithMetadataValue('Filename', 'readme'))" Pack="true" PackagePath="%(Filename)%(Extension)" + CopyToOutputDirectory="Never" Condition="'$(PackReadme)' != 'false' and '$(PackageReadmeFile)' != ''" /> <!-- src-level will need explicit inclusion --> <None Include="$(MSBuildThisFileDirectory)icon.png" Link="icon.png" Visible="false" Pack="true" PackagePath="%(Filename)%(Extension)" + CopyToOutputDirectory="Never" Condition="Exists('$(MSBuildThisFileDirectory)icon.png') and !Exists('$(MSBuildProjectDirectory)\icon.png')" /> <None Include="$(MSBuildThisFileDirectory)readme.md" Link="readme.md" Pack="true" PackagePath="%(Filename)%(Extension)" + CopyToOutputDirectory="Never" Condition="'$(PackReadme)' != 'false' and Exists('$(MSBuildThisFileDirectory)readme.md') and !Exists('$(MSBuildProjectDirectory)\readme.md')" /> </ItemGroup> @@ -94,19 +106,17 @@ <RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUDDY_EXECUTION_BRANCH)' != ''">$(BUDDY_EXECUTION_BRANCH)</RepositoryBranch> </PropertyGroup> - <PropertyGroup Condition="'$(EnableRexCodeGenerator)' == 'true'"> - <!-- VSCode/Razor compatibility --> - <CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn> + <PropertyGroup> + <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html --> + <CoreCompileDependsOn>CoreResGen;$(CoreCompileDependsOn)</CoreCompileDependsOn> </PropertyGroup> - + <ItemGroup> <!-- Consider the project out of date if any of these files changes --> <UpToDateCheck Include="@(None);@(Content);@(EmbeddedResource)" /> - <!-- We'll typically use ThisAssembly.Strings instead of the built-in resource manager codegen --> - <EmbeddedResource Update="@(EmbeddedResource)" Generator="" Condition="'$(EnableRexCodeGenerator)' != 'true'" /> - <EmbeddedResource Update="@(EmbeddedResource)" Condition="'$(EnableRexCodeGenerator)' == 'true'"> + <!-- Opt-in to typed resource generation by setting custom tool to MSBuild:Compile --> + <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Generator', 'MSBuild:Compile'))" Type="Resx"> <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html --> - <Generator>MSBuild:Compile</Generator> <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName> <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage> <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace> diff --git a/src/ISBN/groups.js b/src/ISBN/groups.js index 7760fc5..a8e6e65 100644 --- a/src/ISBN/groups.js +++ b/src/ISBN/groups.js @@ -850,6 +850,18 @@ module.exports = { ranges: [ [ '00', + '25' + ], + [ + '2600', + '2649' + ], + [ + '26500', + '26999' + ], + [ + '27', '39' ], [ @@ -1160,7 +1172,7 @@ module.exports = { ], [ '110', - '519' + '524' ], [ '5250', @@ -1319,11 +1331,15 @@ module.exports = { ranges: [ [ '300', - '349' + '399' ], [ '6500', '6849' + ], + [ + '95000', + '99999' ] ] }, @@ -2384,6 +2400,22 @@ module.exports = { '64771', '64771' ], + [ + '647723', + '647729' + ], + [ + '64773', + '64773' + ], + [ + '647740', + '647769' + ], + [ + '64777', + '64779' + ], [ '647800', '647809' @@ -2410,6 +2442,66 @@ module.exports = { ], [ '6479', + '6493' + ], + [ + '649400', + '649409' + ], + [ + '64941', + '64942' + ], + [ + '649430', + '649449' + ], + [ + '64945', + '64946' + ], + [ + '649470', + '649479' + ], + [ + '64948', + '64948' + ], + [ + '649490', + '649499' + ], + [ + '6495', + '6497' + ], + [ + '64980', + '64980' + ], + [ + '649810', + '649829' + ], + [ + '64983', + '64984' + ], + [ + '649850', + '649869' + ], + [ + '64987', + '64987' + ], + [ + '649880', + '649883' + ], + [ + '6499', '8999' ], [ @@ -3406,7 +3498,11 @@ module.exports = { ], [ '90', - '96' + '95' + ], + [ + '9600', + '9699' ], [ '970', @@ -3526,14 +3622,14 @@ module.exports = { ], [ '4000', - '9399' + '5999' ], [ '94', '94' ], [ - '97', + '96', '99' ] ] @@ -3818,19 +3914,36 @@ module.exports = { ] ] }, + '978-9909': { + name: 'Tunisia', + ranges: [ + [ + '00', + '19' + ], + [ + '750', + '849' + ], + [ + '9800', + '9999' + ] + ] + }, '978-9910': { name: 'Uzbekistan', ranges: [ [ '01', - '08' + '09' ], [ '650', '799' ], [ - '9000', + '8800', '9999' ] ] @@ -3937,14 +4050,22 @@ module.exports = { ], [ '600', - '799' + '789' ], [ - '80', + '79', '91' ], [ - '9250', + '9200', + '9399' + ], + [ + '94', + '94' + ], + [ + '9500', '9999' ] ] @@ -7070,7 +7191,7 @@ module.exports = { '03' ], [ - '075', + '050', '099' ], [ @@ -7179,7 +7300,7 @@ module.exports = { '0' ], [ - '140', + '120', '149' ], [ @@ -7209,10 +7330,10 @@ module.exports = { ], [ '50', - '68' + '71' ], [ - '900', + '885', '999' ] ] @@ -7337,10 +7458,10 @@ module.exports = { ], [ '50', - '54' + '57' ], [ - '975', + '960', '999' ] ] @@ -7367,7 +7488,7 @@ module.exports = { ranges: [ [ '0', - '1' + '2' ], [ '50', @@ -7512,6 +7633,14 @@ module.exports = { '200', '229' ], + [ + '230', + '239' + ], + [ + '3000', + '3199' + ], [ '3200', '3499' @@ -7535,6 +7664,10 @@ module.exports = { [ '9900000', '9929999' + ], + [ + '9985000', + '9999999' ] ] } diff --git a/src/SponsorLink/Analyzer/Analyzer.csproj b/src/SponsorLink/Analyzer/Analyzer.csproj deleted file mode 100644 index 963c77b..0000000 --- a/src/SponsorLink/Analyzer/Analyzer.csproj +++ /dev/null @@ -1,32 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> - <IsRoslynComponent>true</IsRoslynComponent> - <PackFolder>analyzers/dotnet/roslyn4.0</PackFolder> - <PublishRepositoryUrl>true</PublishRepositoryUrl> - <CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets> - <MergeAnalyzerAssemblies>true</MergeAnalyzerAssemblies> - <ImplicitUsings>disable</ImplicitUsings> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="NuGetizer" Version="1.2.2" /> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" Pack="false" /> - <PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.AssemblyInfo" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Git" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Constants" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Strings" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Project" Version="1.4.3" PrivateAssets="all" /> - </ItemGroup> - - <ItemGroup> - <InternalsVisibleTo Include="Tests" /> - </ItemGroup> - - <ItemGroup> - <None Update="buildTransitive\SponsorableLib.targets" Pack="true" /> - </ItemGroup> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/Analyzer/Properties/launchSettings.json b/src/SponsorLink/Analyzer/Properties/launchSettings.json deleted file mode 100644 index de45107..0000000 --- a/src/SponsorLink/Analyzer/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "SponsorableLib": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\Tests\\Tests.csproj", - "environmentVariables": { - "SPONSORLINK_TRACE": "true" - } - } - } -} \ No newline at end of file diff --git a/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs b/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs deleted file mode 100644 index e21acb7..0000000 --- a/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Immutable; -using Devlooped.Sponsors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using static Devlooped.Sponsors.SponsorLink; -using static ThisAssembly.Constants; - -namespace Analyzer; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StatusReportingAnalyzer : DiagnosticAnalyzer -{ - public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray<DiagnosticDescriptor>.Empty; - - public override void Initialize(AnalysisContext context) - { - context.EnableConcurrentExecution(); - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - context.RegisterCodeBlockAction(c => - { - var status = Diagnostics.GetStatus(Funding.Product); - Tracing.Trace($"Status: {status}"); - }); - } -} \ No newline at end of file diff --git a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets b/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets deleted file mode 100644 index fd1e6e4..0000000 --- a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets +++ /dev/null @@ -1,3 +0,0 @@ -<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="Devlooped.Sponsors.targets"/> -</Project> \ No newline at end of file diff --git a/src/SponsorLink/Directory.Build.props b/src/SponsorLink/Directory.Build.props deleted file mode 100644 index c0a3e42..0000000 --- a/src/SponsorLink/Directory.Build.props +++ /dev/null @@ -1,43 +0,0 @@ -<Project> - - <PropertyGroup> - <GenerateDocumentationFile>false</GenerateDocumentationFile> - <LangVersion>latest</LangVersion> - <ImplicitUsings>true</ImplicitUsings> - <Nullable>annotations</Nullable> - <PackOnBuild>true</PackOnBuild> - <!-- Avoid deleting older packed versions to avoid rebuild errors (since package is in same solution, which is uncommon) --> - <EnablePackCleanup>false</EnablePackCleanup> - <PackageOutputPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)bin'))</PackageOutputPath> - - <RestoreSources>https://pkg.kzu.app/index.json;https://api.nuget.org/v3/index.json</RestoreSources> - <RestoreSources Condition="Exists('$(PackageOutputPath)')">$(PackageOutputPath);$(RestoreSources)</RestoreSources> - - <!-- Roslyn caches analyzers aggressively so we are better off using a very dynamic version number - for local builds where a quick devloop is key. We bump version every 10 seconds --> - <Version>42.42.$([System.Math]::Floor($([MSBuild]::Divide($([System.DateTime]::Now.TimeOfDay.TotalSeconds), 10))))</Version> - - <Product>SponsorableLib</Product> - </PropertyGroup> - - <ItemGroup> - <!--<Constant Include="Funding.Product" Value="$(Product)" /> - <Constant Include="Funding.AnalyzerPrefix" Value="SLIB" />--> - <!--<Constant Include="Funding.GraceDays" Value="21" />--> - </ItemGroup> - - <!-- DOGFOODING LOCAL BUILDS --> - <!-- Create a Directory.targets.user alongside this file, with the following content - (update the version number to the number of the built local package): --> - <!-- - <Project> - - <ItemGroup Condition="Exists('$(DevPath)')"> - <PackageReference Update="@(PackageReference -> WithMetadataValue('Identity', 'Devlooped.SponsorLink'))" - Version="42.42.6587" /> - </ItemGroup> - - </Project> - --> - -</Project> diff --git a/src/SponsorLink/Directory.Build.targets b/src/SponsorLink/Directory.Build.targets deleted file mode 100644 index 4ce4c80..0000000 --- a/src/SponsorLink/Directory.Build.targets +++ /dev/null @@ -1,8 +0,0 @@ -<Project> - - <ItemGroup Condition="Exists('$(PackageOutputPath)\SponsorableLib.$(Version).nupkg')"> - <PackageReference Update="@(PackageReference -> WithMetadataValue('Identity', 'SponsorableLib'))" - Version="$(Version)" /> - </ItemGroup> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/Library/Library.csproj b/src/SponsorLink/Library/Library.csproj deleted file mode 100644 index f351273..0000000 --- a/src/SponsorLink/Library/Library.csproj +++ /dev/null @@ -1,31 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> - <PackNone>true</PackNone> - <PackageId>SponsorableLib</PackageId> - <Description>Sample library incorporating SponsorLink checks</Description> - <PackOnBuild>true</PackOnBuild> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="NuGetizer" Version="1.2.2" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\Analyzer\Analyzer.csproj" ReferenceOutputAssembly="false" OutputType="Analyzer" /> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Update="Resources.resx"> - <!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html --> - <Generator>MSBuild:Compile</Generator> - <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName> - <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage> - <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace> - <StronglyTypedNamespace Condition="'%(RelativeDir)' != ''">$(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))</StronglyTypedNamespace> - <StronglyTypedClassName>%(Filename)</StronglyTypedClassName> - </EmbeddedResource> - </ItemGroup> - -</Project> diff --git a/src/SponsorLink/Library/MyClass.cs b/src/SponsorLink/Library/MyClass.cs deleted file mode 100644 index 7b7f6f5..0000000 --- a/src/SponsorLink/Library/MyClass.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace SponsorableLib; - -public class MyClass -{ -} diff --git a/src/SponsorLink/Library/Resources.resx b/src/SponsorLink/Library/Resources.resx deleted file mode 100644 index 636fedc..0000000 --- a/src/SponsorLink/Library/Resources.resx +++ /dev/null @@ -1,123 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" use="required" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <data name="Foo" xml:space="preserve"> - <value>Bar</value> - </data> -</root> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink.targets b/src/SponsorLink/SponsorLink.targets deleted file mode 100644 index de93845..0000000 --- a/src/SponsorLink/SponsorLink.targets +++ /dev/null @@ -1,141 +0,0 @@ -<Project> - <!-- For inclusion in analyzer projects that want to integrate SponsorLink --> - <!-- Import using the following property: --> - <!--<CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets>--> - - <PropertyGroup> - <ShowSponsorLinkInProject Condition="$(ShowSponsorLinkInProject) == '' and '$(TargetFramework)' == 'netstandard2.0'">true</ShowSponsorLinkInProject> - <!-- This ensures we expose only the main assembly in the Dependencies > Analyzers node --> - <MergeAnalyzerAssemblies Condition="'$(MergeAnalyzerAssemblies)' == '' and '$(Configuration)' == 'Release'">true</MergeAnalyzerAssemblies> - <!-- If we are going to merge files, we need to copy local --> - <CopyLocalLockFileAssemblies Condition="'$(MergeAnalyzerAssemblies)' == 'true'">true</CopyLocalLockFileAssemblies> - - <!-- Read public key we validate manifests against --> - <DevloopedJwk>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)SponsorLink/devlooped.pub.jwk'))</DevloopedJwk> - - <!-- Default funding product the Product, which already part of ThisAssembly --> - <FundingProduct Condition="'$(FundingProduct)' == ''">$(Product)</FundingProduct> - <!-- Default prefix is the joined upper-case letters in the product name (i.e. for ThisAssembly, TA) --> - <FundingPrefix Condition="'$(FundingPrefix)' == ''">$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))</FundingPrefix> - <!-- Default grace days for an expired sponsor manifest --> - <FundingGrace Condition="'$(FundingGrace)' == ''">21</FundingGrace> - </PropertyGroup> - - <ItemGroup> - <AssemblyMetadata Include="Funding.GitHub.devlooped" Value="$(DevloopedJwk)" /> - <Constant Include="Funding.Product" Value="$(FundingProduct)" /> - <Constant Include="Funding.Prefix" Value="$(FundingPrefix)" /> - <Constant Include="Funding.Grace" Value="$(FundingGrace)" /> - </ItemGroup> - - <ItemGroup> - <Compile Include="$(MSBuildThisFileDirectory)SponsorLink/*.cs" - Exclude="$(MSBuildThisFileDirectory)SponsorLink/bin/**;$(MSBuildThisFileDirectory)SponsorLink/obj/**" - Source="SponsorLink"/> - <EmbeddedResource Include="$(MSBuildThisFileDirectory)SponsorLink/*.resx" - Exclude="$(MSBuildThisFileDirectory)SponsorLink/bin/**;$(MSBuildThisFileDirectory)SponsorLink/obj/**" - Source="SponsorLink" - ManifestResourceName="Devlooped.%(Filename)"/> - <None Include="$(MSBuildThisFileDirectory)SponsorLink/buildTransitive/*.*" - Source="SponsorLink" - PackagePath="buildTransitive/%(Filename)%(Extension)"/> - </ItemGroup> - - <ItemGroup Condition="$(ShowSponsorLinkInProject) == 'true'"> - <Compile Update="@(Compile -> WithMetadataValue('Source', 'SponsorLink'))"> - <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link> - </Compile> - <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Source', 'SponsorLink'))"> - <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link> - </EmbeddedResource> - <Content Update="@(Content -> WithMetadataValue('Source', 'SponsorLink'))"> - <Link>SponsorLink\%(RecursiveDir)%(Filename)%(Extension)</Link> - </Content> - <None Update="@(None -> WithMetadataValue('Source', 'SponsorLink'))"> - <Link>SponsorLink\%(PackagePath)</Link> - </None> - </ItemGroup> - - <ItemGroup Condition="$(ShowSponsorLinkInProject) != 'true'"> - <Compile Update="@(Compile -> WithMetadataValue('Source', 'SponsorLink'))"> - <Visible>false</Visible> - </Compile> - <EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Source', 'SponsorLink'))"> - <Visible>false</Visible> - </EmbeddedResource> - <Content Update="@(Content -> WithMetadataValue('Source', 'SponsorLink'))"> - <Visible>false</Visible> - </Content> - <None Update="@(None -> WithMetadataValue('Source', 'SponsorLink'))"> - <Visible>false</Visible> - </None> - </ItemGroup> - - <ItemGroup Condition="'$(ManagePackageVersionsCentrally)' == 'true'"> - <PackageReference Include="Humanizer.Core" VersionOverride="2.14.1" PrivateAssets="all" Pack="false" /> - <PackageReference Include="Humanizer.Core.es" VersionOverride="2.14.1" PrivateAssets="all" /> - <PackageReference Include="System.IdentityModel.Tokens.Jwt" VersionOverride="7.6.0" PrivateAssets="all" Pack="false" /> - <PackageReference Include="ILRepack" Version="2.0.33" VersionOverride="all" PrivateAssets="all" Pack="false" /> - </ItemGroup> - - <ItemGroup Condition="'$(ManagePackageVersionsCentrally)' != 'true'"> - <PackageReference Include="Humanizer.Core" Version="2.14.1" PrivateAssets="all" Pack="false" /> - <PackageReference Include="Humanizer.Core.es" Version="2.14.1" PrivateAssets="all" /> - <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" PrivateAssets="all" Pack="false" /> - <PackageReference Include="ILRepack" Version="2.0.33" PrivateAssets="all" Pack="false" /> - </ItemGroup> - - <Target Name="ILRepack" AfterTargets="CoreCompile" BeforeTargets="CopyFilesToOutputDirectory" - Inputs="@(IntermediateAssembly -> '%(FullPath)')" - Outputs="$(IntermediateOutputPath)ilrepack.txt" - Returns="@(MergedAssemblies)" - Condition="Exists(@(IntermediateAssembly -> '%(FullPath)')) And '$(MergeAnalyzerAssemblies)' == 'true'"> - <ItemGroup> - <ReferenceCopyLocalAssemblies Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll' - And !$([MSBuild]::ValueOrDefault('%(FileName)', '').EndsWith('.resources', StringComparison.OrdinalIgnoreCase))" /> - <MergedAssemblies Include="@(ReferenceCopyLocalAssemblies)" Condition=" - !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.CodeAnalysis', StringComparison.OrdinalIgnoreCase)) And - !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.CSharp', StringComparison.OrdinalIgnoreCase)) And - !$([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.', StringComparison.OrdinalIgnoreCase))" - /> - <!-- Brings in System/Microsoft.IdentityModel, System.Text.Encodings.Web, System.Text.Json --> - <MergedAssemblies Include="@(ReferenceCopyLocalAssemblies)" Condition=" - $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.IdentityModel', StringComparison.OrdinalIgnoreCase)) Or - $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('Microsoft.IdentityModel', StringComparison.OrdinalIgnoreCase)) Or - $([MSBuild]::ValueOrDefault('%(FileName)', '').StartsWith('System.Text', StringComparison.OrdinalIgnoreCase))" - /> - </ItemGroup> - <ItemGroup> - <ReferenceCopyLocalDirs Include="@(ReferenceCopyLocalPaths -> '%(RootDir)%(Directory)')" /> - <ReferenceCopyLocalPaths Remove="@(MergedAssemblies)" /> - <LibDir Include="@(ReferenceCopyLocalDirs -> Distinct())" /> - </ItemGroup> - <PropertyGroup> - <AbsoluteAssemblyOriginatorKeyFile Condition="'$(AssemblyOriginatorKeyFile)' != ''">$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','$(AssemblyOriginatorKeyFile)'))))</AbsoluteAssemblyOriginatorKeyFile> - <ILRepackArgs Condition="'$(AbsoluteAssemblyOriginatorKeyFile)' != ''">/keyfile:"$(AbsoluteAssemblyOriginatorKeyFile)" /delaysign</ILRepackArgs> - <ILRepackArgs>$(ILRepackArgs) /internalize</ILRepackArgs> - <ILRepackArgs>$(ILRepackArgs) /union</ILRepackArgs> - <!-- This is needed to merge types with identical names into one, wich happens with IFluentInterface in Merq and Merq.Core (Xamarin.Messaging dependencies) --> - <ILRepackArgs>$(ILRepackArgs) @(LibDir -> '/lib:"%(Identity)."', ' ')</ILRepackArgs> - <ILRepackArgs>$(ILRepackArgs) /out:"@(IntermediateAssembly -> '%(FullPath)')"</ILRepackArgs> - <ILRepackArgs>$(ILRepackArgs) "@(IntermediateAssembly -> '%(FullPath)')"</ILRepackArgs> - <ILRepackArgs>$(ILRepackArgs) @(MergedAssemblies -> '"%(FullPath)"', ' ')</ILRepackArgs> - <!--<ILRepackArgs>$(ILRepackArgs) "/lib:$(NetstandardDirectory)"</ILRepackArgs> --> - <!-- This is needed for ilrepack to find netstandard.dll, which is referenced by the System.Text.Json assembly --> - </PropertyGroup> - <Exec Command=""$(ILRepack)" $(ILRepackArgs)" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" StandardErrorImportance="high" StandardOutputImportance="low" ConsoleToMSBuild="true" ContinueOnError="true"> - <Output TaskParameter="ConsoleOutput" PropertyName="ILRepackOutput" /> - <Output TaskParameter="ExitCode" PropertyName="ExitCode" /> - </Exec> - <Message Importance="high" Text="$(ILRepackOutput)" Condition="'$(ExitCode)' != '0'" /> - <Delete Files="$(IntermediateOutputPath)ilrepack.txt" Condition="'$(ExitCode)' != '0'" /> - <Touch AlwaysCreate="true" Files="$(IntermediateOutputPath)ilrepack.txt" Condition="'$(ExitCode)' == '0'" /> - <Error Text="$(ILRepackOutput)" Condition="'$(ExitCode)' != '0' And '$(ContinueOnError)' != 'true'" /> - <ItemGroup> - <MergedAssembliesToRemove Include="@(MergedAssemblies)" /> - <MergedAssembliesToRemove Remove="@(ReferenceToPreserve)" /> - </ItemGroup> - <Delete Files="@(MergedAssembliesToRemove -> '$(MSBuildProjectDirectory)\$(OutputPath)%(Filename)%(Extension)')" Condition="Exists('$(MSBuildProjectDirectory)\$(OutputPath)%(Filename)%(Extension)')" /> - </Target> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/AppDomainDictionary.cs b/src/SponsorLink/SponsorLink/AppDomainDictionary.cs deleted file mode 100644 index 05cc949..0000000 --- a/src/SponsorLink/SponsorLink/AppDomainDictionary.cs +++ /dev/null @@ -1,36 +0,0 @@ -// <autogenerated /> -#nullable enable -using System; - -namespace Devlooped.Sponsors; - -/// <summary> -/// A helper class to store and retrieve values from the current <see cref="AppDomain"/> -/// as typed named values. -/// </summary> -/// <remarks> -/// This allows tools that run within the same app domain to share state, such as -/// MSBuild tasks or Roslyn analyzers. -/// </remarks> -static class AppDomainDictionary -{ - /// <summary> - /// Gets the value associated with the specified name, or creates a new one if it doesn't exist. - /// </summary> - public static TValue Get<TValue>(string name) where TValue : notnull, new() - { - var data = AppDomain.CurrentDomain.GetData(name); - if (data is TValue firstTry) - return firstTry; - - lock (AppDomain.CurrentDomain) - { - if (AppDomain.CurrentDomain.GetData(name) is TValue secondTry) - return secondTry; - - var newValue = new TValue(); - AppDomain.CurrentDomain.SetData(name, newValue); - return newValue; - } - } -} \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs b/src/SponsorLink/SponsorLink/DiagnosticsManager.cs deleted file mode 100644 index 49143d9..0000000 --- a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs +++ /dev/null @@ -1,138 +0,0 @@ -// <autogenerated /> -#nullable enable -using System; -using System.Collections.Concurrent; -using Humanizer; -using Microsoft.CodeAnalysis; - -namespace Devlooped.Sponsors; - -/// <summary> -/// Manages diagnostics for the SponsorLink analyzer so that there are no duplicates -/// when multiple projects share the same product name (i.e. ThisAssembly). -/// </summary> -class DiagnosticsManager -{ - /// <summary> - /// Acceses the diagnostics dictionary for the current <see cref="AppDomain"/>. - /// </summary> - ConcurrentDictionary<string, Diagnostic> Diagnostics - { - get => AppDomainDictionary.Get<ConcurrentDictionary<string, Diagnostic>>(nameof(Diagnostics)); - } - - /// <summary> - /// Creates a descriptor from well-known diagnostic kinds. - /// </summary> - /// <param name="sponsorable">The names of the sponsorable accounts that can be funded for the given product.</param> - /// <param name="product">The product or project developed by the sponsorable(s).</param> - /// <param name="prefix">Custom prefix to use for diagnostic IDs.</param> - /// <param name="status">The kind of status diagnostic to create.</param> - /// <returns>The given <see cref="DiagnosticDescriptor"/>.</returns> - /// <exception cref="NotImplementedException">The <paramref name="status"/> is not one of the known ones.</exception> - public DiagnosticDescriptor GetDescriptor(string[] sponsorable, string product, string prefix, SponsorStatus status) => status switch - { - SponsorStatus.Unknown => CreateUnknown(sponsorable, product, prefix), - SponsorStatus.Sponsor => CreateSponsor(sponsorable, prefix), - SponsorStatus.Expiring => CreateExpiring(sponsorable, prefix), - SponsorStatus.Expired => CreateExpired(sponsorable, prefix), - _ => throw new NotImplementedException(), - }; - - /// <summary> - /// Pushes a diagnostic for the given product. If an existing one exists, it is replaced. - /// </summary> - /// <returns>The same diagnostic that was pushed, for chained invocations.</returns> - public Diagnostic Push(string product, Diagnostic diagnostic) - { - // Directly sets, since we only expect to get one warning per sponsorable+product - // combination. - Diagnostics[product] = diagnostic; - return diagnostic; - } - - /// <summary> - /// Attemps to remove a diagnostic for the given product. - /// </summary> - /// <param name="product">The product diagnostic that might have been pushed previously.</param> - /// <returns>The removed diagnostic, or <see langword="null" /> if none was previously pushed.</returns> - public Diagnostic? Pop(string product) - { - Diagnostics.TryRemove(product, out var diagnostic); - return diagnostic; - } - - /// <summary> - /// Gets the status of the given product based on a previously stored diagnostic. - /// </summary> - /// <param name="product">The product to check status for.</param> - /// <returns>Optional <see cref="SponsorStatus"/> that was reported, if any.</returns> - public SponsorStatus? GetStatus(string product) - { - // NOTE: the SponsorLinkAnalyzer.SetStatus uses diagnostic properties to store the - // kind of diagnostic as a simple string instead of the enum. We do this so that - // multiple analyzers or versions even across multiple products, which all would - // have their own enum, can still share the same diagnostic kind. - if (Diagnostics.TryGetValue(product, out var diagnostic) && - diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value)) - { - // Switch on value matching DiagnosticKind names - return value switch - { - nameof(SponsorStatus.Unknown) => SponsorStatus.Unknown, - nameof(SponsorStatus.Sponsor) => SponsorStatus.Sponsor, - nameof(SponsorStatus.Expiring) => SponsorStatus.Expiring, - nameof(SponsorStatus.Expired) => SponsorStatus.Expired, - _ => null, - }; - } - - return null; - } - - static DiagnosticDescriptor CreateSponsor(string[] sponsorable, string prefix) => new( - $"{prefix}100", - ThisAssembly.Strings.Sponsor.Title, - ThisAssembly.Strings.Sponsor.MessageFormat, - "SponsorLink", - DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: ThisAssembly.Strings.Sponsor.Description, - helpLinkUri: "https://github.com/devlooped#sponsorlink", - "DoesNotSupportF1Help"); - - static DiagnosticDescriptor CreateUnknown(string[] sponsorable, string product, string prefix) => new( - $"{prefix}101", - ThisAssembly.Strings.Unknown.Title, - ThisAssembly.Strings.Unknown.MessageFormat, - "SponsorLink", - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: ThisAssembly.Strings.Unknown.Description( - sponsorable.Humanize(x => $"https://github.com/sponsors/{x}"), - string.Join(" ", sponsorable)), - helpLinkUri: "https://github.com/devlooped#sponsorlink", - WellKnownDiagnosticTags.NotConfigurable); - - static DiagnosticDescriptor CreateExpiring(string[] sponsorable, string prefix) => new( - $"{prefix}103", - ThisAssembly.Strings.Expiring.Title, - ThisAssembly.Strings.Expiring.MessageFormat, - "SponsorLink", - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: ThisAssembly.Strings.Expiring.Description(string.Join(" ", sponsorable)), - helpLinkUri: "https://github.com/devlooped#autosync", - "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable); - - static DiagnosticDescriptor CreateExpired(string[] sponsorable, string prefix) => new( - $"{prefix}104", - ThisAssembly.Strings.Expired.Title, - ThisAssembly.Strings.Expired.MessageFormat, - "SponsorLink", - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: ThisAssembly.Strings.Expired.Description(string.Join(" ", sponsorable)), - helpLinkUri: "https://github.com/devlooped#autosync", - "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable); -} 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 @@ -// <autogenerated /> -namespace Devlooped.Sponsors; - -/// <summary> -/// The resulting status from validation. -/// </summary> -public enum ManifestStatus -{ - /// <summary> - /// The manifest couldn't be read at all. - /// </summary> - Unknown, - /// <summary> - /// The manifest was read and is valid (not expired and properly signed). - /// </summary> - Valid, - /// <summary> - /// The manifest was read but has expired. - /// </summary> - Expired, - /// <summary> - /// The manifest was read, but its signature is invalid. - /// </summary> - Invalid, -} diff --git a/src/SponsorLink/SponsorLink/SponsorLink.cs b/src/SponsorLink/SponsorLink/SponsorLink.cs deleted file mode 100644 index a5e5beb..0000000 --- a/src/SponsorLink/SponsorLink/SponsorLink.cs +++ /dev/null @@ -1,169 +0,0 @@ -// <autogenerated /> -#nullable enable -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Reflection; -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; - -namespace Devlooped.Sponsors; - -static partial class SponsorLink -{ - public static Dictionary<string, string> Sponsorables { get; } = typeof(SponsorLink).Assembly - .GetCustomAttributes<AssemblyMetadataAttribute>() - .Where(x => x.Key.StartsWith("Funding.GitHub.")) - .Select(x => new { Key = x.Key[15..], x.Value }) - .ToDictionary(x => x.Key, x => x.Value); - - /// <summary> - /// Whether the current process is running in an IDE, either - /// <see cref="IsVisualStudio"/> or <see cref="IsRider"/>. - /// </summary> - public static bool IsEditor => IsVisualStudio || IsRider; - - /// <summary> - /// Whether the current process is running as part of an active Visual Studio instance. - /// </summary> - public static bool IsVisualStudio => - Environment.GetEnvironmentVariable("ServiceHubLogSessionKey") != null || - Environment.GetEnvironmentVariable("VSAPPIDNAME") != null; - - /// <summary> - /// Whether the current process is running as part of an active Rider instance. - /// </summary> - public static bool IsRider => - Environment.GetEnvironmentVariable("RESHARPER_FUS_SESSION") != null || - Environment.GetEnvironmentVariable("IDEA_INITIAL_DIRECTORY") != null; - - /// <summary> - /// Manages the sharing and reporting of diagnostics across the source generator - /// and the diagnostic analyzer, to avoid doing the online check more than once. - /// </summary> - public static DiagnosticsManager Diagnostics { get; } = new(); - - /// <summary> - /// Gets the expiration date from the principal, if any. - /// </summary> - /// <returns> - /// Whichever "exp" claim is the latest, or <see langword="null"/> if none found. - /// </returns> - public static DateTime? GetExpiration(this ClaimsPrincipal principal) - // get all "exp" claims, parse them and return the latest one or null if none found - => principal.FindAll("exp") - .Select(c => c.Value) - .Select(long.Parse) - .Select(DateTimeOffset.FromUnixTimeSeconds) - .Max().DateTime is var exp && exp == DateTime.MinValue ? null : exp; - - /// <summary> - /// Reads all manifests, validating their signatures. - /// </summary> - /// <param name="principal">The combined principal with all identities (and their claims) from each provided and valid JWT</param> - /// <param name="values">The tokens to read and their corresponding JWK for signature verification.</param> - /// <returns><see langword="true"/> if at least one manifest can be successfully read and is valid. - /// <see langword="false"/> otherwise.</returns> - public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, params (string jwt, string jwk)[] values) - => TryRead(out principal, values.AsEnumerable()); - - /// <summary> - /// Reads all manifests, validating their signatures. - /// </summary> - /// <param name="principal">The combined principal with all identities (and their claims) from each provided and valid JWT</param> - /// <param name="values">The tokens to read and their corresponding JWK for signature verification.</param> - /// <returns><see langword="true"/> if at least one manifest can be successfully read and is valid. - /// <see langword="false"/> otherwise.</returns> - public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, IEnumerable<(string jwt, string jwk)> values) - { - principal = null; - - foreach (var value in values) - { - if (string.IsNullOrWhiteSpace(value.jwk) || string.IsNullOrEmpty(value.jwk)) - continue; - - if (Validate(value.jwt, value.jwk, out var token, out var claims, false) == ManifestStatus.Valid && claims != null) - { - if (principal == null) - principal = claims; - else - principal.AddIdentities(claims.Identities); - } - } - - return principal != null; - } - - /// <summary> - /// Validates the manifest signature and optional expiration. - /// </summary> - /// <param name="jwt">The JWT to validate.</param> - /// <param name="jwk">The key to validate the manifest signature with.</param> - /// <param name="token">Except when returning <see cref="Status.Unknown"/>, returns the security token read from the JWT, even if signature check failed.</param> - /// <param name="principal">The associated claims, only when return value is not <see cref="Status.Unknown"/>.</param> - /// <param name="requireExpiration">Whether to check for expiration.</param> - /// <returns>The status of the validation.</returns> - public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsPrincipal? principal, bool validateExpiration) - { - token = default; - principal = default; - - SecurityKey key; - try - { - key = JsonWebKey.Create(jwk); - } - catch (ArgumentException) - { - return ManifestStatus.Unknown; - } - - var handler = new JwtSecurityTokenHandler { 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", - }; - - try - { - principal = handler.ValidateToken(jwt, validation, out token); - 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; - } - catch (SecurityTokenInvalidSignatureException) - { - var jwtToken = handler.ReadJwtToken(jwt); - token = jwtToken; - principal = new ClaimsPrincipal(new ClaimsIdentity(jwtToken.Claims)); - return ManifestStatus.Invalid; - } - catch (SecurityTokenException) - { - var jwtToken = handler.ReadJwtToken(jwt); - token = jwtToken; - principal = new ClaimsPrincipal(new ClaimsIdentity(jwtToken.Claims)); - return ManifestStatus.Invalid; - } - } - -} diff --git a/src/SponsorLink/SponsorLink/SponsorLink.csproj b/src/SponsorLink/SponsorLink/SponsorLink.csproj deleted file mode 100644 index 4b00feb..0000000 --- a/src/SponsorLink/SponsorLink/SponsorLink.csproj +++ /dev/null @@ -1,46 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> - <Product>SponsorLink</Product> - <ImplicitUsings>disable</ImplicitUsings> - <GenerateDocumentationFile>false</GenerateDocumentationFile> - </PropertyGroup> - - <PropertyGroup Label="SponsorLink"> - <DevloopedJwk>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)devlooped.pub.jwk'))</DevloopedJwk> - <!-- Default funding product the Product, which already part of ThisAssembly --> - <FundingProduct Condition="'$(FundingProduct)' == ''">$(Product)</FundingProduct> - <!-- Default prefix is the joined upper-case letters in the product name (i.e. for ThisAssembly, TA) --> - <FundingPrefix Condition="'$(FundingPrefix)' == ''">$([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))</FundingPrefix> - <!-- Default grace days for an expired sponsor manifest --> - <FundingGrace Condition="'$(FundingGrace)' == ''">21</FundingGrace> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Humanizer.Core" Version="2.14.1" /> - <PackageReference Include="Humanizer.Core.es" Version="2.14.1" /> - <PackageReference Include="ILRepack" Version="2.0.33" PrivateAssets="all" /> - <PackageReference Include="NuGetizer" Version="1.2.2" /> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" Pack="false" /> - <PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" /> - <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Constants" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Git" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Strings" Version="1.4.3" PrivateAssets="all" /> - <PackageReference Include="ThisAssembly.Project" Version="1.4.3" PrivateAssets="all" /> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Update="SponsorLink.es.resx" ManifestResourceName="Devlooped.%(Filename)" /> - <EmbeddedResource Update="SponsorLink.resx" ManifestResourceName="Devlooped.%(Filename)" /> - </ItemGroup> - - <ItemGroup> - <AssemblyMetadata Include="Funding.GitHub.devlooped" Value="$(DevloopedJwk)" /> - <Constant Include="Funding.Product" Value="$(FundingProduct)" /> - <Constant Include="Funding.Prefix" Value="$(FundingPrefix)" /> - <Constant Include="Funding.Grace" Value="$(FundingGrace)" /> - </ItemGroup> - -</Project> diff --git a/src/SponsorLink/SponsorLink/SponsorLink.es.resx b/src/SponsorLink/SponsorLink/SponsorLink.es.resx deleted file mode 100644 index d8794ca..0000000 --- a/src/SponsorLink/SponsorLink/SponsorLink.es.resx +++ /dev/null @@ -1,163 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" use="required" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <data name="Unknown_Description" xml:space="preserve"> - <value>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 {links} y ejecutando posteriormente 'gh sponsors sync {spaced}'.</value> - </data> - <data name="Unknown_Message" xml:space="preserve"> - <value>No se pudo determinar el estado de su patrocinio. Funcionalidades exclusivas para patrocinadores pueden no estar disponibles.</value> - </data> - <data name="Unknown_Title" xml:space="preserve"> - <value>Estado de patrocinio desconocido</value> - </data> - <data name="Expired_Description" xml:space="preserve"> - <value>Funcionalidades exclusivas para patrocinadores pueden no estar disponibles. Ejecuta 'gh sponsors sync {spaced}' y, opcionalmente, habilita la sincronización automática.</value> - </data> - <data name="Expired_Message" xml:space="preserve"> - <value>El estado de patrocino ha expirado y la sincronización automática no está habilitada.</value> - </data> - <data name="Expired_Title" xml:space="preserve"> - <value>El estado de patrocino ha expirado</value> - </data> - <data name="Sponsor_Description" xml:space="preserve"> - <value>Eres un verdadero héroe. Tu patrocinio ayuda a mantener el proyecto vivo y próspero 🙏.</value> - </data> - <data name="Sponsor_Message" xml:space="preserve"> - <value>Gracias por apoyar a {0} con tu patrocinio de {1} 💟!</value> - </data> - <data name="Sponsor_Title" xml:space="preserve"> - <value>Eres un patrocinador del proyecto, eres lo máximo 💟!</value> - </data> - <data name="Expiring_Description" xml:space="preserve"> - <value>El estado de patrocino ha expirado y estás en un período de gracia. Ejecuta 'gh sponsors sync {spaced}' y, opcionalmente, habilita la sincronización automática.</value> - </data> - <data name="Expiring_Message" xml:space="preserve"> - <value>El estado de patrocino necesita actualización periódica y la sincronización automática no está habilitada.</value> - </data> - <data name="Expiring_Title" xml:space="preserve"> - <value>El estado de patrocino ha expirado y el período de gracia terminará pronto</value> - </data> - <data name="And" xml:space="preserve"> - <value>y</value> - </data> - <data name="Or" xml:space="preserve"> - <value>o</value> - </data> -</root> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/SponsorLink.resx b/src/SponsorLink/SponsorLink/SponsorLink.resx deleted file mode 100644 index b8cdd5e..0000000 --- a/src/SponsorLink/SponsorLink/SponsorLink.resx +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" use="required" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <data name="Unknown_Description" xml:space="preserve"> - <value>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! -Please consider supporting the project by sponsoring at {links} and running 'gh sponsors sync {spaced}' afterwards.</value> - <comment>Unknown sponsor description</comment> - </data> - <data name="Unknown_Message" xml:space="preserve"> - <value>Please consider supporting {0} by sponsoring {1} 🙏</value> - </data> - <data name="Unknown_Title" xml:space="preserve"> - <value>Unknown sponsor status</value> - </data> - <data name="Expired_Description" xml:space="preserve"> - <value>Sponsor-only features may be disabled. Please run 'gh sponsors sync {spaced}' and optionally enable automatic sync.</value> - </data> - <data name="Expired_Message" xml:space="preserve"> - <value>Sponsor status has expired and automatic sync has not been enabled.</value> - </data> - <data name="Expired_Title" xml:space="preserve"> - <value>Sponsor status expired</value> - </data> - <data name="Sponsor_Description" xml:space="preserve"> - <value>You are a true hero. Your sponsorship helps keep the project alive and thriving 🙏.</value> - </data> - <data name="Sponsor_Message" xml:space="preserve"> - <value>Thank you for supporting {0} with your sponsorship 💟!</value> - </data> - <data name="Sponsor_Title" xml:space="preserve"> - <value>You are a sponsor of the project, you rock 💟!</value> - </data> - <data name="Expiring_Description" xml:space="preserve"> - <value>Sponsor status has expired and you are in the grace period. Please run 'gh sponsors sync {spaced}' and optionally enable automatic sync.</value> - </data> - <data name="Expiring_Message" xml:space="preserve"> - <value>Sponsor status needs periodic updating and automatic sync has not been enabled.</value> - </data> - <data name="Expiring_Title" xml:space="preserve"> - <value>Sponsor status expired, grace period ending soon</value> - </data> - <data name="And" xml:space="preserve"> - <value>and</value> - </data> - <data name="Or" xml:space="preserve"> - <value>or</value> - </data> -</root> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs b/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs deleted file mode 100644 index 2e97528..0000000 --- a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs +++ /dev/null @@ -1,126 +0,0 @@ -// <autogenerated /> -#nullable enable -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using Humanizer; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using static Devlooped.Sponsors.SponsorLink; -using static ThisAssembly.Constants; - -namespace Devlooped.Sponsors; - -/// <summary> -/// Links the sponsor status for the current compilation. -/// </summary> -[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] -public class SponsorLinkAnalyzer : DiagnosticAnalyzer -{ - static readonly int graceDays = int.Parse(Funding.Grace); - static readonly Dictionary<SponsorStatus, DiagnosticDescriptor> descriptors = new() - { - // Requires: - // <Constant Include="Funding.Product" Value="[PRODUCT_NAME]" /> - // <Constant Include="Funding.AnalyzerPrefix" Value="[PREFIX]" /> - { SponsorStatus.Unknown, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Unknown) }, - { SponsorStatus.Sponsor, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Sponsor) }, - { SponsorStatus.Expiring, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Expiring) }, - { SponsorStatus.Expired, Diagnostics.GetDescriptor([.. Sponsorables.Keys], Funding.Product, Funding.Prefix, SponsorStatus.Expired) }, - }; - - public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = descriptors.Values.ToImmutableArray(); - -#pragma warning disable RS1026 // Enable concurrent execution - public override void Initialize(AnalysisContext context) -#pragma warning restore RS1026 // Enable concurrent execution - { -#if !DEBUG - // Only enable concurrent execution in release builds, otherwise debugging is quite annoying. - context.EnableConcurrentExecution(); -#endif - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - -#pragma warning disable RS1013 // Start action has no registered non-end actions - // We do this so that the status is set at compilation start so we can use it - // across all other analyzers. We report only on finish because multiple - // analyzers can report the same diagnostic and we want to avoid duplicates. - context.RegisterCompilationStartAction(ctx => - { - var manifests = ctx.Options.AdditionalFiles - .Where(x => - ctx.Options.AnalyzerConfigOptionsProvider.GetOptions(x).TryGetValue("build_metadata.AdditionalFiles.SourceItemType", out var itemType) && - itemType == "SponsorManifest" && - Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(x.Path))) - .ToImmutableArray(); - - // Setting the status early allows other analyzers to potentially check for it. - var status = SetStatus(manifests); - // Never report any diagnostic unless we're in an editor. - if (IsEditor) - { - // NOTE: even if we don't report the diagnostic, we still set the status so other analyzers can use it. - ctx.RegisterCompilationEndAction(ctx => - { - if (Diagnostics.Pop(Funding.Product) is Diagnostic diagnostic) - { - ctx.ReportDiagnostic(diagnostic); - } - else - { - // This should never happen and would be a bug. - Debug.Assert(true, "We should have provided a diagnostic of some kind for " + Funding.Product); - // We'll report it as unknown as a fallback for now. - ctx.ReportDiagnostic(Diagnostic.Create(descriptors[SponsorStatus.Unknown], null, - properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)), - Funding.Product, Sponsorables.Keys.Humanize(ThisAssembly.Strings.Or))); - } - }); - } - }); -#pragma warning restore RS1013 // Start action has no registered non-end actions - } - - SponsorStatus SetStatus(ImmutableArray<AdditionalText> manifests) - { - if (!SponsorLink.TryRead(out var claims, manifests.Select(text => - (text.GetText()?.ToString() ?? "", Sponsorables[Path.GetFileNameWithoutExtension(text.Path)]))) || - claims.GetExpiration() is not DateTime exp) - { - // report unknown, either unparsed manifest or one with no expiration (which we never emit). - Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Unknown], null, - properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)), - Funding.Product, Sponsorables.Keys.Humanize(ThisAssembly.Strings.Or))); - return SponsorStatus.Unknown; - } - else if (exp < DateTime.Now) - { - // report expired or expiring soon if still within the configured days of grace period - if (exp.AddDays(graceDays) < DateTime.Now) - { - // report expiring soon - Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Expiring], null, - properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Expiring)))); - return SponsorStatus.Expiring; - } - else - { - // report expired - Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Expired], null, - properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Expired)))); - return SponsorStatus.Expired; - } - } - else - { - // report sponsor - Diagnostics.Push(Funding.Product, Diagnostic.Create(descriptors[SponsorStatus.Sponsor], null, - properties: ImmutableDictionary.Create<string, string?>().Add(nameof(SponsorStatus), nameof(SponsorStatus.Sponsor)), - Funding.Product)); - return SponsorStatus.Sponsor; - } - } -} diff --git a/src/SponsorLink/SponsorLink/SponsorStatus.cs b/src/SponsorLink/SponsorLink/SponsorStatus.cs deleted file mode 100644 index 6cdbc90..0000000 --- a/src/SponsorLink/SponsorLink/SponsorStatus.cs +++ /dev/null @@ -1,25 +0,0 @@ -// <autogenerated /> -namespace Devlooped.Sponsors; - -/// <summary> -/// The determined sponsoring status. -/// </summary> -public enum SponsorStatus -{ - /// <summary> - /// Sponsorship status is unknown. - /// </summary> - Unknown, - /// <summary> - /// The sponsors manifest is expired but within the grace period. - /// </summary> - Expiring, - /// <summary> - /// The sponsors manifest is expired and outside the grace period. - /// </summary> - Expired, - /// <summary> - /// The user is sponsoring. - /// </summary> - Sponsor, -} diff --git a/src/SponsorLink/SponsorLink/SponsorableLib.targets b/src/SponsorLink/SponsorLink/SponsorableLib.targets deleted file mode 100644 index 8311ca6..0000000 --- a/src/SponsorLink/SponsorLink/SponsorableLib.targets +++ /dev/null @@ -1,60 +0,0 @@ -<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - - <PropertyGroup> - <!-- This showcases how to use a package-provided file as diagnostics linked content --> - <SponsorableLibSponsorable>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)sponsorable.md))</SponsorableLibSponsorable> - </PropertyGroup> - - <ItemGroup> - <!-- This makes it visible to the generator --> - <CompilerVisibleProperty Include="SponsorableLibSponsorable" /> - </ItemGroup> - - <PropertyGroup> - <!-- This makes sure builds are never broken from sponsorship warnings, even if WarningsAsErrors is used --> - <WarningsNotAsErrors>$(WarningsNotAsErrors);LIB001;LIB002;LIB003;LIB004;LIB005</WarningsNotAsErrors> - - <AutoSyncStampFile>$(BaseIntermediateOutputPath)autosync.stamp</AutoSyncStampFile> - - <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' == 'true'">$(HOME)</UserProfileHome> - <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' != 'true'">$(USERPROFILE)</UserProfileHome> - <!--$(UsingMicrosoftNETSdk)--> - <SponsorLinkSupported>true</SponsorLinkSupported> - <SponsorLinkHome Condition="$(SponsorLinkSupported) == 'true'">$([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))</SponsorLinkHome> - </PropertyGroup> - - <ItemGroup Condition="'$(SponsorLinkHome)' != ''"> - <SponsorManifest Include="$(SponsorLinkHome)/github/*.jwt" /> - <AdditionalFiles Include="@(SponsorManifest -> Distinct())" Kind="SponsorLink" /> - - <CompilerVisibleProperty Include="DesignTimeBuild" /> - - <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Kind" /> - </ItemGroup> - - <Target Name="CleanExpiredAutoSyncStamp" Condition="Exists($(AutoSyncStampFile))" BeforeTargets="AutoSyncSponsors"> - - </Target> - - <Target Name="AutoSyncSponsors" Condition="!Exists($(AutoSyncStampFile))" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun"> - <AutoSyncManifest /> - <WriteLinesToFile File="$(AutoSyncStampFile)" Lines='$([System.DateTime]::Now.ToString("yyyy-MM-yy"))' Overwrite="true" /> - </Target> - - <Target Name="SL_GitConfig" - Condition="'$(SourceControlInformationFeatureSupported)' == 'true'" - BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun" - DependsOnTargets="InitializeSourceControlInformation"> - <ItemGroup> - <GitRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" /> - </ItemGroup> - <PropertyGroup> - <GitRoot>%(GitRoot.FullPath)</GitRoot> - </PropertyGroup> - <ItemGroup Condition="'$(GitRoot)' != ''"> - <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(GitRoot).git/config))" Kind="GitConfig" /> - <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(UserProfileHome)/.gitconfig))" Kind="GitConfig" /> - </ItemGroup> - </Target> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/ThisAssembly.cs b/src/SponsorLink/SponsorLink/ThisAssembly.cs deleted file mode 100644 index 89f2316..0000000 --- a/src/SponsorLink/SponsorLink/ThisAssembly.cs +++ /dev/null @@ -1,31 +0,0 @@ -// <autogenerated /> -partial class ThisAssembly -{ - partial class Strings - { - partial class Unknown - { - public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Unknown_Message"); - } - - partial class Expiring - { - public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Expiring_Message"); - } - - partial class Expired - { - public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Expired_Message"); - } - - partial class Grace - { - public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Grace_Message"); - } - - partial class Sponsor - { - public static string MessageFormat => GetResourceManager("Devlooped.SponsorLink").GetString("Sponsor_Message"); - } - } -} \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/Tracing.cs b/src/SponsorLink/SponsorLink/Tracing.cs deleted file mode 100644 index 9201796..0000000 --- a/src/SponsorLink/SponsorLink/Tracing.cs +++ /dev/null @@ -1,53 +0,0 @@ -// <autogenerated /> -#nullable enable -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -namespace Devlooped.Sponsors; - -static class Tracing -{ - public static void Trace(string message, object? value, [CallerArgumentExpression("value")] string? expression = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0) - => Trace($"{message}: {value} ({expression})", filePath, lineNumber); - - public static void Trace(object? value, [CallerArgumentExpression("value")] string? expression = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0) - => Trace($"{value} ({expression})", filePath, lineNumber); - - public static void Trace([CallerMemberName] string? message = null, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0) - { - var trace = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SPONSORLINK_TRACE")); -#if DEBUG - trace = true; -#endif - - if (!trace) - return; - - var line = new StringBuilder() - .Append($"[{DateTime.Now:O}]") - .Append($"[{Process.GetCurrentProcess().ProcessName}:{Process.GetCurrentProcess().Id}]") - .Append($" {message} ") - .AppendLine($" -> {filePath}({lineNumber})") - .ToString(); - - var dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create); - var tries = 0; - // Best-effort only - while (tries < 10) - { - try - { - File.AppendAllText(Path.Combine(dir, "SponsorLink.log"), line); - Debugger.Log(0, "SponsorLink", line); - return; - } - catch (IOException) - { - tries++; - } - } - } -} diff --git a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets b/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets deleted file mode 100644 index 471f37f..0000000 --- a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets +++ /dev/null @@ -1,99 +0,0 @@ -<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - - <PropertyGroup> - <!-- We autosync manifests daily, if opted-in --> - <Today>$([System.DateTime]::Now.ToString("yyyy-MM-yy"))</Today> - <!-- Daily sync stamp, if opted-in --> - <SyncStampFile>$(BaseIntermediateOutputPath)autosync-$(Today).stamp</SyncStampFile> - <!-- Incremental read of autosync setting --> - <AutoSyncStampFile>$(BaseIntermediateOutputPath)autosync.stamp</AutoSyncStampFile> - - <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' == 'true'">$(HOME)</UserProfileHome> - <UserProfileHome Condition="'$([MSBuild]::IsOSUnixLike())' != 'true'">$(USERPROFILE)</UserProfileHome> - <!-- Root dir for SL files --> - <SponsorLinkHome>$([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))</SponsorLinkHome> - <!-- SL dotnet-config file --> - <SponsorLinkConfig>$([System.IO.Path]::Combine('$(SponsorLinkHome)', '.netconfig'))</SponsorLinkConfig> - </PropertyGroup> - - <ItemGroup> - <!-- All GH manifests. For now, we don't support other funding platforms --> - <SponsorManifest Include="$(SponsorLinkHome)/github/*.jwt" /> - - <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceItemType" /> - <!-- To quickly exit if true --> - <CompilerVisibleProperty Include="DesignTimeBuild" /> - </ItemGroup> - - <PropertyGroup> - <SLDependsOn>SL_CollectDependencies</SLDependsOn> - <SLDependsOn Condition="'$(BuildingInsideVisualStudio)' == 'true' and '$(DesignTimeBuild)' != 'true'">$(SLDependsOn);SL_CheckAutoSync;SL_ReadAutoSyncEnabled;SL_SyncSponsors</SLDependsOn> - </PropertyGroup> - - <Target Name="SL" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun" DependsOnTargets="$(SLDependsOn)"> - <ItemGroup> - <!-- Make manifests visible to analyzers --> - <AdditionalFiles Include="@(SponsorManifest -> Distinct())" SourceItemType="SponsorManifest" /> - </ItemGroup> - </Target> - - <Target Name="SL_CollectDependencies" DependsOnTargets="_GenerateRestoreGraph"> - <!-- Makes direct dependencies visible to the compiler, so that sponsoring checks can selectively skip indirectly referenced analyzer --> - <ItemGroup> - <CompilerVisibleProperty Include="$([MSBuild]::ValueOrDefault('%(_RestoreGraphEntry.Id)', '').Replace('.', '_'))" /> - </ItemGroup> - <CreateProperty Value="%(_RestoreGraphEntry.VersionRange)" Condition="%(_RestoreGraphEntry.Type) == 'Dependency'" > - <Output TaskParameter="Value" PropertyName="$([MSBuild]::ValueOrDefault('%(_RestoreGraphEntry.Id)', '').Replace('.', '_'))"/> - </CreateProperty> - </Target> - - <Target Name="SL_Clean" AfterTargets="Clean"> - <!-- Cleanup of stamp files --> - <ItemGroup> - <AutoSyncStampFile Include="$(BaseIntermediateOutputPath)autosync*.stamp" /> - </ItemGroup> - <Delete Files="@(AutoSyncStampFile)" /> - </Target> - - <Target Name="SL_CheckAutoSync" Inputs="$(SponsorLinkConfig)" Outputs="$(AutoSyncStampFile)" Condition="Exists($(SponsorLinkConfig))"> - <!-- Read autosync setting from config file and cache its value --> - <ReadLinesFromFile File="$(SponsorLinkConfig)"> - <Output TaskParameter="Lines" ItemName="SLConfig" /> - </ReadLinesFromFile> - <ItemGroup> - <SLConfigAutoSync Include="$([MSBuild]::ValueOrDefault(%(SLConfig.Identity), '').Trim())" - Condition="$([MSBuild]::ValueOrDefault(%(SLConfig.Identity), '').Trim().StartsWith('autosync'))" /> - </ItemGroup> - <PropertyGroup> - <SLConfigAutoSync>%(SLConfigAutoSync.Identity)</SLConfigAutoSync> - <AutoSyncEnabled Condition="$(SLConfigAutoSync.EndsWith('true'))">true</AutoSyncEnabled> - <AutoSyncEnabled Condition="$(SLConfigAutoSync.EndsWith('false'))">false</AutoSyncEnabled> - </PropertyGroup> - <WriteLinesToFile File="$(AutoSyncStampFile)" Lines="$(AutoSyncEnabled)" Overwrite="true" /> - </Target> - - <Target Name="SL_ReadAutoSyncEnabled" Condition="'$(AutoSyncEnabled)' == '' and Exists($(AutoSyncStampFile))"> - <!-- In subsequent runs, directly read the cached value from the previous target --> - <PropertyGroup> - <AutoSyncEnabled>$([System.IO.File]::ReadAllText($(AutoSyncStampFile)).Trim())</AutoSyncEnabled> - </PropertyGroup> - </Target> - - <Target Name="SL_SyncSponsors" Inputs="@(SponsorLinkManifest);$(SponsorLinkConfig)" Outputs="$(SyncStampFile)" Condition="'$(AutoSyncEnabled)' == 'true'"> - <!-- Sync all local manifests in unattended mode (non-interactive). - It's possible that some manifests will need interactive sync, and we'll render the - messages in that case. - Note that since running this requires autosync=true, we can safely assume the user - has already run `gh sponsors [...] -autosync` at least once to turn it on. Otherwise, - this target won't run at all. - --> - <Exec Command="gh sponsors sync --local --unattended" StandardErrorImportance="high" StandardOutputImportance="low" ConsoleToMSBuild="true" ContinueOnError="true"> - <Output TaskParameter="ExitCode" PropertyName="SponsorsExitCode" /> - <Output TaskParameter="ConsoleOutput" PropertyName="SponsorsOutput" /> - </Exec> - <Message Importance="high" Text="$(SponsorsOutput)" Condition="'$(SponsorsExitCode)' != '0'" /> - <!-- We only touch the stamp file to avoid future sync runs if we could sync all manifests --> - <Touch Files="$(SyncStampFile)" Condition="$(SponsorsExitCode) == '0'" AlwaysCreate="True" /> - </Target> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/devlooped.pub.jwk b/src/SponsorLink/SponsorLink/devlooped.pub.jwk deleted file mode 100644 index cdf45c2..0000000 --- a/src/SponsorLink/SponsorLink/devlooped.pub.jwk +++ /dev/null @@ -1,5 +0,0 @@ -{ - "e": "AQAB", - "kty": "RSA", - "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V" -} \ No newline at end of file diff --git a/src/SponsorLink/SponsorLink/sponsorable.md b/src/SponsorLink/SponsorLink/sponsorable.md deleted file mode 100644 index c023c25..0000000 --- a/src/SponsorLink/SponsorLink/sponsorable.md +++ /dev/null @@ -1,5 +0,0 @@ -# Why Sponsor - -Well, why not? It's super cheap :) - -This could even be partially auto-generated from FUNDING.yml and what-not. \ No newline at end of file diff --git a/src/SponsorLink/SponsorLinkAnalyzer.sln b/src/SponsorLink/SponsorLinkAnalyzer.sln deleted file mode 100644 index be206b1..0000000 --- a/src/SponsorLink/SponsorLinkAnalyzer.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.10.34928.147 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzer", "Analyzer\Analyzer.csproj", "{584984D6-926B-423D-9416-519613423BAE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Library", "Library\Library.csproj", "{598CD398-A172-492C-8367-827D43276029}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{EA02494C-6ED4-47A0-8D43-20F50BE8554F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SponsorLink", "SponsorLink\SponsorLink.csproj", "{B91C7E99-3D2E-4FDF-B017-9123E810197F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {584984D6-926B-423D-9416-519613423BAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {584984D6-926B-423D-9416-519613423BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {584984D6-926B-423D-9416-519613423BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {584984D6-926B-423D-9416-519613423BAE}.Release|Any CPU.Build.0 = Release|Any CPU - {598CD398-A172-492C-8367-827D43276029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {598CD398-A172-492C-8367-827D43276029}.Debug|Any CPU.Build.0 = Debug|Any CPU - {598CD398-A172-492C-8367-827D43276029}.Release|Any CPU.ActiveCfg = Release|Any CPU - {598CD398-A172-492C-8367-827D43276029}.Release|Any CPU.Build.0 = Release|Any CPU - {EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA02494C-6ED4-47A0-8D43-20F50BE8554F}.Release|Any CPU.Build.0 = Release|Any CPU - {B91C7E99-3D2E-4FDF-B017-9123E810197F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B91C7E99-3D2E-4FDF-B017-9123E810197F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B91C7E99-3D2E-4FDF-B017-9123E810197F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B91C7E99-3D2E-4FDF-B017-9123E810197F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1DDA0EFF-BEF6-49BB-8AA8-D71FE1CD3E6F} - EndGlobalSection -EndGlobal diff --git a/src/SponsorLink/Tests/.netconfig b/src/SponsorLink/Tests/.netconfig deleted file mode 100644 index 3b3bd0d..0000000 --- a/src/SponsorLink/Tests/.netconfig +++ /dev/null @@ -1,15 +0,0 @@ -[file "SponsorableManifest.cs"] - url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/SponsorableManifest.cs - sha = 976ecefc44d87217e04933d9cd7f6b950468410b - etag = e0c95e7fc6c0499dbc8c5cd28aa9a6a5a49c9d0ad41fe028a5a085aca7e00eaf - weak -[file "JsonOptions.cs"] - url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/JsonOptions.cs - sha = 79dc56ce45fc36df49e1c4f8875e93c297edc383 - etag = 6e9a1b12757a97491441b9534ced4e5dac6d9d6334008fa0cd20575650bbd935 - weak -[file "Extensions.cs"] - url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/Extensions.cs - sha = d204b667eace818934c49e09b5b08ea82aef87fa - etag = f68e11894103f8748ce290c29927bf1e4f749e743ae33d5350e72ed22c15d245 - weak diff --git a/src/SponsorLink/Tests/Attributes.cs b/src/SponsorLink/Tests/Attributes.cs deleted file mode 100644 index aa5f48d..0000000 --- a/src/SponsorLink/Tests/Attributes.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Xunit; - -public class SecretsFactAttribute : FactAttribute -{ - public SecretsFactAttribute(params string[] secrets) - { - var configuration = new ConfigurationBuilder() - .AddUserSecrets<SecretsFactAttribute>() - .Build(); - - var missing = new HashSet<string>(); - - foreach (var secret in secrets) - { - if (string.IsNullOrEmpty(configuration[secret])) - missing.Add(secret); - } - - if (missing.Count > 0) - Skip = "Missing user secrets: " + string.Join(',', missing); - } -} - -public class LocalFactAttribute : SecretsFactAttribute -{ - public LocalFactAttribute(params string[] secrets) : base(secrets) - { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) - Skip = "Non-CI test"; - } -} - -public class CIFactAttribute : FactAttribute -{ - public CIFactAttribute() - { - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) - Skip = "CI-only test"; - } -} - -public class LocalTheoryAttribute : TheoryAttribute -{ - public LocalTheoryAttribute() - { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) - Skip = "Non-CI test"; - } -} - -public class CITheoryAttribute : TheoryAttribute -{ - public CITheoryAttribute() - { - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"))) - Skip = "CI-only test"; - } -} \ No newline at end of file diff --git a/src/SponsorLink/Tests/Extensions.cs b/src/SponsorLink/Tests/Extensions.cs deleted file mode 100644 index 75a78b4..0000000 --- a/src/SponsorLink/Tests/Extensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; - -namespace Devlooped.Sponsors; - -static class Extensions -{ - public static HashCode Add(this HashCode hash, params object[] items) - { - foreach (var item in items) - hash.Add(item); - - return hash; - } - - - public static HashCode AddRange<T>(this HashCode hash, IEnumerable<T> items) - { - foreach (var item in items) - hash.Add(item); - - return hash; - } - - public static Array Cast(this Array array, Type elementType) - { - //Convert the object list to the destination array type. - var result = Array.CreateInstance(elementType, array.Length); - Array.Copy(array, result, array.Length); - return result; - } - - public static void Assert(this ILogger logger, [DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? message = default, params object?[] args) - { - if (!condition) - { - //Debug.Assert(condition, message); - logger.LogError(message, args); - throw new InvalidOperationException(message); - } - } -} diff --git a/src/SponsorLink/Tests/JsonOptions.cs b/src/SponsorLink/Tests/JsonOptions.cs deleted file mode 100644 index c816eba..0000000 --- a/src/SponsorLink/Tests/JsonOptions.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Microsoft.IdentityModel.Tokens; - -namespace Devlooped.Sponsors; - -static partial class JsonOptions -{ - public static JsonSerializerOptions Default { get; } = -#if NET6_0_OR_GREATER - new(JsonSerializerDefaults.Web) -#else - new() -#endif - { - AllowTrailingCommas = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - ReadCommentHandling = JsonCommentHandling.Skip, -#if NET6_0_OR_GREATER - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull, -#endif - WriteIndented = true, - Converters = - { - new JsonStringEnumConverter(allowIntegerValues: false), -#if NET6_0_OR_GREATER - new DateOnlyJsonConverter() -#endif - } - }; - - public static JsonSerializerOptions JsonWebKey { get; } = new(JsonSerializerOptions.Default) - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = new DefaultJsonTypeInfoResolver - { - Modifiers = - { - info => - { - if (info.Type != typeof(JsonWebKey)) - return; - - foreach (var prop in info.Properties) - { - // Don't serialize empty lists, makes for more concise JWKs - prop.ShouldSerialize = (obj, value) => - value is not null && - (value is not IList<string> list || list.Count > 0); - } - } - } - } - }; - - -#if NET6_0_OR_GREATER - public class DateOnlyJsonConverter : JsonConverter<DateOnly> - { - public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => DateOnly.Parse(reader.GetString()?[..10] ?? "", CultureInfo.InvariantCulture); - - public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) - => writer.WriteStringValue(value.ToString("O", CultureInfo.InvariantCulture)); - } -#endif -} diff --git a/src/SponsorLink/Tests/Sample.cs b/src/SponsorLink/Tests/Sample.cs deleted file mode 100644 index 6249e62..0000000 --- a/src/SponsorLink/Tests/Sample.cs +++ /dev/null @@ -1,59 +0,0 @@ -extern alias Analyzer; -using System; -using System.Globalization; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using Analyzer::Devlooped.Sponsors; -using Xunit; -using Xunit.Abstractions; - -namespace Tests; - -public class Sample(ITestOutputHelper output) -{ - [Theory] - [InlineData("es-AR", SponsorStatus.Unknown)] - [InlineData("es-AR", SponsorStatus.Expiring)] - [InlineData("es-AR", SponsorStatus.Expired)] - [InlineData("es-AR", SponsorStatus.Sponsor)] - [InlineData("en", SponsorStatus.Unknown)] - [InlineData("en", SponsorStatus.Expiring)] - [InlineData("en", SponsorStatus.Expired)] - [InlineData("en", SponsorStatus.Sponsor)] - [InlineData("", SponsorStatus.Unknown)] - [InlineData("", SponsorStatus.Expiring)] - [InlineData("", SponsorStatus.Expired)] - [InlineData("", SponsorStatus.Sponsor)] - public void Test(string culture, SponsorStatus kind) - { - Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = - culture == "" ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture); - - var diag = new DiagnosticsManager().GetDescriptor(["foo"], "bar", "FB", kind); - - output.WriteLine(diag.Title.ToString()); - output.WriteLine(diag.MessageFormat.ToString()); - output.WriteLine(diag.Description.ToString()); - } - - [Fact] - public void RenderSponsorables() - { - Assert.NotEmpty(SponsorLink.Sponsorables); - - foreach (var pair in SponsorLink.Sponsorables) - { - output.WriteLine($"{pair.Key} = {pair.Value}"); - // Read the JWK - var jsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey.Create(pair.Value); - - Assert.NotNull(jsonWebKey); - - using var key = RSA.Create(new RSAParameters - { - Modulus = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes(jsonWebKey.N), - Exponent = Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes(jsonWebKey.E), - }); - } - } -} \ No newline at end of file diff --git a/src/SponsorLink/Tests/SponsorLinkTests.cs b/src/SponsorLink/Tests/SponsorLinkTests.cs deleted file mode 100644 index 7625e2c..0000000 --- a/src/SponsorLink/Tests/SponsorLinkTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -extern alias Analyzer; -using System.Security.Cryptography; -using System.Text.Json; -using Analyzer::Devlooped.Sponsors; -using Devlooped.Sponsors; -using Microsoft.IdentityModel.Tokens; -using Xunit; - -namespace Devlooped.Tests; - -public class SponsorLinkTests -{ - // We need to convert to jwk string since the analyzer project has merged the JWT assembly and types. - public static string ToJwk(SecurityKey key) - => JsonSerializer.Serialize( - JsonWebKeyConverter.ConvertFromSecurityKey(key), - JsonOptions.JsonWebKey); - - [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); - - // NOTE: sponsorable manifest doesn't have expiration date. - var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false); - - Assert.Equal(ManifestStatus.Valid, 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 jwk = ToJwk(new RsaSecurityKey(RSA.Create())); - - var status = SponsorLink.Validate(jwt, jwk, out var token, out var principal, false); - - Assert.Equal(ManifestStatus.Invalid, status); - - // We should still be a able to read the data, knowing it may have been tampered with. - Assert.NotNull(principal); - Assert.NotNull(token); - } - - [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); - - // Will be expired after this. - Thread.Sleep(1000); - - var status = SponsorLink.Validate(sponsor, jwk, out var token, out var principal, true); - - Assert.Equal(ManifestStatus.Expired, status); - - // We should still be a able to read the data, even if expired (but not tampered with). - Assert.NotNull(principal); - Assert.NotNull(token); - } - - [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 status = SponsorLink.Validate("asdfasdf", jwk, out var token, out var principal, false); - - Assert.Equal(ManifestStatus.Unknown, status); - - // Nothing could be read at all. - Assert.Null(principal); - Assert.Null(token); - } - - [Fact] - public void TryRead() - { - var fooSponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/foo")], "ASDF1234"); - var barSponsorable = SponsorableManifest.Create(new Uri("https://bar.com"), [new Uri("https://github.com/sponsors/bar")], "GHJK5678"); - - // Org sponsor and member of team - var fooSponsor = fooSponsorable.Sign([new("sub", "kzu"), new("email", "me@foo.com"), new("roles", "org"), new("roles", "team")], expiration: TimeSpan.FromDays(30)); - // Org + personal sponsor - var barSponsor = barSponsorable.Sign([new("sub", "kzu"), new("email", "me@bar.com"), new("roles", "org"), new("roles", "user")], expiration: TimeSpan.FromDays(30)); - - Assert.True(SponsorLink.TryRead(out var principal, [(fooSponsor, ToJwk(fooSponsorable.SecurityKey)), (barSponsor, ToJwk(barSponsorable.SecurityKey))])); - - // Can check role across both JWTs - Assert.True(principal.IsInRole("org")); - Assert.True(principal.IsInRole("team")); - Assert.True(principal.IsInRole("user")); - - Assert.True(principal.HasClaim("sub", "kzu")); - Assert.True(principal.HasClaim("email", "me@foo.com")); - Assert.True(principal.HasClaim("email", "me@bar.com")); - } - - [LocalFact] - public void ValidateCachedManifest() - { - var path = Environment.ExpandEnvironmentVariables("%userprofile%\\.sponsorlink\\github\\devlooped.jwt"); - if (!File.Exists(path)) - return; - - var jwt = File.ReadAllText(path); - - var status = SponsorLink.Validate(jwt, - """ - { - "e": "AQAB", - "kty": "RSA", - "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V" - } - """ - , out var token, out var principal, false); - - Assert.Equal(ManifestStatus.Valid, status); - } -} diff --git a/src/SponsorLink/Tests/SponsorableManifest.cs b/src/SponsorLink/Tests/SponsorableManifest.cs deleted file mode 100644 index 5ae6e3f..0000000 --- a/src/SponsorLink/Tests/SponsorableManifest.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text.Json; -using Microsoft.IdentityModel.Tokens; - -namespace Devlooped.Sponsors; - -/// <summary> -/// The serializable manifest of a sponsorable user, as persisted -/// in the .github/sponsorlink.jwt file. -/// </summary> -public class SponsorableManifest -{ - /// <summary> - /// Overall manifest status. - /// </summary> - public enum Status - { - /// <summary> - /// SponsorLink manifest is invalid. - /// </summary> - Invalid, - /// <summary> - /// The manifest has an audience that doesn't match the sponsorable account. - /// </summary> - AccountMismatch, - /// <summary> - /// SponsorLink manifest not found for the given account, so it's not supported. - /// </summary> - NotFound, - /// <summary> - /// Manifest was successfully fetched and validated. - /// </summary> - OK, - } - - /// <summary> - /// Creates a new manifest with a new RSA key pair. - /// </summary> - public static SponsorableManifest Create(Uri issuer, Uri[] audience, string clientId) - { - var rsa = RSA.Create(3072); - var pub = Convert.ToBase64String(rsa.ExportRSAPublicKey()); - - return new SponsorableManifest(issuer, audience, clientId, new RsaSecurityKey(rsa), pub); - } - - public static async Task<(Status, SponsorableManifest?)> FetchAsync(string sponsorable, string? branch, HttpClient? http = default) - { - // Try to detect sponsorlink manifest in the sponsorable .github repo - var url = $"https://github.com/{sponsorable}/.github/raw/{branch ?? "main"}/sponsorlink.jwt"; - - // Manifest should be public, so no need for any special HTTP client. - using (http ??= new HttpClient()) - { - var response = await http.GetAsync(url); - if (!response.IsSuccessStatusCode) - return (Status.NotFound, default); - - var jwt = await response.Content.ReadAsStringAsync(); - if (!TryRead(jwt, out var manifest, out var missingClaim)) - return (Status.Invalid, default); - - // Manifest audience should match the sponsorable account to avoid weird issues? - if (sponsorable != manifest.Sponsorable) - return (Status.AccountMismatch, default); - - return (Status.OK, manifest); - } - } - - /// <summary> - /// Parses a JWT into a <see cref="SponsorableManifest"/>. - /// </summary> - /// <param name="jwt">The JWT containing the sponsorable information.</param> - /// <param name="manifest">The parsed manifest, if not required claims are missing.</param> - /// <param name="missingClaim">The missing required claim, if any.</param> - /// <returns>A validated manifest.</returns> - public static bool TryRead(string jwt, [NotNullWhen(true)] out SponsorableManifest? manifest, out string? missingClaim) - { - var handler = new JwtSecurityTokenHandler { MapInboundClaims = false }; - missingClaim = null; - manifest = default; - - if (!handler.CanReadToken(jwt)) - return false; - - var token = handler.ReadJwtToken(jwt); - var issuer = token.Issuer; - - if (token.Audiences.FirstOrDefault(x => x.StartsWith("https://github.com/")) is null) - { - missingClaim = "aud"; - return false; - } - - if (token.Claims.FirstOrDefault(c => c.Type == "client_id")?.Value is not string clientId) - { - missingClaim = "client_id"; - return false; - } - - if (token.Claims.FirstOrDefault(c => c.Type == "pub")?.Value is not string pub) - { - missingClaim = "pub"; - return false; - } - - if (token.Claims.FirstOrDefault(c => c.Type == "sub_jwk")?.Value is not string jwk) - { - missingClaim = "sub_jwk"; - return false; - } - - var key = new JsonWebKeySet { Keys = { JsonWebKey.Create(jwk) } }.GetSigningKeys().First(); - manifest = new SponsorableManifest(new Uri(issuer), token.Audiences.Select(x => new Uri(x)).ToArray(), clientId, key, pub); - - return true; - } - - public SponsorableManifest(Uri issuer, Uri[] audience, string clientId, SecurityKey publicKey, string publicRsaKey) - { - Issuer = issuer.AbsoluteUri; - Audience = audience.Select(a => a.AbsoluteUri.TrimEnd('/')).ToArray(); - ClientId = clientId; - SecurityKey = publicKey; - PublicKey = publicRsaKey; - Sponsorable = audience.Where(x => x.Host == "github.com").Select(x => x.Segments.LastOrDefault()?.TrimEnd('/')).FirstOrDefault() ?? - throw new ArgumentException("At least one of the intended audience must be a GitHub sponsors URL."); - } - - /// <summary> - /// Converts (and optionally signs) the manifest into a JWT. Never exports the private key. - /// </summary> - /// <param name="signing">Optional credentials when signing the resulting manifest. Defaults to the <see cref="SecurityKey"/> if it has a private key.</param> - /// <returns>The JWT manifest.</returns> - public string ToJwt(SigningCredentials? signing = default) - { - var jwk = JsonWebKeyConverter.ConvertFromSecurityKey(SecurityKey); - - // Automatically sign if the manifest was created with a private key - if (SecurityKey is RsaSecurityKey rsa && rsa.PrivateKeyStatus == PrivateKeyStatus.Exists) - { - signing ??= new SigningCredentials(rsa, SecurityAlgorithms.RsaSha256); - - // Ensure we never serialize the private key - jwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(new RsaSecurityKey(rsa.Rsa.ExportParameters(false))); - } - - var token = new JwtSecurityToken( - claims: - new[] { new Claim(JwtRegisteredClaimNames.Iss, Issuer) } - .Concat(Audience.Select(x => new Claim(JwtRegisteredClaimNames.Aud, x))) - .Concat( - [ - // See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6 - new(JwtRegisteredClaimNames.Iat, Math.Truncate((DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString()), - new("client_id", ClientId), - // non-standard claim containing the base64-encoded public key - new("pub", PublicKey), - // standard claim, serialized as a JSON string, not an encoded JSON object - new("sub_jwk", JsonSerializer.Serialize(jwk, JsonOptions.JsonWebKey), JsonClaimValueTypes.Json), - ]), - signingCredentials: signing); - - return new JwtSecurityTokenHandler().WriteToken(token); - } - - /// <summary> - /// Sign the JWT claims with the provided RSA key. - /// </summary> - public string Sign(IEnumerable<Claim> claims, RSA rsa, TimeSpan? expiration = default) - => Sign(claims, new RsaSecurityKey(rsa), expiration); - - public string Sign(IEnumerable<Claim> claims, RsaSecurityKey? key = default, TimeSpan? expiration = default) - { - var rsa = key ?? SecurityKey as RsaSecurityKey; - if (rsa?.PrivateKeyStatus != PrivateKeyStatus.Exists) - throw new NotSupportedException("No private key found to sign the manifest."); - - var signing = new SigningCredentials(rsa, SecurityAlgorithms.RsaSha256); - - var expirationDate = expiration != null ? - DateTime.UtcNow.Add(expiration.Value) : - // Expire the first day of the next month - new DateTime( - DateTime.UtcNow.AddMonths(1).Year, - DateTime.UtcNow.AddMonths(1).Month, 1, - // Use current time so they don't expire all at the same time - DateTime.UtcNow.Hour, - DateTime.UtcNow.Minute, - DateTime.UtcNow.Second, - DateTime.UtcNow.Millisecond, - DateTimeKind.Utc); - - var tokenClaims = claims.Where(x => x.Type != JwtRegisteredClaimNames.Iat && x.Type != JwtRegisteredClaimNames.Exp).ToList(); - - // See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6 - tokenClaims.Add(new(JwtRegisteredClaimNames.Iat, Math.Truncate((DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString())); - - if (tokenClaims.Find(c => c.Type == JwtRegisteredClaimNames.Iss) is { } issuer) - { - if (issuer.Value != Issuer) - throw new ArgumentException($"The received claims contain an incompatible 'iss' claim. If present, the claim must contain the value '{Issuer}' but was '{issuer.Value}'."); - } - else - { - tokenClaims.Insert(0, new(JwtRegisteredClaimNames.Iss, Issuer)); - } - - if (tokenClaims.Find(c => c.Type == "client_id") is { } clientId) - { - if (clientId.Value != ClientId) - throw new ArgumentException($"The received claims contain an incompatible 'client_id' claim. If present, the claim must contain the value '{ClientId}' but was '{clientId.Value}'."); - } - else - { - tokenClaims.Add(new("client_id", ClientId)); - } - - // Avoid duplicating audience claims - foreach (var audience in Audience) - { - // Always compare ignoring trailing / - if (tokenClaims.Find(c => c.Type == JwtRegisteredClaimNames.Aud && c.Value.TrimEnd('/') == audience.TrimEnd('/')) == null) - tokenClaims.Insert(1, new(JwtRegisteredClaimNames.Aud, audience)); - } - - // The other claims (client_id, pub, sub_jwk) claims are mostly for the SL manifest itself, - // not for the user, so for now we don't add them. - - // Don't allow mismatches of public manifest key and the one used to sign, to avoid - // weird run-time errors verifiying manifests that were signed with a different key. - var pubKey = Convert.ToBase64String(rsa.Rsa.ExportRSAPublicKey()); - if (pubKey != PublicKey) - throw new ArgumentException($"Cannot sign with a private key that does not match the manifest public key."); - - var jwt = new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken( - claims: tokenClaims, - expires: expirationDate, - signingCredentials: signing - )); - - return jwt; - } - - public ClaimsPrincipal Validate(string jwt, out SecurityToken? token) => new JwtSecurityTokenHandler().ValidateToken(jwt, new TokenValidationParameters - { - RequireExpirationTime = true, - // NOTE: setting this to false allows checking sponsorships even when the manifest is expired. - // This might be useful if package authors want to extend the manifest lifetime beyond the default - // 30 days and issue a warning on expiration, rather than an error and a forced sync. - // If this is not set (or true), a SecurityTokenExpiredException exception will be thrown. - ValidateLifetime = false, - RequireAudience = true, - // At least one of the audiences must match the manifest audiences - AudienceValidator = (audiences, _, _) => Audience.Intersect(audiences.Select(x => x.TrimEnd('/'))).Any(), - ValidIssuer = Issuer, - IssuerSigningKey = SecurityKey, - }, out token); - - /// <summary> - /// Gets the GitHub sponsorable account. - /// </summary> - public string Sponsorable { get; } - - /// <summary> - /// The web endpoint that issues signed JWT to authenticated users. - /// </summary> - /// <remarks> - /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.1 - /// </remarks> - public string Issuer { get; } - - /// <summary> - /// The audience for the JWT, which includes the sponsorable account and potentially other sponsoring platforms. - /// </summary> - /// <remarks> - /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3 - /// </remarks> - public string[] Audience { get; } - - /// <summary> - /// The OAuth client ID (i.e. GitHub OAuth App ID) that is used to - /// authenticate the user. - /// </summary> - /// <remarks> - /// See https://www.rfc-editor.org/rfc/rfc8693.html#name-client_id-client-identifier - /// </remarks> - public string ClientId { get; internal set; } - - /// <summary> - /// Public key that can be used to verify JWT signatures. - /// </summary> - public string PublicKey { get; } - - /// <summary> - /// Public key in a format that can be used to verify JWT signatures. - /// </summary> - public SecurityKey SecurityKey { get; } - - /// <inheritdoc/> - public override int GetHashCode() => new HashCode().Add(Issuer, ClientId, PublicKey).AddRange(Audience).ToHashCode(); - - /// <inheritdoc/> - public override bool Equals(object? obj) => obj is SponsorableManifest other && GetHashCode() == other.GetHashCode(); -} diff --git a/src/SponsorLink/Tests/Tests.csproj b/src/SponsorLink/Tests/Tests.csproj deleted file mode 100644 index f753aad..0000000 --- a/src/SponsorLink/Tests/Tests.csproj +++ /dev/null @@ -1,42 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> - <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" /> - <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> - <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.6.0" /> - <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> - <PackageReference Include="xunit" Version="2.8.1" /> - <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" /> - <PackageReference Include="Humanizer.Core" Version="2.14.1" /> - <PackageReference Include="Humanizer.Core.es" Version="2.14.1" /> - </ItemGroup> - - <ItemGroup> - <!-- This project reference allows debugging the source generator/analyzer project --> - <ProjectReference Include="..\Analyzer\Analyzer.csproj" Aliases="Analyzer" /> - <Analyzer Include="..\Analyzer\bin\$(Configuration)\netstandard2.0\*.dll" /> - </ItemGroup> - - <Target Name="Sample_GitConfig" Condition="'$(SourceControlInformationFeatureSupported)' == 'true'" DependsOnTargets="InitializeSourceControlInformation"> - <!-- Follows recommendation from Microsoft.Common.CurrentVersion.targets on how to depend on SCC info. --> - <ItemGroup> - <GitRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" /> - </ItemGroup> - <PropertyGroup> - <GitRoot>%(GitRoot.FullPath)</GitRoot> - </PropertyGroup> - <ItemGroup Condition="'$(GitRoot)' != ''"> - <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(GitRoot).git/config))" SourceItemType="GitConfig" /> - <AdditionalFiles Include="$([System.IO.Path]::GetFullPath($(UserProfileHome)/.gitconfig))" SourceItemType="GitConfig" /> - </ItemGroup> - </Target> - - <Import Project="..\SponsorLink\buildTransitive\Devlooped.Sponsors.targets" /> - -</Project> \ No newline at end of file diff --git a/src/SponsorLink/readme.md b/src/SponsorLink/readme.md deleted file mode 100644 index cb651a1..0000000 --- a/src/SponsorLink/readme.md +++ /dev/null @@ -1,34 +0,0 @@ -# SponsorLink .NET Analyzer - -This is one opinionated implementation of [SponsorLink](https://devlooped.com/SponsorLink) -for .NET projects leveraging Roslyn analyzers. - -It is intended for use by [devlooped](https://github.com/devlooped) projects, but can be -used as a template for other sponsorables as well. Supporting arbitrary sponsoring scenarios -is out of scope though, since we just use GitHub sponsors for now. - -## Usage - -A project initializing from this template repo via [dotnet-file](https://github.com/devlooped/dotnet-file) -will have all the sources cloned under `src\SponsorLink`. - -Including the analyzer and targets in a project involves two steps. - -1. Create an analyzer project and add the following property: - -```xml - <PropertyGroup> - ... - <CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.targets</CustomAfterMicrosoftCSharpTargets> - </PropertyGroup> -``` - -2. Add a `buildTransitive\[PackageId].targets` file with the following import: - -```xml -<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="Devlooped.Sponsors.targets"/> -</Project> -``` - -As long as NuGetizer is used, the right packaging will be done automatically. \ No newline at end of file